与Autofac 4和vNext自注册库 [英] Self-Registering Libraries with Autofac 4 and vNext

查看:229
本文介绍了与Autofac 4和vNext自注册库的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想创建一个插件环境对我的ASP.Net 5.0 / MVC 6应用。我使用Autofac作为IoC容器,我喜欢从DNX LibraryManager构建加载插件(类库)。使用库管理的目标是,我没有在意的NuGet包和​​框架。



这个问题我是有生命周期的,我要建该LibraryManager的实例之前IoC容器可用。由于Autofac容器提供了自己的IServiceProvider实例,我有到ConfigureService()方法调用(AddAutofac)内注入。



有谁知道如何得到这个工作?



更新:我已经固定我的问题与戴维斯的帮助和更新的代码即可获得它与候选发布版的工作。此外,我已经添加了配置支持



在我的DNX类库我实现了自注册一个类:

 公共类AutofacModule:模块
{
保护覆盖无效负载(ContainerBuilder建设者)
{
builder.Register( C =>新建SimpleService())
。至于< IService>()
.InstancePerLifetimeScope();
}
}

在我的MVC WebApplication的我已经添加了类库为依赖。



Startup.cs

 公共IConfiguration配置{搞定;组; } 

公共类启动
{
公共启动(IApplicationEnvironment applicationEnvironment)
{
IConfigurationBuilder configurationBuilder =新ConfigurationBuilder();
configurationBuilder.SetBasePath(applicationEnvironment.ApplicationBasePath);

configurationBuilder.AddJsonFile(appsettings.json);
configurationBuilder.AddJsonFile(autofac.json);
configurationBuilder.AddEnvironmentVariables();

this.Configuration = configurationBuilder.Build();
}

公共无效ConfigureServices(IServiceCollection服务)
{
services.AddMvc();
services.AddDependencies();
}

公共无效配置(IApplicationBuilder applicationBuilder,IHostingEnvironment hostingEnvironment)
{
applicationBuilder.UseDependencies(this.Configuration);
applicationBuilder.UseStaticFiles();
applicationBuilder.UseMvc();
}
}



我已经创建了一个DependencyResolver保持ContainerBuilder实例。



DependencyResolver.cs

 公共类DependencyResolver:的IDependencyResolver 
{
私人的IContainer容器;
私人只读ContainerBuilder建设者;

公共DependencyResolver()
{
this.builder =新ContainerBuilder();
}

公共无效RegisterModule(IModule的模块)
{
this.builder.RegisterModule(模块);
}

公共无效RegisterModules(IEnumerable的<大会>组件)
{
this.builder.RegisterAssemblyModules(assemblies.ToArray());
}

公共无效填充(IServiceCollection服务)
{
this.builder.Populate(服务);
}

公共无效生成()
{
this.container = this.builder.Build();
}

公共牛逼解决< T>()其中T:类
{
返回this.container .Resolve< T>();
}
}



IDependencyResolver.cs

 公共接口的IDependencyResolver 
{
无效RegisterModule(IModule的模块);
无效RegisterModules(IEnumerable的<大会>组件);
空白填充(IServiceCollection服务);
无效生成(); $ B $(B T)解决< T>()其中T:类;
}



最后但并非最不重要的我已经创建了一个扩展类



