ChatGPT解决这个技术问题 Extra ChatGPT

如何在 ConfigureServices 中解析 IOptions 实例?

是否可以从 Startup 中的 ConfigureServices 方法解析 IOptions<AppSettings> 的实例? The documentation explicitly says

不要在 Startup.ConfigureServices 中使用 IOptions 或 IOptionsMonitor。由于服务注册的顺序,可能存在不一致的选项状态。

您可以使用 serviceCollection.BuildServiceProvider() 手动创建服务提供者,但这会导致警告:

从应用程序代码调用“BuildServiceProvider”会导致创建单例服务的附加副本。考虑替代方案,例如依赖注入服务作为“配置”的参数。

我怎样才能做到这一点?

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<AppSettings>(
        configuration.GetConfigurationSection(nameof(AppSettings)));

    // How can I resolve IOptions<AppSettings> here?
}

H
Henk Mollema

如果您需要使用服务提供者手动解析服务,您可以使用此 AddSingleton/AddScoped/AddTransient 重载:

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

如果您真的想要,您可以使用 IServiceCollection 上的 BuildServiceProvider() 方法构建一个中间服务提供者:

public void ConfigureService(IServiceCollection services)
{
    // Configure the services
    services.AddTransient<IFooService, FooServiceImpl>();
    services.Configure<AppSettings>(configuration.GetSection(nameof(AppSettings)));

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

    // Resolve the services from the service provider
    var fooService = sp.GetService<IFooService>();
    var options = sp.GetService<IOptions<AppSettings>>();
}

为此,您需要 Microsoft.Extensions.DependencyInjection 软件包。

但是,请注意,这会导致多个服务提供者实例,而这又可能导致多个单例实例。

如果只需要在 ConfigureServices 中绑定一些选项,也可以使用 Bind 方法:

var appSettings = new AppSettings();
configuration.GetSection(nameof(AppSettings)).Bind(appSettings);

此功能可通过 Microsoft.Extensions.Configuration.Binder 包获得。


如果您需要在应用程序的另一部分解析此服务怎么办?我确定这不是在 ConfigureServices() 中完成的,对吧?
@Ray 然后您可以使用默认的依赖注入机制,例如构造函数注入。这个问题专门关于在 ConfigureServices 方法中解析服务。
@pcdev 当你这样做时你得到 NULL 然后尝试解析实例。您必须先添加服务。
虽然这在添加服务的方法没有实现工厂重载(例如 here)的情况下可能很有用,但如果在应用程序代码(例如 ConfigureServices)中使用 BuildServiceProvider,则会导致警告在正在创建的单例服务的附加副本中。 Ehsan Mirsaeedi 的回答 here 是此类案例的最理想解决方案。
这个答案是错误的,好像有任何单例注册,它可能导致创建多次单例。 @ehsan-mirsaeedi 答案更好,应该是公认的答案。
s
spottedmahn

实例化依赖于其他服务的类的最佳方法是使用为您提供 IServiceProvider 的 AddXXX 重载。这样您就不需要实例化中间服务提供者。

以下示例展示了如何在 AddSingleton/AddTransient 方法中使用此重载。

services.AddSingleton(serviceProvider =>
{
    var options = serviceProvider.GetService<IOptions<AppSettings>>();
    var foo = new Foo(options);
    return foo ;
});


services.AddTransient(serviceProvider =>
{
    var options = serviceProvider.GetService<IOptions<AppSettings>>();
    var bar = new Bar(options);
    return bar;
});

使用此解决方案而不是 .Net Core 3 或更高版本的公认答案!
@Joshit我不太确定这是在所有情况下都可以接受的答案的可行替代品。 IServiceProvider 可用于即 AddSingleton、AddScoped、AddTransient。但是还有很多其他的 Add 方法不提供这种重载,即 AddCors、AddAuthentication、AddAuthorization。
@Jpsy您混淆了无关的东西。 AddCors、AddAuthentication 等是在注册方法下调用以连接各种底层中间件的助手。 AddTransient、AddSingleton、AddScoped 是三个注册(带有三个常用的生命周期)
这并不涵盖所有情况。请参阅 my answer 以获得可行的解决方案。
我认为这些内部 lamdas 正在异步运行或在其他时间运行。我们有 BuildServiceProvider 方法,我想消除构建警告,所以我转向了这种方法。现在我看到时间很重要的事情的一些副作用(AddIdentity 与 ConfigureApplicationCookie)
I
Ian Kemp

告诉您手动构建 IServiceProvider 以获取 IOptions<T> 实例的所有其他答案都是危险,因为它们是错误(至少在 ASP.NET Core 中) 3.0)!实际上,如果您今天使用这些答案,您将收到以下编译器警告:

从应用程序代码调用“BuildServiceProvider”会导致创建单例服务的附加副本。考虑替代方案,例如依赖注入服务作为“配置”的参数。

正确的方法是实现这一点,在所有版本的 ASP.NET Core 中都可以安全可靠地工作,实现自 .NET Core 1.0 以来就存在的 IConfigureOptions<TOptions> 接口 - 但似乎到目前为止知道 how it makes things Just Work™ 的人太少。

