一.插件简介

插件用于扩展nopCommerce的功能。nopCommerce有几种类型的插件如:支付、税率、配送方式、小部件等(接口如下图),更多插件可以访问nopCommerce官网

我们看下后台如何配置管理插件的。

【后台管理】【商城配置】【挂件管理】用于配置小部件,【插件管理】【本地插件】管理本地所有插件

nop自带小部件:Nop.Plugin.Widgets.NivoSlider插件用于主页显示幻灯片,后续我们以此插件为例介绍nop是如何加载小部件的

本文主要介绍的是用于在网站中显示的小部件widgets(实现IWidgetPlugin接口)。

只介绍网站中是如何调用的,后续文章中再介绍如何创建IWidgetPlugin插件。

二.小部件介绍及使用

小部件也可以叫小挂件,继承IWidgetPlugin接口。

是用于在网站中显示的小插件。

自带有:Nop.Plugin.Widgets.NivoSlider插件显示幻灯片。如下图红色区域

我们先看下NivoSlider插件文档结构,每一个插件都有一个Description.txt用于插件的描述。

NivoSlider插件Description.txt内容如下图。

SystemName:系统名称唯一。

SupportedVersions: 该插件支持的nop版本号,nop版本号不对可是在插件列表里不显示的。

FileName:程序集dll文件名,一定要和插件生成的dll文件名一样,否则报错

对比后台理解更直观些

安装、卸载插件在后台管理中心进行管理,这里就不多说了。

安装成功的插件会将系统名称保存在项目文件"~/App_Data/InstalledPlugins.txt"中。

三.小部件调用原理分析

我们看网站是如显示小部件的,还是以NivoSlider插件为例.NivoSlider是在首页显示的,打开首页试图“Home\Index.chtml”

我们发现有很多像@Html.Widget("home_page_top")的节点。

@Html.Widget会调用WidgetController控制器下WidgetsByZone方法从而获取显示内容并输出到页面中

  public static MvcHtmlString Widget(this HtmlHelper helper, string widgetZone, object additionalData = null, string area = null)
{
return helper.Action("WidgetsByZone", "Widget", new { widgetZone = widgetZone, additionalData = additionalData, area = area });
}

再来看下小部件中NivoSliderPlugin类,它继承了IWidgetPlugin接口

并实现了IWidgetPlugin接口GetWidgetZones()方法返回显示位置名称集合。

我们发现NivoSlider插件包含了“home_page_top”的位置。

Index.chtml试图中也出现了@Html.Widget("home_page_top"),因此该小部件会在首页中显示。

所以试图中想要使用小部件,使用@Html.Widget("位置名称")就可以了。

ok,知道怎么使用了,我们再看看源码中涉及到哪些相关接口,之间调用关系是怎样的,先上图。

首先WidgetController控制器WidgetsByZone会返回部分视图

  [ChildActionOnly]
public virtual ActionResult WidgetsByZone(string widgetZone, object additionalData = null)
{
//查找到符合要求的List<RenderWidgetModel>
var model = _widgetModelFactory.GetRenderWidgetModels(widgetZone, additionalData); //no data?
if (!model.Any())
return Content(""); return PartialView(model);
}

WidgetsByZone.cshtml中代码如下,我们发现这里重新调用了插件中某个action

 @model List<RenderWidgetModel>
@using Nop.Web.Models.Cms;
@foreach (var widget in Model)
{
@Html.Action(widget.ActionName, widget.ControllerName, widget.RouteValues)
}

那上边的Action信息又是哪里得到的呢?IWidgetPlugin接口GetDisplayWidgetRoute就是用来返回显示时调用的处理Action信息。

NivoSliderPlugin类实现了GetDisplayWidgetRoute代码如下。

 /// <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}
};
}

总结下:WidgetController->WidgetsByZone负责调用显示插件。

而我们开发的小部件需要实现IWidgetPlugin接口GetDisplayWidgetRoute方法告诉上层,我的显示入口是哪个controller  下的action。

下面我们分析下nop是如何找到我们开发的小部件呢?继续看图。

IWidgetModelFactory

GetRenderWidgetModels(widgetZone, additionalData)方法,

传入小部件位置widgetZone(本例中为"home_page_top")获取List<RenderWidgetModel>

IWidgetService

LoadActiveWidgetsByWidgetZone(widgetZone, _workContext.CurrentCustomer, _storeContext.CurrentStore.Id)

