New ASP.NET Core Feature coming to 1.1 : Better Integration of Third Party Containers in Startup Class

by

  • 2017.04.15: Updated to ASP.NET Core 1.1 (I did not find this feature in 1.1)

David Fowler tweeted a cool feature in ASP.NET Core coming in next version 1.1.

In ASP.NET Core, dependency injection is a first class citizen. In this blog post, we will get a glimpse about the improved intergration of third party containers in the Startup class.

StructureMap Integration

StructureMap is the oldest, continuously used IoC/DI container for .Net dating back to its first public release and production usage all the way back in June 2004 on .Net 1.1. The current 4.* release represents more than 12 years of lessons learned in the StructureMap and greater .Net community -- while also wiping away a great deal of legacy design decisions that no longer make sense today. StructureMap supports Lazy.

using System.IO;  
using Microsoft.AspNetCore.Hosting;

namespace ServiceProviderFactorySample  
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseKestrel()
                .UseStructureMap()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseIISIntegration()
                .UseStartup<Startup>()
                .Build();

            host.Run();
        }
    }
}

Line 12 is a call to the extension method .UseStructureMap() implemented as below :

using System;  
using Microsoft.AspNetCore.Hosting;  
using Microsoft.Extensions.DependencyInjection;  
using StructureMap;

namespace ServiceProviderFactorySample  
{
    public static class StructureMapExtensions
    {
        public static IServiceCollection AddStructureMap(this IServiceCollection services, Action<ConfigurationExpression> configure = null)
        {
            return services.AddSingleton<IServiceProviderFactory<IContainer>>(new StructureMapServiceProviderFactory(configure));
        }

        public static IWebHostBuilder UseStructureMap(this IWebHostBuilder builder, Action<ConfigurationExpression> configure = null)
        {
            return builder.ConfigureServices(services => services.AddStructureMap(configure));
        }

        private class StructureMapServiceProviderFactory : IServiceProviderFactory<IContainer>
        {
            public StructureMapServiceProviderFactory(Action<ConfigurationExpression> configure)
            {
                Configure = configure ?? (config => { });
            }

            private Action<ConfigurationExpression> Configure { get; }

            public IContainer CreateBuilder(IServiceCollection services)
            {
                var container = new Container(Configure);

                container.Populate(services);

                return container;
            }

            public IServiceProvider CreateServiceProvider(IContainer container)
            {
                return container.GetInstance<IServiceProvider>();
            }
        }
    }
}

Lines 17 through 24 show how elegant and easy will be the integration of your preferred container :

using System;  
using Microsoft.AspNetCore.Builder;  
using Microsoft.AspNetCore.Http;  
using StructureMap;  
using StructureMap.Pipeline;  
using Microsoft.Extensions.DependencyInjection;

namespace ServiceProviderFactorySample  
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            // Add services here that have extension methods on IServiceCollection
        }

        public void ConfigureContainer(IContainer container)
        {
            // Add services using your custom container here. In this case structuremap
            container.Configure(config =>
            {
                config.For<IFoo>().Use().SetLifecycleTo<SingletonLifecycle>();
            });
        }

        public void Configure(IApplicationBuilder app, Lazy foo)
        {
            app.Run(async (context) =>
            {
                await context.Response.WriteAsync($"Hello World! {foo.Value.GetHashCode()}");
            });
        }

        public class Foo : IFoo
        {

        }

        public interface IFoo
        {

        }
    }
}

Autofac Integration

Autofac is an addictive IoC container for .NET Standard Library. It manages the dependencies between classes so that applications stay easy to change as they grow in size and complexity. This is achieved by treating regular .NET classes as components. Autofac supports Lazy too. Lazy means A needs B at some point in the future. It's a delayed instantiation.

using System.IO;  
using Microsoft.AspNetCore.Hosting;

namespace ServiceProviderFactorySample  
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseKestrel()
                .UseAutofac()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseIISIntegration()
                .UseStartup<Startup>()
                .Build();

            host.Run();
        }
    }
}

