文章目录
概述
NopCommerce源码架构详解-插件机制相关源码分析2。
内容
上一篇文章介绍了一下NopCommerce插件机制相关所有到一些核心类的主要功能和作用。现在我们就来看看这些类具体是怎么实现的。
- nop.Core.Plugins.PluginDescriptor
我们还是先来看看类PluginDescriptor相关的类图:

PluginDescriptor实现接口IPlugin和IComparable,其源码如下:
using System; using System.Collections.Generic; using System.IO; using System.Reflection; using Nop.Core.Infrastructure; namespace Nop.Core.Plugins { public class PluginDescriptor : IComparable<PluginDescriptor> { public PluginDescriptor() { this.SupportedVersions = new List<string>(); this.LimitedToStores = new List<int>(); } public PluginDescriptor(Assembly referencedAssembly, FileInfo originalAssemblyFile, Type pluginType) : this() { this.ReferencedAssembly = referencedAssembly; this.OriginalAssemblyFile = originalAssemblyFile; this.PluginType = pluginType; } /// <summary> /// 插件的文件名 /// </summary> public virtual string PluginFileName { get; set; } /// <summary> /// 插件的type类型 /// </summary> public virtual Type PluginType { get; set; } /// <summary> /// The assembly that has been shadow copied that is active in the application /// </summary> public virtual Assembly ReferencedAssembly { get; internal set; } /// <summary> /// The original assembly file that a shadow copy was made from it /// </summary> public virtual FileInfo OriginalAssemblyFile { get; internal set; } /// <summary> /// Gets or sets the plugin group /// </summary> public virtual string Group { get; set; } /// <summary> /// Gets or sets the friendly name /// </summary> public virtual string FriendlyName { get; set; } /// <summary> /// Gets or sets the system name /// </summary> public virtual string SystemName { get; set; } /// <summary> /// 插件版本 /// </summary> public virtual string Version { get; set; } /// <summary> /// 插件所支持版本列表 /// </summary> public virtual IList<string> SupportedVersions { get; set; } /// <summary> /// 作者 /// </summary> public virtual string Author { get; set; } /// <summary> /// Gets or sets the display order /// </summary> public virtual int DisplayOrder { get; set; } /// <summary> /// 插件所支持店铺列表.如果为空标示该插件在所有店铺中可用。 /// </summary> public virtual IList<int> LimitedToStores { get; set; } /// <summary> /// Gets or sets the value indicating whether plugin is installed /// </summary> public virtual bool Installed { get; set; } //获取插件实例对象 public virtual T Instance<T>() where T : class, IPlugin { object instance; //通过IoC容器获取插件类型的实例对象 if (!EngineContext.Current.ContainerManager.TryResolve(PluginType, null, out instance)) { //not resolved instance = EngineContext.Current.ContainerManager.ResolveUnregistered(PluginType); } var typedInstance = instance as T; if (typedInstance != null) typedInstance.PluginDescriptor = this; return typedInstance; } public IPlugin Instance() { return Instance<IPlugin>(); } public int CompareTo(PluginDescriptor other) { //实现CompareTo方法是为了以后插件排序 if (DisplayOrder != other.DisplayOrder) return DisplayOrder.CompareTo(other.DisplayOrder); else return FriendlyName.CompareTo(other.FriendlyName); } public override string ToString() { return FriendlyName; } public override bool Equals(object obj) { var other = obj as PluginDescriptor; return other != null && SystemName != null && SystemName.Equals(other.SystemName); } public override int GetHashCode() { return SystemName.GetHashCode(); } } }
PluginDescriptor封装了插件描述相关的信息及一些插件相关的操作。
- Nop.Core.Plugins.PluginFinder

using System; using System.Collections.Generic; using System.linq; namespace Nop.Core.Plugins { /// <summary> /// Plugin finder /// </summary> public class PluginFinder : IPluginFinder { #region Fields private IList<PluginDescriptor> _plugins; private bool _arePluginsLoaded = false; #endregion #region Utilities /// <summary> /// 确保插件被加载 /// </summary> protected virtual void EnsurePluginsAreLoaded() { if (!_arePluginsLoaded) { var foundPlugins = PluginManager.ReferencedPlugins.ToList(); foundPlugins.Sort(); //sort _plugins = foundPlugins.ToList(); _arePluginsLoaded = true; } } #endregion #region Methods /// <summary> /// 检查插件是否在在指定店铺中可用 /// </summary> /// <param name="pluginDescriptor">Plugin descriptor to check</param> /// <param name="storeId">Store identifier to check</param> /// <returns>true - available; false - no</returns> public virtual bool AuthenticateStore(PluginDescriptor pluginDescriptor, int storeId) { if (pluginDescriptor == null) throw new ArgumentNullException("pluginDescriptor"); //no validation required if (storeId == 0) return true; if (pluginDescriptor.LimitedToStores.Count == 0) return true; return pluginDescriptor.LimitedToStores.Contains(storeId); } /// <summary> /// 获取指定类型的插件集合 /// </summary> /// <typeparam name="T">The type of plugins to get.</typeparam> /// <param name="installedOnly">A value indicating whether to load only installed plugins</param> /// <param name="storeId">Load records allowed only in a specified store; pass 0 to load all records</param> /// <returns>Plugins</returns> public virtual IEnumerable<T> GetPlugins<T>(bool installedOnly = true, int storeId = 0) where T : class, IPlugin { EnsurePluginsAreLoaded(); foreach (var plugin in _plugins) if (typeof(T).IsAssignableFrom(plugin.PluginType)) if (!installedOnly || plugin.Installed) if (AuthenticateStore(plugin, storeId)) yield return plugin.Instance<T>(); } /// <summary> /// 获取插件描述descriptors--泛型方法 /// </summary> /// <param name="installedOnly">A value indicating whether to load only installed plugins</param> /// <param name="storeId">Load records allowed only in a specified store; pass 0 to load all records</param> /// <returns>Plugin descriptors</returns> public virtual IEnumerable<PluginDescriptor> GetPluginDescriptors(bool installedOnly = true, int storeId = 0) { EnsurePluginsAreLoaded(); foreach (var plugin in _plugins) if (!installedOnly || plugin.Installed) if (AuthenticateStore(plugin, storeId)) yield return plugin; } /// <summary> /// 获取指定类型的插件描述descriptors--泛型方法 /// </summary> /// <typeparam name="T">The type of plugin to get.</typeparam> /// <param name="installedOnly">A value indicating whether to load only installed plugins</param> /// <param name="storeId">Load records allowed only in a specified store; pass 0 to load all records</param> /// <returns>Plugin descriptors</returns> public virtual IEnumerable<PluginDescriptor> GetPluginDescriptors<T>(bool installedOnly = true, int storeId = 0) where T : class, IPlugin { EnsurePluginsAreLoaded(); foreach (var plugin in _plugins) if (typeof(T).IsAssignableFrom(plugin.PluginType)) if (!installedOnly || plugin.Installed) if (AuthenticateStore(plugin, storeId)) yield return plugin; } /// <summary> /// 通过插件的系统名字获取插件 /// </summary> /// <param name="systemName">Plugin system name</param> /// <param name="installedOnly">A value indicating whether to load only installed plugins</param> /// <returns>>Plugin descriptor</returns> public virtual PluginDescriptor GetPluginDescriptorBySystemName(string systemName, bool installedOnly = true) { return GetPluginDescriptors(installedOnly) .SingleOrDefault(p => p.SystemName.Equals(systemName, StringComparison.InvariantCultureIgnoreCase)); } /// <summary> /// 通过插件的系统名字获取插件--泛型方法 /// </summary> /// <typeparam name="T">The type of plugin to get.</typeparam> /// <param name="systemName">Plugin system name</param> /// <param name="installedOnly">A value indicating whether to load only installed plugins</param> /// <returns>>Plugin descriptor</returns> public virtual PluginDescriptor GetPluginDescriptorBySystemName<T>(string systemName, bool installedOnly = true) where T : class, IPlugin { return GetPluginDescriptors<T>(installedOnly) .SingleOrDefault(p => p.SystemName.Equals(systemName, StringComparison.InvariantCultureIgnoreCase)); } /// <summary> /// 重新加载插件 /// </summary> public virtual void ReloadPlugins() { _arePluginsLoaded = false; EnsurePluginsAreLoaded(); } #endregion } }
PluginFinder起到一个插件查找器的作用,这和我之前写的文章ITypeFinder有点类似。NopCommerce源码架构详解–TypeFinder程序集类型自动查找及操作相关源码分析
- Nop.Core.Plugins.PluginManager

虽然PluginFinder源码有点长,但是我还是要放出来看看它是如何实现的:
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 System.Web.Hosting; using Nop.Core.ComponentModel; using Nop.Core.Plugins; //Application开始运行前就运行这个类PluginManager的方法Initialize [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> /// 初始化 /// </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(HostingEnvironment.MapPath(PluginsPath)); _shadowCopyFolder = new DirectoryInfo(HostingEnvironment.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()); 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) { try { File.Delete(f.FullName); } catch (Exception exc) { Debug.WriteLine("Error deleting file " + f.Name + ". Exception: " + exc); } } } //加载描述文件 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) { var msg = string.Empty; foreach (var e in ex.LoaderExceptions) msg += e.Message + Environment.NewLine; var fail = new Exception(msg, ex); Debug.WriteLine(fail.Message, fail); 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); Debug.WriteLine(fail.Message, fail); 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 = HostingEnvironment.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 = HostingEnvironment.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 = HostingEnvironment.MapPath(InstalledPluginsFilePath); if (File.Exists(filePath)) File.Delete(filePath); } #endregion #region Utilities /// <summary> /// Get description files /// </summary> /// <param name="pluginFolder">Plugin direcotry 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)); } 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) { try { string fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileInfo.FullName); if (fileNameWithoutExt == null) throw new Exception(string.Format("Cannot get file extnension 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.Parent == null) throw new InvalidOperationException("The plugin directory for the " + plug.Name + " file exists in a folder outside of the allowed nopCommerce folder heirarchy"); FileInfo shadowCopiedPlug; if (CommonHelper.GetTrustLevel() != AspNetHostingPermissionLevel.Unrestricted) { //all plugins will need to be copied to ~/Plugins/bin/ //this is aboslutely 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 { 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() { var filePath = HostingEnvironment.MapPath(InstalledPluginsFilePath); return filePath; } #endregion } }
在程序运行的时候最开始就要执行PluginFinder的初始化Initialize方法,获取插件目录所有的插件及描述信息及其它初始化操作。可以把PluginFinder理解成主要用来管理插件的,就像windows任务管理器一样。
- Nop.Core.Plugins.PluginFileParser

源码:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; namespace Nop.Core.Plugins { /// <summary> /// Plugin files parser /// </summary> public static class PluginFileParser { public static IList<string> ParseInstalledPluginsFile(string filePath) { //read and parse the file if (!File.Exists(filePath)) return new List<string>(); var text = File.ReadAllText(filePath); if (String.IsNullOrEmpty(text)) return new List<string>(); //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). //var lines = text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); var lines = new List<string>(); using (var reader = new StringReader(text)) { string str; while ((str = reader.ReadLine()) != null) { if (String.IsNullOrWhiteSpace(str)) continue; lines.Add(str.Trim()); } } return lines; } public static void SaveInstalledPluginsFile(IList<String> pluginSystemNames, string filePath) { string result = ""; foreach (var sn in pluginSystemNames) result += string.Format("{0}{1}", sn, Environment.NewLine); File.WriteAllText(filePath, result); } public static PluginDescriptor ParsePluginDescriptionFile(string filePath) { var descriptor = new PluginDescriptor(); var text = File.ReadAllText(filePath); if (String.IsNullOrEmpty(text)) return descriptor; var settings = new List<string>(); using (var reader = new StringReader(text)) { string str; while ((str = reader.ReadLine()) != null) { if (String.IsNullOrWhiteSpace(str)) continue; settings.Add(str.Trim()); } } //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). //var settings = text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); foreach (var setting in settings) { var separatorIndex = setting.IndexOf(':'); if (separatorIndex == -1) { continue; } string key = setting.Substring(0, separatorIndex).Trim(); string value = setting.Substring(separatorIndex + 1).Trim(); switch (key) { case "Group": descriptor.Group = value; break; case "FriendlyName": descriptor.FriendlyName = value; break; case "SystemName": descriptor.SystemName = value; break; case "Version": descriptor.Version = value; break; case "SupportedVersions": { //parse supported versions descriptor.SupportedVersions = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim()) .ToList(); } break; case "Author": descriptor.Author = value; break; case "DisplayOrder": { int displayOrder; int.TryParse(value, out displayOrder); descriptor.DisplayOrder = displayOrder; } break; case "FileName": descriptor.PluginFileName = value; break; case "LimitedToStores": { //parse list of store IDs foreach (var str1 in value.Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim())) { int storeId = 0; if (int.TryParse(str1, out storeId)) { descriptor.LimitedToStores.Add(storeId); } } } break; default: break; } } //nopCommerce 2.00 didn't have 'SupportedVersions' parameter //so let's set it to "2.00" if (descriptor.SupportedVersions.Count == 0) descriptor.SupportedVersions.Add("2.00"); return descriptor; } public static void SavePluginDescriptionFile(PluginDescriptor plugin) { if (plugin == null) throw new ArgumentException("plugin"); //get the Description.txt file path if (plugin.OriginalAssemblyFile == null) throw new Exception(string.Format("Cannot load original assembly path for {0} plugin.", plugin.SystemName)); var filePath = Path.Combine(plugin.OriginalAssemblyFile.Directory.FullName, "Description.txt"); if (!File.Exists(filePath)) throw new Exception(string.Format("Description file for {0} plugin does not exist. {1}", plugin.SystemName, filePath)); var keyValues = new List<KeyValuePair<string, string>>(); keyValues.Add(new KeyValuePair<string, string>("Group", plugin.Group)); keyValues.Add(new KeyValuePair<string, string>("FriendlyName", plugin.FriendlyName)); keyValues.Add(new KeyValuePair<string, string>("SystemName", plugin.SystemName)); keyValues.Add(new KeyValuePair<string, string>("Version", plugin.Version)); keyValues.Add(new KeyValuePair<string, string>("SupportedVersions", string.Join(",", plugin.SupportedVersions))); keyValues.Add(new KeyValuePair<string, string>("Author", plugin.Author)); keyValues.Add(new KeyValuePair<string, string>("DisplayOrder", plugin.DisplayOrder.ToString())); keyValues.Add(new KeyValuePair<string, string>("FileName", plugin.PluginFileName)); if (plugin.LimitedToStores.Count > 0) { var storeList = ""; for (int i = 0; i < plugin.LimitedToStores.Count; i++) { storeList += plugin.LimitedToStores[i]; if (i != plugin.LimitedToStores.Count - 1) storeList += ","; } keyValues.Add(new KeyValuePair<string, string>("LimitedToStores", storeList)); } var sb = new StringBuilder(); for (int i = 0; i < keyValues.Count; i++) { var key = keyValues[i].Key; var value = keyValues[i].Value; sb.AppendFormat("{0}: {1}", key, value); if (i != keyValues.Count -1) sb.Append(Environment.NewLine); } //save the file File.WriteAllText(filePath, sb.ToString()); } } }
可以从上面看到这类主要是用来解析插件描述信息文件的。主要有两个作用:
一、PluginDescriptor对象中的插件的信息写到一个文本文件中。
二、解析插件描述信息成文本文件并返回一PluginDescriptor对象。