DependencyResolverExtensions.cs

 公共静态类DependencyResolverExtensions 
{
公静态IServiceCollection AddDependencies(此IServiceCollection服务)
{
dependencyResolver dependencyResolver =新dependencyResolver();
dependencyResolver.Populate(服务);

ServiceDescriptor serviceDescriptor =新ServiceDescriptor(typeof运算(的IDependencyResolver),dependencyResolver);
services.TryAdd(serviceDescriptor);

回访服务;
}

公共静态IApplicationBuilder UseDependencies(这IApplicationBuilder applicationBuilder,IConfiguration配置)
{
的IDependencyResolver dependencyResolver = applicationBuilder.GetService<&的IDependencyResolver GT;();
如果(dependencyResolver == NULL)返回applicationBuilder;

ILibraryManager libraryManager = applicationBuilder.GetService< ILibraryManager>();
如果(libraryManager == NULL)返回applicationBuilder;

IEnumerable的<大会>装配= libraryManager.GetLoadableAssemblies();
dependencyResolver.RegisterModules(组件);

ConfigurationModule configurationModule =新ConfigurationModule(配置);
dependencyResolver.RegisterModule(configurationModule);

dependencyResolver.Build();

的IServiceProvider的ServiceProvider = dependencyResolver.Resolve<&IServiceProvider的GT;();
applicationBuilder.ApplicationServices =的ServiceProvider;

返回applicationBuilder;
}

公共静态的IEnumerable<大会> GetLoadableAssemblies(这ILibraryManager libraryManager)
{
名单,LT;大会>结果=新的List<大会>();

IEnumerable的<图书馆及GT;库= libraryManager.GetLibraries();

IEnumerable的<&的AssemblyName GT; assemblyNames = libraries.SelectMany(E => e.Assemblies).Distinct();
assemblyNames = Enumerable.Where(assemblyNames,E => e.Name.StartsWith(MyLib中。));

的foreach(的AssemblyName的AssemblyName在assemblyNames)
{
装配装配=的Assembly.Load(的AssemblyName);
result.Add(组装);
}

返回结果;
}

公共静态牛逼的GetService< T>(这IApplicationBuilder applicationBuilder)其中T:类
{
返回applicationBuilder.ApplicationServices.GetService(typeof运算(T))为T;
}
}

如果您有需要,不同的实现之间进行切换就像模拟而真实的数据则可以使用Autofac配置。



autofac.json

  {
成分:[
{
类型:MyLib.Data.EF.EntitiesData,MyLib.Data.EF,
服务:[
{
类型:MyLib.Abstractions.IDataRepository,MyLib.Abstractions
}
]
}
]
$} b $ b


解决方案

我已经想出了使用一部分的解决方案这一点,但也使用解决了DependencyResolver潜在的内存泄漏ComponentContainer。这也适用于RC1。 。目前还不能确定有关RC2,因为它是不完整的足以让我测试



ComponentContainer看起来是这样的:

 公共静态类ComponentContainer {
静态的IContainer _container;
静态ContainerBuilder _containerBuilder;

公共静态ContainerBuilder生成器{
获得{
如果(_containerBuilder == NULL)
_containerBuilder =新ContainerBuilder();
返回_containerBuilder;
}
}

公共静态的IServiceProvider的ServiceProvider {
获得{
如果(_container == NULL)
_container = _containerBuilder.Build( );
返回_container.Resolve<&IServiceProvider的GT;();
}
}

公共静态ComponentFactory< TObject的>元件与LT; TObject的>()=>新ComponentFactory< TObject的>(_容器);

公共静态无效RegisterAssembly(议会会议){
如果(组装== NULL)回报;

的foreach(在assembly.GetTypes VAR OBJ()式(T =方式> t.GetCustomAttribute< ExportAttribute>()!= NULL)){
ExportAttribute ATT = obj.GetCustomAttribute< ExportAttribute>();
如果(att.ContractType!= NULL){
_containerBuilder.RegisterType(OBJ)。至于(att.ContractType);
}其他{
的foreach(在obj.GetInterfaces VAR INTF())
_containerBuilder.RegisterType(OBJ)。至于(INTF);
}
}
}
}

公共类ComponentFactory< TObject的> :IDisposable的{
保护TObject的CurrentObject;
保护ILifetimeScope CurrentScope;
公共TObject的电流=> (TObject的)CurrentObject;
公共ComponentFactory(集装箱的IContainer){
CurrentScope = container.BeginLifetimeScope();
CurrentObject = CurrentScope.Resolve< TObject的>();
}

公共TObject的组件= GT; CurrentObject;

公共无效的Dispose(){
(CurrentObject为IDisposable接口)?的Dispose();
CurrentScope.Dispose();
}
}