Line 12 is a call to the extension method .UseAutofac() implemented as below :

using System;  
using Autofac;  
using Autofac.Extensions.DependencyInjection;  
using Microsoft.AspNetCore.Hosting;  
using Microsoft.Extensions.DependencyInjection;

namespace ServiceProviderFactorySample  
{
    public static class AutofacExtensions
    {
        public static IServiceCollection AddAutofac(this IServiceCollection services)
        {
            return services.AddSingleton<IServiceProviderFactory<ContainerBuilder>, AutofacServiceProviderFactory>();
        }

        public static IWebHostBuilder UseAutofac(this IWebHostBuilder builder)
        {
            return builder.ConfigureServices(services => services.AddAutofac());
        }

        private class AutofacServiceProviderFactory : IServiceProviderFactory<ContainerBuilder>
        {
            public ContainerBuilder CreateBuilder(IServiceCollection services)
            {
                var containerBuilder = new ContainerBuilder();

                containerBuilder.Populate(services);

                return containerBuilder;
            }

            public IServiceProvider CreateServiceProvider(ContainerBuilder builder)
            {
                return new AutofacServiceProvider(builder.Build());
            }
        }
    }
}

Line 27 shows .Populate method which populates the Autofac container builder with the set of registered service descriptors and makes System.IServiceProvider and Microsoft.Extensions.DependencyInjection.IServiceScopeFactory available in the container.

using System;  
using Autofac;  
using Microsoft.AspNetCore.Builder;  
using Microsoft.AspNetCore.Hosting;  
using Microsoft.AspNetCore.Http;  
using Microsoft.Extensions.DependencyInjection;

namespace ServiceProviderFactorySample  
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            // Add services here that have extension methods on IServiceCollection
        }

        public void ConfigureContainer(ContainerBuilder containerBuilder)
        {
            // Add services using your custom container here. In this case autofac
            containerBuilder.RegisterType<Foo>().AsImplementedInterfaces().SingleInstance();
        }

        public void Configure(IApplicationBuilder app, Lazy<IFoo> foo)
        {
            app.Run(async (context) =>
            {
                await context.Response.WriteAsync($"Hello World! {foo.Value.GetHashCode()}");
            });
        }

        public class Foo : IFoo
        {

        }

        public interface IFoo
        {

        }
    }
}

Experimenting Ninject Integration...

Personally, I wrote the following extension but not yet tested for Ninject integration. The nuget package for Ninject referenced here is Ninject": "4.0.0-beta-0134. Note also that Kernel in Ninject represents the name of the container!

using system;  
using Ninject;  
using Microsoft.AspNetCore.Hosting;  
using Microsoft.Extensions.DependencyInjection;


namespace ServiceProviderFactorySample.Extensions  
{
    public static class NinjectExtensions
    {
        public static IServiceCollection AddNinject(this IServiceCollection services, Action<KernelConfiguration> configure = null)
        {
            return services.AddSingleton<IServiceProviderFactory<IReadOnlyKernel>>(new NinjectExtensions.NinjectServiceProviderFactory(configure));
        }

        public static IWebHostBuilder UseNinject(this IWebHostBuilder builder, Action<KernelConfiguration> configure = null)
        {
            return builder.ConfigureServices(services => services.AddNinject(configure));
        }

        private class NinjectServiceProviderFactory : IServiceProviderFactory<IReadOnlyKernel>
        {
            public NinjectServiceProviderFactory(Action<KernelConfiguration> configure)
            {
                Configure = configure ?? (config => { });
            }

            private Action<KernelConfiguration> Configure { get; }

            public IReadOnlyKernel CreateBuilder(IServiceCollection services)
            {
                var container = new StandardKernel();
                container.Inject(Configure);
                container.Inject(services);
                return container;
            }

            public IServiceProvider CreateServiceProvider(IReadOnlyKernel container)
            {
                return container.GetService<IServiceProvider>();
            }
        }
    }
}

Anynone tested another container?

Maher Jendoubi

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.