使用ASP.NET Core DI解析实例

Dav*_*New 245 c# dependency-injection asp.net-core-mvc asp.net-core

如何使用ASP.NET Core MVC内置依赖注入框架手动解析类型?

设置容器很容易:

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

    services.AddTransient<ISomeService, SomeConcreteService>();
}
Run Code Online (Sandbox Code Playgroud)

但是如何在ISomeService不进行注射的情况下解决?例如,我想这样做:

ISomeService service = services.Resolve<ISomeService>();
Run Code Online (Sandbox Code Playgroud)

没有这样的方法IServiceCollection.

Hen*_*ema 392

IServiceCollection接口用于构建依赖注入容器.在完全构建之后,它会被组合到一个IServiceProvider可用于解析服务的实例.你可以注入IServiceProvider任何类.的IApplicationBuilderHttpContext类可以提供所述服务提供商,以及,经由所述ApplicationServicesRequestServices分别性质.

IServiceProvider定义GetService(Type type)解析服务的方法:

var service = (IFooService)serviceProvider.GetService(typeof(IFooService));
Run Code Online (Sandbox Code Playgroud)

还有一些便利扩展方法,例如serviceProvider.GetService<IFooService>().(添加用于using).

解析启动类中的服务

注入依赖项

运行时可以在Microsoft.Extensions.DependencyInjection类的构造函数中注入服务,例如Startup,IHostingEnvironmentIConfiguration.请注意,此服务提供程序是由托管层构建的实例,仅包含启动应用程序的服务.

服务也可以在IServiceProvider方法中注入.您可以在参数后添加任意数量的Configure()参数.您也可以注入自己在此IApplicationBuilder方法中注册的服务,它们将从应用程序服务提供商而不是托管服务提供商处解析.

public void Configure(IApplicationBuilder app, IFooService fooService)
{
   // ...
}
Run Code Online (Sandbox Code Playgroud)

ConfigureServices()但是该方法不允许注入服务,它只接受一个ConfigureServices()参数.这是配置应用程序依赖项注入容器的方法.您可以在此处使用在启动构造函数中注入的服务.例如:

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

public IConfiguration Configuration { get; }

public void ConfigureServices(IServiceCollection services)
{
    // Use Configuration here
}
Run Code Online (Sandbox Code Playgroud)

手动解决依赖关系

如果你想手动解决服务,可以让运行时注入一个IServiceCollection实例在构造函数或使用IServiceProvider所提供ApplicationServicesIApplicationBuilder方法:

public Startup(IServiceProvider serviceProvider)
{
    var hostingEnv = serviceProvider.GetService<IHostingEnvironment>();
}
Run Code Online (Sandbox Code Playgroud)

要么

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

但是,如果需要解析Configure()方法中的服务,则需要采用不同的方法.您可以ConfigureServices()IServiceProvider包含在此之前注册的服务的实例构建中间件:

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

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

你需要这个IServiceCollection包.

请注意:
通常您不应该解析Microsoft.Extensions.DependencyInjection方法中的服务,因为这实际上是您配置应用程序服务的地方.有时您只需要访问某个ConfigureServices()实例.您可以通过将IOptions<MyOptions>实例中的值绑定到实例IConfiguration(实际上是选项框架的作用)来实现此目的:

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

手动解析服务(也称为服务定位器)通常被称为反模式.虽然它有用例(对于框架和/或基础架构层),但您应该尽可能地避免使用它.

  • *警告*如果你需要解决`ConfigureServices`中的服务,并且该服务是单例,它将是你的`Controller使用的单独的单独的单独的!我认为这是因为它使用了一个不同的`IServiceProvider` - 以避免这种情况不通过`BuildServiceProvider`解决,而是将单例的查找从`ConfigureServices`移动到`Configure(..其他参数,IServiceProvider serviceProvider)`. Startup.cs` (42认同)
  • @HenkMollema但是如果我不能注入任何东西,我的意思是我不能注入`IServiceCollection`,一些正在手动创建的类(_out中间件scope_),在我的情况下是一个调度程序,它定期需要一些生成和发送电子邮件的服务. (13认同)
  • @wal好点.因为它是一个不同的`IServiceProvider`实例,所以它将创建一个新的单例实例.您可以通过从"ConfigureServices"方法返回服务提供程序实例来避免这种情况,这样您的应用程序也将使用该容器. (3认同)
  • @HenkMollema 你如何让它只与一个服务提供者实例一起工作?通常,您会 1) 注册一些依赖项 2) 构建一个临时服务提供者实例 3) 使用该服务提供者解决您需要注册一些其他依赖项的问题。之后,您无法返回临时实例,因为它缺少您的一些依赖项(在 3 中注册)。我错过了什么吗? (2认同)
  • “在ConfigureServices() 中注册的任何服务都可以注入到Configure() 方法中;您可以在IApplicationBuilder 参数后添加任意数量的服务” - 这句话改变了我的生活。非常感谢。这是在文档中的某个地方吗? (2认同)

