• 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 (
     becomes 
    ).
                //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(mainPluginFile);
  • 相关阅读:
    idea 新建maven项目时,避免每次都需要指定自己的maven目录
    springboot2.X版本得@Transactional注解事务不回滚不起作用
    SpringBoot事务注解@Transactional
    #{}, ${}取值区别
    Mybaits多个参数的传递
    Mybaits基本的CURD操作
    mappers:将sql映射注册到全局配置中
    Mybaits配置多个数据库操作sql环境
    为java类起别名
    Mybaits成长之路
  • 原文地址:https://www.cnblogs.com/runit/p/4211401.html
Copyright © 2020-2023  润新知