负责返回符合要求的IList<IWidgetPlugin>集合,过滤条件为部件位置,用户,商城。

  /// <summary>
/// Load active widgets
/// </summary>
/// <param name="widgetZone">Widget zone</param>
/// <param name="customer">Load records allowed only to a specified customer; pass null to ignore ACL permissions</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, Customer customer = null, int storeId = 0)
{
if (String.IsNullOrWhiteSpace(widgetZone))
return new List<IWidgetPlugin>(); return LoadActiveWidgets(customer, storeId)
.Where(x => x.GetWidgetZones().Contains(widgetZone, StringComparer.InvariantCultureIgnoreCase)).ToList();
} /// <summary>
/// Load active widgets
/// </summary>
/// <param name="customer">Load records allowed only to a specified customer; pass null to ignore ACL permissions</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> LoadActiveWidgets(Customer customer = null, int storeId = 0)
{
return LoadAllWidgets(customer, storeId)
.Where(x => _widgetSettings.ActiveWidgetSystemNames.Contains(x.PluginDescriptor.SystemName, StringComparer.InvariantCultureIgnoreCase)).ToList();
} /// <summary>
/// Load all widgets
/// </summary>
/// <param name="customer">Load records allowed only to a specified customer; pass null to ignore ACL permissions</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> LoadAllWidgets(Customer customer = null, int storeId = 0)
{
return _pluginFinder.GetPlugins<IWidgetPlugin>(customer: customer, storeId: storeId).ToList();
}

按条件获取到可用小部件

IPluginFinder

_pluginFinder.GetPlugins<IWidgetPlugin>(customer: customer, storeId: storeId).ToList();

查询继承IWidgetPlugin接口的插件也就是小部件了,这里只返回IWidgetPlugin实现类。

PluginManager

 using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Web;
