紙殼CMS的插件加載機制

紙殼CMS是一個開源的可視化設計CMS,經過拖拽,在線編輯的方式來建立網站。html

GitHub

https://github.com/SeriaWei/ZKEACMS.Coregit

歡迎Star,Fork,發PR。:)github

插件化設計

紙殼CMS是基於插件化設計的,能夠經過擴展插件來實現不一樣的功能。如何經過插件來擴展,能夠參考這篇文章:ide

http://www.zkea.net/codesnippet/detail/zkeacms-plugin-development.html網站

紙殼CMS的插件是相互獨立的,各插件的引用也相互獨立,即各插件均可引用各自須要的nuget包來達到目的。而不用把引用加到底層。ui

插件存放目錄

紙殼CMS的插件的存放目錄在開發環境和已發佈的程序中是不同的。在開發環境,插件和其它的項目統一放在src目錄下:spa

而發佈程序之後,插件會在wwwroot/Plugins目錄下:.net

因此,若是在開發過程當中要使用插件目錄時,須要使用特定的方法來獲取真實的目錄,如:插件

PluginBase.GetPath<SectionPlug>()

 

相關代碼

有關插件用到的全部相關代碼,都在 EasyFrameWork/Mvc/Plugin 目錄下:設計

插件加載

紙殼CMS在程序啓動時加載全部啓用的插件Loader.cs:

public IEnumerable<IPluginStartup> LoadEnablePlugins(IServiceCollection serviceCollection)
{
    var start = DateTime.Now;
    Loaders.AddRange(GetPlugins().Where(m => m.Enable && m.ID.IsNotNullAndWhiteSpace()).Select(m =>
    {
        var loader = new AssemblyLoader();
        loader.CurrentPath = m.RelativePath;
        var assemblyPath = Path.Combine(m.RelativePath, (HostingEnvironment.IsDevelopment() ? Path.Combine(AltDevelopmentPath) : string.Empty), m.FileName);

        Console.WriteLine("Loading: {0}", m.Name);

        var assemblies = loader.LoadPlugin(assemblyPath);
        assemblies.Each(assembly =>
        {
            if (!LoadedAssemblies.ContainsKey(assembly.FullName))
            {
                LoadedAssemblies.Add(assembly.FullName, assembly);
            }
        });
        return loader;
    }));
    Console.WriteLine("All plugins are loaded. Elapsed: {0}ms", (DateTime.Now - start).Milliseconds);
    return serviceCollection.ConfigurePlugin().BuildServiceProvider().GetPlugins();
}

AssemblyLoader

AssemblyLoader是加載插件DLL的關鍵,紙殼CMS主要經過它來加載插件,並加載插件的相關依賴,並註冊插件。

namespace Easy.Mvc.Plugin
{
    public class AssemblyLoader
    {
        private const string ControllerTypeNameSuffix = "Controller";
        private static bool Resolving { get; set; }
        public AssemblyLoader()
        {
            DependencyAssemblies = new List<Assembly>();
        }
        public string CurrentPath { get; set; }
        public string AssemblyPath { get; set; }
        public Assembly CurrentAssembly { get; private set; }
        public List<Assembly> DependencyAssemblies { get; private set; }
        private TypeInfo PluginTypeInfo = typeof(IPluginStartup).GetTypeInfo();
        public IEnumerable<Assembly> LoadPlugin(string path)
        {
            if (CurrentAssembly == null)
            {
                AssemblyPath = path;
                
                CurrentAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
                ResolveDenpendency(CurrentAssembly);
                RegistAssembly(CurrentAssembly);
                yield return CurrentAssembly;
                foreach (var item in DependencyAssemblies)
                {
                    yield return item;
                }
            }
            else { throw new Exception("A loader just can load one assembly."); }
        }

        private void ResolveDenpendency(Assembly assembly)
        {
            string currentName = assembly.GetName().Name;
            var dependencyCompilationLibrary = DependencyContext.Load(assembly)
                .CompileLibraries.Where(de => de.Name != currentName && !DependencyContext.Default.CompileLibraries.Any(m => m.Name == de.Name))
                .ToList();

            dependencyCompilationLibrary.Each(libaray =>
            {
                bool depLoaded = false;
                foreach (var item in libaray.Assemblies)
                {
                    var files = new DirectoryInfo(Path.GetDirectoryName(assembly.Location)).GetFiles(Path.GetFileName(item));
                    foreach (var file in files)
                    {
                        DependencyAssemblies.Add(AssemblyLoadContext.Default.LoadFromAssemblyPath(file.FullName));
                        depLoaded = true;
                        break;
                    }
                }
                if (!depLoaded)
                {
                    foreach (var item in libaray.ResolveReferencePaths())
                    {
                        if (File.Exists(item))
                        {
                            DependencyAssemblies.Add(AssemblyLoadContext.Default.LoadFromAssemblyPath(item));
                            break;
                        }
                    }
                }
            });


        }