Muh*_*eed 81

手动解析实例涉及使用IServiceProvider界面:

解决Startup.ConfigureServices中的依赖关系

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

    var serviceProvider = services.BuildServiceProvider();
    var service = serviceProvider.GetService<IMyService>();
}
Run Code Online (Sandbox Code Playgroud)

解决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>();

    // ...
}
Run Code Online (Sandbox Code Playgroud)

使用运行时注入服务

某些类型可以作为方法参数注入:

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)
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

解决控制器操作中的依赖关系

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

  • @AfsharMohebbi 通用的`GetService` 是`Microsoft.Extensions.DependencyInjection` 命名空间中的扩展方法。 (2认同)

Bru*_*oLM 14

如果您使用模板生成应用程序,那么您将在Startup类中使用以下内容:

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

    services.AddMvc();
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以在那里添加依赖项,例如:

services.AddTransient<ITestService, TestService>();
Run Code Online (Sandbox Code Playgroud)

如果你想ITestService在你的控制器上访问你可以添加IServiceProvider构造函数,它将被注入:

public HomeController(IServiceProvider serviceProvider)
Run Code Online (Sandbox Code Playgroud)

然后,您可以解决您添加的服务:

var service = serviceProvider.GetService<ITestService>();
Run Code Online (Sandbox Code Playgroud)

请注意,要使用通用版本,您必须包含带有扩展名的命名空间:

using Microsoft.Extensions.DependencyInjection;
Run Code Online (Sandbox Code Playgroud)

ITestService.cs

public interface ITestService
{
    int GenerateRandom();
}
Run Code Online (Sandbox Code Playgroud)

TestService.cs

public class TestService : ITestService
{
    public int GenerateRandom()
    {
        return 4;
    }
}
Run Code Online (Sandbox Code Playgroud)

Startup.cs(ConfigureServices)

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

    services.AddTransient<ITestService, TestService>();
}
Run Code Online (Sandbox Code Playgroud)

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();
        }
Run Code Online (Sandbox Code Playgroud)


Bor*_*dın 8

您可以通过这种方式在 AuthorizeAttribute 等属性中注入依赖项

var someservice = (ISomeService)context.HttpContext.RequestServices.GetService(typeof(ISomeService));
Run Code Online (Sandbox Code Playgroud)

  • 这就是我一直在寻找的..谢谢 (2认同)
  • 非常好的答案,只是补充一下 - 更简洁的写法是:`var someservice = context.HttpContext.RequestServices.GetService&lt;ISomeService&gt;();` (2认同)

rat*_*rus 6

如果只需要解析一个依赖项以将其传递给正在注册的另一个依赖项的构造函数,则可以执行此操作。

假设您有一个接受字符串和ISomeService的服务。

public class AnotherService : IAnotherService
{
    public AnotherService(ISomeService someService, string serviceUrl)
    {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

当您在Startup.cs中进行注册时,您需要执行以下操作:

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

  • 事实上,这应该是公认的答案......虽然 Henk Mollema 的答案非常说明性,但现在你的答案更干净,并且不会引入与构建中间 IServiceProvider 相关的问题(单例的不同实例......)。2015 年 Henk 回答时,这个解决方案可能还不可用,但现在这是可行的方法。 (2认同)
  • `services.AddScoped&lt;IAnotherService&gt;(ctx =&gt; ActivatorUtilities.CreateInstance&lt;AnotherService&gt;(ctx, "https://someservice.com/") );` 应该是首选解决方案。 (2认同)

Izz*_*zzy 6

我知道这是一个老问题,但我很惊讶这里没有一个相当明显和令人作呕的黑客。

您可以利用定义自己的 ctor 函数的能力,在定义服务时从服务中获取必要的值……显然,除非您明确删除/清除并重新添加服务的定义,否则每次请求服务时都会运行这项服务在开发 ctor 的第一个建设中

此方法的优点是不需要您在配置服务期间构建或使用服务树。您仍在定义如何配置服务。

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);
    });
}
Run Code Online (Sandbox Code Playgroud)

修复此模式的方法是对 提供OtherService显式依赖IServiceINeedToUse,而不是隐式依赖于它或其方法的返回值……或以其他方式显式解析该依赖。