Asp.net Vnext 模块化实现
概述
本文已经同步到《Asp.net Vnext 系列教程 》中]
在程序中实现模块化可以加快开发效率,通过替换模块实现升级.
架构
vnext 没有 Virtualpathprovider,本文通过IFileProvider实现模块
ModularVNext.Startup 启动类
public class Startup { private IFileProvider _modulesFileProvider; private readonly string ApplicationBasePath; private readonly IAssemblyLoadContextAccessor _assemblyLoadContextAccessor; private readonly IAssemblyLoaderContainer _assemblyLoaderContainer; public Startup(IHostingEnvironment hostingEnvironment, IApplicationEnvironment applicationEnvironment, IAssemblyLoaderContainer assemblyLoaderContainer, IAssemblyLoadContextAccessor assemblyLoadContextAccessor) { //程序集加载上下文访问 _assemblyLoadContextAccessor = assemblyLoadContextAccessor; //程序集加载容器 _assemblyLoaderContainer = assemblyLoaderContainer; //程序基本路径 ApplicationBasePath = applicationEnvironment.ApplicationBasePath; Configuration = new Configuration() .AddJsonFile("config.json") .AddEnvironmentVariables(); } public IConfiguration Configuration { get; set; } public void ConfigureServices(IServiceCollection services) { var basePaths = Configuration.Get("additionalFileProviderBasePaths")?.Split(';') ?? new string[] { }; var modulesPath = Path.Combine(ApplicationBasePath.Substring(0,ApplicationBasePath.IndexOf("src")), Configuration.Get("moduleLoadPath")); var moduleAssemblies = LoadAssembliesFrom(modulesPath, _assemblyLoaderContainer, _assemblyLoadContextAccessor); _modulesFileProvider = GetModulesFileProvider(basePaths, moduleAssemblies); services.AddInstance(Configuration); services.AddMvc(); services.Configure<RazorViewEngineOptions>(o => { o.FileProvider = _modulesFileProvider; }); services.AddInstance(new ModuleAssemblyLocator(moduleAssemblies)); services.AddTransient<DefaultAssemblyProvider>(); services.AddTransient<IAssemblyProvider, ModuleAwareAssemblyProvider>(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory) { loggerfactory.AddConsole(); if (string.Equals(env.EnvironmentName, "Development", StringComparison.OrdinalIgnoreCase)) { app.UseBrowserLink(); app.UseErrorPage(ErrorPageOptions.ShowAll); } else { app.UseErrorHandler("/Home/Error"); } app.UseStaticFiles(new StaticFileOptions { FileProvider = _modulesFileProvider }); app.UseMvc(routes => { routes.MapRoute( name: "areaRoute", template: "{area:exists}/{controller}/{action}", defaults: new { controller = "Home", action = "Index" } ); routes.MapRoute( name: "default", template: "{controller}/{action}/{id?}", defaults: new { controller = "Home", action = "Index" }); }); } private List<Assembly> LoadAssembliesFrom(string modulesDirectory, IAssemblyLoaderContainer assemblyLoaderContainer, IAssemblyLoadContextAccessor loadContextAccessor) { var assemblies = new List<Assembly>(); var loadContext = _assemblyLoadContextAccessor.GetLoadContext(typeof(Startup).GetTypeInfo().Assembly); using (assemblyLoaderContainer.AddLoader(new DirectoryLoader(modulesDirectory, loadContext))) { foreach (var modulePath in Directory.EnumerateFiles(@"C:\Users\Administrator\Documents\Visual Studio 2015\Projects1\ModularVNext\artifacts\bin\Module1\Debug\dnx451", "*.dll")) { var name = Path.GetFileNameWithoutExtension(modulePath); assemblies.Add(loadContext.Load(name)); } } return assemblies; } private IFileProvider GetModulesFileProvider(string[] basePaths, List<Assembly> moduleAssemblies) { var redirectedFileProviders = basePaths .Select(path => Path.IsPathRooted(path) ? path : Path.Combine(ApplicationBasePath, path)) .Select(root => new PhysicalFileProvider(root)).ToList(); var resourceFileProviders = moduleAssemblies.Select(a => new SafeEmbeddedFileProvider(a)).ToList(); var redirectedFileProviders1= redirectedFileProviders.Select(x => x as IFileProvider); var resourceFileProviders1= resourceFileProviders.Select(x => x as IFileProvider); var co= redirectedFileProviders1.Concat<IFileProvider>(resourceFileProviders1).ToList(); IFileProvider rootProvider = new PhysicalFileProvider(ApplicationBasePath); co.Add(rootProvider); return new CompositeFileProvider(co); } }
ConfigureServices 方法主要是获取模块程序集和模块程序提供者
var moduleAssemblies = LoadAssembliesFrom(modulesPath, _assemblyLoaderContainer, _assemblyLoadContextAccessor);
通过LoadAssembliesFrom方法获取模块程序集
参数:
modulesPath 模块的路径
也就是 Module1的程序集路径
选择该选项,然后编译就可以输出程序集了
在根目录下\artifacts\bin\Module1\Debug\会产生两个程序集分别是 dnx451 和 dnxcore50 ,这里我选择的是dnx451
集体实现
private List<Assembly> LoadAssembliesFrom(string modulesDirectory, IAssemblyLoaderContainer assemblyLoaderContainer, IAssemblyLoadContextAccessor loadContextAccessor) { var assemblies = new List<Assembly>(); //程序集加载上下文 var loadContext = _assemblyLoadContextAccessor.GetLoadContext(typeof(Startup).GetTypeInfo().Assembly); //添加程序集加载 using (assemblyLoaderContainer.AddLoader(new DirectoryLoader(modulesDirectory, loadContext))) { //找出文件夹下的程序集,我这里写的是硬编码 foreach (var modulePath in Directory.EnumerateFiles(@"C:\Users\Administrator\Documents\Visual Studio 2015\Projects1\ModularVNext\artifacts\bin\Module1\Debug\dnx451", "*.dll")) { var name = Path.GetFileNameWithoutExtension(modulePath); //加载程序集 assemblies.Add(loadContext.Load(name)); } } return assemblies; }
返回程序集在条用_modulesFileProvider 获取文件提供者然后把获取到的文件提供者设置到RazorViewEngineOptions
services.Configure<RazorViewEngineOptions>(o => { o.FileProvider = _modulesFileProvider; });
ModularVNext.Infrastructure.CompositeFileProvider这是实现IFileProvider的类
public class CompositeFileProvider : IFileProvider { //提供者目录 private List<IFileProvider> _fileProviders; //触发器 private readonly Dictionary<string, TestFileTrigger> _fileTriggers = new Dictionary<string, TestFileTrigger>(StringComparer.Ordinal); public CompositeFileProvider(IEnumerable<IFileProvider> fileProviders) { _fileProviders = fileProviders.ToList(); } public IDirectoryContents GetDirectoryContents(string subpath) { foreach (var fileProvider in _fileProviders) { //更具subpat获取目录 var contents = fileProvider.GetDirectoryContents(subpath); if (contents != null && contents.Exists) { return contents; } } return new NotFoundDirectoryContents(); } public IFileInfo GetFileInfo(string subpath) { foreach (var fileProvider in _fileProviders) {//更具subpat获取文件信息 var fileInfo = fileProvider.GetFileInfo(subpath); if (fileInfo != null && fileInfo.Exists) { return fileInfo; } } return new NotFoundFileInfo(subpath); } public IExpirationTrigger Watch(string filter) { //触发器 TestFileTrigger trigger; trigger = new TestFileTrigger(); return trigger; } } internal class TestFileTrigger : IExpirationTrigger { public bool ActiveExpirationCallbacks { get { return false; } } private CancellationToken Token { get; set; } public bool IsExpired { get { return false; } } public IDisposable RegisterExpirationCallback(Action<object> callback, object state) { return null; } }
没有对Module1引用
运行程序