NopCommerce源码架构详解--插件机制相关源码分析二
上一篇文章介绍了一下NopCommerce插件机制相关所有到一些核心类的主要功能和作用。现在我们就来看看这些类具体是怎么实现的。
nop.Core.Plugins.PluginDescriptor
我们还是先来看看类PluginDescriptor相关的类图:
PluginDescriptor实现接口IPlugin和IComparable<PluginDescriptor>,其源码如下:
- 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对象。