紙殼CMS是一個開源的可視化設計CMS,通過拖拽,線上編輯的方式來創建網站。紙殼CMS是基於插件化設計的,可以通過擴展插件來實現不同的功能。並且紙殼CMS的插件是相互獨立的,各插件的引用也相互獨立,即各插件都可引用各自需要的nuget包來達到目的。而不用把引用加到底層。 ...
紙殼CMS是一個開源的可視化設計CMS,通過拖拽,線上編輯的方式來創建網站。
GitHub
https://github.com/SeriaWei/ZKEACMS.Core
歡迎Star,Fork,發PR。:)
插件化設計
紙殼CMS是基於插件化設計的,可以通過擴展插件來實現不同的功能。如何通過插件來擴展,可以參考這篇文章:
http://www.zkea.net/codesnippet/detail/zkeacms-plugin-development.html
紙殼CMS的插件是相互獨立的,各插件的引用也相互獨立,即各插件都可引用各自需要的nuget包來達到目的。而不用把引用加到底層。
插件存放目錄
紙殼CMS的插件的存放目錄在開發環境和已發佈的程式中是不一樣的。在開發環境,插件和其它的項目統一放在src目錄下:
而發佈程式以後,插件會在wwwroot/Plugins目錄下:
所以,如果在開發過程中要使用插件目錄時,需要使用特定的方法來獲取真實的目錄,如:
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); });