NopCommerce源码架构详解--插件机制相关源码分析二

上一篇文章介绍了一下NopCommerce插件机制相关所有到一些核心类的主要功能和作用。现在我们就来看看这些类具体是怎么实现的。

nop.Core.Plugins.PluginDescriptor

我们还是先来看看类PluginDescriptor相关的类图:

NopCommerce源码架构详解--插件机制相关源码分析二

PluginDescriptor实现接口IPlugin和IComparable<PluginDescriptor>,其源码如下:

 
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Reflection;
  5. using Nop.Core.Infrastructure;
  6.  
  7. namespace Nop.Core.Plugins
  8. {
  9. public class PluginDescriptor : IComparable<PluginDescriptor>
  10. {
  11. public PluginDescriptor()
  12. {
  13. this.SupportedVersions = new List<string>();
  14. this.LimitedToStores = new List<int>();
  15. }
  16.  
  17.  
  18. public PluginDescriptor(Assembly referencedAssembly, FileInfo originalAssemblyFile,
  19. Type pluginType)
  20. : this()
  21. {
  22. this.ReferencedAssembly = referencedAssembly;
  23. this.OriginalAssemblyFile = originalAssemblyFile;
  24. this.PluginType = pluginType;
  25. }
  26. /// <summary>
  27. /// 插件的文件名
  28. /// </summary>
  29. public virtual string PluginFileName { get; set; }
  30.  
  31. /// <summary>
  32. /// 插件的type类型
  33. /// </summary>
  34. public virtual Type PluginType { get; set; }
  35.  
  36. /// <summary>
  37. /// The assembly that has been shadow copied that is active in the application
  38. /// </summary>
  39. public virtual Assembly ReferencedAssembly { get; internal set; }
  40.  
  41. /// <summary>
  42. /// The original assembly file that a shadow copy was made from it
  43. /// </summary>
  44. public virtual FileInfo OriginalAssemblyFile { get; internal set; }
  45.  
  46. /// <summary>
  47. /// Gets or sets the plugin group
  48. /// </summary>
  49. public virtual string Group { get; set; }
  50.  
  51. /// <summary>
  52. /// Gets or sets the friendly name
  53. /// </summary>
  54. public virtual string FriendlyName { get; set; }
  55.  
  56. /// <summary>
  57. /// Gets or sets the system name
  58. /// </summary>
  59. public virtual string SystemName { get; set; }
  60.  
  61. /// <summary>
  62. /// 插件版本
  63. /// </summary>
  64. public virtual string Version { get; set; }
  65.  
  66. /// <summary>
  67. /// 插件所支持版本列表
  68. /// </summary>
  69. public virtual IList<string> SupportedVersions { get; set; }
  70.  
  71. /// <summary>
  72. /// 作者
  73. /// </summary>
  74. public virtual string Author { get; set; }
  75.  
  76. /// <summary>
  77. /// Gets or sets the display order
  78. /// </summary>
  79. public virtual int DisplayOrder { get; set; }
  80.  
  81. /// <summary>
  82. /// 插件所支持店铺列表.如果为空标示该插件在所有店铺中可用。
  83. /// </summary>
  84. public virtual IList<int> LimitedToStores { get; set; }
  85.  
  86. /// <summary>
  87. /// Gets or sets the value indicating whether plugin is installed
  88. /// </summary>
  89. public virtual bool Installed { get; set; }
  90.  
  91. //获取插件实例对象
  92. public virtual T Instance<T>() where T : class, IPlugin
  93. {
  94. object instance;
  95. //通过IoC容器获取插件类型的实例对象
  96. if (!EngineContext.Current.ContainerManager.TryResolve(PluginType, null, out instance))
  97. {
  98. //not resolved
  99. instance = EngineContext.Current.ContainerManager.ResolveUnregistered(PluginType);
  100. }
  101. var typedInstance = instance as T;
  102. if (typedInstance != null)
  103. typedInstance.PluginDescriptor = this;
  104. return typedInstance;
  105. }
  106.  
  107. public IPlugin Instance()
  108. {
  109. return Instance<IPlugin>();
  110. }
  111.  
  112. public int CompareTo(PluginDescriptor other)
  113. { //实现CompareTo方法是为了以后插件排序
  114. if (DisplayOrder != other.DisplayOrder)
  115. return DisplayOrder.CompareTo(other.DisplayOrder);
  116. else
  117. return FriendlyName.CompareTo(other.FriendlyName);
  118. }
  119.  
  120. public override string ToString()
  121. {
  122. return FriendlyName;
  123. }
  124.  
  125. public override bool Equals(object obj)
  126. {
  127. var other = obj as PluginDescriptor;
  128. return other != null &&
  129. SystemName != null &&
  130. SystemName.Equals(other.SystemName);
  131. }
  132.  
  133. public override int GetHashCode()
  134. {
  135. return SystemName.GetHashCode();
  136. }
  137. }
  138. }

