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

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

问题描述

我想为我的 ASP.Net 5.0/MVC 6 应用程序创建一个插件环境.我使用 Autofac 作为 IOC 容器,我喜欢从 DNX LibraryManager 中的构建加载插件(类库).使用库管理器的目标是,我不必关心 NuGet 包和框架.

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.

我遇到的问题是生命周期,我必须在 LibraryManager 的实例可用之前构建 IOC 容器.因为 Autofac 容器提供了他自己的 IServiceProvider 实例,我必须在 ConfigureService() 方法调用 (AddAutofac) 中注入该实例.

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?

更新:我已经通过 Davids 帮助解决了我的问题并更新了代码以使其与候选发布版本一起工作.我还添加了对配置的支持.

在我的 DNX 类库中,我实现了一个用于自注册的类:

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

在我的 MVC WebApplication 中,我添加了类库作为依赖项.

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

我创建了一个 DependencyResolver 来保留 ContainerBuilder 实例.

I have created an DependencyResolver to keep the ContainerBuilder instance.

DependencyResolver.cs

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

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

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 配置.

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

autofac.json

autofac.json

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

推荐答案

我想出了一个解决方案,它使用了其中的一部分,但也使用了一个 ComponentContainer 来解决 DependencyResolver 中潜在的内存泄漏问题.这也适用于 RC1.还不确定 RC2,因为它不够完整,我无法测试.

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.

ComponentContainer 看起来像这样:

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

然后在 Startup.cs 中我执行以下操作:

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

要导出程序集中的模块,我要么用 ExportAttribute 标记它们,要么向实现 Autofac 的 IModule 的程序集添加一个类.ConfigureServices 中的代码将枚举应用程序的模块并将它们提供给 ComponentContainer 中的静态构建器.构建容器后,您可以通过在构造函数中注入来解析模块,也可以通过以下方式请求特定类型:

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

随着 RC2 的发布,此代码不再有效,因为程序集和类的枚举将失败.我还没有想出好的解决办法.如果其他人对在 RC2 中枚举程序集有任何建议,请告诉我.

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天全站免登陆