Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add service outside Startup

Is there a way to add services outside Startup class. In other words can we get access to ServiceCollection for current request outside Startup?

There's a typical scenario in IoC when you have a factory which creates child container, register additional services depending on input parameters (i. e. query parameter in case of Web Api) and then resolve a specific service. This workflow it supported by every IoC tool I've ever worked with but I don't see a way to do this via ASP.NET Core Dependency Injection. It seems like every sample I can find adds services inside Startup class.

like image 668
SiberianGuy Avatar asked Feb 24 '17 07:02

SiberianGuy


1 Answers

Even though im guilty myself of mis-using the IOC container as factory method or repository. it has not made for this, and is kind-of an anti pattern.

So: Microsoft tries to protect against this by: Splitting the IOC container in a IServiceCollection (the builder) and the IServiceProvider (the resolver).

In the Microsoft.AspNetCore.Hosting.StartupBase:

public virtual IServiceProvider ConfigureServices(IServiceCollection services)
{
  return services.BuildServiceProvider();
}

Turns the IServiceCollection into a IServiceProvider.

The default implementation does not provide on the fly adding of new dependencies.

If you want this, you should use another Container, or make your own implementation that falls back to the default container.

Why does microsoft do this

I'm not sure what the exact reasons are behind this. But i will list a few:

  • Adding new entries is generally heavy because they have to be compiled. (True dynamic creations of these lambda's could lead to memory issues, don't worry you probably wont have this issue)
  • If you change an implementation you don't know what will happen for already existing instances (what if other type is expected, could lead to weird runtime issues).
  • if you would be able to resolve with a string key name (like a factory method). Then your code is tightly coupled to the registration of this key. (and not loosely coupled).

DIY pseudo solution

public class MyServiceProviderWrapper : IServiceProvider
{
    private readonly IServiceProvider _msDefaultProvider;

    public MyServiceProviderWrapper(IServiceProvider msDefaultProvider)
    {
        _msDefaultProvider = msDefaultProvider;
    }
    public object GetService(Type serviceType)
    {
        //I can return my own implementation here:
        //If(myRegistrations.contains(serviceType)) myRegistrations.get(serviceType)

        //fallback to microsofts default container:
        return _msDefaultProvider.GetService(serviceType);
    }
}


public class MyStartup : Microsoft.AspNetCore.Hosting.StartupBase
{
    public override IServiceProvider ConfigureServices(IServiceCollection services)
    {
        // We build the di container. 
        // we could save MyServiceProviderWrapper on a static property for later use. (or  turn itself into a singleton).
        return new MyServiceProviderWrapper(base.ConfigureServices(services));
    }
 }
like image 59
Joel Harkes Avatar answered Oct 05 '22 16:10

Joel Harkes