如何解析 ConfigureServices 中的 IOptions 实例?

是否可以从启动时的 ConfigureServices方法解析 IOptions<AppSettings>的实例? 文档明确地说:

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

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

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

我怎么才能做到呢?

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


// How can I resolve IOptions<AppSettings> here?
}
85796 次浏览

你是否在寻找类似下面这样的东西? 你可以看看我在代码中的注释:

// 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

如果您需要使用服务提供者手动解析服务,您可以使用这个 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包获得。

实例化依赖于其他服务的类的最佳方法是使用提供 < em > IServiceProvider 添加 < strong > XXX 重载。这样就不需要实例化中间服务提供者。

下面的示例演示如何在 < em > AddSingleton/Add瞬变 方法中使用此重载。

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

希望帮助其他人谁看起来相同的事情,但当使用自动工厂太。

如果你想得到 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>();
}

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

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

正确的方法是实现这一点,它在所有版本的 ASP.NET Core 中都能安全可靠地工作,就是实现从那时起就存在的 IConfigureOptions<TOptions>接口。NET Core 1.0-但是似乎很少有人知道 它是如何让事情顺其自然的

例如,您希望添加一个自定义模型验证器,该验证器依赖于应用程序的其他服务之一。最初看起来似乎是不可能的——没有办法解析 IMyServiceDependency,因为你没有访问 IServiceProvider的权限:

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

实际上,在 ConfigureServices中的 Add***(***Options)委托中进行的任何设置现在都移到了 IConfigureOptions<TOptions>类的 Configure方法中。然后你用注册其他服务的同样方式注册这些选项,然后你就可以开始了!

欲了解更多细节,以及有关如何在幕后工作的信息,请访问 我建议你去看看一向优秀的安德鲁 · 洛克

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

除了@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 < AppSettings > 。