2

Is it possible to set up injection scopes for the default DI in Asp.Net Core? I mean For example:

services.AddSingleton<IUser, UserService>
services.AddSingleton<IUser, UserService>

And for the second configuration somehow specify that it should be injected into only HomeController. Unlike the first one should be injected to all others. Is it possible with default DI?

asd
  • 51
  • 2
  • 5
  • What is the difference between first and second configuration? Are there multiple `Constructor` for `UserService`? What is the issue while using only one configuration? – Edward Jul 25 '18 at 03:26

3 Answers3

1

I answered a similar question here but using scoped instead of singleton:

How to register multiple implementations of the same interface in Asp.Net Core?

My gut feeling is that this might be what you're trying to achieve, or might be a better approach, and you might be mixing up the User with the UserService. When you have multiple implementations of the same interface DI will add these to a collection, so it's then possible to retrieve the version you want from the collection using typeof.

// In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped(IUserService, UserServiceA);
    services.AddScoped(IUserService, UserServiceB);
    services.AddScoped(IUserService, UserServiceC);
}

// Any class that uses the service(s)
public class Consumer
{
    private readonly IEnumerable<IUserService> _myServices;

    public Consumer(IEnumerable<IUserService> myServices)
    {
        _myServices = myServices;
    }

    public UserServiceA()
    {
        var userServiceA = _myServices.FirstOrDefault(t => t.GetType() == typeof(UserServiceA));
        userServiceA.DoTheThing();
    }

    public UserServiceB()
    {
        var userServiceB = _myServices.FirstOrDefault(t => t.GetType() == typeof(UserServiceB));
        userServiceB.DoTheThing();
    }

    public UseServiceC()
    {
        var userServiceC = _myServices.FirstOrDefault(t => t.GetType() == typeof(UserServiceC));
        userServiceC.DoTheThing();
    }
}
Ciarán Bruen
  • 5,221
  • 13
  • 59
  • 69
0

Assuming this registration, how should the dependency injection container possibly know which “singleton” (it’s not really a singleton when there are two of them) it should inject into the HomeController, or a different service, when they are all just depend on IUser?

The type the dependency gets registered as, in your case IUser, is the “key” which DI containers use to resolve the dependency. So two services that both depend on IUser will get their dependency resolved in the same way. With a singleton lifetime, this means that both services get the same instance.

Service registrations are also usually replacing. So if you have one registration AddSingleton<X, Y>() and then have another one AddSingleton<X, Z>(), then the latter will replace the former. So all services dependending on X will receive Z.

DI containers, including the default container that ships with ASP.NET Core, do usually support resolving all registrations by depending on IEnumerable<X> instead. But for this example this just means that a services would get both Y and Z.

The closest thing you are looking for are keyed or named dependencies. While these are supported in some DI containers, they are technically not part of dependency injection and as such often deliberately absent from many containers, including the ASP.NET Core one. See this answer for more details on that and for some idea to get around that.


To get back to your use case, you should really think about what you are actually doing there. If you have two “singleton” instances of UserService, you should really think about why that is the case: Why isn’t there just one? And if there is support for multiple, why not register it as transient?

More importantly, what would possibly differ between those two instances? After all, they are both instances of the same implementation, so there isn’t much that they can do differently.

If you can identify that, and also confirm that this is something that actually makes the instances different, then consider splitting this up in the type hierarchy as well. It’s difficult to explain this without having a use case here, but what you should try is to end up with two different interfaces that each do exactly what each dependent service type needs. So HomeController can depend on IUserA, and others can depend on IUserB (please choose better names than this).

poke
  • 369,085
  • 72
  • 557
  • 602
0

I have the similar issue. There is my solution.

On the top level in controller I use custom attribute for the action, where I need specific service implementation (for reports for example):

 public class HomeController : ControllerBase
  {

    private readonly IService _service;

    public HomeController(IService service)
    {
      _service = service;
    }

    [HttpGet]
    [ReportScope]
    public IEnumerable<WeatherForecast> Get()
    {
      _service.DoSomeThing();
    }

This attribute is processed by custom middleware:

public class ReportScopeLoggingMiddleware
  {
    private readonly RequestDelegate _next;

    public ReportScopeLoggingMiddleware(RequestDelegate next)
    {
      _next = next;            
    }

    public async Task Invoke(HttpContext context, ReportScopeContext scopeContext)
    {        
      var controllerActionDescriptor = context
        .GetEndpoint()
        .Metadata
        .GetMetadata<ControllerActionDescriptor>();

      bool analytical = controllerActionDescriptor.EndpointMetadata.Any(m => m is ReportScopeAttribute);
      if (analytical) scopeContext.SetActive();
      
      await _next(context);
    }       
  }

In this middleware I use ReportScopeContext.

  public class ReportScopeContext
  {
    public bool Active { get; private set; } = false;
    public void SetActive()
    {
      Active = true;
    }
    
  }

This ReportScopeContext has scoped lifetime in DI and I use it to select an implementation of IService:

  services.AddScoped<ReportScopeContext>();
  services.AddTransient<Service2>();
  services.AddTransient<Service1>();
  services.AddTransient<IService>(sp =>
    sp.GetRequiredService<ReportScopeContext>().Active
      ? sp.GetRequiredService<Service1>()
      : sp.GetRequiredService<Service2>());
Tselofan
  • 146
  • 7