目前在学习ASP.NET CORE MVC中,今天看到微软在ASP.NET CORE MVC 2.0中又恢复了允许开发人员引用第三方DLL程序集的功能,感到甚是高兴!于是我急忙写了个Demo想试试,我的项目结构如下:
可以看到解决方案中就两个项目,AspNetCoreWebApp就是一个ASP.NET CORE MVC 2.0的项目,而MyNetCoreLib是一个.Net Core 2.0的类库项目,为了体现AspNetCoreWebApp是通过程序集来引用MyNetCoreLib的,我还在解决方案中创建了一个文件夹叫Reference,将类库项目MyNetCoreLib编译后生成的DLL文件放到了Reference文件夹中,然后再AspNetCoreWebApp中通过添加引用程序集的方式引用了MyNetCoreLib.dll,如下图所示:
然后编译整个解决方案,调试AspNetCoreWebApp这个项目,运行立马报错。。。错误如下:
这明显是一个运行时错误,因我在编译整个解决方案的时候是成功的,没有报任何错误。后来去网上查了查资料,发现虽然我们在项目AspNetCoreWebApp中引用了MyNetCoreLib.dll,而且项目AspNetCoreWebApp编译后也在其Bin目录下输出了MyNetCoreLib.dll这个文件,如下图所示:
但是ASP.NET CORE MVC的依赖注入环境其实并不知道该到哪里去找MyNetCoreLib.dll这个文件,所以才会在运行时报出InvalidOperationException: Cannot find compilation library location for package ‘MyNetCoreLib‘这种错误。。。开发人员需要用代码去告诉ASP.NET CORE MVC应该到哪里去找到MyNetCoreLib.dll这个文件。
因此首先我们需要定义一个叫MetadataReferenceFeatureProvider的类,代码如下,其关键代码就是告诉ASP.NET CORE MVC的依赖注入环境去AppDomain.CurrentDomain.BaseDirectory(也就是Bin目录)下找我们在项目中引用的程序集文件
using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.CodeAnalysis; using Microsoft.Extensions.DependencyModel; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection.PortableExecutable; using System.Threading.Tasks; namespace AspNetCoreWebApp.Utils { public class ReferencesMetadataReferenceFeatureProvider : IApplicationFeatureProvider<MetadataReferenceFeature> { public void PopulateFeature(IEnumerable<ApplicationPart> parts, MetadataReferenceFeature feature) { var libraryPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (var assemblyPart in parts.OfType<AssemblyPart>()) { var dependencyContext = DependencyContext.Load(assemblyPart.Assembly); if (dependencyContext != null) { foreach (var library in dependencyContext.CompileLibraries) { if (string.Equals("reference", library.Type, StringComparison.OrdinalIgnoreCase)) { foreach (var libraryAssembly in library.Assemblies) { //告诉ASP.NET CORE MVC如果现在项目中有引用第三方程序集,要到AppDomain.CurrentDomain.BaseDirectory这个文件夹(就是Bin目录)下去寻找该程序集的dll文件 libraryPaths.Add(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, libraryAssembly)); } } else { foreach (var path in library.ResolveReferencePaths()) { libraryPaths.Add(path); } } } } else { libraryPaths.Add(assemblyPart.Assembly.Location); } } foreach (var path in libraryPaths) { feature.MetadataReferences.Add(CreateMetadataReference(path)); } } private static MetadataReference CreateMetadataReference(string path) { using (var stream = File.OpenRead(path)) { var moduleMetadata = ModuleMetadata.CreateFromStream(stream, PEStreamOptions.PrefetchMetadata); var assemblyMetadata = AssemblyMetadata.Create(moduleMetadata); return assemblyMetadata.GetReference(filePath: path); } } } }
其次我们还要在项目AspNetCoreWebApp的Startup.cs文件中的services.AddMvc()方法上注册我们定义的这个Provider,代码如下(注意ConfigureServices方法中的代码):
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Mvc.Razor.Compilation; using AspNetCoreWebApp.Utils; namespace AspNetCoreWebApp { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMvc().ConfigureApplicationPartManager(manager => { //移除ASP.NET CORE MVC管理器中默认内置的MetadataReferenceFeatureProvider,该Provider如果不移除,还是会引发InvalidOperationException: Cannot find compilation library location for package ‘MyNetCoreLib‘这个错误 manager.FeatureProviders.Remove(manager.FeatureProviders.First(f => f is MetadataReferenceFeatureProvider)); //注册我们定义的ReferencesMetadataReferenceFeatureProvider到ASP.NET CORE MVC管理器来代替上面移除的MetadataReferenceFeatureProvider manager.FeatureProviders.Add(new ReferencesMetadataReferenceFeatureProvider()); }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseBrowserLink(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } } }
然后重新编译代码,调试项目AspNetCoreWebApp,好了这下项目成功运行了,没有报任何错误。
也不知道本文讨论的这个问题是ASP.NET CORE MVC 2.0的一个缺陷,会在以后版本中修复,还是微软故意为之?因为我试了下在.NET CORE 2.0的控制台项目中,直接引用第三方程序集DLL文件是完全没问题的,不需要写任何额外的代码就可以使用。既然微软在ASP.NET CORE MVC中也开放了引用第三方程序集这个功能,其实就完全可以把它做的和老.Net Framework一样,自动去Bin目录下面寻找DLL文件即可,希望ASP.NET CORE MVC以后的版本能够完善这个功能,不再需要开发人员在引用DLL文件后,还要添加额外的代码。