文章目录
概述
NopCommerce源码架构详解-插件机制相关源码分析3。
内容
前面两篇文章写了NopCommerce插件机制的底层实现原理,现在我来举一个具体的插件例子,分析一下nop插件的文件结构及关键代码。研究过NopCommerce源码的同学如果细心都会发现,Nop前台首页的幻片图片其实是调用的一个插件。下面我们就来分析这个幻灯插件Nop.Plugin.Widgets.NivoSlider是如何实现的。
首先,我们看看这个NivoSlider插件的文件结构。

从上图就可以看到Nop自带的插件它其实是就一个完整的mvc项目,也有MVC相应的文件结构。我来看看个项目的属性中的生成设置是什么?

可以从上图中插件的生成目录是Nop.Web的Plugins目录下,也就是所有的插件都在Web项目的里,一个插件一个文件夹,如下图:

请请注意每个插件项目都有两个类名分别是以Plugin和Settings结尾的类。比如:插件Nop.Plugin.Widgets.NivoSlider有下面的两个类:
1、NivoSliderPlugin
2、NivoSliderSettings
Nop.Plugin.Widgets.GoogleAnalytics插件也有两个类:
1、GoogleAnalyticPlugin
2、GoogleAnalyticsSettings
其实,聪明的你看这个两个类的命名就知道他们的作用。
Plugin是插件的核心类,它继承于基类BasePlugin和实现接口IWidgetPlugin,作用是插件最终要执行的Controller与Action的路由配置信息、插件安装,卸载以及插件调用的区域名字(GetWidgetZones)等等。
Settings就是插件本身的一些基本设置信息。
在Home/Index.cshtml中有下面代码:
@Html.Widget("home_page_top")
@Html.Widget其实是调用的HtmlHelper的一个扩展方法。
public static MvcHtmlString Widget(this HtmlHelper helper, string widgetZone, object additionalData = null) { return helper.Action("WidgetsByZone", "Widget", new { widgetZone = widgetZone, additionalData = additionalData }); }
从上面可以看到它最终是调用的Controller为Widget的一个Action方法WidgetsByZone。下面我们就来看这个方法为WidgetsByZone到底做了些什么?
Nop.Web.Controllers.WidgetController : BasePublicController
[ChildActionOnly] public ActionResult WidgetsByZone(string widgetZone, object additionalData = null) { var cacheKey = string.Format(ModelCacheEventConsumer.WIDGET_MODEL_KEY, _storeContext.CurrentStore.Id, widgetZone); var cacheModel = _cacheManager.Get(cacheKey, () => { //model var model = new List<RenderWidgetModel>(); var widgets = _widgetService.LoadActiveWidgetsByWidgetZone(widgetZone, _storeContext.CurrentStore.Id); foreach (var widget in widgets) { var widgetModel = new RenderWidgetModel(); string actionName; string controllerName; RouteValueDictionary routeValues; widget.GetDisplayWidgetRoute(widgetZone, out actionName, out controllerName, out routeValues); widgetModel.ActionName = actionName; widgetModel.ControllerName = controllerName; widgetModel.RouteValues = routeValues; model.Add(widgetModel); } return model; }); //如果没有Model返回为空字符串 if (cacheModel.Count == 0) return Content(""); //"RouteValues" property of widget models depends on "additionalData". //We need to clone the cached model before modifications (the updated one should not be cached) var clonedModel = new List<RenderWidgetModel>(); foreach (var widgetModel in cacheModel) { var clonedWidgetModel = new RenderWidgetModel(); clonedWidgetModel.ActionName = widgetModel.ActionName; clonedWidgetModel.ControllerName = widgetModel.ControllerName; if (widgetModel.RouteValues != null) clonedWidgetModel.RouteValues = new RouteValueDictionary(widgetModel.RouteValues); if (additionalData != null) { if (clonedWidgetModel.RouteValues == null) clonedWidgetModel.RouteValues = new RouteValueDictionary(); clonedWidgetModel.RouteValues.Add("additionalData", additionalData); } clonedModel.Add(clonedWidgetModel); } return PartialView(clonedModel); }
上面的_cacheManager.Get是用到了缓存,你可以先不用关心这一块,以后我会专门用一篇文章到介绍Nop中的缓存实现。今天我们只关心_cacheManager.Get里面的lambda方法体。里面有一句代码:
var widgets = _widgetService.LoadActiveWidgetsByWidgetZone(widgetZone, _storeContext.CurrentStore.Id);
上面是调用了widgetService的方法LoadActiveWidgetsByWidgetZone来获取满足的WidgetPlugin集合。下面我们来看看WidgetService有些什么?
Nop.Services.Cms
WidgetService : IWidgetService
/// <summary> /// Load active widgets /// </summary> /// <param name="widgetZone">Widget zone</param> /// <param name="storeId">Load records allowed only in a specified store; pass 0 to load all records</param> /// <returns>Widgets</returns> public virtual IList<IWidgetPlugin> LoadActiveWidgetsByWidgetZone(string widgetZone, int storeId = 0) { if (String.IsNullOrWhiteSpace(widgetZone)) return new List<IWidgetPlugin>(); //通过GetWidgetZones方法返回的集合与widgetZone做比较 return LoadActiveWidgets(storeId) .Where(x => x.GetWidgetZones().Contains(widgetZone, StringComparer.InvariantCultureIgnoreCase)) .ToList(); } /// <summary> /// Load active widgets /// </summary> /// <param name="storeId">Load records allowed only in a specified store; pass 0 to load all records</param> /// <returns>Widgets</returns> public virtual IList<IWidgetPlugin> LoadActiveWidgets(int storeId = 0) { return LoadAllWidgets(storeId) .Where(x => _widgetSettings.ActiveWidgetSystemNames.Contains(x.PluginDescriptor.SystemName, StringComparer.InvariantCultureIgnoreCase)) .ToList(); } /// <summary> /// Load all widgets /// </summary> /// <param name="storeId">Load records allowed only in a specified store; pass 0 to load all records</param> /// <returns>Widgets</returns> public virtual IList<IWidgetPlugin> LoadAllWidgets(int storeId = 0) { return _pluginFinder.GetPlugins<IWidgetPlugin>(storeId: storeId).ToList(); }
最后,我们来看看该插件的核心类NivoSlider:
Nop.Plugin.Widgets.NivoSlider NivoSliderPlugin : BasePlugin, IWidgetPlugin using System.Collections.Generic; using System.IO; using System.Web.Routing; using Nop.Core; using Nop.Core.Plugins; using Nop.Services.Cms; using Nop.Services.Configuration; using Nop.Services.Localization; using Nop.Services.Media; namespace Nop.Plugin.Widgets.NivoSlider { /// <summary> /// PLugin /// </summary> public class NivoSliderPlugin : BasePlugin, IWidgetPlugin { private readonly IPictureService _pictureService; private readonly ISettingService _settingService; private readonly IWebHelper _webHelper; public NivoSliderPlugin(IPictureService pictureService, ISettingService settingService, IWebHelper webHelper) { this._pictureService = pictureService; this._settingService = settingService; this._webHelper = webHelper; } /// <summary> /// 获取该插件的所有区域名称,到时可以通过Html.Widget来调用 /// </summary> /// <returns>Widget zones</returns> public IList<string> GetWidgetZones() { return new List<string>() { "home_page_top" }; } /// <summary> /// 获取该插件的路由配置信息 /// </summary> public void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) { actionName = "Configure"; controllerName = "WidgetsNivoSlider"; routeValues = new RouteValueDictionary() { { "Namespaces", "Nop.Plugin.Widgets.NivoSlider.Controllers" }, { "area", null } }; } /// <summary> /// 获取要显示的指定区域的路由信息 /// </summary> /// <param name="widgetZone">Widget zone where it's displayed</param> /// <param name="actionName">Action name</param> /// <param name="controllerName">Controller name</param> /// <param name="routeValues">Route values</param> public void GetDisplayWidgetRoute(string widgetZone, out string actionName, out string controllerName, out RouteValueDictionary routeValues) { actionName = "PublicInfo"; controllerName = "WidgetsNivoSlider"; routeValues = new RouteValueDictionary() { {"Namespaces", "Nop.Plugin.Widgets.NivoSlider.Controllers"}, {"area", null}, {"widgetZone", widgetZone} }; } /// <summary> /// 安装插件 /// </summary> public override void Install() { //图片信息 var sampleImagesPath = _webHelper.MapPath("~/Plugins/Widgets.NivoSlider/Content/nivoslider/sample-images/"); //插件相关的配置信息 var settings = new NivoSliderSettings() { Picture1Id = _pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "banner1.jpg"), "image/pjpeg", "banner_1", true).Id, Text1 = "", Link1 = _webHelper.GetStoreLocation(false), Picture2Id = _pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "banner2.jpg"), "image/pjpeg", "banner_2", true).Id, Text2 = "", Link2 = _webHelper.GetStoreLocation(false), Picture3Id = _pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "banner3.jpg"), "image/pjpeg", "banner_3", true).Id, Text3 = "", Link3 = _webHelper.GetStoreLocation(false), //Picture4Id = _pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "banner4.jpg"), "image/pjpeg", "banner_4", true).Id, //Text4 = "", //Link4 = _webHelper.GetStoreLocation(false), }; _settingService.SaveSetting(settings); this.AddOrUpdatePluginLocaleResource("Plugins.Widgets.NivoSlider.Picture1", "Picture 1"); this.AddOrUpdatePluginLocaleResource("Plugins.Widgets.NivoSlider.Picture2", "Picture 2"); this.AddOrUpdatePluginLocaleResource("Plugins.Widgets.NivoSlider.Picture3", "Picture 3"); this.AddOrUpdatePluginLocaleResource("Plugins.Widgets.NivoSlider.Picture4", "Picture 4"); this.AddOrUpdatePluginLocaleResource("Plugins.Widgets.NivoSlider.Picture5", "Picture 5"); this.AddOrUpdatePluginLocaleResource("Plugins.Widgets.NivoSlider.Picture", "Picture"); this.AddOrUpdatePluginLocaleResource("Plugins.Widgets.NivoSlider.Picture.Hint", "Upload picture."); this.AddOrUpdatePluginLocaleResource("Plugins.Widgets.NivoSlider.Text", "Comment"); this.AddOrUpdatePluginLocaleResource("Plugins.Widgets.NivoSlider.Text.Hint", "Enter comment for picture. Leave empty if you don't want to display any text."); this.AddOrUpdatePluginLocaleResource("Plugins.Widgets.NivoSlider.Link", "URL"); this.AddOrUpdatePluginLocaleResource("Plugins.Widgets.NivoSlider.Link.Hint", "Enter URL. Leave empty if you don't want this picture to be clickable."); base.Install(); } /// <summary> /// 卸载插件 /// </summary> public override void Uninstall() { //settings _settingService.DeleteSetting<NivoSliderSettings>(); //locales this.DeletePluginLocaleResource("Plugins.Widgets.NivoSlider.Picture1"); this.DeletePluginLocaleResource("Plugins.Widgets.NivoSlider.Picture2"); this.DeletePluginLocaleResource("Plugins.Widgets.NivoSlider.Picture3"); this.DeletePluginLocaleResource("Plugins.Widgets.NivoSlider.Picture4"); this.DeletePluginLocaleResource("Plugins.Widgets.NivoSlider.Picture5"); this.DeletePluginLocaleResource("Plugins.Widgets.NivoSlider.Picture"); this.DeletePluginLocaleResource("Plugins.Widgets.NivoSlider.Picture.Hint"); this.DeletePluginLocaleResource("Plugins.Widgets.NivoSlider.Text"); this.DeletePluginLocaleResource("Plugins.Widgets.NivoSlider.Text.Hint"); this.DeletePluginLocaleResource("Plugins.Widgets.NivoSlider.Link"); this.DeletePluginLocaleResource("Plugins.Widgets.NivoSlider.Link.Hint"); base.Uninstall(); } } }
上面纯代码看起来可以有点枯燥,下图是我根据理解画的上面逻辑的UML序列图。
