使用ASP.NET Core DI解决实例


302

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

设置容器非常简单:

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

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

但是,ISomeService不进行注射怎么办?例如,我要这样做:

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

中没有此类方法IServiceCollection



3
您要使用ConfigureServices()方法(使用IServiceCollection)还是在应用程序中的任何位置解析它们?
Henk Mollema 2015年

2
@HenkMollema:实际上在启动中的任何位置。
Dave New

Answers:


484

IServiceCollection接口用于构建依赖项注入容器。完全构建后,它将组成一个IServiceProvider实例,您可以使用该实例来解析服务。您可以将注入IServiceProvider到任何类中。的IApplicationBuilderHttpContext类可以提供所述服务提供商以及经由其ApplicationServicesRequestServices分别性质。

IServiceProvider定义一种GetService(Type type)解决服务的方法:

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

还有一些便利的扩展方法可用,如serviceProvider.GetService<IFooService>()(添加usingMicrosoft.Extensions.DependencyInjection)。

在启动类中解析服务

注入依赖

运行时的托管服务提供程序可以将某些服务注入到Startup类的构造函数中,例如IConfigurationIWebHostEnvironmentIHostingEnvironment在3.0之前的版本中)ILoggerFactoryIServiceProvider。请注意,后者是由托管层构建的实例,并且仅包含用于启动应用程序的基本服务

ConfigureServices()方法不允许注入服务,它仅接受一个IServiceCollection参数。这是有道理的,因为ConfigureServices()您可以在其中注册应用程序所需的服务。但是,您可以在此处使用注入启动程序构造函数中的服务,例如:

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

public IConfiguration Configuration { get; }

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

ConfigureServices()然后,可以将注册的任何服务注入该Configure()方法;您可以在IApplicationBuilder参数之后添加任意数量的服务:

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IFooService>();
}

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

手动解决依赖关系

如果您需要手动解决服务,你最好使用ApplicationServices提供了通过IApplicationBuilderConfigure()方法:

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

可以IServiceProviderStartup类的构造函数中直接传递in ,但是如上所述,这将包含services的有限子集,因此实用程序也受到限制:

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

如果必须使用该ConfigureServices()方法解析服务,则需要使用其他方法。您可以IServiceProviderIServiceCollection实例构建一个中间层,该中间层包含到那时为止已注册的服务:

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

    // Build the intermediate service provider
    var sp = services.BuildServiceProvider();

    // This will succeed.
    var fooService = sp.GetService<IFooService>();
    // This will fail (return null), as IBarService hasn't been registered yet.
    var barService = sp.GetService<IBarService>();
}

请注意: 通常,您应该避免在ConfigureServices()方法内部解析服务,因为实际上这是您配置应用程序服务的地方。有时您只需要访问一个IOptions<MyOptions>实例。您可以通过将IConfiguration实例中的值绑定到实例来实现此目的MyOptions(本质上是选项框架所做的事情):

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

手动解决服务(又称为服务定位器)通常被认为是反模式。尽管有其用例(用于框架和/或基础结构层),但您应尽可能避免使用它。


14
@HenkMollema但是如果我不能注入任何东西,我的意思是我不能IServiceCollection注入,某些是手动创建的类(超出中间件范围),在我的情况下是一个调度程序,它定期需要一些服务来生成并发送电子邮件。
Merdan Gochmuradov '16

52
警告如果您需要解析服务,ConfigureServices并且该服务是一个单例,它将与您Controller使用的单例不同!我认为这是因为它使用了不同的方法IServiceProvider-为避免发生这种情况,请不要通过解析BuildServiceProvider,而应将查找的单例从ConfigureServices移到Configure(..other params, IServiceProvider serviceProvider)Startup.cs
-wal

3
@沃尔好点。因为它是一个不同的IServiceProvider实例,所以它将创建一个新的单例实例。您可以通过从ConfigureServices方法返回服务提供者实例来避免这种情况,这样它也将成为您的应用程序使用的容器。
Henk Mollema

1
调用collection.BuildServiceProvider();是我所需要的,谢谢!
克里斯·马里西克

