An Enthusiastic Programmer

Understanding built-in IoC container

|

In this article, we are going dive into the built-in IoC container. In the previous article, we have the following code.

public void ConfigureServices(IServiceCollection services)
{
  services.AddTransient<ConsoleLog>();//transient
}

As you have already known that the IServiceCollection is the place for register services. But, where is the place to retrieve services? The answer is IServiceProvider.

There have two types of assemblies, which are Microsoft.Extensions.DependencyInjection.Abstractions and Microsoft.Extensions.DependencyInjection. The previous one define interfaces, and another implement that. The following is a diagram illustrating the working mechanism.

Alt

As we can see from the above diagram, the ServiceProvider (link) depends on the IEnumerator<ServiceDescriptor>, which generated by the GetEnumerator method in ServiceCollection(link), where is the place registers our service.

The IServiceProviderEngine instance implements the logic behind it in ServiceProvider, and the IServiceProviderEngine has a lot of implementation classes. Each implementation has a different performance.

Now, we understand the working mechanism. Put our services into IServiceCollection, retrieve them back from IServiceProvider. As the ServiceCollection constructors are public, so it’s easy for us to get its instance. We can new a ServiceCollection instance, or directly use it in ConfigureServices() in the ASP.NET Core application.

Startup.cs

public class Startup{
  public void ConfigureServices(IServiceCollection services){
    //some operations
  }
}

The ServiceProvider constructor is an internal access modifier, so we can’t instance it. So, how can we get the ServiceProvider instance?

We can’t directly new a ServiceProvider instance. But, Microsoft provides many extension methods to retrieve the ServiceProvider instance. The following lists some ways for that.

get IServiceProvider through IServiceCollection

public class Startup{
  public void ConfigureServices(IServiceCollection services)
  {
    IServiceProvider provider = services.BuildServiceProvider();
    //T service = provider.getService<T>();
  }
}

get IServiceProvider through HttpContext

public class CustomController : Controller
{
  public IActionResult Index()
  {
    IServiceProvider provider = HttpContext.RequestServices;
    //T service = provider.getService<T>();
    return View();
  }
}

get IServiceProvider through IApplicationBuilder

public class Startup{
  public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  {
    IServiceProvider provider = app.ApplicationServices;
    //T service = provider.getService<T>();
  }
}

get IServiceProvider through IHost

public class Program
{
  public static void Main(string[] args)
  {
    IHost host = CreateHostBuilder(args).Build();
    IServiceProvider provider = host.Services;
    //T service = provider.getService<T>();
    host.Run();
  }
  public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });
}

Because we can get IServiceProvider from IServiceCollection, and IServiceCollection has a public constructor. So, built-in DI also can be used in non-ASP applications. Here is an example that illustrates how to use built-in DI in a .NET Core Console application.

Comments