using System.Web.Compilation;
using Nop.Core.ComponentModel;
using Nop.Core.Plugins; //Contributor: Umbraco (http://www.umbraco.com). Thanks a lot!
//SEE THIS POST for full details of what this does - http://shazwazza.com/post/Developing-a-plugin-framework-in-ASPNET-with-medium-trust.aspx [assembly: PreApplicationStartMethod(typeof(PluginManager), "Initialize")]
namespace Nop.Core.Plugins
{
/// <summary>
/// Sets the application up for the plugin referencing
/// </summary>
public class PluginManager
{
#region Const private const string InstalledPluginsFilePath = "~/App_Data/InstalledPlugins.txt";
private const string PluginsPath = "~/Plugins";
private const string ShadowCopyPath = "~/Plugins/bin"; #endregion #region Fields private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();
private static DirectoryInfo _shadowCopyFolder;
private static bool _clearShadowDirectoryOnStartup; #endregion #region Methods /// <summary>
/// Returns a collection of all referenced plugin assemblies that have been shadow copied
/// </summary>
public static IEnumerable<PluginDescriptor> ReferencedPlugins { get; set; } /// <summary>
/// Returns a collection of all plugin which are not compatible with the current version
/// </summary>
public static IEnumerable<string> IncompatiblePlugins { get; set; } /// <summary>
/// Initialize
/// </summary>
public static void Initialize()
{
using (new WriteLockDisposable(Locker))
{
// TODO: Add verbose exception handling / raising here since this is happening on app startup and could
// prevent app from starting altogether
var pluginFolder = new DirectoryInfo(CommonHelper.MapPath(PluginsPath));
_shadowCopyFolder = new DirectoryInfo(CommonHelper.MapPath(ShadowCopyPath)); var referencedPlugins = new List<PluginDescriptor>();
var incompatiblePlugins = new List<string>(); _clearShadowDirectoryOnStartup = !String.IsNullOrEmpty(ConfigurationManager.AppSettings["ClearPluginsShadowDirectoryOnStartup"]) &&
Convert.ToBoolean(ConfigurationManager.AppSettings["ClearPluginsShadowDirectoryOnStartup"]); try
{
var installedPluginSystemNames = PluginFileParser.ParseInstalledPluginsFile(GetInstalledPluginsFilePath()); Debug.WriteLine("Creating shadow copy folder and querying for dlls");
//ensure folders are created
Directory.CreateDirectory(pluginFolder.FullName);
Directory.CreateDirectory(_shadowCopyFolder.FullName); //get list of all files in bin
var binFiles = _shadowCopyFolder.GetFiles("*", SearchOption.AllDirectories);
if (_clearShadowDirectoryOnStartup)
{
//clear out shadow copied plugins
foreach (var f in binFiles)
{
Debug.WriteLine("Deleting " + f.Name);
try
{
File.Delete(f.FullName);
}
catch (Exception exc)
{
Debug.WriteLine("Error deleting file " + f.Name + ". Exception: " + exc);
}
}
} //load description files
foreach (var dfd in GetDescriptionFilesAndDescriptors(pluginFolder))
{
var descriptionFile = dfd.Key;
var pluginDescriptor = dfd.Value; //ensure that version of plugin is valid
if (!pluginDescriptor.SupportedVersions.Contains(NopVersion.CurrentVersion, StringComparer.InvariantCultureIgnoreCase))
{
incompatiblePlugins.Add(pluginDescriptor.SystemName);
continue;
} //some validation
if (String.IsNullOrWhiteSpace(pluginDescriptor.SystemName))
throw new Exception(string.Format("A plugin '{0}' has no system name. Try assigning the plugin a unique name and recompiling.", descriptionFile.FullName));
if (referencedPlugins.Contains(pluginDescriptor))
throw new Exception(string.Format("A plugin with '{0}' system name is already defined", pluginDescriptor.SystemName)); //set 'Installed' property
pluginDescriptor.Installed = installedPluginSystemNames
.FirstOrDefault(x => x.Equals(pluginDescriptor.SystemName, StringComparison.InvariantCultureIgnoreCase)) != null; try
{
if (descriptionFile.Directory == null)
throw new Exception(string.Format("Directory cannot be resolved for '{0}' description file", descriptionFile.Name));
//get list of all DLLs in plugins (not in bin!)
var pluginFiles = descriptionFile.Directory.GetFiles("*.dll", SearchOption.AllDirectories)
//just make sure we're not registering shadow copied plugins
.Where(x => !binFiles.Select(q => q.FullName).Contains(x.FullName))
.Where(x => IsPackagePluginFolder(x.Directory))
.ToList(); //other plugin description info
var mainPluginFile = pluginFiles
.FirstOrDefault(x => x.Name.Equals(pluginDescriptor.PluginFileName, StringComparison.InvariantCultureIgnoreCase));
pluginDescriptor.OriginalAssemblyFile = mainPluginFile; //shadow copy main plugin file
pluginDescriptor.ReferencedAssembly = PerformFileDeploy(mainPluginFile); //load all other referenced assemblies now
foreach (var plugin in pluginFiles
.Where(x => !x.Name.Equals(mainPluginFile.Name, StringComparison.InvariantCultureIgnoreCase))
.Where(x => !IsAlreadyLoaded(x)))
PerformFileDeploy(plugin); //init plugin type (only one plugin per assembly is allowed)
foreach (var t in pluginDescriptor.ReferencedAssembly.GetTypes())
if (typeof(IPlugin).IsAssignableFrom(t))
if (!t.IsInterface)
if (t.IsClass && !t.IsAbstract)
{
pluginDescriptor.PluginType = t;
break;
} referencedPlugins.Add(pluginDescriptor);
}
catch (ReflectionTypeLoadException ex)
{
//add a plugin name. this way we can easily identify a problematic plugin
var msg = string.Format("Plugin '{0}'. ", pluginDescriptor.FriendlyName);
foreach (var e in ex.LoaderExceptions)
msg += e.Message + Environment.NewLine; var fail = new Exception(msg, ex);
throw fail;
}
catch (Exception ex)
{
//add a plugin name. this way we can easily identify a problematic plugin
var msg = string.Format("Plugin '{0}'. {1}", pluginDescriptor.FriendlyName, ex.Message); var fail = new Exception(msg, ex);
throw fail;
}
}
}
catch (Exception ex)
{
var msg = string.Empty;
for (var e = ex; e != null; e = e.InnerException)
msg += e.Message + Environment.NewLine; var fail = new Exception(msg, ex);
throw fail;
} ReferencedPlugins = referencedPlugins;
IncompatiblePlugins = incompatiblePlugins; }
} /// <summary>
/// Mark plugin as installed
/// </summary>
/// <param name="systemName">Plugin system name</param>
public static void MarkPluginAsInstalled(string systemName)
{
if (String.IsNullOrEmpty(systemName))
throw new ArgumentNullException("systemName"); var filePath = CommonHelper.MapPath(InstalledPluginsFilePath);
if (!File.Exists(filePath))
using (File.Create(filePath))
{
//we use 'using' to close the file after it's created
} var installedPluginSystemNames = PluginFileParser.ParseInstalledPluginsFile(GetInstalledPluginsFilePath());
bool alreadyMarkedAsInstalled = installedPluginSystemNames
.FirstOrDefault(x => x.Equals(systemName, StringComparison.InvariantCultureIgnoreCase)) != null;
if (!alreadyMarkedAsInstalled)
installedPluginSystemNames.Add(systemName);
PluginFileParser.SaveInstalledPluginsFile(installedPluginSystemNames,filePath);
} /// <summary>
/// Mark plugin as uninstalled
/// </summary>
/// <param name="systemName">Plugin system name</param>
public static void MarkPluginAsUninstalled(string systemName)
{
if (String.IsNullOrEmpty(systemName))
throw new ArgumentNullException("systemName"); var filePath = CommonHelper.MapPath(InstalledPluginsFilePath);
if (!File.Exists(filePath))
using (File.Create(filePath))
{
//we use 'using' to close the file after it's created
} var installedPluginSystemNames = PluginFileParser.ParseInstalledPluginsFile(GetInstalledPluginsFilePath());
bool alreadyMarkedAsInstalled = installedPluginSystemNames
.FirstOrDefault(x => x.Equals(systemName, StringComparison.InvariantCultureIgnoreCase)) != null;
if (alreadyMarkedAsInstalled)
installedPluginSystemNames.Remove(systemName);
PluginFileParser.SaveInstalledPluginsFile(installedPluginSystemNames,filePath);
} /// <summary>
/// Mark plugin as uninstalled
/// </summary>
public static void MarkAllPluginsAsUninstalled()
{
var filePath = CommonHelper.MapPath(InstalledPluginsFilePath);
if (File.Exists(filePath))
File.Delete(filePath);
} #endregion #region Utilities /// <summary>
/// Get description files
/// </summary>
/// <param name="pluginFolder">Plugin directory info</param>
/// <returns>Original and parsed description files</returns>
private static IEnumerable<KeyValuePair<FileInfo, PluginDescriptor>> GetDescriptionFilesAndDescriptors(DirectoryInfo pluginFolder)
{
if (pluginFolder == null)
throw new ArgumentNullException("pluginFolder"); //create list (<file info, parsed plugin descritor>)
var result = new List<KeyValuePair<FileInfo, PluginDescriptor>>();
//add display order and path to list
foreach (var descriptionFile in pluginFolder.GetFiles("Description.txt", SearchOption.AllDirectories))
{
if (!IsPackagePluginFolder(descriptionFile.Directory))
continue; //parse file
var pluginDescriptor = PluginFileParser.ParsePluginDescriptionFile(descriptionFile.FullName); //populate list
result.Add(new KeyValuePair<FileInfo, PluginDescriptor>(descriptionFile, pluginDescriptor));
} //sort list by display order. NOTE: Lowest DisplayOrder will be first i.e 0 , 1, 1, 1, 5, 10
//it's required: http://www.nopcommerce.com/boards/t/17455/load-plugins-based-on-their-displayorder-on-startup.aspx
result.Sort((firstPair, nextPair) => firstPair.Value.DisplayOrder.CompareTo(nextPair.Value.DisplayOrder));
return result;
} /// <summary>
/// Indicates whether assembly file is already loaded
/// </summary>
/// <param name="fileInfo">File info</param>
/// <returns>Result</returns>
private static bool IsAlreadyLoaded(FileInfo fileInfo)
{
//compare full assembly name
//var fileAssemblyName = AssemblyName.GetAssemblyName(fileInfo.FullName);
//foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
//{
// if (a.FullName.Equals(fileAssemblyName.FullName, StringComparison.InvariantCultureIgnoreCase))
// return true;
//}
//return false; //do not compare the full assembly name, just filename
try
{
string fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileInfo.FullName);
if (fileNameWithoutExt == null)
throw new Exception(string.Format("Cannot get file extension for {0}", fileInfo.Name));
foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
{
string assemblyName = a.FullName.Split(new[] { ',' }).FirstOrDefault();
if (fileNameWithoutExt.Equals(assemblyName, StringComparison.InvariantCultureIgnoreCase))
return true;
}
}
catch (Exception exc)
{
Debug.WriteLine("Cannot validate whether an assembly is already loaded. " + exc);
}
return false;
} /// <summary>
/// Perform file deply
/// </summary>
/// <param name="plug">Plugin file info</param>
/// <returns>Assembly</returns>
private static Assembly PerformFileDeploy(FileInfo plug)
{
if (plug.Directory == null || plug.Directory.Parent == null)
throw new InvalidOperationException("The plugin directory for the " + plug.Name + " file exists in a folder outside of the allowed nopCommerce folder hierarchy"); FileInfo shadowCopiedPlug; if (CommonHelper.GetTrustLevel() != AspNetHostingPermissionLevel.Unrestricted)
{
//all plugins will need to be copied to ~/Plugins/bin/
//this is absolutely required because all of this relies on probingPaths being set statically in the web.config //were running in med trust, so copy to custom bin folder
var shadowCopyPlugFolder = Directory.CreateDirectory(_shadowCopyFolder.FullName);
shadowCopiedPlug = InitializeMediumTrust(plug, shadowCopyPlugFolder);
}
else
{
var directory = AppDomain.CurrentDomain.DynamicDirectory;
Debug.WriteLine(plug.FullName + " to " + directory);
//were running in full trust so copy to standard dynamic folder
shadowCopiedPlug = InitializeFullTrust(plug, new DirectoryInfo(directory));
} //we can now register the plugin definition
var shadowCopiedAssembly = Assembly.Load(AssemblyName.GetAssemblyName(shadowCopiedPlug.FullName)); //add the reference to the build manager
Debug.WriteLine("Adding to BuildManager: '{0}'", shadowCopiedAssembly.FullName);
BuildManager.AddReferencedAssembly(shadowCopiedAssembly); return shadowCopiedAssembly;
} /// <summary>
/// Used to initialize plugins when running in Full Trust
/// </summary>
/// <param name="plug"></param>
/// <param name="shadowCopyPlugFolder"></param>
/// <returns></returns>
private static FileInfo InitializeFullTrust(FileInfo plug, DirectoryInfo shadowCopyPlugFolder)
{
var shadowCopiedPlug = new FileInfo(Path.Combine(shadowCopyPlugFolder.FullName, plug.Name));
try
{
File.Copy(plug.FullName, shadowCopiedPlug.FullName, true);
}
catch (IOException)
{
Debug.WriteLine(shadowCopiedPlug.FullName + " is locked, attempting to rename");
//this occurs when the files are locked,
//for some reason devenv locks plugin files some times and for another crazy reason you are allowed to rename them
//which releases the lock, so that it what we are doing here, once it's renamed, we can re-shadow copy
try
{
var oldFile = shadowCopiedPlug.FullName + Guid.NewGuid().ToString("N") + ".old";
File.Move(shadowCopiedPlug.FullName, oldFile);
}
catch (IOException exc)
{
throw new IOException(shadowCopiedPlug.FullName + " rename failed, cannot initialize plugin", exc);
}
//ok, we've made it this far, now retry the shadow copy
File.Copy(plug.FullName, shadowCopiedPlug.FullName, true);
}
return shadowCopiedPlug;
} /// <summary>
/// Used to initialize plugins when running in Medium Trust
/// </summary>
/// <param name="plug"></param>
/// <param name="shadowCopyPlugFolder"></param>
/// <returns></returns>
private static FileInfo InitializeMediumTrust(FileInfo plug, DirectoryInfo shadowCopyPlugFolder)
{
var shouldCopy = true;
var shadowCopiedPlug = new FileInfo(Path.Combine(shadowCopyPlugFolder.FullName, plug.Name)); //check if a shadow copied file already exists and if it does, check if it's updated, if not don't copy
if (shadowCopiedPlug.Exists)
{
//it's better to use LastWriteTimeUTC, but not all file systems have this property
//maybe it is better to compare file hash?
var areFilesIdentical = shadowCopiedPlug.CreationTimeUtc.Ticks >= plug.CreationTimeUtc.Ticks;
if (areFilesIdentical)
{
Debug.WriteLine("Not copying; files appear identical: '{0}'", shadowCopiedPlug.Name);
shouldCopy = false;
}
else
{
//delete an existing file //More info: http://www.nopcommerce.com/boards/t/11511/access-error-nopplugindiscountrulesbillingcountrydll.aspx?p=4#60838
Debug.WriteLine("New plugin found; Deleting the old file: '{0}'", shadowCopiedPlug.Name);
File.Delete(shadowCopiedPlug.FullName);
}
} if (shouldCopy)
{
try
{
File.Copy(plug.FullName, shadowCopiedPlug.FullName, true);
}
catch (IOException)
{
Debug.WriteLine(shadowCopiedPlug.FullName + " is locked, attempting to rename");
//this occurs when the files are locked,
//for some reason devenv locks plugin files some times and for another crazy reason you are allowed to rename them
//which releases the lock, so that it what we are doing here, once it's renamed, we can re-shadow copy
try
{
var oldFile = shadowCopiedPlug.FullName + Guid.NewGuid().ToString("N") + ".old";
File.Move(shadowCopiedPlug.FullName, oldFile);
}
catch (IOException exc)
{
throw new IOException(shadowCopiedPlug.FullName + " rename failed, cannot initialize plugin", exc);
}
//ok, we've made it this far, now retry the shadow copy
File.Copy(plug.FullName, shadowCopiedPlug.FullName, true);
}
} return shadowCopiedPlug;
} /// <summary>
/// Determines if the folder is a bin plugin folder for a package
/// </summary>
/// <param name="folder"></param>
/// <returns></returns>
private static bool IsPackagePluginFolder(DirectoryInfo folder)
{
if (folder == null) return false;
if (folder.Parent == null) return false;
if (!folder.Parent.Name.Equals("Plugins", StringComparison.InvariantCultureIgnoreCase)) return false;
return true;
} /// <summary>
/// Gets the full path of InstalledPlugins.txt file
/// </summary>
/// <returns></returns>
private static string GetInstalledPluginsFilePath()
{
return CommonHelper.MapPath(InstalledPluginsFilePath);
} #endregion
}
}