例如,您想要添加一个自定义模型验证器,该验证器依赖于您的应用程序的其他服务之一。最初似乎不可能 - 因为您无权访问 IServiceProvider,所以无法解决 IMyServiceDependency

public class MyModelValidatorProvider : IModelValidatorProvider
{
    public MyModelValidatorProvider(IMyServiceDependency dependency)
    {
        ...
    }
}

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers(options =>
    {
        options.ModelValidatorProviders.Add(new MyModelValidatorProvider(??????));
    });
}

但是 IConfigureOptions<TOptions> 的“魔力”让它变得如此简单:

public class ConfigureMvcOptions : IConfigureOptions<MvcOptions>
{
    private IMyServiceDependency _dependency;

    public MyMvcOptions(IMyServiceDependency dependency)
        => _dependency = dependency;

    public void Configure(MvcOptions options)
        => options.ModelValidatorProviders.Add(new MyModelValidatorProvider(_dependency));
}

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

    ...

    // or scoped, or transient, as necessary for your service
    services.AddSingleton<IConfigureOptions<MvcOptions>, ConfigureMvcOptions>();
}

本质上,您在 ConfigureServicesAdd***(***Options) 委托中所做的任何设置现在都移至 IConfigureOptions<TOptions> 类的 Configure 方法。然后,您以与注册任何其他服务相同的方式注册选项,然后就可以走了!

如需更多详细信息以及有关其如何在幕后工作的信息,请参阅I refer you to the always-excellent Andrew Lock


如果在调用其他库的扩展方法时只需要读取配置对象怎么办?有没有办法呈现/解析配置以通知您在注册其他服务时提供的值?
@AlexanderTrauzzi 只需将 IConfiguration 传递给 ConfigureServices - IOC 将提供它。
作为图书馆作者,有什么办法可以解决这个问题,因为它将采用扩展方法?
@AlexanderTrauzzi 我建议您问一个问题,详细说明您要完成的工作。
假设您使用这种方法来配置 MvcOptions,但您也以正常方式配置它。您的方式是“添加”到现有配置,还是“替换”它?
K
Kiran

您是否正在寻找类似以下的内容?你可以看看我在代码中的注释:

// this call would new-up `AppSettings` type
services.Configure<AppSettings>(appSettings =>
{
    // bind the newed-up type with the data from the configuration section
    ConfigurationBinder.Bind(appSettings, Configuration.GetConfigurationSection(nameof(AppSettings)));

    // modify these settings if you want to
});

// your updated app settings should be available through DI now

s
simply good

想帮助其他看起来相同但也在使用 Autofac 的人。

如果您想获得 ILifetimeScope(即当前范围的容器),您需要在 Configure(IApplicationBuilder app) 中调用 app.ApplicationServices.GetAutofacRoot() 方法,这将返回 ILifetimeScope 实例,您可以使用它来解析服务

public void Configure(IApplicationBuilder app)
    {
        //app middleware registrations 
        //...
        //

        ILifetimeScope autofacRoot = app.ApplicationServices.GetAutofacRoot();
        var repository = autofacRoot.Resolve<IRepository>();
    }

这个答案对 AutoFac 来说太具体了,这不在这个问题的范围内。
我是通过使用 autofac 前缀谷歌搜索这个问题来到这里的,不幸的是没有找到任何特定的主题。所以我希望其他也会遇到这个问题的人能够找到答案。
f
fededim

除了@henkmollema 回答,您还可以直接在 IConfiguration 上使用 Get 方法,例如

public void ConfigureServices(IServiceCollection services)
{
    var iConf=configuration.GetSection(nameof(AppSettings));

    services.Configure<AppSettings>(iConf);

    // How can I resolve IOptions<AppSettings> here?
    var opts=iConf.Get<AppSettings>();
}

注意:通过这种方式,您将直接获得 AppSettings 而不是 IOptions


S
Saeed Nawaz

在 MVC Core 3.1 或 .Net 5 中,您可以通过两行将 IOptions 传递给 Startup.cs 中的服务:

首先注册您的 IOptions 设置:

services.Configure<MySettings>(Configuration.GetSection("MySection"));

然后您的服务注册,传入 IOptions

services.AddSingleton<IMyService, MyService>(x => new MyService(x.GetService<IOptions<MySettings>>()));

这与接受的答案有何不同?
@Ian Kemp - 接受的答案详细说明了很好的解释,但我无法让我的代码使用它。我的回答只是对无数答案的补充。我在各种 MVC 框架中发现了细微差别,并暗示了我如何通过 DI 为 MVC Core 3.1 或 .Net 5 传递选项。它可能对某人有所帮助。当我使用 Stackoverflow 时,我通常会找到针对我的问题的特定答案或从其他答案中找到的代码片段,但并不总是被接受的答案。因此,即使它们与公认的答案相似,也有助于通过不同的示例获得更多答案。