Resolving instances with ASP.NET Core DI

爱⌒轻易说出口 提交于 2019-12-17 02:40:19

问题


How do I manually resolve a type using the ASP.NET Core MVC built-in dependency injection framework?

Setting up the container is easy enough:

public void ConfigureServices(IServiceCollection services)
{
    // ...

    services.AddTransient<ISomeService, SomeConcreteService>();
}

But how can I resolve ISomeService without performing injection? For example, I want to do this:

ISomeService service = services.Resolve<ISomeService>();

There are no such methods in IServiceCollection.


回答1:


The IServiceCollection interface is used for building a dependency injection container. After it's fully build, it gets composed to an IServiceProvider instance which you can use to resolve services. You can inject an IServiceProvider into any class. The IApplicationBuilder and HttpContext classes can provide the service provider as well, via the ApplicationServices or RequestServices properties respectively.

IServiceProvider defines a GetService(Type type) method to resolve a service:

var service = (IFooService)serviceProvider.GetService(typeof(IFooService));

There are also several convenience extension methods available, such as serviceProvider.GetService<IFooService>() (add a using for Microsoft.Extensions.DependencyInjection).

Resolving services inside the startup class

Injecting dependencies

The runtime can inject services in the constructor of the Startup class, such as IHostingEnvironment, IConfiguration and IServiceProvider. Please note that this service provider is an instance built by the hosting layer and contains just the services for starting up an application.

Services can also be injected in the Configure() method. You can add an arbitrary list of parameters after the IApplicationBuilder parameter. You can also inject your own services which are registered in the ConfigureServices() method here, they will be resolved from the application service provider rather than the hosting service provider.

public void Configure(IApplicationBuilder app, IFooService fooService)
{
   // ...
}

The ConfigureServices() method however does not allow injecting services, it only accepts an IServiceCollection argument. This is the method where you configure your application dependency injection container. You can use services injected in the startup's constructor here. For example:

public Startup(IConfiguration configuration)
{
    Configuration = configuration;
}

public IConfiguration Configuration { get; }

public void ConfigureServices(IServiceCollection services)
{
    // Use Configuration here
}

Manually resolving dependencies

If you want to manually resolve services, you can let the runtime inject an IServiceProvider instance in the constructor or use the ApplicationServices provided by IApplicationBuilder in the Configure() method:

public Startup(IServiceProvider serviceProvider)
{
    var hostingEnv = serviceProvider.GetService<IHostingEnvironment>();
}

or

public void Configure(IApplicationBuilder app)
{
    var serviceProvider = app.ApplicationServices;
    var hostingEnv = serviceProvider.GetService<IHostingEnvironment>();
}

However, if you need to resolve services in the ConfigureServices() method, you need a different approach. You can build an intermediate IServiceProvider from an IServiceCollection instance which contains the services which are registered until then:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IFooService, FooService>();

    // Build the intermediate service provider
    var sp = services.BuildServiceProvider();
    var fooService = sp.GetService<IFooService>();
}

You need the Microsoft.Extensions.DependencyInjection package for this.

Please note:
Generally you shouldn't resolve services inside the ConfigureServices() method, as this is actually the place where you're configuring the application services. Sometimes you just need access to some IOptions<MyOptions> instance. You can accomplish this by binding the values from the IConfiguration instance to an instance of MyOptions (which is essentially what the options framework does):

public void ConfigureServices(IServiceCollection services)
{
    var myOptions = new MyOptions();
    Configuration.GetSection("SomeSection").Bind(myOptions);
}

Manually resolving services (aka Service Locator) in general is known as an anti-pattern. While it has its use-cases (for frameworks and/or infrastructure layers), you should avoid it as much as possible.




回答2:


Manually resolving instances involves using the IServiceProvider interface:

Resolving Dependency in Startup.ConfigureServices

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IMyService, MyService>();

    var serviceProvider = services.BuildServiceProvider();
    var service = serviceProvider.GetService<IMyService>();
}

Resolving Dependencies in Startup.Configure

public void Configure(
    IApplicationBuilder application,
    IServiceProvider serviceProvider)
{
    // By type.
    var service1 = (MyService)serviceProvider.GetService(typeof(MyService));

    // Using extension method.
    var service2 = serviceProvider.GetService<MyService>();

    // ...
}

Using Runtime Injected Services

Some types can be injected as method parameters:

