文章目录
概述
NopCommerce源码架构详解-Widget网站部件的实现相关源码分析。
内容
在网站系统中有一个优秀的设计就是可以把页面中要显示的每一个部分划分成一个个Widget网站部件,然后结合插件技术可以让这些Widget部件动态的加载,达到可插拔,复用等特性。nop就是基于这个思想,今天我来分析一下Nop中Widget网站部件的实现相关源码。下面是最终页面的调用方法。
项目:Nop.Web
视图:Nop.Web\Views\Home\Index.cshtml
@{ Layout = "~/Views/Shared/_ColumnsThree.cshtml"; } <div class="page home-page"> <div class="page-body"> @Html.Widget("home_page_top") @Html.Action("TopicBlock", "Topic", new { systemName = "HomePageText" }) @Html.Action("HomepageCategories", "Catalog") @Html.Action("HomepageProducts", "Product") @Html.Action("HomepageBestSellers", "Product") @Html.Action("HomePageNews", "News") @Html.Action("HomePagePolls", "Poll") @Html.Widget("home_page_bottom") </div> </div>
1、@Html.Widget扩展方法
上面视图Index.cshmtl中就用到了@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 }); }
2、WidgetController中的Action方法WidgetsByZone
扩展方法调用的是WidgetController中的WidgetByZone方法,最终返回的一个类型为MvcHtmlString字符串。
WidgetsByZone方法:
[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; }); //no data? 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); }
3、视图WidgetsByZone
这个Action对应的视图Nop.Web\Views\Widget\WidgetsByZone.cshtml
@model List<RenderWidgetModel> @using Nop.Web.Models.Cms; @foreach (var widget in Model) { @Html.Action(widget.ActionName, widget.ControllerName, widget.RouteValues) }
在视图中依次调用@Html.Action,执行model对应的Action方法并返回相应的结果。
4、Nop.Services.Cms.WidgetService
在WidgetController中的Action方法WidgetsByZone中我们可以看到里面最终调用WidgetService的方法LoadActiveWidgetsByWidgetZone并把结果缓存起来,而LoadActiveWidgetsByWidgetZone返回是一个类型IWidgetPlugin部件插件集合IList。
public virtual IList<IWidgetPlugin> LoadActiveWidgetsByWidgetZone(string widgetZone, int storeId = 0) { if (String.IsNullOrWhiteSpace(widgetZone)) return new List<IWidgetPlugin>(); return LoadActiveWidgets(storeId) .Where(x => x.GetWidgetZones().Contains(widgetZone, StringComparer.InvariantCultureIgnoreCase)) .ToList(); }
public virtual IList<IWidgetPlugin> LoadAllWidgets(int storeId = 0) { return _pluginFinder.GetPlugins<IWidgetPlugin>(storeId: storeId).ToList(); }
5、@Html.Widget(home_page_top)
最后我们回到我们刚开始的谈到的Nop.Web\Views\Home\Index.cshtml里面的@Html.Widget(home_page_top),其实也就是首页的焦点图部件。效果如下:

根据我上面的介绍的知识,你可以很轻松的知道@Html.Widget(home_page_top)其实就是调用的一个WidgetZone为”home_page_top“部件插件,其实执行也就是Nop.Plugin.Widgets.NivoSlider插件。

在NivoSliderPlugin有一个属性GetWidgetZones:
public IList<string> GetWidgetZones() { return new List<string>() { "home_page_top" }; }
这个home_page_top正好和@Html.Widget(home_page_top)一致,因为Nop.Services.Cms.WidgetService里面最终就是通过属性GetWidgetZones来筛选Nop系统中的部件插件。
public virtual IList<IWidgetPlugin> LoadActiveWidgetsByWidgetZone(string widgetZone, int storeId = 0) { if (String.IsNullOrWhiteSpace(widgetZone)) return new List<IWidgetPlugin>(); return LoadActiveWidgets(storeId) .Where(x => x.GetWidgetZones().Contains(widgetZone, StringComparer.InvariantCultureIgnoreCase)) .ToList(); }