PluginDescriptor封装了插件描述相关的信息及一些插件相关的操作。

Nop.Core.Plugins.PluginFinder

NopCommerce源码架构详解--插件机制相关源码分析二

 

 
  1. using System;
  2. using System.Collections.Generic;
  3. using System.linq;
  4.  
  5. namespace Nop.Core.Plugins
  6. {
  7. /// <summary>
  8. /// Plugin finder
  9. /// </summary>
  10. public class PluginFinder : IPluginFinder
  11. {
  12. #region Fields
  13.  
  14. private IList<PluginDescriptor> _plugins;
  15.  
  16. private bool _arePluginsLoaded = false;
  17.  
  18. #endregion
  19.  
  20. #region Utilities
  21.  
  22. /// <summary>
  23. /// 确保插件被加载
  24. /// </summary>
  25. protected virtual void EnsurePluginsAreLoaded()
  26. {
  27. if (!_arePluginsLoaded)
  28. {
  29. var foundPlugins = PluginManager.ReferencedPlugins.ToList();
  30. foundPlugins.Sort(); //sort
  31. _plugins = foundPlugins.ToList();
  32.  
  33. _arePluginsLoaded = true;
  34. }
  35. }
  36.  
  37. #endregion
  38.  
  39. #region Methods
  40.  
  41. /// <summary>
  42. /// 检查插件是否在在指定店铺中可用
  43. /// </summary>
  44. /// <param name="pluginDescriptor">Plugin descriptor to check</param>
  45. /// <param name="storeId">Store identifier to check</param>
  46. /// <returns>true - available; false - no</returns>
  47. public virtual bool AuthenticateStore(PluginDescriptor pluginDescriptor, int storeId)
  48. {
  49. if (pluginDescriptor == null)
  50. throw new ArgumentNullException("pluginDescriptor");
  51.  
  52. //no validation required
  53. if (storeId == 0)
  54. return true;
  55.  
  56. if (pluginDescriptor.LimitedToStores.Count == 0)
  57. return true;
  58.  
  59. return pluginDescriptor.LimitedToStores.Contains(storeId);
  60. }
  61.  
  62. /// <summary>
  63. /// 获取指定类型的插件集合
  64. /// </summary>
  65. /// <typeparam name="T">The type of plugins to get.</typeparam>
  66. /// <param name="installedOnly">A value indicating whether to load only installed plugins</param>
  67. /// <param name="storeId">Load records allowed only in a specified store; pass 0 to load all records</param>
  68. /// <returns>Plugins</returns>
  69. public virtual IEnumerable<T> GetPlugins<T>(bool installedOnly = true, int storeId = 0) where T : class, IPlugin
  70. {
  71. EnsurePluginsAreLoaded();
  72.  
  73. foreach (var plugin in _plugins)
  74. if (typeof(T).IsAssignableFrom(plugin.PluginType))
  75. if (!installedOnly || plugin.Installed)
  76. if (AuthenticateStore(plugin, storeId))
  77. yield return plugin.Instance<T>();
  78. }
  79.  
  80. /// <summary>
  81. /// 获取插件描述descriptors--泛型方法
  82. /// </summary>
  83. /// <param name="installedOnly">A value indicating whether to load only installed plugins</param>
  84. /// <param name="storeId">Load records allowed only in a specified store; pass 0 to load all records</param>
  85. /// <returns>Plugin descriptors</returns>
  86. public virtual IEnumerable<PluginDescriptor> GetPluginDescriptors(bool installedOnly = true, int storeId = 0)
  87. {
  88. EnsurePluginsAreLoaded();
  89.  
  90. foreach (var plugin in _plugins)
  91. if (!installedOnly || plugin.Installed)
  92. if (AuthenticateStore(plugin, storeId))
  93. yield return plugin;
  94. }
  95.  
  96. /// <summary>
  97. /// 获取指定类型的插件描述descriptors--泛型方法
  98. /// </summary>
  99. /// <typeparam name="T">The type of plugin to get.</typeparam>
  100. /// <param name="installedOnly">A value indicating whether to load only installed plugins</param>
  101. /// <param name="storeId">Load records allowed only in a specified store; pass 0 to load all records</param>
  102. /// <returns>Plugin descriptors</returns>
  103. public virtual IEnumerable<PluginDescriptor> GetPluginDescriptors<T>(bool installedOnly = true, int storeId = 0)
  104. where T : class, IPlugin
  105. {
  106. EnsurePluginsAreLoaded();
  107.  
  108. foreach (var plugin in _plugins)
  109. if (typeof(T).IsAssignableFrom(plugin.PluginType))
  110. if (!installedOnly || plugin.Installed)
  111. if (AuthenticateStore(plugin, storeId))
  112. yield return plugin;
  113. }
  114.  
  115. /// <summary>
  116. /// 通过插件的系统名字获取插件
  117. /// </summary>
  118. /// <param name="systemName">Plugin system name</param>
  119. /// <param name="installedOnly">A value indicating whether to load only installed plugins</param>
  120. /// <returns>>Plugin descriptor</returns>
  121. public virtual PluginDescriptor GetPluginDescriptorBySystemName(string systemName, bool installedOnly = true)
  122. {
  123. return GetPluginDescriptors(installedOnly)
  124. .SingleOrDefault(p => p.SystemName.Equals(systemName, StringComparison.InvariantCultureIgnoreCase));
  125. }
  126.  
  127. /// <summary>
  128. /// 通过插件的系统名字获取插件--泛型方法
  129. /// </summary>
  130. /// <typeparam name="T">The type of plugin to get.</typeparam>
  131. /// <param name="systemName">Plugin system name</param>
  132. /// <param name="installedOnly">A value indicating whether to load only installed plugins</param>
  133. /// <returns>>Plugin descriptor</returns>
  134. public virtual PluginDescriptor GetPluginDescriptorBySystemName<T>(string systemName, bool installedOnly = true) where T : class, IPlugin
  135. {
  136. return GetPluginDescriptors<T>(installedOnly)
  137. .SingleOrDefault(p => p.SystemName.Equals(systemName, StringComparison.InvariantCultureIgnoreCase));
  138. }
  139.  
  140. /// <summary>
  141. /// 重新加载插件
  142. /// </summary>
  143. public virtual void ReloadPlugins()
  144. {
  145. _arePluginsLoaded = false;
  146. EnsurePluginsAreLoaded();
  147. }
  148.  
  149. #endregion
  150. }
  151. }