public class Startup
{
    public Startup(
        IHostingEnvironment hostingEnvironment,
        ILoggerFactory loggerFactory)
    {
    }

    public void ConfigureServices(
        IServiceCollection services)
    {
    }

    public void Configure(
        IApplicationBuilder application,
        IHostingEnvironment hostingEnvironment,
        IServiceProvider serviceProvider,
        ILoggerFactory loggerfactory,
        IApplicationLifetime applicationLifetime)
    {
    }
}

Resolving Dependencies in Controller Actions

[HttpGet("/some-action")]
public string SomeAction([FromServices] IMyService myService) => "Hello";



回答3:


If you generate an application with a template you are going to have something like this on the Startup class:

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddApplicationInsightsTelemetry(Configuration);

    services.AddMvc();
}

You can then add dependencies there, for example:

services.AddTransient<ITestService, TestService>();

If you want to access ITestService on your controller you can add IServiceProvider on the constructor and it will be injected:

public HomeController(IServiceProvider serviceProvider)

Then you can resolve the service you added:

var service = serviceProvider.GetService<ITestService>();

Note that to use the generic version you have to include the namespace with the extensions:

using Microsoft.Extensions.DependencyInjection;

ITestService.cs

public interface ITestService
{
    int GenerateRandom();
}

TestService.cs

public class TestService : ITestService
{
    public int GenerateRandom()
    {
        return 4;
    }
}

Startup.cs (ConfigureServices)

public void ConfigureServices(IServiceCollection services)
{
    services.AddApplicationInsightsTelemetry(Configuration);
    services.AddMvc();

    services.AddTransient<ITestService, TestService>();
}

HomeController.cs

using Microsoft.Extensions.DependencyInjection;

namespace Core.Controllers
{
    public class HomeController : Controller
    {
        public HomeController(IServiceProvider serviceProvider)
        {
            var service = serviceProvider.GetService<ITestService>();
            int rnd = service.GenerateRandom();
        }



回答4:


If you just need to resolve one dependency for the purpose of passing it to the constructor of another dependency you are registering, you can do this.

Let's say you had a service that took in a string and an ISomeService.

public class AnotherService : IAnotherService
{
    public AnotherService(ISomeService someService, string serviceUrl)
    {
        ...
    }
}

When you go to register this inside Startup.cs, you'll need to do this:

services.AddScoped<IAnotherService>(ctx => 
      new AnotherService(ctx.GetService<ISomeService>(), "https://someservice.com/")
);



回答5:


You can inject dependencies in attributes like AuthorizeAttribute in this way

var someservice = (ISomeService)context.HttpContext.RequestServices.GetService(typeof(ISomeService));



回答6:


I know this is an old question but I'm astonished that a rather obvious and disgusting hack isn't here.

You can exploit the ability to define your own ctor function to grab necessary values out of your services as you define them... obviously this would be ran every time the service was requested unless you explicitly remove/clear and re-add the definition of this service within the first construction of the exploiting ctor.

This method has the advantage of not requiring you to build the service tree, or use it, during the configuration of the service. You are still defining how services will be configured.

public void ConfigureServices(IServiceCollection services)
{
    //Prey this doesn't get GC'd or promote to a static class var
    string? somevalue = null;

    services.AddSingleton<IServiceINeedToUse, ServiceINeedToUse>(scope => {
         //create service you need
         var service = new ServiceINeedToUse(scope.GetService<IDependantService>())
         //get the values you need
         somevalue = somevalue ?? service.MyDirtyHack();
         //return the instance
         return service;
    });
    services.AddTransient<IOtherService, OtherService>(scope => {
         //Explicitly ensuring the ctor function above is called, and also showcasing why this is an anti-pattern.
         scope.GetService<IServiceINeedToUse>();
         //TODO: Clean up both the IServiceINeedToUse and IOtherService configuration here, then somehow rebuild the service tree.
         //Wow!
         return new OtherService(somevalue);
    });
}

The way to fix this pattern would be to give OtherService an explicit dependency on IServiceINeedToUse, rather than either implicitly depending on it or its method's return value... or resolving that dependency explicitly in some other fashion.




回答7:


public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    services.AddDbContext<ConfigurationRepository>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("SqlConnectionString")));

    services.AddScoped<IConfigurationBL, ConfigurationBL>();
    services.AddScoped<IConfigurationRepository, ConfigurationRepository>();
}


来源:https://stackoverflow.com/questions/32459670/resolving-instances-with-asp-net-core-di

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