PluginManager

PluginManager.ReferencedPlugins获取到所有的插件。

PluginManager类用于管理插件,我们发现[assembly: PreApplicationStartMethod(typeof(PluginManager), "Initialize")]表示应用程序启动就调用PluginManager类下边的Initialize方法进行初始化。初始化过程中会把所有插件保存到ReferencedPlugins变量中。

经过上边一层层的过滤查找,终于找到了符合要求的插件了并保存在IList<IWidgetPlugin>集合中。最后经过IWidgetModelFactory接口GetRenderWidgetModels(widgetZone, additionalData)方法处理保存为List<RenderWidgetModel>最后为WidgetsByZone试图使用。

ps: 调用关系描述的不是很清晰,请见谅,大家还是看图,结合代码理解吧。

四.总结

1.nop支持各种插件,不同插件继承接口不一样

2.小部件继承IWidgetPlugin,用于在网页中显示。

3.IWidgetPlugin接口 IList<string> GetWidgetZones()返回显示部件的位置名称集合。GetDisplayWidgetRoute方法返回显示插件时的路由。

4.网站视图中用@Html.Widget("位置名称")来显示插件。”位置名称”包含在插件GetWidgetZones()返回的集合中。

文中有错误的理解和不正确的观点请指正、留言、一起交流共同进步。

