Customizing ASP.NET Core Part 3: Dependency Injection

In the third part of this series, we'll take a look into the ASP.NET Core dependency injection and how to customize it to use a different dependency injection container if needed.

The Series Topics
  • Customizing ASP.NET Core Part 01: Logging
  • Customizing ASP.NET Core Part 02: Configuration
  • Customizing ASP.NET Core Part 03: Dependency Injection - This article
  • Customizing ASP.NET Core Part 04: HTTPS
  • Customizing ASP.NET Core Part 05: HostedServices
  • Customizing ASP.NET Core Part 06: MiddleWares
  • Customizing ASP.NET Core Part 07: OutputFormatter
  • Customizing ASP.NET Core Part 08: ModelBinder
  • Customizing ASP.NET Core Part 09: ActionFilter
  • Customizing ASP.NET Core Part 10: TagHelpers
  • Why Use a Different Dependency Injection Container

    In the most projects, you don't really need to use a different dependency injection Container. The DI implementation in ASP.NET Core supports the basic features and works well and pretty fast. Anyway, some other DI containers support some interesting features you may want to use in your application.

  • Maybe you want to create an application that supports modules as lightweight dependencies.
  • E.g. modules you want to put into a specific directory and they get automatically registered in your application.
  • This could be done with NInject.
  • Maybe you want to configure the services in a configuration file outside the application, in an XML or JSON file instead of in C# only.
  • Maybe you don't want to have an immutable DI container, because you want to add services at runtime.
  • A Look at the ConfigureServices Method

    Create a new ASP.NET Core project and open the Startup.cs. You will find the method to configure the services which look like this:

    // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddTransient<IService, MyService>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); }

    This method gets the IServiceCollection, which is already filled with a bunch of services which are needed by ASP.NET Core. These services got added by the hosting services and parts of ASP.NET Core that got executed before the method ConfigureSercices is called.

    Inside the method, some more services get added. First, a configuration class that contains cookie policy options is added to the ServiceCollection. In this sample, I also add a custom service called MyService that implements the IService interface. After that, the method AddMvc() adds another bunch of services needed by the MVC framework. Until yet we have around 140 services registered to the IServiceCollection. But the service collections isn't the actual dependency injection container.

    The actual DI container is wrapped in the so-called service provider, which will be created out of the service collection. The IServiceCollection has an extension method registered to create a IServiceProvider out of the service collection.

    IServiceProvider provider = services.BuildServiceProvider()

    The ServiceProvider than contains the immutable container that cannot be changed at runtime. With the default ConfigureServices method, the IServiceProvider gets created in the background after this method was called, but it is possible to change the method a little bit:

    public IServiceProvider ConfigureServices(IServiceCollection services) { services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddTransient<IService, MyService>(); // custom service services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); return services.BuildServiceProvider() }

    I changed the return type to IServiceProvider and returned the ServiceProvider created with the method BuildServiceProvider(). This change will still work in ASP.NET Core.

    Use a Different ServiceProvider

    To change to a different or custom DI container you need to replace the default implementation of the IServiceProvider with a different one. Additionally, you need to find a way to move the already registered services to the new container.

    The next code sample uses Autofac as a third-party container. I use Autofac in this snippet because you are easily able to see what is happening here:

    public IServiceProvider ConfigureServices(IServiceCollection services) { services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); //services.AddTransient<IService, MyService>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); // create a Autofac container builder var builder = new ContainerBuilder(); // read service collection to Autofac builder.Populate(services); // use and configure Autofac builder.RegisterType<MyService>().As<IService>(); // build the Autofac container ApplicationContainer = builder.Build(); // creating the IServiceProvider out of the Autofac container return new AutofacServiceProvider(ApplicationContainer); } // IContainer instance in the Startup class public IContainer ApplicationContainer { get; private set; }

    Also, Autofac works with a kind of a service collection inside the ContainerBuilder and it creates the actual container out of the ContainerBuilder. To get the registered services out of the IServiceCollection into the ContainerBuilder, Autofac uses the Populate() method. This copies all the existing services to the Autofac container.

    Our custom service, MyService, now gets registered using the Autofac way.

    After that, the container gets built and stored in a property of type IContainer. In the last line of the method ConfigureServices we create a AutofacServiceProvider and pass in the IContainer. This is the IServiceProvider we need to return to use Autofac within our application.

    UPDATE: Introducing Scrutor

    You don't always need to replace the existing .NET Core DI container to get and use nice features. In the beginning, I mentioned the auto registration of services. This can also be done with a nice NuGet package called Scrutor by Kristian Hellang (https://kristian.hellang.com/). Scrutor extends the IServiceCollection to automatically register services to the .NET Core DI container.

    "Assembly scanning and decoration extensions for Microsoft.Extensions.DependencyInjection" https://github.com/khellang/Scrutor

    Andrew Lock published a pretty detailed blog post about Scrutor. It doesn't make sense to repeat that. Read that awesome post and learn more about it: Using Scrutor to automatically register your services with the ASP.NET Core DI container

    Conclusion

    Using this approach you are able to use any .NET Standard compatible DI container to replace the existing one. If the container of your choice doesn't provide an ServiceProvider, create your own that implements IServiceProvider and uses the DI container inside. If the container of your choice doesn't provide a method to populate the registered services into the container, create your own method. Loop over the registered services and add them to the other container.

    Actually, the last step sounds easy but can be a hard task. Because you need to translate all the possible IServiceCollection registrations into registrations of the different container. The complexity of that task depends on the implementation details of the other one.

    Anyway, you have the choice to use any DI container which is compatible with the .NET Standard. You have the choice to change a lot of the default implementations in ASP.NET Core.

    So you can with the default HTTPS behavior on Windows. To learn more about that please read the next post about Customizing ASP.NET Core Part 04: HTTPS (available in a few days).

    Topics:

    web dev ,dependency injection ,asp.net core ,web application development

    Comments

    Amazon

    Popular posts from this blog

    ASP.NET Core: Read the GPS Coordinates of a Photo

    Progress is One of the First Corporate Sponsors for the .NET Foundation; Ensures Zero-Day Support of Microsoft Visual Studio ...