Self-registering libraries with Autofac 4 and vNext

I would like to create an Enviroment plugin for my ASP.Net 5.0 / MVC 6 application. I use Autofac as an IOC container and I like to load plugins (class libraries) from the assembly in the DNX LibraryManager. The purpose of using the library manager is that I don’t need to worry about NuGet packages and infrastructures.

The problem I have is LifeCycle, I need to create an IOC container before the LibraryManager instance is available. Since Autofac Container provides its own instance of IServiceProvider, which I have to enter inside a call to the ConfigureService () (AddAutofac) method.

Does anyone know how to do this?

Update: I fixed my problem with Davids and updated the code to make it work with release candidates. I also added configuration support.

In my DNX class library, I implemented a self-registration class:

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

In my MVC WebApplication, I added the class library as dependent.

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 created a DependencyResolver to store an instance of 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; } 

Last but not least, I 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, such as layout 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" } ] } ] } 
+4
source share
2 answers

I came up with a solution that uses some of this, but also uses a ComponentContainer that addresses potential memory leaks in DependencyResolver. This also works with RC1. Still not sure what RC2 is, since it is not complete enough for testing.

The ComponentContainer component is as follows:

  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 label them with ExportAttribute , or add a class to the assembly that implements Autofac IModule. The code in ConfigureServices will list through the application modules and pass them to the static Builder in the ComponentContainer. After the container has been created, you can either enable the modules by injection into the constructor, or request a specific type:

 (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, since the enumeration of assemblies and classes will not be performed. I have not yet come up with a good solution. If anyone has any suggestions for listing assemblies in RC2, please let me know.

+1
source

It's a shame that ConfigureServices is not injected, which will make this easier.

Looking at the code, you should be safe to replace IServiceProvider inside Configure(...) , not inside ConfigureServices(...) , and get the expected behavior. ApplicationServices customizable .

In your UseAutofac method UseAutofac you can do something like:

 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; } 
+3
source

All Articles