本文地址:http://www.cnblogs.com/yaoshangjin/p/7239183.html

本文为大波浪原创、转载请注明出处。

nopCommerce 3.9 大波浪系列 之 网页加载Widgets插件原理的更多相关文章

  1. nopCommerce 3.9 大波浪系列 之 微信公众平台登录插件

    一.简介 插件源码下载:点击下载 微信公众平台网站授权帮助地址:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp142114084 ...

  2. nopCommerce 3.9 大波浪系列 之 可退款的支付宝插件(上)

    一.简介 nop通过插件机制可以支持更多的支付扩展,我们通过编写支持退款的支付宝插件来更好的理解支付插件的扩展. 先分享下支付宝插件源码点击下载,由于时间原因,本篇只介绍使用该插件,下一篇结合插件进行 ...

  3. nopCommerce 3.9 大波浪系列 之 可退款的支付宝插件(下)

    一.回顾 支付宝插件源码下载地址:点击下载 上篇介绍了使用支付宝插件进行支付,全额退款,部分退款还有插件的多店铺配置,本文介绍下如何实现的. 二.前期准备 插件主要有3个功能: 多店铺插件配置 支付功 ...

  4. Qt加载网页(加载浏览器插件)和制作托盘后台运行(南信大财务报账看号)

    程序模块要添加QNetWork和QWebKit模块: nuistfinancevideo.h文件: #ifndef NUISTFINANCEVIDEO_H #define NUISTFINANCEVI ...

  5. nopCommerce 3.9 大波浪系列 之 引擎 NopEngine

    本章涉及到的内容如下 1.EngineContext初始化IEngine实例 2.Autofac依赖注入初始化 3.AutoMapper框架初始化 4.启动任务初始化 一.EngineContext初 ...

  6. nopCommerce 3.9 大波浪系列 之 外部授权登录插件的开发实现

    一.简介 nop支持第三方外部授权登录的扩展,本篇通过编写微信公众平台登录插件进一步了解nop授权登录的开发过程. 微信公众平台.微信开放平台使用场景不一样,前者通过微信客户端进行开发如公众号,后者基 ...

  7. nopCommerce 3.9 大波浪系列 之 使用Redis主从高可用缓存

    一.概述 nop支持Redis作为缓存,Redis出众的性能在企业中得到了广泛的应用.Redis支持主从复制,HA,集群. 一般来说,只有一台Redis是不可行的,原因如下: 单台Redis服务器会发 ...

  8. nopCommerce 3.9 大波浪系列 之 使用部署在Docker中的Redis缓存主从服务

    一.概述 nop支持Redis作为缓存,Redis出众的性能在企业中得到了广泛的应用.Redis支持主从复制,HA,集群. 一般来说,只有一台Redis是不可行的,原因如下: 单台Redis服务器会发 ...

  9. nopCommerce 3.9 大波浪系列 之 global.asax

    一.nop的global.asax文件 nop3.9基于ASP.NET MVC 5框架开发,而ASP.NET MVC中global.asax文件包含全局应用程序事件的事件处理程序,它响应应用程序级别和 ...

