ChatGPT解决这个技术问题 Extra ChatGPT

在 ConfigureServices 中使用 ASP.NET Core DI 解析实例

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

设置容器很简单:

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

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

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

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

IServiceCollection 中没有此类方法。

您想在 ConfigureServices() 方法(使用 IServiceCollection)中解决它们还是在应用程序的任何地方解决它们?
@HenkMollema:实际上在 Startup 的任何地方。
如果您希望在 .net core 3.1 中执行此操作stackoverflow.com/a/65245884

H
Henk Mollema

IServiceCollection 接口用于构建一个依赖注入容器。完全构建后,它会组合成一个 IServiceProvider 实例,您可以使用该实例来解析服务。您可以将 IServiceProvider 注入任何类。 IApplicationBuilderHttpContext 类也可以分别通过其 ApplicationServicesRequestServices 属性提供服务提供者。

IServiceProvider 定义了一个 GetService(Type type) 方法来解析服务:

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

还有几种方便的扩展方法可用,例如 serviceProvider.GetService<IFooService>()(为 Microsoft.Extensions.DependencyInjection 添加 using)。

在启动类中解析服务

注入依赖

运行时的托管服务提供者可以将某些服务注入到 Startup 类的构造函数中,例如 IConfigurationIWebHostEnvironment(在 3.0 之前的版本中为 IHostingEnvironment)、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();
}

手动解决依赖关系

如果需要手动解析服务,最好在Configure()方法中使用IApplicationBuilder提供的ApplicationServices

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

可以在 Startup 类的构造函数中传递并直接使用 IServiceProvider,但如上所述,这将包含有限的服务子集,因此实用性有限:

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

如果您必须在 ConfigureServices() 方法中解析服务,则需要使用不同的方法。您可以从 IServiceCollection 实例构建一个中间 IServiceProvider,其中包含到那时已注册的服务:

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

或者对 AddSingleton/AddScoped/AddTransient 使用重载:

// Works for AddScoped and AddTransient as well
services.AddSingleton<IBarService>(sp =>
{
    var fooService = sp.GetRequiredService<IFooService>();
    return new BarService(fooService);
}

手动解析服务(又名服务定位器)是 generally considered an anti-pattern。虽然它有其用例(用于框架和/或基础设施层),但您应该尽可能避免使用它。


@HenkMollema 但是如果我不能注入任何东西怎么办,我的意思是我不能注入 IServiceCollection,一些手动创建的类(超出中间件范围),调度程序我的情况,它定期需要一些服务来生成和发送电子邮件。
警告如果您需要解析 ConfigureServices 中的服务并且该服务是单例,它将是与您的 Controller 使用的不同的单例!我认为这是因为它使用了不同的 IServiceProvider - 为避免这种情况,请不要通过 BuildServiceProvider 解决,而是将您对单例的查找从 ConfigureServices 移动到 Startup.cs 中的 Configure(..other params, IServiceProvider serviceProvider)
@wal 好点。因为它是一个不同的 IServiceProvider 实例,所以它将创建一个新的单例实例。您可以通过从 ConfigureServices 方法返回服务提供者实例来避免这种情况,这样它也将成为您的应用程序使用的容器。
我需要调用 collection.BuildServiceProvider();,谢谢!
@HenkMollema 你如何让它只与一个服务提供者实例一起工作?通常,您会 1) 注册一些依赖项 2) 构建一个临时服务提供者实例 3) 使用该服务提供者来解决您需要注册一些其他依赖项的问题。之后,您无法返回临时实例,因为它缺少您的一些依赖项(在 3 中注册)。我错过了什么吗?
M
Muhammad Rehan Saeed