2
@HenkMollema如何使它仅与一个服务提供商实例一起使用?通常,您将:1)注册您的一些依赖项2)构建一个临时服务提供者实例3)使用该服务提供者解决您需要注册一些其他依赖项的问题。之后,您将无法返回临时实例,因为它缺少一些依赖项(已在3中注册)。我想念什么吗?
Filip

109

手动解析实例涉及使用IServiceProvider接口:

解决Startup.ConfigureServices中的依赖项

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

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

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

    // ...
}

在ASP.NET Core 3中解决Startup.Configure中的依赖项

public void Configure(
    IApplicationBuilder application,
    IWebHostEnvironment webHostEnvironment)
{
    app.ApplicationServices.GetService<MyService>();
}

使用运行时注入服务

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

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)
    {
    }
}

解决控制器动作中的依赖性

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

1
@AfsharMohebbi GetService是通用的,是Microsoft.Extensions.DependencyInjection名称空间中的扩展方法。
ahmadali shafiee

关于扩展方法:扩展方法是一种向类中添加功能的静态方法,您可以声明公共静态TheReturnType TheMethodName(this TheTypeYouExtend theTypeYouExtend {// BODY},然后可以像这样使用它:TheTypeYouExtend.TheMethodName();具有成为.NET的核心一个非常常见的形式给出,这样开发人员可以在这里扩展基本功能...很好的例子:docs.microsoft.com/en-us/dotnet/csharp/programming-guide/...
胡安

17

如果使用模板生成应用程序,则Startup该类上将具有以下内容:

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

    services.AddMvc();
}

然后,您可以在其中添加依赖项,例如:

services.AddTransient<ITestService, TestService>();

如果要访问ITestService控制器,则可以添加IServiceProvider构造函数,它将被注入:

public HomeController(IServiceProvider serviceProvider)

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

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

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

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();
        }

10

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

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

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

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

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

OP没有在ConfigureService方法中说明需要解决服务的原因,但这很可能是有人认为要这样做的原因
kilkfoe

1
实际上,这应该是公认的答案。尽管Henk Mollema的答案非常具有说明性,但是如今您的答案更加简洁,并且不会引入与构建中间IServiceProvider(单例的不同实例...)相关的问题。可能在Henk投入使用时2015年尚无此解决方案,但现在这是要走的路。
Vi100

尝试过这个,但ISomeService对我来说仍然无效。
ajbeaven

2个问题:1)如果服务类AnotherService的参数构造函数发生更改(删除或添加了服务),那么我需要修改服务IAnotherService的寄存器段,并且该寄存器段一直在变化?2)相反,我只能为带有1个参数的AnotherService添加一个构造函数,例如public AnotherService(IServiceProvider serviceProvider),并从该构造函数中获取所需的服务。而且我只需要在服务类之类的Startup类中注册服务类AnotherService,AddTransient <IAnotherService,AnotherService>(sp => {var service = new AnotherService(sp); return service;});
Thomas.Benz

2

您可以通过这种方式在诸如AuthorizeAttribute之类的属性中注入依赖项

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

这就是我正在寻找的..谢谢
Reyan Chougle

0

我知道这是一个老问题,但是我很惊讶没有一个显而易见的令人作呕的骇客出现在这里。

您可以利用定义自己的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);
    });
}

解决此模式的方法是对赋予OtherService显式依赖IServiceINeedToUse,而不是隐式依赖于其或其方法的返回值...或以其他方式显式解决该依赖。


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

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

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

5
如果您提供有关以下内容的简要说明,则您的答案更有可能被接受和认可 为什么它是一个好的答案,而不仅仅是代码片段,那么。它还可以帮助询问者确保这实际上是在回答他们提出的问题。
Jim L

有人错误地将您的答案标记为低质量。您应该添加一些附带的文字来说明您的答案如何起作用,以防止进一步的举报和/或降票。仅代码的答案不是低质量的。它会尝试回答这个问题吗?如果不是,则标记为“不是答案”或建议删除(如果在审阅队列中)。b)技术上不正确吗?下注或评论。从审查
李慧夏
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.