PluginFinder起到一个插件查找器的作用,这和我之前写的文章ITypeFinder有点类似。NopCommerce源码架构详解--TypeFinder程序集类型自动查找及操作相关源码分析

Nop.Core.Plugins.PluginManager

NopCommerce源码架构详解--插件机制相关源码分析二

 

虽然PluginFinder源码有点长,但是我还是要放出来看看它是如何实现的:

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

在程序运行的时候最开始就要执行PluginFinder的初始化Initialize方法,获取插件目录所有的插件及描述信息及其它初始化操作。可以把PluginFinder理解成主要用来管理插件的,就像windows任务管理器一样。

Nop.Core.Plugins.PluginFileParser

NopCommerce源码架构详解--插件机制相关源码分析二

 

源码:

 
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Text;
  6.  
  7. namespace Nop.Core.Plugins
  8. {
  9. /// <summary>
  10. /// Plugin files parser
  11. /// </summary>
  12. public static class PluginFileParser
  13. {
  14. public static IList<string> ParseInstalledPluginsFile(string filePath)
  15. {
  16. //read and parse the file
  17. if (!File.Exists(filePath))
  18. return new List<string>();
  19.  
  20. var text = File.ReadAllText(filePath);
  21. if (String.IsNullOrEmpty(text))
  22. return new List<string>();
  23.  
  24. //Old way of file reading. This leads to unexpected behavior when a user's FTP program transfers these files as ASCII (\r\n becomes \n).
  25. //var lines = text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
  26.  
  27. var lines = new List<string>();
  28. using (var reader = new StringReader(text))
  29. {
  30. string str;
  31. while ((str = reader.ReadLine()) != null)
  32. {
  33. if (String.IsNullOrWhiteSpace(str))
  34. continue;
  35. lines.Add(str.Trim());
  36. }
  37. }
  38. return lines;
  39. }
  40.  
  41. public static void SaveInstalledPluginsFile(IList<String> pluginSystemNames, string filePath)
  42. {
  43. string result = "";
  44. foreach (var sn in pluginSystemNames)
  45. result += string.Format("{0}{1}", sn, Environment.NewLine);
  46.  
  47. File.WriteAllText(filePath, result);
  48. }
  49.  
  50. public static PluginDescriptor ParsePluginDescriptionFile(string filePath)
  51. {
  52. var descriptor = new PluginDescriptor();
  53. var text = File.ReadAllText(filePath);
  54. if (String.IsNullOrEmpty(text))
  55. return descriptor;
  56.  
  57. var settings = new List<string>();
  58. using (var reader = new StringReader(text))
  59. {
  60. string str;
  61. while ((str = reader.ReadLine()) != null)
  62. {
  63. if (String.IsNullOrWhiteSpace(str))
  64. continue;
  65. settings.Add(str.Trim());
  66. }
  67. }
  68.  
  69. //Old way of file reading. This leads to unexpected behavior when a user's FTP program transfers these files as ASCII (\r\n becomes \n).
  70. //var settings = text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
  71.  
  72. foreach (var setting in settings)
  73. {
  74. var separatorIndex = setting.IndexOf(':');
  75. if (separatorIndex == -1)
  76. {
  77. continue;
  78. }
  79. string key = setting.Substring(0, separatorIndex).Trim();
  80. string value = setting.Substring(separatorIndex + 1).Trim();
  81.  
  82. switch (key)
  83. {
  84. case "Group":
  85. descriptor.Group = value;
  86. break;
  87. case "FriendlyName":
  88. descriptor.FriendlyName = value;
  89. break;
  90. case "SystemName":
  91. descriptor.SystemName = value;
  92. break;
  93. case "Version":
  94. descriptor.Version = value;
  95. break;
  96. case "SupportedVersions":
  97. {
  98. //parse supported versions
  99. descriptor.SupportedVersions = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
  100. .Select(x => x.Trim())
  101. .ToList();
  102. }
  103. break;
  104. case "Author":
  105. descriptor.Author = value;
  106. break;
  107. case "DisplayOrder":
  108. {
  109. int displayOrder;
  110. int.TryParse(value, out displayOrder);
  111. descriptor.DisplayOrder = displayOrder;
  112. }
  113. break;
  114. case "FileName":
  115. descriptor.PluginFileName = value;
  116. break;
  117. case "LimitedToStores":
  118. {
  119. //parse list of store IDs
  120. foreach (var str1 in value.Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries)
  121. .Select(x => x.Trim()))
  122. {
  123. int storeId = 0;
  124. if (int.TryParse(str1, out storeId))
  125. {
  126. descriptor.LimitedToStores.Add(storeId);
  127. }
  128. }
  129. }
  130. break;
  131. default:
  132. break;
  133. }
  134. }
  135.  
  136. //nopCommerce 2.00 didn't have 'SupportedVersions' parameter
  137. //so let's set it to "2.00"
  138. if (descriptor.SupportedVersions.Count == 0)
  139. descriptor.SupportedVersions.Add("2.00");
  140.  
  141. return descriptor;
  142. }
  143.  
  144. public static void SavePluginDescriptionFile(PluginDescriptor plugin)
  145. {
  146. if (plugin == null)
  147. throw new ArgumentException("plugin");
  148.  
  149. //get the Description.txt file path
  150. if (plugin.OriginalAssemblyFile == null)
  151. throw new Exception(string.Format("Cannot load original assembly path for {0} plugin.", plugin.SystemName));
  152. var filePath = Path.Combine(plugin.OriginalAssemblyFile.Directory.FullName, "Description.txt");
  153. if (!File.Exists(filePath))
  154. throw new Exception(string.Format("Description file for {0} plugin does not exist. {1}", plugin.SystemName, filePath));
  155.  
  156. var keyValues = new List<KeyValuePair<string, string>>();
  157. keyValues.Add(new KeyValuePair<string, string>("Group", plugin.Group));
  158. keyValues.Add(new KeyValuePair<string, string>("FriendlyName", plugin.FriendlyName));
  159. keyValues.Add(new KeyValuePair<string, string>("SystemName", plugin.SystemName));
  160. keyValues.Add(new KeyValuePair<string, string>("Version", plugin.Version));
  161. keyValues.Add(new KeyValuePair<string, string>("SupportedVersions", string.Join(",", plugin.SupportedVersions)));
  162. keyValues.Add(new KeyValuePair<string, string>("Author", plugin.Author));
  163. keyValues.Add(new KeyValuePair<string, string>("DisplayOrder", plugin.DisplayOrder.ToString()));
  164. keyValues.Add(new KeyValuePair<string, string>("FileName", plugin.PluginFileName));
  165. if (plugin.LimitedToStores.Count > 0)
  166. {
  167. var storeList = "";
  168. for (int i = 0; i < plugin.LimitedToStores.Count; i++)
  169. {
  170. storeList += plugin.LimitedToStores[i];
  171. if (i != plugin.LimitedToStores.Count - 1)
  172. storeList += ",";
  173. }
  174. keyValues.Add(new KeyValuePair<string, string>("LimitedToStores", storeList));
  175. }
  176.  
  177. var sb = new StringBuilder();
  178. for (int i = 0; i < keyValues.Count; i++)
  179. {
  180. var key = keyValues[i].Key;
  181. var value = keyValues[i].Value;
  182. sb.AppendFormat("{0}: {1}", key, value);
  183. if (i != keyValues.Count -1)
  184. sb.Append(Environment.NewLine);
  185. }
  186. //save the file
  187. File.WriteAllText(filePath, sb.ToString());
  188. }
  189. }
  190. }

可以从上面看到这类主要是用来解析插件描述信息文件的。主要有两个作用:

一、PluginDescriptor对象中的插件的信息写到一个文本文件中。

二、解析插件描述信息成文本文件并返回一PluginDescriptor对象。

 

文章转载自:蓝狐软件工作室 » NopCommerce源码架构详解--插件机制相关源码分析二