2015-07-28 60 views
4

我想爲我的ASP.Net 5.0/MVC 6應用程序創建一個插件環境。我使用Autofac作爲IOC容器,並且我喜歡從DNX LibraryManager中的構建中加載插件(類庫)。使用庫管理器的目標是,我不必關心NuGet包和框架。自動註冊庫Autofac 4和vNext

我遇到的問題是LifeCycle,我必須在庫管理器的實例可用之前構建IOC容器。因爲Autofac容器提供了他自己的IServiceProvider實例,我必須在ConfigureService()方法調用(AddAutofac)中注入它。

有誰知道如何得到這個工作?

更新:我已經解決了我與戴維斯幫助的問題,並更新了代碼以使其與候選版本一起工作。另外我還添加了對配置的支持。

在我的DNX類庫我實現了自注冊一個類別:

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

在我的MVC WebApplication的我已經添加了類庫的依賴。

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

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

最後但並非最不重要我已經創建了一個擴展類

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" 
       } 
      ] 
     } 
    ] 
} 
+0

我一直在尋找解決這個問題的方法,我認爲你在這裏有一個好主意,但根據AutoFac文檔(http://docs.autofac.org/en/latest/resolve/),您的Resolve方法可能會導致內存泄漏。我已經構建了一個ConfigurationContainer,我認爲它解決了這個問題,並且將與上面的一些解決方案一起實現。如果你想看到它或者其他地方,如果它會更好,我可以發佈代碼作爲答案。 – DrewB

+0

我認爲代碼不再工作,因爲微軟一遍又一遍地改變着一切。但是,是的,如果你可以改進代碼,那將是非常好的。我還花了幾個小時的時間通過使用內部依賴解析器和多個啓動文件來創建沒有autofac的版本。但是現在我已經停止了與vnext的合作,因爲沒有機會獲得穩定的版本。 – endeffects

回答

1

我想出了一個使用其中的一部分,也是一個解決方案使用,在解決潛在的內存泄漏ComponentContainer依賴關係解析器。這也適用於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或添加一個實現Autofac的IModule的程序集的類。 ConfigureServices中的代碼將通過應用程序的模塊進行枚舉並將它們提供給ComponentContainer中的靜態生成器。一旦容器已經建成,既可以解決通過注射模塊構造也可以通過請求特定類型:

(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中列舉程序集的建議,請讓我知道。

3

這是一個恥辱,ConfigureServices不注射,這將使這個方便很多。

看着代碼,你應該可以安全地更換Configure(...)裏面的IServiceProvider而不是ConfigureServices(...),並獲得預期的行爲。 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; 
} 
+0

哇非常感謝,這是一個簡單的解決方案,它的工作原理! – endeffects

+0

有趣的是'ILibraryManager'是實例化的,但是應用程序託管方式的方式,它在ConfigureServices內部不可用。希望它運作良好。 –

+0

嗨@endeffects,不錯的工作!您能否請您發佈最終解決方案?我沒有得到大衛的回答。非常感謝! – stevo