一.插件简介

插件用于扩展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方法从而获取显示内容并输出到页面中

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

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

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

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

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

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

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

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

  1. [ChildActionOnly]
  2. public virtual ActionResult WidgetsByZone(string widgetZone, object additionalData = null)
  3. {
  4. //查找到符合要求的List<RenderWidgetModel>
  5. var model = _widgetModelFactory.GetRenderWidgetModels(widgetZone, additionalData);
  6.  
  7. //no data?
  8. if (!model.Any())
  9. return Content("");
  10.  
  11. return PartialView(model);
  12. }

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

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

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

NivoSliderPlugin类实现了GetDisplayWidgetRoute代码如下。

  1. /// <summary>
  2. /// 获取显示插件的路由
  3. /// </summary>
  4. /// <param name="widgetZone">Widget zone where it's displayed</param>
  5. /// <param name="actionName">Action name</param>
  6. /// <param name="controllerName">Controller name</param>
  7. /// <param name="routeValues">Route values</param>
  8. public void GetDisplayWidgetRoute(string widgetZone, out string actionName, out string controllerName, out RouteValueDictionary routeValues)
  9. {
  10. actionName = "PublicInfo";
  11. controllerName = "WidgetsNivoSlider";
  12. routeValues = new RouteValueDictionary
  13. {
  14. {"Namespaces", "Nop.Plugin.Widgets.NivoSlider.Controllers"},
  15. {"area", null},
  16. {"widgetZone", widgetZone}
  17. };
  18. }

总结下: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>集合,过滤条件为部件位置,用户,商城。

  1. /// <summary>
  2. /// Load active widgets
  3. /// </summary>
  4. /// <param name="widgetZone">Widget zone</param>
  5. /// <param name="customer">Load records allowed only to a specified customer; pass null to ignore ACL permissions</param>
  6. /// <param name="storeId">Load records allowed only in a specified store; pass 0 to load all records</param>
  7. /// <returns>Widgets</returns>
  8. public virtual IList<IWidgetPlugin> LoadActiveWidgetsByWidgetZone(string widgetZone, Customer customer = null, int storeId = 0)
  9. {
  10. if (String.IsNullOrWhiteSpace(widgetZone))
  11. return new List<IWidgetPlugin>();
  12.  
  13. return LoadActiveWidgets(customer, storeId)
  14. .Where(x => x.GetWidgetZones().Contains(widgetZone, StringComparer.InvariantCultureIgnoreCase)).ToList();
  15. }
  16.  
  17. /// <summary>
  18. /// Load active widgets
  19. /// </summary>
  20. /// <param name="customer">Load records allowed only to a specified customer; pass null to ignore ACL permissions</param>
  21. /// <param name="storeId">Load records allowed only in a specified store; pass 0 to load all records</param>
  22. /// <returns>Widgets</returns>
  23. public virtual IList<IWidgetPlugin> LoadActiveWidgets(Customer customer = null, int storeId = 0)
  24. {
  25. return LoadAllWidgets(customer, storeId)
  26. .Where(x => _widgetSettings.ActiveWidgetSystemNames.Contains(x.PluginDescriptor.SystemName, StringComparer.InvariantCultureIgnoreCase)).ToList();
  27. }
  28.  
  29. /// <summary>
  30. /// Load all widgets
  31. /// </summary>
  32. /// <param name="customer">Load records allowed only to a specified customer; pass null to ignore ACL permissions</param>
  33. /// <param name="storeId">Load records allowed only in a specified store; pass 0 to load all records</param>
  34. /// <returns>Widgets</returns>
  35. public virtual IList<IWidgetPlugin> LoadAllWidgets(Customer customer = null, int storeId = 0)
  36. {
  37. return _pluginFinder.GetPlugins<IWidgetPlugin>(customer: customer, storeId: storeId).ToList();
  38. }

按条件获取到可用小部件

IPluginFinder

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

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