        private void RegistAssembly(Assembly assembly)
        {
            List<TypeInfo> controllers = new List<TypeInfo>();
            PluginDescriptor plugin = null;
            foreach (var typeInfo in assembly.DefinedTypes)
            {
                if (typeInfo.IsAbstract || typeInfo.IsInterface) continue;

                if (IsController(typeInfo) && !controllers.Contains(typeInfo))
                {
                    controllers.Add(typeInfo);
                }
                else if (PluginTypeInfo.IsAssignableFrom(typeInfo))
                {
                    plugin = new PluginDescriptor();
                    plugin.PluginType = typeInfo.AsType();
                    plugin.Assembly = assembly;
                    plugin.CurrentPluginPath = CurrentPath;
                }
            }
            if (controllers.Count > 0 && !ActionDescriptorProvider.PluginControllers.ContainsKey(assembly.FullName))
            {
                ActionDescriptorProvider.PluginControllers.Add(assembly.FullName, controllers);
            }
            if (plugin != null)
            {
                PluginActivtor.LoadedPlugins.Add(plugin);
            }
        }
        protected bool IsController(TypeInfo typeInfo)
        {
            if (!typeInfo.IsClass)
            {
                return false;
            }

            if (typeInfo.IsAbstract)
            {
                return false;
            }


            if (!typeInfo.IsPublic)
            {
                return false;
            }

            if (typeInfo.ContainsGenericParameters)
            {
                return false;
            }

            if (typeInfo.IsDefined(typeof(NonControllerAttribute)))
            {
                return false;
            }

            if (!typeInfo.Name.EndsWith(ControllerTypeNameSuffix, StringComparison.OrdinalIgnoreCase) &&
                !typeInfo.IsDefined(typeof(ControllerAttribute)))
            {
                return false;
            }

            return true;
        }
    }
}

註冊插件時,須要將插件中的全部Controller分析出來,當用戶訪問到插件的對應Controller時,才能夠實例化Controller並調用。

動態編譯插件視圖

ASP.NET MVC 的視圖(cshtml)是能夠動態編譯的。但因爲插件是動態加載的,編譯器並不知道編譯視圖所須要的引用在什麼地方,這會致使插件中的視圖編譯失敗。而且程序也須要告訴編譯器到哪裏去找這個視圖。PluginRazorViewEngineOptionsSetup.cs 便起到了這個做用。

因爲開發環境的目錄不一樣,對以針對開發環境,須要一個視圖文件提供程序來解析視圖文件位置:

if (hostingEnvironment.IsDevelopment())
{
    options.FileProviders.Add(new DeveloperViewFileProvider(hostingEnvironment));
}

loader.GetPlugins().Where(m => m.Enable && m.ID.IsNotNullAndWhiteSpace()).Each(m =>
{
    var directory = new DirectoryInfo(m.RelativePath);
    if (hostingEnvironment.IsDevelopment())
    {
        options.ViewLocationFormats.Add($"{DeveloperViewFileProvider.ProjectRootPath}{directory.Name}" + "/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
        options.ViewLocationFormats.Add($"{DeveloperViewFileProvider.ProjectRootPath}{directory.Name}" + "/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
        options.ViewLocationFormats.Add($"{DeveloperViewFileProvider.ProjectRootPath}{directory.Name}" + "/Views/{0}" + RazorViewEngine.ViewExtension);
    }
    else
    {
        options.ViewLocationFormats.Add($"/wwwroot/{Loader.PluginFolder}/{directory.Name}" + "/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
        options.ViewLocationFormats.Add($"/wwwroot/{Loader.PluginFolder}/{directory.Name}" + "/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
        options.ViewLocationFormats.Add($"/wwwroot/{Loader.PluginFolder}/{directory.Name}" + "/Views/{0}" + RazorViewEngine.ViewExtension);
    }
});
options.ViewLocationFormats.Add("/Views/{0}" + RazorViewEngine.ViewExtension);

爲了解決引用問題,須要把插件相關的全部引用都加入到編譯環境中:

loader.GetPluginAssemblies().Each(assembly =>
{
    var reference = MetadataReference.CreateFromFile(assembly.Location);
    options.AdditionalCompilationReferences.Add(reference);                
});
相關文章
相關標籤/搜索