使用 Autofac 4 和 vNext 自注册库
Self-Registering Libraries with Autofac 4 and vNext
我想为我的 ASP.Net 5.0 / MVC 6 应用程序创建一个插件环境。我使用 Autofac 作为 IOC 容器,我喜欢从 DNX LibraryManager 中的构建加载插件(Class 库)。使用库管理器的目的是,我不必关心 NuGet 包和框架。
我遇到的问题是生命周期,我必须在 LibraryManager 实例可用之前构建 IOC 容器。因为 Autofac 容器提供了他自己的 IServiceProvider 实例,我必须在 ConfigureService() 方法调用 (AddAutofac) 中注入它。
有人知道如何让它工作吗?
更新:我已经在 David 的帮助下解决了我的问题,并更新了代码以使其与发布候选版本一起使用。我还添加了对配置的支持。
在我的 DNX Class 库中,我实现了一个 Class 用于自注册:
public class AutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.Register(c => new SimpleService())
.As<IService>()
.InstancePerLifetimeScope();
}
}
在我的 MVC WebApplication 中,我添加了 Class 库作为依赖项。
Startup.cs
public IConfiguration Configuration { get; set; }
public class Startup
{
public Startup( IApplicationEnvironment applicationEnvironment )
{
IConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
configurationBuilder.SetBasePath( applicationEnvironment.ApplicationBasePath );
configurationBuilder.AddJsonFile( "appsettings.json" );
configurationBuilder.AddJsonFile( "autofac.json" );
configurationBuilder.AddEnvironmentVariables();
this.Configuration = configurationBuilder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDependencies();
}
public void Configure(IApplicationBuilder applicationBuilder, IHostingEnvironment hostingEnvironment)
{
applicationBuilder.UseDependencies( this.Configuration );
applicationBuilder.UseStaticFiles();
applicationBuilder.UseMvc();
}
}
我创建了一个 DependencyResolver 来保留 ContainerBuilder 实例。
DependencyResolver.cs
public class DependencyResolver : IDependencyResolver
{
private IContainer container;
private readonly ContainerBuilder builder;
public DependencyResolver()
{
this.builder = new ContainerBuilder();
}
public void RegisterModule( IModule module )
{
this.builder.RegisterModule( module );
}
public void RegisterModules( IEnumerable<Assembly> assemblies )
{
this.builder.RegisterAssemblyModules(assemblies.ToArray());
}
public void Populate( IServiceCollection services)
{
this.builder.Populate( services );
}
public void Build()
{
this.container = this.builder.Build();
}
public T Resolve<T>() where T : class
{
return this.container?.Resolve<T>();
}
}
我DependencyResolver.cs
public interface IDependencyResolver
{
void RegisterModule( IModule module );
void RegisterModules( IEnumerable<Assembly> assemblies );
void Populate(IServiceCollection services);
void Build();
T Resolve<T>() where T : class;
}
最后但同样重要的是我创建了一个扩展 Class
DependencyResolverExtensions.cs
public static class DependencyResolverExtensions
{
public static IServiceCollection AddDependencies( this IServiceCollection services )
{
DependencyResolver dependencyResolver = new DependencyResolver();
dependencyResolver.Populate(services);
ServiceDescriptor serviceDescriptor = new ServiceDescriptor(typeof ( IDependencyResolver ), dependencyResolver );
services.TryAdd(serviceDescriptor);
return services;
}
public static IApplicationBuilder UseDependencies(this IApplicationBuilder applicationBuilder, IConfiguration configuration)
{
IDependencyResolver dependencyResolver = applicationBuilder.GetService<IDependencyResolver>();
if (dependencyResolver == null) return applicationBuilder;
ILibraryManager libraryManager = applicationBuilder.GetService<ILibraryManager>();
if (libraryManager == null) return applicationBuilder;
IEnumerable<Assembly> assemblies = libraryManager.GetLoadableAssemblies();
dependencyResolver.RegisterModules(assemblies);
ConfigurationModule configurationModule = new ConfigurationModule( configuration );
dependencyResolver.RegisterModule( configurationModule );
dependencyResolver.Build();
IServiceProvider serviceProvider = dependencyResolver.Resolve<IServiceProvider>();
applicationBuilder.ApplicationServices = serviceProvider;
return applicationBuilder;
}
public static IEnumerable<Assembly> GetLoadableAssemblies(this ILibraryManager libraryManager)
{
List<Assembly> result = new List<Assembly>();
IEnumerable<Library> libraries = libraryManager.GetLibraries();
IEnumerable<AssemblyName> assemblyNames = libraries.SelectMany(e => e.Assemblies).Distinct();
assemblyNames = Enumerable.Where(assemblyNames, e => e.Name.StartsWith("MyLib."));
foreach (AssemblyName assemblyName in assemblyNames)
{
Assembly assembly = Assembly.Load(assemblyName);
result.Add(assembly);
}
return result;
}
public static T GetService<T>(this IApplicationBuilder applicationBuilder) where T : class
{
return applicationBuilder.ApplicationServices.GetService(typeof (T)) as T;
}
}
如果您需要在不同的实现之间切换,例如模拟数据和真实数据,您可以使用 Autofac 配置。
autofac.json
{
"components": [
{
"type": "MyLib.Data.EF.EntitiesData, MyLib.Data.EF",
"services": [
{
"type": "MyLib.Abstractions.IDataRepository, MyLib.Abstractions"
}
]
}
]
}
遗憾的是 ConfigureServices 不可注入,这会让这件事变得容易得多。
查看代码,您应该可以安全地替换 Configure(...)
中的 IServiceProvider
而不是 ConfigureServices(...)
中的 IServiceProvider
并获得预期的行为。 ApplicationServices
is setable.
在您的 UseAutofac
方法中,您应该能够执行如下操作:
public static IApplicationBuilder UseAutofac( [NotNull] this IApplicationBuilder applicationBuilder )
{
IAutofacResolver autofacResolver = applicationBuilder.GetService<IAutofacResolver>();
ILibraryManager libraryManager = applicationBuilder.GetService<ILibraryManager>();
autofacResolver.RegisterLibraryModules( libraryManager);
applicationBuilder.ApplicationServices = autofacResolver.Resolve();
return applicationBuilder;
}
我想出了一个解决方案,它使用了其中的一部分,但也使用了一个 ComponentContainer 来解决 DependencyResolver 中潜在的内存泄漏问题。这也适用于 RC1。还不确定 RC2,因为它不够完整,我无法测试。
ComponentContainer 看起来像这样:
public static class ComponentContainer {
static IContainer _container;
static ContainerBuilder _containerBuilder;
public static ContainerBuilder Builder {
get {
if (_containerBuilder == null)
_containerBuilder = new ContainerBuilder();
return _containerBuilder;
}
}
public static IServiceProvider ServiceProvider {
get {
if (_container == null)
_container = _containerBuilder.Build();
return _container.Resolve<IServiceProvider>();
}
}
public static ComponentFactory<TObject> Component<TObject>() => new ComponentFactory<TObject>(_container);
public static void RegisterAssembly(Assembly assembly) {
if (assembly == null) return;
foreach (var obj in assembly.GetTypes().Where(t => t.GetCustomAttribute<ExportAttribute>() != null)) {
ExportAttribute att = obj.GetCustomAttribute<ExportAttribute>();
if (att.ContractType != null) {
_containerBuilder.RegisterType(obj).As(att.ContractType);
} else {
foreach (var intf in obj.GetInterfaces())
_containerBuilder.RegisterType(obj).As(intf);
}
}
}
}
public class ComponentFactory<TObject> : IDisposable {
protected TObject CurrentObject;
protected ILifetimeScope CurrentScope;
public TObject Current => (TObject)CurrentObject;
public ComponentFactory(IContainer container) {
CurrentScope = container.BeginLifetimeScope();
CurrentObject = CurrentScope.Resolve<TObject>();
}
public TObject Component => CurrentObject;
public void Dispose() {
(CurrentObject as IDisposable)?.Dispose();
CurrentScope.Dispose();
}
}
然后在 Startup.cs 中我执行以下操作:
public virtual IServiceProvider ConfigureServices(IServiceCollection services) {
services.AddMvc();
services.AddOptions();
services.AddSession();
services.AddCaching();
var assemblyLoadContextAccessor = services.FirstOrDefault(s => s.ServiceType == typeof(IAssemblyLoadContextAccessor)).ImplementationInstance as IAssemblyLoadContextAccessor;
var libraryManager = services.FirstOrDefault(s => s.ServiceType == typeof(ILibraryManager)).ImplementationInstance as ILibraryManager;
var loadContext = assemblyLoadContextAccessor.Default;
foreach(var library in libraryManager.GetLibraries()) {
var assembly = loadContext.Load(library.Name);
if(assembly != null) {
var module = assembly.GetTypes().FirstOrDefault(t => t == typeof(IModule));
if(module != null)
ComponentContainer.Builder.RegisterAssemblyModules(assembly);
else
ComponentContainer.RegisterAssembly(assembly);
}
}
ComponentContainer.Builder.Populate(services);
return ComponentContainer.ServiceProvider;
}
要在程序集中导出模块,我要么用 ExportAttribute
标记它们,要么将 class 添加到实现 Autofac 的 IModule 的程序集中。 ConfigureServices 中的代码将枚举应用程序的模块并将它们提供给 ComponentContainer 中的静态 Builder。构建容器后,您可以通过在构造函数中注入来解析模块,也可以通过以下方式请求特定类型:
(using var myComponentFactory = ComponentContainer.Component<IMyModule>()) {
//You can now access your component through myComponentFactory.Component
//Once it passes out of scope of using, it will be properly disposed of
//along with the scope from which it was created.
}
编辑: 随着 RC2 的发布,此代码不再有效,因为程序集的枚举和 classes 将失败。我还没有想出一个好的解决方案。如果其他人对在 RC2 中枚举程序集有任何建议,请告诉我。
我想为我的 ASP.Net 5.0 / MVC 6 应用程序创建一个插件环境。我使用 Autofac 作为 IOC 容器,我喜欢从 DNX LibraryManager 中的构建加载插件(Class 库)。使用库管理器的目的是,我不必关心 NuGet 包和框架。
我遇到的问题是生命周期,我必须在 LibraryManager 实例可用之前构建 IOC 容器。因为 Autofac 容器提供了他自己的 IServiceProvider 实例,我必须在 ConfigureService() 方法调用 (AddAutofac) 中注入它。
有人知道如何让它工作吗?
更新:我已经在 David 的帮助下解决了我的问题,并更新了代码以使其与发布候选版本一起使用。我还添加了对配置的支持。
在我的 DNX Class 库中,我实现了一个 Class 用于自注册:
public class AutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.Register(c => new SimpleService())
.As<IService>()
.InstancePerLifetimeScope();
}
}
在我的 MVC WebApplication 中,我添加了 Class 库作为依赖项。
Startup.cs
public IConfiguration Configuration { get; set; }
public class Startup
{
public Startup( IApplicationEnvironment applicationEnvironment )
{
IConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
configurationBuilder.SetBasePath( applicationEnvironment.ApplicationBasePath );
configurationBuilder.AddJsonFile( "appsettings.json" );
configurationBuilder.AddJsonFile( "autofac.json" );
configurationBuilder.AddEnvironmentVariables();
this.Configuration = configurationBuilder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDependencies();
}
public void Configure(IApplicationBuilder applicationBuilder, IHostingEnvironment hostingEnvironment)
{
applicationBuilder.UseDependencies( this.Configuration );
applicationBuilder.UseStaticFiles();
applicationBuilder.UseMvc();
}
}
我创建了一个 DependencyResolver 来保留 ContainerBuilder 实例。
DependencyResolver.cs
public class DependencyResolver : IDependencyResolver
{
private IContainer container;
private readonly ContainerBuilder builder;
public DependencyResolver()
{
this.builder = new ContainerBuilder();
}
public void RegisterModule( IModule module )
{
this.builder.RegisterModule( module );
}
public void RegisterModules( IEnumerable<Assembly> assemblies )
{
this.builder.RegisterAssemblyModules(assemblies.ToArray());
}
public void Populate( IServiceCollection services)
{
this.builder.Populate( services );
}
public void Build()
{
this.container = this.builder.Build();
}
public T Resolve<T>() where T : class
{
return this.container?.Resolve<T>();
}
}
我DependencyResolver.cs
public interface IDependencyResolver
{
void RegisterModule( IModule module );
void RegisterModules( IEnumerable<Assembly> assemblies );
void Populate(IServiceCollection services);
void Build();
T Resolve<T>() where T : class;
}
最后但同样重要的是我创建了一个扩展 Class
DependencyResolverExtensions.cs
public static class DependencyResolverExtensions
{
public static IServiceCollection AddDependencies( this IServiceCollection services )
{
DependencyResolver dependencyResolver = new DependencyResolver();
dependencyResolver.Populate(services);
ServiceDescriptor serviceDescriptor = new ServiceDescriptor(typeof ( IDependencyResolver ), dependencyResolver );
services.TryAdd(serviceDescriptor);
return services;
}
public static IApplicationBuilder UseDependencies(this IApplicationBuilder applicationBuilder, IConfiguration configuration)
{
IDependencyResolver dependencyResolver = applicationBuilder.GetService<IDependencyResolver>();
if (dependencyResolver == null) return applicationBuilder;
ILibraryManager libraryManager = applicationBuilder.GetService<ILibraryManager>();
if (libraryManager == null) return applicationBuilder;
IEnumerable<Assembly> assemblies = libraryManager.GetLoadableAssemblies();
dependencyResolver.RegisterModules(assemblies);
ConfigurationModule configurationModule = new ConfigurationModule( configuration );
dependencyResolver.RegisterModule( configurationModule );
dependencyResolver.Build();
IServiceProvider serviceProvider = dependencyResolver.Resolve<IServiceProvider>();
applicationBuilder.ApplicationServices = serviceProvider;
return applicationBuilder;
}
public static IEnumerable<Assembly> GetLoadableAssemblies(this ILibraryManager libraryManager)
{
List<Assembly> result = new List<Assembly>();
IEnumerable<Library> libraries = libraryManager.GetLibraries();
IEnumerable<AssemblyName> assemblyNames = libraries.SelectMany(e => e.Assemblies).Distinct();
assemblyNames = Enumerable.Where(assemblyNames, e => e.Name.StartsWith("MyLib."));
foreach (AssemblyName assemblyName in assemblyNames)
{
Assembly assembly = Assembly.Load(assemblyName);
result.Add(assembly);
}
return result;
}
public static T GetService<T>(this IApplicationBuilder applicationBuilder) where T : class
{
return applicationBuilder.ApplicationServices.GetService(typeof (T)) as T;
}
}
如果您需要在不同的实现之间切换,例如模拟数据和真实数据,您可以使用 Autofac 配置。
autofac.json
{
"components": [
{
"type": "MyLib.Data.EF.EntitiesData, MyLib.Data.EF",
"services": [
{
"type": "MyLib.Abstractions.IDataRepository, MyLib.Abstractions"
}
]
}
]
}
遗憾的是 ConfigureServices 不可注入,这会让这件事变得容易得多。
查看代码,您应该可以安全地替换 Configure(...)
中的 IServiceProvider
而不是 ConfigureServices(...)
中的 IServiceProvider
并获得预期的行为。 ApplicationServices
is setable.
在您的 UseAutofac
方法中,您应该能够执行如下操作:
public static IApplicationBuilder UseAutofac( [NotNull] this IApplicationBuilder applicationBuilder )
{
IAutofacResolver autofacResolver = applicationBuilder.GetService<IAutofacResolver>();
ILibraryManager libraryManager = applicationBuilder.GetService<ILibraryManager>();
autofacResolver.RegisterLibraryModules( libraryManager);
applicationBuilder.ApplicationServices = autofacResolver.Resolve();
return applicationBuilder;
}
我想出了一个解决方案,它使用了其中的一部分,但也使用了一个 ComponentContainer 来解决 DependencyResolver 中潜在的内存泄漏问题。这也适用于 RC1。还不确定 RC2,因为它不够完整,我无法测试。
ComponentContainer 看起来像这样:
public static class ComponentContainer {
static IContainer _container;
static ContainerBuilder _containerBuilder;
public static ContainerBuilder Builder {
get {
if (_containerBuilder == null)
_containerBuilder = new ContainerBuilder();
return _containerBuilder;
}
}
public static IServiceProvider ServiceProvider {
get {
if (_container == null)
_container = _containerBuilder.Build();
return _container.Resolve<IServiceProvider>();
}
}
public static ComponentFactory<TObject> Component<TObject>() => new ComponentFactory<TObject>(_container);
public static void RegisterAssembly(Assembly assembly) {
if (assembly == null) return;
foreach (var obj in assembly.GetTypes().Where(t => t.GetCustomAttribute<ExportAttribute>() != null)) {
ExportAttribute att = obj.GetCustomAttribute<ExportAttribute>();
if (att.ContractType != null) {
_containerBuilder.RegisterType(obj).As(att.ContractType);
} else {
foreach (var intf in obj.GetInterfaces())
_containerBuilder.RegisterType(obj).As(intf);
}
}
}
}
public class ComponentFactory<TObject> : IDisposable {
protected TObject CurrentObject;
protected ILifetimeScope CurrentScope;
public TObject Current => (TObject)CurrentObject;
public ComponentFactory(IContainer container) {
CurrentScope = container.BeginLifetimeScope();
CurrentObject = CurrentScope.Resolve<TObject>();
}
public TObject Component => CurrentObject;
public void Dispose() {
(CurrentObject as IDisposable)?.Dispose();
CurrentScope.Dispose();
}
}
然后在 Startup.cs 中我执行以下操作:
public virtual IServiceProvider ConfigureServices(IServiceCollection services) {
services.AddMvc();
services.AddOptions();
services.AddSession();
services.AddCaching();
var assemblyLoadContextAccessor = services.FirstOrDefault(s => s.ServiceType == typeof(IAssemblyLoadContextAccessor)).ImplementationInstance as IAssemblyLoadContextAccessor;
var libraryManager = services.FirstOrDefault(s => s.ServiceType == typeof(ILibraryManager)).ImplementationInstance as ILibraryManager;
var loadContext = assemblyLoadContextAccessor.Default;
foreach(var library in libraryManager.GetLibraries()) {
var assembly = loadContext.Load(library.Name);
if(assembly != null) {
var module = assembly.GetTypes().FirstOrDefault(t => t == typeof(IModule));
if(module != null)
ComponentContainer.Builder.RegisterAssemblyModules(assembly);
else
ComponentContainer.RegisterAssembly(assembly);
}
}
ComponentContainer.Builder.Populate(services);
return ComponentContainer.ServiceProvider;
}
要在程序集中导出模块,我要么用 ExportAttribute
标记它们,要么将 class 添加到实现 Autofac 的 IModule 的程序集中。 ConfigureServices 中的代码将枚举应用程序的模块并将它们提供给 ComponentContainer 中的静态 Builder。构建容器后,您可以通过在构造函数中注入来解析模块,也可以通过以下方式请求特定类型:
(using var myComponentFactory = ComponentContainer.Component<IMyModule>()) {
//You can now access your component through myComponentFactory.Component
//Once it passes out of scope of using, it will be properly disposed of
//along with the scope from which it was created.
}
编辑: 随着 RC2 的发布,此代码不再有效,因为程序集的枚举和 classes 将失败。我还没有想出一个好的解决方案。如果其他人对在 RC2 中枚举程序集有任何建议,请告诉我。