手动解析实例涉及使用 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)
{
    application.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";

@AfsharMohebbi 通用的 GetServiceMicrosoft.Extensions.DependencyInjection 命名空间中的扩展方法。
关于扩展方法:扩展方法是为类添加功能的静态方法,您可以声明 public static TheReturnType TheMethodName(this TheTypeYouExtend theTypeYouExtend { // BODY} 然后您可以像这样使用它:TheTypeYouExtend.TheMethodName();使用 .NET Core 成为一种非常常见的方法,以便开发人员可以扩展基本功能......这里有很好的例子:docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
B
BrunoLM

如果您使用模板生成应用程序,您将在 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();
}

测试服务.cs

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

Startup.cs(配置服务)

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

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

家庭控制器.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();
        }

k
kilkfoe

如果您只需要解析一个依赖项以将其传递给您正在注册的另一个依赖项的构造函数,则可以这样做。

假设您有一个接收字符串和 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 方法中解析服务的原因,但这很可能是有人会考虑这样做的原因
事实上,这应该是公认的答案......虽然 Henk Mollema 的回答非常具有说明性,但现在你的答案更清晰,并且不会引入与构建中间 IServiceProvider 相关的问题(单例的不同实例......)。 2015 年,当 Henk 回答时,这个解决方案可能还不可用,但现在它是要走的路。
试过这个,但 ISomeService 对我来说仍然是空的。
2个问题: 1)如果服务类AnotherService的参数构造函数发生变化(删除或添加服务),那么我需要修改服务IAnotherService的注册段并且它一直在变化? 2) 相反,我只能为具有 1 个参数的 AnotherService 添加一个构造函数,例如 public AnotherService (IServiceProvider serviceProvider) 并从构造函数获取我需要的服务。我只需要在 Startup 类中注册服务类 AnotherService,例如 services.AddTransient(sp => { var service = new AnotherService(sp); return service; });
services.AddScoped<IAnotherService>(ctx => ActivatorUtilities.CreateInstance<AnotherService>(ctx, "https://someservice.com/") ); 应该是首选解决方案。
B
Bora Aydın

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

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

这就是我正在寻找的..谢谢
谢谢你,波拉艾登。与构造函数注入相比,这样做有什么缺点吗?
您可以在构造函数注入不可用的情况下使用这种类型的解析,例如属性。据我所知,它没有缺点。这只是解决依赖关系的另一种方式。
如果您将 IHttpContextAccessor 用作与您的存储库混合的 DI,这就是他们要走的路。
非常好的答案,只是补充一下 - 写这个更简洁的方法是:var someservice = context.HttpContext.RequestServices.GetService<ISomeService>();
I
Izzy

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

您可以利用定义自己的 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,而不是隐式依赖它或其方法的返回值...或以其他方式显式解决该依赖关系。


当您无法使用选项模式时,这是一个很好的解决方案,例如使用 3rd 方库
@Rory 我只能向你保证,自从这篇文章发布以来,会有更多可怕的黑客攻击:'D
B
Bibin Gangadharan

您可以通过这种方式使用 IApplicationBuilder 实例注入依赖项

public void Configure(IApplicationBuilder app)
    {
        //---------- Your code
        
        using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
        {
            var resultLogic = serviceScope.ServiceProvider.GetService<IResultLogic>();
            resultLogic.YourMethod();
        }           

        //---------- Your code
    }

u
user1437868

公共无效配置服务(IServiceCollection 服务){

        services.AddSingleton<ISelfServiceConfigLoad, SelfServiceConfigLoader>();
        var sp = services.BuildServiceProvider();
        var configservice = sp.GetServices<ISelfServiceConfigLoad>();
        services.AddSingleton<IExtractor, ConfigExtractor>( sp =>
        {
            var con = sp.GetRequiredService<ISelfServiceConfigLoad>();
             var config = con.Load();
            return new ConfigExtractor(config.Result);
        });
        services.AddSingleton<IProcessor<EventMessage>, SelfServiceProcessor>();          
        services.AddTransient<ISolrPush, SolrDataPush>();
        services.AddSingleton<IAPICaller<string, string>, ApiRestCaller<string, string>>();
        services.AddSingleton<IDataRetriever<SelfServiceApiRequest, IDictionary<string, object>>, SelfServiceDataRetriever>();
       


    }

这如何回答这个问题?请提供一些解释性文字。
您的答案可以通过额外的支持信息得到改进。请edit添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。您可以找到有关如何写出好答案的更多信息in the help center
这没有提供问题的答案。一旦你有足够的reputation,你就可以comment on any post;相反,provide answers that don't require clarification from the asker。 - From Review
在您提供的代码片段中,您正在注册服务。但问题是,如何在 ConfigureServices 中获取任何服务的实例?
他使用 AddSingleton 重载获取实例,该重载提供对 serviceProvider 的访问权限。而不是调用 IServiceCollection.BuildServiceProvider() stackoverflow.com/q/31863981/423356 在 ConfigureServices 中调用 BuildServiceProvider() 似乎不是最佳实践
W
Wai Ha Lee
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

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

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

如果您简要解释为什么它是一个好的答案,而不仅仅是代码片段,那么您的答案更有可能被接受和赞成。它还有助于提问者确保这实际上是在回答他们提出的问题。
有人错误地将您的答案标记为低质量。您应该添加一些随附的文字来解释您的答案是如何工作的,以防止进一步的标记和/或否决。仅代码答案 is not low-quality。它是否试图回答这个问题?如果不是,则标记为“不是答案”或建议删除(如果在审核队列中)。 b) 它在技术上不正确吗?投反对票或发表评论。 From review