NOP源码分析 六
上节讲到Head.cshtml,里面实际做的是根据supportRtl、supportResponsive、themeName三个变量,引入相应的CSS文件等。
接着看这一句:
@Html.Widget("head_html_tag")
扩展方法:
public static MvcHtmlString Widget(this HtmlHelper helper, string widgetZone, object additionalData = null)
{
return helper.Action("WidgetsByZone", "Widget", new { widgetZone = widgetZone, additionalData = additionalData });
}
其实她就是调用一个action,后面是路由信息。看一下这个action:
[ChildActionOnly]
public ActionResult WidgetsByZone(string widgetZone, object additionalData = null)
{
var cacheKey = string.Format(ModelCacheEventConsumer.WIDGET_MODEL_KEY, _storeContext.CurrentStore.Id, widgetZone);
var cacheModel = _cacheManager.Get(cacheKey, () =>
{
//model
var model = new List<RenderWidgetModel>();
var widgets = _widgetService.LoadActiveWidgetsByWidgetZone(widgetZone, _storeContext.CurrentStore.Id);
foreach (var widget in widgets)
{
var widgetModel = new RenderWidgetModel();
string actionName;
string controllerName;
RouteValueDictionary routeValues;
widget.GetDisplayWidgetRoute(widgetZone, out actionName, out controllerName, out routeValues);
widgetModel.ActionName = actionName;
widgetModel.ControllerName = controllerName;
widgetModel.RouteValues = routeValues;
model.Add(widgetModel);
}
return model;
});
//no data?
if (cacheModel.Count == 0)
return Content("");
//"RouteValues" property of widget models depends on "additionalData".
//We need to clone the cached model before modifications (the updated one should not be cached)
var clonedModel = new List<RenderWidgetModel>();
foreach (var widgetModel in cacheModel)
{
var clonedWidgetModel = new RenderWidgetModel();
clonedWidgetModel.ActionName = widgetModel.ActionName;
clonedWidgetModel.ControllerName = widgetModel.ControllerName;
if (widgetModel.RouteValues != null)
clonedWidgetModel.RouteValues = new RouteValueDictionary(widgetModel.RouteValues);
if (additionalData != null)
{
if (clonedWidgetModel.RouteValues == null)
clonedWidgetModel.RouteValues = new RouteValueDictionary();
clonedWidgetModel.RouteValues.Add("additionalData", additionalData);
}
clonedModel.Add(clonedWidgetModel);
}
return PartialView(clonedModel);
}
看LoadActiveWidgetsByWidgetZone方法。
跟踪发现,最终通过如下方法,加载所有插件:
/// <summary>
/// Load all widgets
/// </summary>
/// <param name="storeId">Load records allowed only in a specified store; pass 0 to load all records</param>
/// <returns>Widgets</returns>
public virtual IList<IWidgetPlugin> LoadAllWidgets(int storeId = 0)
{
return _pluginFinder.GetPlugins<IWidgetPlugin>(storeId: storeId).ToList();
}
调用的是:
/// <summary>
/// Gets plugins
/// </summary>
/// <typeparam name="T">The type of plugins to get.</typeparam>
/// <param name="loadMode">Load plugins mode</param>
/// <param name="storeId">Load records allowed only in a specified store; pass 0 to load all records</param>
/// <param name="group">Filter by plugin group; pass null to load all records</param>
/// <returns>Plugins</returns>
public virtual IEnumerable<T> GetPlugins<T>(LoadPluginsMode loadMode = LoadPluginsMode.InstalledOnly,
int storeId = 0, string group = null) where T : class, IPlugin
{
return GetPluginDescriptors<T>(loadMode, storeId, group).Select(p => p.Instance<T>());
}
调用:
/// <summary>
/// Get plugin descriptors
/// </summary>
/// <typeparam name="T">The type of plugin to get.</typeparam>
/// <param name="loadMode">Load plugins mode</param>
/// <param name="storeId">Load records allowed only in a specified store; pass 0 to load all records</param>
/// <param name="group">Filter by plugin group; pass null to load all records</param>
/// <returns>Plugin descriptors</returns>
public virtual IEnumerable<PluginDescriptor> GetPluginDescriptors<T>(LoadPluginsMode loadMode = LoadPluginsMode.InstalledOnly,
int storeId = 0, string group = null)
where T : class, IPlugin
{
return GetPluginDescriptors(loadMode, storeId, group)
.Where(p => typeof(T).IsAssignableFrom(p.PluginType));
}
我们看到它取出的是IEnumerable<PluginDescriptor> 然后筛选是IWidgetPlugin的子类吧 应该是。。。
public virtual IEnumerable<PluginDescriptor> GetPluginDescriptors(LoadPluginsMode loadMode = LoadPluginsMode.InstalledOnly,
int storeId = 0, string group = null)
{
//ensure plugins are loaded
EnsurePluginsAreLoaded();
return _plugins.Where(p => CheckLoadMode(p, loadMode) && AuthenticateStore(p, storeId) && CheckGroup(p, group));
}
先确保插件已加载,如下:
protected virtual void EnsurePluginsAreLoaded()
{
if (!_arePluginsLoaded)
{
var foundPlugins = PluginManager.ReferencedPlugins.ToList();
foundPlugins.Sort();
_plugins = foundPlugins.ToList();
_arePluginsLoaded = true;
}
}
看一下PluginManager:
//Contributor: Umbraco (http://www.umbraco.com). Thanks a lot!
//SEE THIS POST for full details of what this does - http://shazwazza.com/post/Developing-a-plugin-framework-in-ASPNET-with-medium-trust.aspx
[assembly: PreApplicationStartMethod(typeof(PluginManager), "Initialize")]
namespace Nop.Core.Plugins
{
/// <summary>
/// Sets the application up for the plugin referencing
/// </summary>
public class PluginManager
{
#region Const
private const string InstalledPluginsFilePath = "~/App_Data/InstalledPlugins.txt";
private const string PluginsPath = "~/Plugins";
private const string ShadowCopyPath = "~/Plugins/bin";
#endregion
#region Fields
private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();
private static DirectoryInfo _shadowCopyFolder;
private static bool _clearShadowDirectoryOnStartup;
#endregion
#region Methods
/// <summary>
/// Returns a collection of all referenced plugin assemblies that have been shadow copied
/// </summary>
public static IEnumerable<PluginDescriptor> ReferencedPlugins { get; set; }
/// <summary>
/// Returns a collection of all plugin which are not compatible with the current version
/// </summary>
public static IEnumerable<string> IncompatiblePlugins { get; set; }
/// <summary>
/// Initialize
/// </summary>
public static void Initialize()
{
using (new WriteLockDisposable(Locker))
{
// TODO: Add verbose exception handling / raising here since this is happening on app startup and could
// prevent app from starting altogether
var pluginFolder = new DirectoryInfo(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());
Debug.WriteLine("Creating shadow copy folder and querying for dlls");
//ensure folders are created
Directory.CreateDirectory(pluginFolder.FullName);
Directory.CreateDirectory(_shadowCopyFolder.FullName);
//get list of all files in bin
var binFiles = _shadowCopyFolder.GetFiles("*", SearchOption.AllDirectories);
if (_clearShadowDirectoryOnStartup)
{
//clear out shadow copied plugins
foreach (var f in binFiles)
{
Debug.WriteLine("Deleting " + f.Name);
try
{
File.Delete(f.FullName);
}
catch (Exception exc)
{
Debug.WriteLine("Error deleting file " + f.Name + ". Exception: " + exc);
}
}
}
//load description files
foreach (var dfd in GetDescriptionFilesAndDescriptors(pluginFolder))
{
var descriptionFile = dfd.Key;
var pluginDescriptor = dfd.Value;
//ensure that version of plugin is valid
if (!pluginDescriptor.SupportedVersions.Contains(NopVersion.CurrentVersion, StringComparer.InvariantCultureIgnoreCase))
{
incompatiblePlugins.Add(pluginDescriptor.SystemName);
continue;
}
//some validation
if (String.IsNullOrWhiteSpace(pluginDescriptor.SystemName))
throw new Exception(string.Format("A plugin '{0}' has no system name. Try assigning the plugin a unique name and recompiling.", descriptionFile.FullName));
if (referencedPlugins.Contains(pluginDescriptor))
throw new Exception(string.Format("A plugin with '{0}' system name is already defined", pluginDescriptor.SystemName));
//set 'Installed' property
pluginDescriptor.Installed = installedPluginSystemNames
.FirstOrDefault(x => x.Equals(pluginDescriptor.SystemName, StringComparison.InvariantCultureIgnoreCase)) != null;
try
{
if (descriptionFile.Directory == null)
throw new Exception(string.Format("Directory cannot be resolved for '{0}' description file", descriptionFile.Name));
//get list of all DLLs in plugins (not in bin!)
var pluginFiles = descriptionFile.Directory.GetFiles("*.dll", SearchOption.AllDirectories)
//just make sure we're not registering shadow copied plugins
.Where(x => !binFiles.Select(q => q.FullName).Contains(x.FullName))
.Where(x => IsPackagePluginFolder(x.Directory))
.ToList();
//other plugin description info
var mainPluginFile = pluginFiles
.FirstOrDefault(x => x.Name.Equals(pluginDescriptor.PluginFileName, StringComparison.InvariantCultureIgnoreCase));
pluginDescriptor.OriginalAssemblyFile = mainPluginFile;
//shadow copy main plugin file
pluginDescriptor.ReferencedAssembly = PerformFileDeploy(mainPluginFile);
//load all other referenced assemblies now
foreach (var plugin in pluginFiles
.Where(x => !x.Name.Equals(mainPluginFile.Name, StringComparison.InvariantCultureIgnoreCase))
.Where(x => !IsAlreadyLoaded(x)))
PerformFileDeploy(plugin);
//init plugin type (only one plugin per assembly is allowed)
foreach (var t in pluginDescriptor.ReferencedAssembly.GetTypes())
if (typeof(IPlugin).IsAssignableFrom(t))
if (!t.IsInterface)
if (t.IsClass && !t.IsAbstract)
{
pluginDescriptor.PluginType = t;
break;
}
referencedPlugins.Add(pluginDescriptor);
}
catch (ReflectionTypeLoadException ex)
{
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));
}
//sort list by display order. NOTE: Lowest DisplayOrder will be first i.e 0 , 1, 1, 1, 5, 10
//it's required: http://www.nopcommerce.com/boards/t/17455/load-plugins-based-on-their-displayorder-on-startup.aspx
result.Sort((firstPair, nextPair) => firstPair.Value.DisplayOrder.CompareTo(nextPair.Value.DisplayOrder));
return result;
}
/// <summary>
/// Indicates whether assembly file is already loaded
/// </summary>
/// <param name="fileInfo">File info</param>
/// <returns>Result</returns>
private static bool IsAlreadyLoaded(FileInfo fileInfo)
{
//compare full assembly name
//var fileAssemblyName = AssemblyName.GetAssemblyName(fileInfo.FullName);
//foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
//{
// if (a.FullName.Equals(fileAssemblyName.FullName, StringComparison.InvariantCultureIgnoreCase))
// return true;
//}
//return false;
//do not compare the full assembly name, just filename
try
{
string fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileInfo.FullName);
if (fileNameWithoutExt == null)
throw new Exception(string.Format("Cannot get file 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
{
//delete an existing file
//More info: http://www.nopcommerce.com/boards/t/11511/access-error-nopplugindiscountrulesbillingcountrydll.aspx?p=4#60838
Debug.WriteLine("New plugin found; Deleting the old file: '{0}'", shadowCopiedPlug.Name);
File.Delete(shadowCopiedPlug.FullName);
}
}
if (shouldCopy)
{
try
{
File.Copy(plug.FullName, shadowCopiedPlug.FullName, true);
}
catch (IOException)
{
Debug.WriteLine(shadowCopiedPlug.FullName + " is locked, attempting to rename");
//this occurs when the files are locked,
//for some reason devenv locks plugin files some times and for another crazy reason you are allowed to rename them
//which releases the lock, so that it what we are doing here, once it's renamed, we can re-shadow copy
try
{
var oldFile = shadowCopiedPlug.FullName + Guid.NewGuid().ToString("N") + ".old";
File.Move(shadowCopiedPlug.FullName, oldFile);
}
catch (IOException exc)
{
throw new IOException(shadowCopiedPlug.FullName + " rename failed, cannot initialize plugin", exc);
}
//ok, we've made it this far, now retry the shadow copy
File.Copy(plug.FullName, shadowCopiedPlug.FullName, true);
}
}
return shadowCopiedPlug;
}
/// <summary>
/// Determines if the folder is a bin plugin folder for a package
/// </summary>
/// <param name="folder"></param>
/// <returns></returns>
private static bool IsPackagePluginFolder(DirectoryInfo folder)
{
if (folder == null) return false;
if (folder.Parent == null) return false;
if (!folder.Parent.Name.Equals("Plugins", StringComparison.InvariantCultureIgnoreCase)) return false;
return true;
}
/// <summary>
/// Gets the full path of InstalledPlugins.txt file
/// </summary>
/// <returns></returns>
private static string GetInstalledPluginsFilePath()
{
var filePath = HostingEnvironment.MapPath(InstalledPluginsFilePath);
return filePath;
}
#endregion
}
}
从注释可以看到 他是参考了Umbraco。
调用了
/// <summary>
/// Returns a collection of all referenced plugin assemblies that have been shadow copied
/// </summary>
public static IEnumerable<PluginDescriptor> ReferencedPlugins { get; set; }
Initialize方法应该对他进行了设置,看它都做了什么:
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());
Debug.WriteLine("Creating shadow copy folder and querying for dlls");
//ensure folders are created
Directory.CreateDirectory(pluginFolder.FullName);
Directory.CreateDirectory(_shadowCopyFolder.FullName);
//get list of all files in bin
var binFiles = _shadowCopyFolder.GetFiles("*", SearchOption.AllDirectories);
if (_clearShadowDirectoryOnStartup)
{
//clear out shadow copied plugins
foreach (var f in binFiles)
{
Debug.WriteLine("Deleting " + f.Name);
try
{
File.Delete(f.FullName);
}
catch (Exception exc)
{
Debug.WriteLine("Error deleting file " + f.Name + ". Exception: " + exc);
}
}
}
//load description files
foreach (var dfd in GetDescriptionFilesAndDescriptors(pluginFolder))
{
var descriptionFile = dfd.Key;
var pluginDescriptor = dfd.Value;
//ensure that version of plugin is valid
if (!pluginDescriptor.SupportedVersions.Contains(NopVersion.CurrentVersion, StringComparer.InvariantCultureIgnoreCase))
{
incompatiblePlugins.Add(pluginDescriptor.SystemName);
continue;
}
//some validation
if (String.IsNullOrWhiteSpace(pluginDescriptor.SystemName))
throw new Exception(string.Format("A plugin '{0}' has no system name. Try assigning the plugin a unique name and recompiling.", descriptionFile.FullName));
if (referencedPlugins.Contains(pluginDescriptor))
throw new Exception(string.Format("A plugin with '{0}' system name is already defined", pluginDescriptor.SystemName));
//set 'Installed' property
pluginDescriptor.Installed = installedPluginSystemNames
.FirstOrDefault(x => x.Equals(pluginDescriptor.SystemName, StringComparison.InvariantCultureIgnoreCase)) != null;
try
{
if (descriptionFile.Directory == null)
throw new Exception(string.Format("Directory cannot be resolved for '{0}' description file", descriptionFile.Name));
//get list of all DLLs in plugins (not in bin!)
var pluginFiles = descriptionFile.Directory.GetFiles("*.dll", SearchOption.AllDirectories)
//just make sure we're not registering shadow copied plugins
.Where(x => !binFiles.Select(q => q.FullName).Contains(x.FullName))
.Where(x => IsPackagePluginFolder(x.Directory))
.ToList();
//other plugin description info
var mainPluginFile = pluginFiles
.FirstOrDefault(x => x.Name.Equals(pluginDescriptor.PluginFileName, StringComparison.InvariantCultureIgnoreCase));
pluginDescriptor.OriginalAssemblyFile = mainPluginFile;
//shadow copy main plugin file
pluginDescriptor.ReferencedAssembly = PerformFileDeploy(mainPluginFile);
//load all other referenced assemblies now
foreach (var plugin in pluginFiles
.Where(x => !x.Name.Equals(mainPluginFile.Name, StringComparison.InvariantCultureIgnoreCase))
.Where(x => !IsAlreadyLoaded(x)))
PerformFileDeploy(plugin);
//init plugin type (only one plugin per assembly is allowed)
foreach (var t in pluginDescriptor.ReferencedAssembly.GetTypes())
if (typeof(IPlugin).IsAssignableFrom(t))
if (!t.IsInterface)
if (t.IsClass && !t.IsAbstract)
{
pluginDescriptor.PluginType = t;
break;
}
referencedPlugins.Add(pluginDescriptor);
}
catch (ReflectionTypeLoadException ex)
{
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;
}
}
try catch内部 根据 private const string InstalledPluginsFilePath = "~/App_Data/InstalledPlugins.txt"; 获得已安装插件名字的LIST<STRING>.
获得启动时清除目录为false。
调用GetDescriptionFilesAndDescriptors方法。:
/// <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));
}
//sort list by display order. NOTE: Lowest DisplayOrder will be first i.e 0 , 1, 1, 1, 5, 10
//it's required: http://www.nopcommerce.com/boards/t/17455/load-plugins-based-on-their-displayorder-on-startup.aspx
result.Sort((firstPair, nextPair) => firstPair.Value.DisplayOrder.CompareTo(nextPair.Value.DisplayOrder));
return result;
}
创建一个键值对单元类的List集合。
获取pluginFolder下的所有Description .txt文件,遍历。。:
/// <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;
}
就是确保 这个文件夹是plugin的直接子文件夹。
然后获得插件描述文件:
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 [] { ',' }, 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 [] {','}, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim()))
{
int storeId;
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;
}
最后根据DisplayOrder排序 返回List.
循环List根据值 ,如果不包含当前版本号,则加入到不兼容列表,并continue.
然后验证 要有systemName,并插件没哟重复。
然后根据安装文件LIST<STRING>列表查找是否已安装。
获取所有的plugin目录的DLL文件列表,并且不再BIN目录中,而且是直接子目录。
根据上步的列表找出与pluginDescriptor.PluginFileName同名的DLL文件。
然后赋值:
pluginDescriptor.OriginalAssemblyFile = mainPluginFile;
//shadow copy main plugin file
pluginDescriptor.ReferencedAssembly = PerformFileDeploy(mainPluginF

浙公网安备 33010602011771号