简体   繁体   English

使用Autofac 4和vNext自行注册库

[英]Self-Registering Libraries with Autofac 4 and vNext

i'd like to create a Plugin Enviroment for my ASP.Net 5.0 / MVC 6 Application. 我想为我的ASP.Net 5.0 / MVC 6应用程序创建一个Plugin Enviroment。 I'm using Autofac as IOC Container and i like to load the Plugins (Class Libraries) from the build in DNX LibraryManager. 我正在使用Autofac作为IOC容器,我喜欢从DNX LibraryManager中的构建加载插件(类库)。 The goal of using the Library Manager is, that i don't have to care about NuGet Packages and Frameworks. 使用库管理器的目的是,我不必关心NuGet包和框架。

The Problem i have is the LifeCycle, i have to build the IOC Container before the instance of the LibraryManager is available. 我遇到的问题是LifeCycle,我必须在LibraryManager的实例可用之前构建IOC容器。 Because the Autofac Container provides his own IServiceProvider Instance which i have to inject within the ConfigureService() Method call (AddAutofac). 因为Autofac容器提供了他自己的IServiceProvider实例,我必须在ConfigureService()方法调用(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: 在我的DNX类库中,我实现了一个自我注册类:

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. 在我的MVC WebApplication中,我已将类库添加为依赖项。

Startup.cs 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来保存ContainerBuilder实例。

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

If you need to switch between different implementations, like mock and real data you can use the Autofac Configuration. 如果您需要在不同的实现之间切换,例如模拟和实际数据,您可以使用Autofac配置。

autofac.json autofac.json

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

It's a shame that ConfigureServices is not injectable, that would make this a lot easier. 令人遗憾的是,ConfigureServices不可注入,这将使这更容易。

Looking at the code you should be safe to replace the IServiceProvider inside Configure(...) instead of inside ConfigureServices(...) and get the intended behavior. 查看代码,您可以安全地替换Configure(...)IServiceProvider而不是在ConfigureServices(...)内部,并获得预期的行为。 ApplicationServices is setable . ApplicationServices是可以选择的

In your UseAutofac method you should be able to do something like: 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;
}

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. 我想出了一个使用其中一部分的解决方案,但也使用了一个ComponentContainer来解决DependencyResolver中潜在的内存泄漏问题。 This also works with RC1. 这也适用于RC1。 Not sure yet about RC2 as it's not complete enough for me to test. 还不确定RC2,因为它不足以让我测试。

The ComponentContainer looks like this: 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();
    }
}

Then in Startup.cs I do the following: 然后在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;
    }

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. 要导出程序ExportAttribute模块,我要么使用ExportAttribute标记它们,要么将类添加到实现Autofac的IModule的程序集中。 The code in ConfigureServices will enumerate through the application's modules and feed them to the static Builder in ComponentContainer. ConfigureServices中的代码将枚举应用程序的模块,并将它们提供给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. 编辑:随着RC2的发布,此代码不再有效,因为程序集和类的枚举将失败。 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. 如果其他人对在RC2中枚举程序集有任何建议,请告诉我。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM