how to use scoped dependency in a singleton in C# / ASP

£可爱£侵袭症+ 提交于 2019-12-20 03:43:28

问题


I'm new to C#/ASP coming from a Java world. I've read this article: https://docs.asp.net/en/latest/fundamentals/dependency-injection.html#service-lifetimes-and-registration-options which wisely warns about the dangers associated with injecting a dependency with a smaller scope. Unfortunately it does not explain how to solve this issue in C#/ASP. In Java there's a concept of Provider

interface Provider<T> { T get(); }

which, among other things helps to solve the scoping issue: whenever a binding for some type T is register we can inject an automatically generated instance of Provider<T> instead of T and then get an instance of T whenever it is needed: an automatically generated Provider makes sure that we get an instance appropriate for the current scope (whatever this scope is: HTTP request, HTTP session or other custom scopes). The standard DI framework built into ASP.NET core does not have anything like this, but I thought in C# it should be very easy to implement as C# generics don't suck like java's do (https://docs.oracle.com/javase/tutorial/java/generics/erasure.html). So I've created the following class:

public class Provider<T>: IProvider<T> {
  private readonly IServiceProvider serviceProvider;
  public Provider(IServiceProvider serviceProvider) {
    this.serviceProvider = serviceProvider;
  }
  public T IProvider<T>.Get() {
    return serviceProvider.GetService<T>();
  }
}

and I attemtped to use it the following way:

public class SingletonService : ISingletonService {
  private readonly IProvider<IScopedService> scopedServiceProvider;

  public SingletonService(IProvider<IScopedService> scopedServiceProvider) {
    this.scopedServiceProvider = scopedServiceProvider;
  }

  public string PerformMyTask() {
    var scopedDependency = scopedServiceProvider.Get();
    // do something with scopedDependency to verify we get instances
    // appropriate for the current scope
  }
}

and in my Startup class:

public void ConfigureServices(IServiceCollection services) {
  services.AddSingleton<ISingletonService, SingletonService>();
  services.AddScoped<IScopedService, ScopedService>();
  services.AddTransient<IProvider<IScopedService>, Provider<IScopedService>>();
  // other bindings here
}

Unfortunately this does not work the way I intended as IServiceProvider instance seems to be also scoped to the current HTTP request and I get exactly the same instance of ScopedDependency from my provider during processing of different requests :(

Any hints how can I solve this problem? Is there any "higher level" object than ServiceProvider maybe, bound roughly to application lifecycle (not to the current request) that creates instances of request scoped objects (or of ServiceProvider itself) that I can inject into my Provider objects instead of ServiceProvider? For example in Java if I use google Guice as a DI framework there is an Injector object, usually created at the startup of an application which holds all the type bindings and has a method

<T> T getInstance(Class<T> type);

which checks what is the current scope and returns a corresponding instance.

edit:

I think that one possible way to do it would be to get a new reference to instance of ServiceProvider each time in the Proivder<T>.Get() method instead of injecting in the constructor and storing as an instance var. This way my components would still not be polluted with a reference to the framework specific IServiceProvider as it would be hidden from them in the implementation of Provider<T> that they access via the abstract IProvider<T> interface. I can't however find on the web if it's possible to get such a reference from my Provider class and how to do this. Any pointers in this direction would be appreciated :)

Thanks!


回答1:


ok, found it:

public class Provider<T> : IProvider<T> {
    IHttpContextAccessor contextAccessor;
    public Provider(IHttpContextAccessor contextAccessor) {
        this.contextAccessor = contextAccessor;
    }
    T IProvider<T>.Get() {
        return contextAccessor.HttpContext.RequestServices.GetService<T>();
    }
}

and in Startup:

    public void ConfigureServices(IServiceCollection services) {
        services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddSingleton<ISingletonService, SingletonService>();
        services.AddScoped<IScopedService, ScopedService>();
        services.AddTransient<IProvider<IScopedService>, Provider<IScopedService>>();
        // other bindings
    }

:)

see https://github.com/aspnet/Hosting/issues/793 for more details about using and registering HttpContextAccessor



来源:https://stackoverflow.com/questions/40544187/how-to-use-scoped-dependency-in-a-singleton-in-c-sharp-asp

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!