随机推荐

  1. HTTPS反向代理嗅探

    两年前的文章,一直在本地笔记放着,发现博客上竟然没存.发出来. 先说说什么是SSL:     SSL是Secure Socket Layer的简称,中文意思是安全套接层,由NetScape公司开发,用 ...

  2. DISCUZ积分及点评需求

    1.点评设置(可增强用户互动,但又不会顶帖刷屏):目前很难限制用户通过点评刷积分,点评等同于回复但却不需要审核,目前只是简单地关闭了点评功能.需求:可以审核点评内容:可以限制点评不获得积分或每天点评获 ...

  3. top的用法

    top命令可以用来方便地观察当前系统中运行的进程的信息,并可以在运行过程中执行改变进程的优先级.更改排序规则.导出状态信息等操作,非常方便. 1.主要选项 -d:后接秒数,状态更新的秒数,默认5秒-b ...

  4. ionic 项目中添加modal的步骤流程

    1.首先在templates文件夹下面定义一个新页面,xxx.html,template文件夹在空项目里面是没有的,需要手动添加一个,放在WWW文件夹下面. <ion-modal-view> ...

  5. PHP的魔法方法

    PHP将所有以__(两个下划线)开头的类方法保留为魔术方法.所以在定义方法是,除了魔术方法,建议不要用两个下划线前缀. 魔术方法(Magic methods)有 __construct(),__des ...

  6. css透明度的设置

    Css代码 .transparent_class { filter:alpha(opacity=50); -moz-opacity:0.5; -khtml-opacity: 0.5; opacity: ...

  7. 基于spring多数据源动态调用及其事务处理

    需求: 有些时候,我们需要连接多个数据库,但是,在方法调用前并不知道到底是调用哪个.即同时保持多个数据库的连接,在方法中根据传入的参数来确定. 下图的单数据源的调用和多数据源动态调用的流程,可以看出在 ...

  8. OpenCV学习3-----利用鼠标、键盘回调函数实现标定人体关节点

    最近做实验,需要一些人体关节点的ground truth,需要自己手动标定,于是尝试使用OpenCV的鼠标键盘回调函数实现. 期间遇到不少问题,记录一下. 首先就是鼠标回调函数注册, namedWin ...

  9. weblogic 部署问题定位与解决

    weblogic 做为商用中间件在(EJB.jndi 数据源.日志管理.内存管理.资源配置管理...)  是一些开源免费小型容器无法望其项背的. weblogic 最早由 weblogic Inc. ...

  10. SQL联表查询

    数据库中最最常用的语法----select.简单的select语法很直白: select column from table where expression: 从((from)存储数据的地方(tab ...