PluginManager

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Configuration;
  4. using System.Diagnostics;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Reflection;
  8. using System.Threading;
  9. using System.Web;
  10. using System.Web.Compilation;
  11. using Nop.Core.ComponentModel;
  12. using Nop.Core.Plugins;
  13.  
  14. //Contributor: Umbraco (http://www.umbraco.com). Thanks a lot!
  15. //SEE THIS POST for full details of what this does - http://shazwazza.com/post/Developing-a-plugin-framework-in-ASPNET-with-medium-trust.aspx
  16.  
  17. [assembly: PreApplicationStartMethod(typeof(PluginManager), "Initialize")]
  18. namespace Nop.Core.Plugins
  19. {
  20. /// <summary>
  21. /// Sets the application up for the plugin referencing
  22. /// </summary>
  23. public class PluginManager
  24. {
  25. #region Const
  26.  
  27. private const string InstalledPluginsFilePath = "~/App_Data/InstalledPlugins.txt";
  28. private const string PluginsPath = "~/Plugins";
  29. private const string ShadowCopyPath = "~/Plugins/bin";
  30.  
  31. #endregion
  32.  
  33. #region Fields
  34.  
  35. private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();
  36. private static DirectoryInfo _shadowCopyFolder;
  37. private static bool _clearShadowDirectoryOnStartup;
  38.  
  39. #endregion
  40.  
  41. #region Methods
  42.  
  43. /// <summary>
  44. /// Returns a collection of all referenced plugin assemblies that have been shadow copied
  45. /// </summary>
  46. public static IEnumerable<PluginDescriptor> ReferencedPlugins { get; set; }
  47.  
  48. /// <summary>
  49. /// Returns a collection of all plugin which are not compatible with the current version
  50. /// </summary>
  51. public static IEnumerable<string> IncompatiblePlugins { get; set; }
  52.  
  53. /// <summary>
  54. /// Initialize
  55. /// </summary>
  56. public static void Initialize()
  57. {
  58. using (new WriteLockDisposable(Locker))
  59. {
  60. // TODO: Add verbose exception handling / raising here since this is happening on app startup and could
  61. // prevent app from starting altogether
  62. var pluginFolder = new DirectoryInfo(CommonHelper.MapPath(PluginsPath));
  63. _shadowCopyFolder = new DirectoryInfo(CommonHelper.MapPath(ShadowCopyPath));
  64.  
  65. var referencedPlugins = new List<PluginDescriptor>();
  66. var incompatiblePlugins = new List<string>();
  67.  
  68. _clearShadowDirectoryOnStartup = !String.IsNullOrEmpty(ConfigurationManager.AppSettings["ClearPluginsShadowDirectoryOnStartup"]) &&
  69. Convert.ToBoolean(ConfigurationManager.AppSettings["ClearPluginsShadowDirectoryOnStartup"]);
  70.  
  71. try
  72. {
  73. var installedPluginSystemNames = PluginFileParser.ParseInstalledPluginsFile(GetInstalledPluginsFilePath());
  74.  
  75. Debug.WriteLine("Creating shadow copy folder and querying for dlls");
  76. //ensure folders are created
  77. Directory.CreateDirectory(pluginFolder.FullName);
  78. Directory.CreateDirectory(_shadowCopyFolder.FullName);
  79.  
  80. //get list of all files in bin
  81. var binFiles = _shadowCopyFolder.GetFiles("*", SearchOption.AllDirectories);
  82. if (_clearShadowDirectoryOnStartup)
  83. {
  84. //clear out shadow copied plugins
  85. foreach (var f in binFiles)
  86. {
  87. Debug.WriteLine("Deleting " + f.Name);
  88. try
  89. {
  90. File.Delete(f.FullName);
  91. }
  92. catch (Exception exc)
  93. {
  94. Debug.WriteLine("Error deleting file " + f.Name + ". Exception: " + exc);
  95. }
  96. }
  97. }
  98.  
  99. //load description files
  100. foreach (var dfd in GetDescriptionFilesAndDescriptors(pluginFolder))
  101. {
  102. var descriptionFile = dfd.Key;
  103. var pluginDescriptor = dfd.Value;
  104.  
  105. //ensure that version of plugin is valid
  106. if (!pluginDescriptor.SupportedVersions.Contains(NopVersion.CurrentVersion, StringComparer.InvariantCultureIgnoreCase))
  107. {
  108. incompatiblePlugins.Add(pluginDescriptor.SystemName);
  109. continue;
  110. }
  111.  
  112. //some validation
  113. if (String.IsNullOrWhiteSpace(pluginDescriptor.SystemName))
  114. throw new Exception(string.Format("A plugin '{0}' has no system name. Try assigning the plugin a unique name and recompiling.", descriptionFile.FullName));
  115. if (referencedPlugins.Contains(pluginDescriptor))
  116. throw new Exception(string.Format("A plugin with '{0}' system name is already defined", pluginDescriptor.SystemName));
  117.  
  118. //set 'Installed' property
  119. pluginDescriptor.Installed = installedPluginSystemNames
  120. .FirstOrDefault(x => x.Equals(pluginDescriptor.SystemName, StringComparison.InvariantCultureIgnoreCase)) != null;
  121.  
  122. try
  123. {
  124. if (descriptionFile.Directory == null)
  125. throw new Exception(string.Format("Directory cannot be resolved for '{0}' description file", descriptionFile.Name));
  126. //get list of all DLLs in plugins (not in bin!)
  127. var pluginFiles = descriptionFile.Directory.GetFiles("*.dll", SearchOption.AllDirectories)
  128. //just make sure we're not registering shadow copied plugins
  129. .Where(x => !binFiles.Select(q => q.FullName).Contains(x.FullName))
  130. .Where(x => IsPackagePluginFolder(x.Directory))
  131. .ToList();
  132.  
  133. //other plugin description info
  134. var mainPluginFile = pluginFiles
  135. .FirstOrDefault(x => x.Name.Equals(pluginDescriptor.PluginFileName, StringComparison.InvariantCultureIgnoreCase));
  136. pluginDescriptor.OriginalAssemblyFile = mainPluginFile;
  137.  
  138. //shadow copy main plugin file
  139. pluginDescriptor.ReferencedAssembly = PerformFileDeploy(mainPluginFile);
  140.  
  141. //load all other referenced assemblies now
  142. foreach (var plugin in pluginFiles
  143. .Where(x => !x.Name.Equals(mainPluginFile.Name, StringComparison.InvariantCultureIgnoreCase))
  144. .Where(x => !IsAlreadyLoaded(x)))
  145. PerformFileDeploy(plugin);
  146.  
  147. //init plugin type (only one plugin per assembly is allowed)
  148. foreach (var t in pluginDescriptor.ReferencedAssembly.GetTypes())
  149. if (typeof(IPlugin).IsAssignableFrom(t))
  150. if (!t.IsInterface)
  151. if (t.IsClass && !t.IsAbstract)
  152. {
  153. pluginDescriptor.PluginType = t;
  154. break;
  155. }
  156.  
  157. referencedPlugins.Add(pluginDescriptor);
  158. }
  159. catch (ReflectionTypeLoadException ex)
  160. {
  161. //add a plugin name. this way we can easily identify a problematic plugin
  162. var msg = string.Format("Plugin '{0}'. ", pluginDescriptor.FriendlyName);
  163. foreach (var e in ex.LoaderExceptions)
  164. msg += e.Message + Environment.NewLine;
  165.  
  166. var fail = new Exception(msg, ex);
  167. throw fail;
  168. }
  169. catch (Exception ex)
  170. {
  171. //add a plugin name. this way we can easily identify a problematic plugin
  172. var msg = string.Format("Plugin '{0}'. {1}", pluginDescriptor.FriendlyName, ex.Message);
  173.  
  174. var fail = new Exception(msg, ex);
  175. throw fail;
  176. }
  177. }
  178. }
  179. catch (Exception ex)
  180. {
  181. var msg = string.Empty;
  182. for (var e = ex; e != null; e = e.InnerException)
  183. msg += e.Message + Environment.NewLine;
  184.  
  185. var fail = new Exception(msg, ex);
  186. throw fail;
  187. }
  188.  
  189. ReferencedPlugins = referencedPlugins;
  190. IncompatiblePlugins = incompatiblePlugins;
  191.  
  192. }
  193. }
  194.  
  195. /// <summary>
  196. /// Mark plugin as installed
  197. /// </summary>
  198. /// <param name="systemName">Plugin system name</param>
  199. public static void MarkPluginAsInstalled(string systemName)
  200. {
  201. if (String.IsNullOrEmpty(systemName))
  202. throw new ArgumentNullException("systemName");
  203.  
  204. var filePath = CommonHelper.MapPath(InstalledPluginsFilePath);
  205. if (!File.Exists(filePath))
  206. using (File.Create(filePath))
  207. {
  208. //we use 'using' to close the file after it's created
  209. }
  210.  
  211. var installedPluginSystemNames = PluginFileParser.ParseInstalledPluginsFile(GetInstalledPluginsFilePath());
  212. bool alreadyMarkedAsInstalled = installedPluginSystemNames
  213. .FirstOrDefault(x => x.Equals(systemName, StringComparison.InvariantCultureIgnoreCase)) != null;
  214. if (!alreadyMarkedAsInstalled)
  215. installedPluginSystemNames.Add(systemName);
  216. PluginFileParser.SaveInstalledPluginsFile(installedPluginSystemNames,filePath);
  217. }
  218.  
  219. /// <summary>
  220. /// Mark plugin as uninstalled
  221. /// </summary>
  222. /// <param name="systemName">Plugin system name</param>
  223. public static void MarkPluginAsUninstalled(string systemName)
  224. {
  225. if (String.IsNullOrEmpty(systemName))
  226. throw new ArgumentNullException("systemName");
  227.  
  228. var filePath = CommonHelper.MapPath(InstalledPluginsFilePath);
  229. if (!File.Exists(filePath))
  230. using (File.Create(filePath))
  231. {
  232. //we use 'using' to close the file after it's created
  233. }
  234.  
  235. var installedPluginSystemNames = PluginFileParser.ParseInstalledPluginsFile(GetInstalledPluginsFilePath());
  236. bool alreadyMarkedAsInstalled = installedPluginSystemNames
  237. .FirstOrDefault(x => x.Equals(systemName, StringComparison.InvariantCultureIgnoreCase)) != null;
  238. if (alreadyMarkedAsInstalled)
  239. installedPluginSystemNames.Remove(systemName);
  240. PluginFileParser.SaveInstalledPluginsFile(installedPluginSystemNames,filePath);
  241. }
  242.  
  243. /// <summary>
  244. /// Mark plugin as uninstalled
  245. /// </summary>
  246. public static void MarkAllPluginsAsUninstalled()
  247. {
  248. var filePath = CommonHelper.MapPath(InstalledPluginsFilePath);
  249. if (File.Exists(filePath))
  250. File.Delete(filePath);
  251. }
  252.  
  253. #endregion
  254.  
  255. #region Utilities
  256.  
  257. /// <summary>
  258. /// Get description files
  259. /// </summary>
  260. /// <param name="pluginFolder">Plugin directory info</param>
  261. /// <returns>Original and parsed description files</returns>
  262. private static IEnumerable<KeyValuePair<FileInfo, PluginDescriptor>> GetDescriptionFilesAndDescriptors(DirectoryInfo pluginFolder)
  263. {
  264. if (pluginFolder == null)
  265. throw new ArgumentNullException("pluginFolder");
  266.  
  267. //create list (<file info, parsed plugin descritor>)
  268. var result = new List<KeyValuePair<FileInfo, PluginDescriptor>>();
  269. //add display order and path to list
  270. foreach (var descriptionFile in pluginFolder.GetFiles("Description.txt", SearchOption.AllDirectories))
  271. {
  272. if (!IsPackagePluginFolder(descriptionFile.Directory))
  273. continue;
  274.  
  275. //parse file
  276. var pluginDescriptor = PluginFileParser.ParsePluginDescriptionFile(descriptionFile.FullName);
  277.  
  278. //populate list
  279. result.Add(new KeyValuePair<FileInfo, PluginDescriptor>(descriptionFile, pluginDescriptor));
  280. }
  281.  
  282. //sort list by display order. NOTE: Lowest DisplayOrder will be first i.e 0 , 1, 1, 1, 5, 10
  283. //it's required: http://www.nopcommerce.com/boards/t/17455/load-plugins-based-on-their-displayorder-on-startup.aspx
  284. result.Sort((firstPair, nextPair) => firstPair.Value.DisplayOrder.CompareTo(nextPair.Value.DisplayOrder));
  285. return result;
  286. }
  287.  
  288. /// <summary>
  289. /// Indicates whether assembly file is already loaded
  290. /// </summary>
  291. /// <param name="fileInfo">File info</param>
  292. /// <returns>Result</returns>
  293. private static bool IsAlreadyLoaded(FileInfo fileInfo)
  294. {
  295. //compare full assembly name
  296. //var fileAssemblyName = AssemblyName.GetAssemblyName(fileInfo.FullName);
  297. //foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
  298. //{
  299. // if (a.FullName.Equals(fileAssemblyName.FullName, StringComparison.InvariantCultureIgnoreCase))
  300. // return true;
  301. //}
  302. //return false;
  303.  
  304. //do not compare the full assembly name, just filename
  305. try
  306. {
  307. string fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileInfo.FullName);
  308. if (fileNameWithoutExt == null)
  309. throw new Exception(string.Format("Cannot get file extension for {0}", fileInfo.Name));
  310. foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
  311. {
  312. string assemblyName = a.FullName.Split(new[] { ',' }).FirstOrDefault();
  313. if (fileNameWithoutExt.Equals(assemblyName, StringComparison.InvariantCultureIgnoreCase))
  314. return true;
  315. }
  316. }
  317. catch (Exception exc)
  318. {
  319. Debug.WriteLine("Cannot validate whether an assembly is already loaded. " + exc);
  320. }
  321. return false;
  322. }
  323.  
  324. /// <summary>
  325. /// Perform file deply
  326. /// </summary>
  327. /// <param name="plug">Plugin file info</param>
  328. /// <returns>Assembly</returns>
  329. private static Assembly PerformFileDeploy(FileInfo plug)
  330. {
  331. if (plug.Directory == null || plug.Directory.Parent == null)
  332. throw new InvalidOperationException("The plugin directory for the " + plug.Name + " file exists in a folder outside of the allowed nopCommerce folder hierarchy");
  333.  
  334. FileInfo shadowCopiedPlug;
  335.  
  336. if (CommonHelper.GetTrustLevel() != AspNetHostingPermissionLevel.Unrestricted)
  337. {
  338. //all plugins will need to be copied to ~/Plugins/bin/
  339. //this is absolutely required because all of this relies on probingPaths being set statically in the web.config
  340.  
  341. //were running in med trust, so copy to custom bin folder
  342. var shadowCopyPlugFolder = Directory.CreateDirectory(_shadowCopyFolder.FullName);
  343. shadowCopiedPlug = InitializeMediumTrust(plug, shadowCopyPlugFolder);
  344. }
  345. else
  346. {
  347. var directory = AppDomain.CurrentDomain.DynamicDirectory;
  348. Debug.WriteLine(plug.FullName + " to " + directory);
  349. //were running in full trust so copy to standard dynamic folder
  350. shadowCopiedPlug = InitializeFullTrust(plug, new DirectoryInfo(directory));
  351. }
  352.  
  353. //we can now register the plugin definition
  354. var shadowCopiedAssembly = Assembly.Load(AssemblyName.GetAssemblyName(shadowCopiedPlug.FullName));
  355.  
  356. //add the reference to the build manager
  357. Debug.WriteLine("Adding to BuildManager: '{0}'", shadowCopiedAssembly.FullName);
  358. BuildManager.AddReferencedAssembly(shadowCopiedAssembly);
  359.  
  360. return shadowCopiedAssembly;
  361. }
  362.  
  363. /// <summary>
  364. /// Used to initialize plugins when running in Full Trust
  365. /// </summary>
  366. /// <param name="plug"></param>
  367. /// <param name="shadowCopyPlugFolder"></param>
  368. /// <returns></returns>
  369. private static FileInfo InitializeFullTrust(FileInfo plug, DirectoryInfo shadowCopyPlugFolder)
  370. {
  371. var shadowCopiedPlug = new FileInfo(Path.Combine(shadowCopyPlugFolder.FullName, plug.Name));
  372. try
  373. {
  374. File.Copy(plug.FullName, shadowCopiedPlug.FullName, true);
  375. }
  376. catch (IOException)
  377. {
  378. Debug.WriteLine(shadowCopiedPlug.FullName + " is locked, attempting to rename");
  379. //this occurs when the files are locked,
  380. //for some reason devenv locks plugin files some times and for another crazy reason you are allowed to rename them
  381. //which releases the lock, so that it what we are doing here, once it's renamed, we can re-shadow copy
  382. try
  383. {
  384. var oldFile = shadowCopiedPlug.FullName + Guid.NewGuid().ToString("N") + ".old";
  385. File.Move(shadowCopiedPlug.FullName, oldFile);
  386. }
  387. catch (IOException exc)
  388. {
  389. throw new IOException(shadowCopiedPlug.FullName + " rename failed, cannot initialize plugin", exc);
  390. }
  391. //ok, we've made it this far, now retry the shadow copy
  392. File.Copy(plug.FullName, shadowCopiedPlug.FullName, true);
  393. }
  394. return shadowCopiedPlug;
  395. }
  396.  
  397. /// <summary>
  398. /// Used to initialize plugins when running in Medium Trust
  399. /// </summary>
  400. /// <param name="plug"></param>
  401. /// <param name="shadowCopyPlugFolder"></param>
  402. /// <returns></returns>
  403. private static FileInfo InitializeMediumTrust(FileInfo plug, DirectoryInfo shadowCopyPlugFolder)
  404. {
  405. var shouldCopy = true;
  406. var shadowCopiedPlug = new FileInfo(Path.Combine(shadowCopyPlugFolder.FullName, plug.Name));
  407.  
  408. //check if a shadow copied file already exists and if it does, check if it's updated, if not don't copy
  409. if (shadowCopiedPlug.Exists)
  410. {
  411. //it's better to use LastWriteTimeUTC, but not all file systems have this property
  412. //maybe it is better to compare file hash?
  413. var areFilesIdentical = shadowCopiedPlug.CreationTimeUtc.Ticks >= plug.CreationTimeUtc.Ticks;
  414. if (areFilesIdentical)
  415. {
  416. Debug.WriteLine("Not copying; files appear identical: '{0}'", shadowCopiedPlug.Name);
  417. shouldCopy = false;
  418. }
  419. else
  420. {
  421. //delete an existing file
  422.  
  423. //More info: http://www.nopcommerce.com/boards/t/11511/access-error-nopplugindiscountrulesbillingcountrydll.aspx?p=4#60838
  424. Debug.WriteLine("New plugin found; Deleting the old file: '{0}'", shadowCopiedPlug.Name);
  425. File.Delete(shadowCopiedPlug.FullName);
  426. }
  427. }
  428.  
  429. if (shouldCopy)
  430. {
  431. try
  432. {
  433. File.Copy(plug.FullName, shadowCopiedPlug.FullName, true);
  434. }
  435. catch (IOException)
  436. {
  437. Debug.WriteLine(shadowCopiedPlug.FullName + " is locked, attempting to rename");
  438. //this occurs when the files are locked,
  439. //for some reason devenv locks plugin files some times and for another crazy reason you are allowed to rename them
  440. //which releases the lock, so that it what we are doing here, once it's renamed, we can re-shadow copy
  441. try
  442. {
  443. var oldFile = shadowCopiedPlug.FullName + Guid.NewGuid().ToString("N") + ".old";
  444. File.Move(shadowCopiedPlug.FullName, oldFile);
  445. }
  446. catch (IOException exc)
  447. {
  448. throw new IOException(shadowCopiedPlug.FullName + " rename failed, cannot initialize plugin", exc);
  449. }
  450. //ok, we've made it this far, now retry the shadow copy
  451. File.Copy(plug.FullName, shadowCopiedPlug.FullName, true);
  452. }
  453. }
  454.  
  455. return shadowCopiedPlug;
  456. }
  457.  
  458. /// <summary>
  459. /// Determines if the folder is a bin plugin folder for a package
  460. /// </summary>
  461. /// <param name="folder"></param>
  462. /// <returns></returns>
  463. private static bool IsPackagePluginFolder(DirectoryInfo folder)
  464. {
  465. if (folder == null) return false;
  466. if (folder.Parent == null) return false;
  467. if (!folder.Parent.Name.Equals("Plugins", StringComparison.InvariantCultureIgnoreCase)) return false;
  468. return true;
  469. }
  470.  
  471. /// <summary>
  472. /// Gets the full path of InstalledPlugins.txt file
  473. /// </summary>
  474. /// <returns></returns>
  475. private static string GetInstalledPluginsFilePath()
  476. {
  477. return CommonHelper.MapPath(InstalledPluginsFilePath);
  478. }
  479.  
  480. #endregion
  481. }
  482. }

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. win7下 mysql安装(mysql-5.7.18-winx64.zip)

    cmd到mysql/bin目录下 应该是先mysqld --initialize然后mysqld -install最后net start mysql

  2. 影响国内WinCE7发展的最大障碍是没有D版下载

    WinCE红火的时代已经过去,做嵌入式系统时考虑WinCE的越来越少,网络上相关文章也是越来越少. 但真正用过WinCE的应该有体会,它集成了文件系统,tcp/ip,GUI系统,usb驱动,就这些,你 ...

  3. 浅谈如何保证discuz插件安全

    1.非直接执行程序请加上 if(!defined('IN_DISCUZ')) { exit('Access Denied'); } 2.记得过滤 比如说uid等id需要intval过滤,避免溢出    ...

  4. 基于Jmeter的轻量级接口压力测试(一)

    一.操作步骤: 1.在测试计划下新增一个线程组,并在线程组下新增一个http请求: 2.读取配置文件中的参数:在添加的http请求下添加配置元件-CSV DATA SET CONFIG 3.配置待测试 ...

  5. JS阻塞以及CSS阻塞

    一.JS阻塞 所有的浏览器在下载JS文件的时候,会阻塞页面上的其他活动,包括其他资源的下载以及页面内容的呈现等等,只有当JS下载.解析.执行完,才会进行后面的 操作.在现代的浏览器中CSS资源和图片i ...

  6. 基于Vue全家桶开发的前端组件管理平台

    项目背景 项目背景是外包类建站公司里,设计环节沉淀了大量可复用组件,设计师往往只需要微调组件就拼凑出页面,交付给前端,理论上这些组件在前端也可以复用,但实际上前端每次都要重新实现整个页面,浪费很多人力 ...

  7. Python os模块实例之遍历目录及子目录指定扩展名的文件

    需求:在该目录下有很多子目录(如下图,截图了部分),现要从该目录和所有子目录下找到所有扩展名为.meta的文件,并获取文件中第二行guid的值(': '后面的),然后将所有guid的值输出到另一文件中 ...

  8. forfiles命令批处理删除过期文件

    命令格式: forfiles.exe /p "D:\备份" /s /m *.zip /d -7 /c "cmd /c del @path" /p:指定目录 /s ...

  9. 使用CoApp创建NuGet C++静态库包

    NuGet是微软开发平台下的包管理软件,使用它你可以非常方便的将一些第三方的库.框架整合进自己的项目中,省去了不少麻烦的配置过程.但是从官方文档上来看,貌似NuGet对C++的支持不是很好,并且在现阶 ...

  10. javaScript高级程序设计笔记 2

    Undefinde Null Boolean Number String    基本类型 Object    引用类型 只有引用类型才能动态的添加属性 赋值基本类型和引用类型也不相同,复制的基本类型的 ...