然后在Startup.cs我做了以下内容:

 公共虚拟的IServiceProvider ConfigureServices(IServiceCollection服务){
services.AddMvc();
services.AddOptions();
services.AddSession();
services.AddCaching();

VAR assemblyLoadContextAccessor ​​= services.FirstOrDefault(S = GT; s.ServiceType == typeof运算(IAssemblyLoadContextAccessor))ImplementationInstance为IAssemblyLoadContextAccessor。
VAR libraryManager = services.FirstOrDefault(S = GT; s.ServiceType == typeof运算(ILibraryManager))ImplementationInstance为ILibraryManager。

VAR loadContext = assemblyLoadContextAccessor.Default;

的foreach(在libraryManager.GetLibraries变种文库()){
无功组件= loadContext.Load(library.Name);

如果(组装!= NULL){
VAR模块= assembly.GetTypes()FirstOrDefault(T =>吨== typeof运算(的IModule))。

如果(模块!= NULL)
ComponentContainer.Builder.RegisterAssemblyModules(组装);
,否则
ComponentContainer.RegisterAssembly(组装);
}
}
ComponentContainer.Builder.Populate(服务);

返回ComponentContainer.ServiceProvider;
}

要在装配中导出的模块,我将其标记为与 ExportAttribute 或添加一个类实现Autofac的IModule的大会。在ConfigureServices的代码将枚举通过应用程序的模块和它们提供给在ComponentContainer静态生成器。一旦容器已经建成,既可以解决通过注射模块构造或者你可以请求特定类型的方式:

  (需要使用var myComponentFactory = ComponentContainer.Component< IMyModule>()){
//您现在可以通过myComponentFactory.Component
访问组件//一旦流出的使用范围,这将是适当的处置
//从创建它的范围沿。
}



编辑:随着RC2的发布,这代码不再作为组件和类的枚举将失败有效。我还没有想出一个好的解决方案呢。如果任何人有在RC2枚举组件有任何建议,请让我知道。


i'd like to create a Plugin Enviroment for my ASP.Net 5.0 / MVC 6 Application. I'm using Autofac as IOC Container and i like to load the Plugins (Class Libraries) from the build in DNX LibraryManager. The goal of using the Library Manager is, that i don't have to care about NuGet Packages and Frameworks.

The Problem i have is the LifeCycle, i have to build the IOC Container before the instance of the LibraryManager is available. Because the Autofac Container provides his own IServiceProvider Instance which i have to inject within the ConfigureService() Method call (AddAutofac).

Does anyone know how to get this working?

Update: I have fixed my problem with Davids help and updated the code to get it working with the release candidates. Also i have added support for configuration.

In my DNX Class Library i implemented a Class for Self-Registration:

public class AutofacModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.Register(c => new SimpleService())
               .As<IService>()
               .InstancePerLifetimeScope();
    }
}

In my MVC WebApplication i have added the Class Library as Dependency.

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();
    }
}     

I have created an DependencyResolver to keep the ContainerBuilder instance.

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>();              
    }      
}

IDependencyResolver.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;
}

Last but not least i have created an Extension 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;
    }
}

If you need to switch between different implementations, like mock and real data you can use the Autofac Configuration.

autofac.json

{
    "components": [
        {
            "type": "MyLib.Data.EF.EntitiesData, MyLib.Data.EF",
            "services": [
                {
                    "type": "MyLib.Abstractions.IDataRepository, MyLib.Abstractions"
                }
            ]
        }
    ]
}

解决方案

I've come up with a solution that uses part of this, but also uses a ComponentContainer that addresses the potential memory leaks in the DependencyResolver. This also works with RC1. Not sure yet about RC2 as it's not complete enough for me to test.

The ComponentContainer looks like this:

    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();
    }
}

Then in Startup.cs I do the following:

    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;
    }

To export modules within an assembly, I either mark them with an ExportAttribute or add a class to the assembly that implements Autofac's IModule. The code in ConfigureServices will enumerate through the application's modules and feed them to the static Builder in ComponentContainer. Once the container has been built, you can either resolve modules through injection in a constructor or you can request a specific type by:

(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.
}

Edit: With the release of RC2, this code is no longer valid as the enumeration of assemblies and classes will fail. I haven't come up with a good solution yet. If anyone else has any suggestions for enumerating assemblies in RC2, please let me know.

这篇关于与Autofac 4和vNext自注册库的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