如何解析 ConfigureServices 中的 IOptions 实例?
How to resolve IOptions instance inside ConfigureServices?
是否可以从 Startup 中的 ConfigureServices
方法解析 IOptions<AppSettings>
的实例? The documentation explicitly says:
Don't use IOptions<TOptions>
or IOptionsMonitor<TOptions>
in Startup.ConfigureServices.
An inconsistent options state may exist due to the ordering of service registrations.
您可以使用 serviceCollection.BuildServiceProvider()
手动创建服务提供者,但这会导致警告:
Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'.
我怎样才能做到这一点?
public void ConfigureServices(IServiceCollection services)
{
services.Configure<AppSettings>(
configuration.GetConfigurationSection(nameof(AppSettings)));
// How can I resolve IOptions<AppSettings> here?
}
您是否在寻找类似以下的内容?大家可以看看我在代码中的注释:
// 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
包获得。
实例化依赖于其他服务的 类 的最佳方法是使用 AddXXX 提供的重载你用 IServiceProvider。这样就不需要实例化中间服务提供者了。
以下示例展示了如何在 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;
});
想帮助其他看起来相同但也在使用 Autofac 的人。
如果你想获得 ILifetimeScope(即当前范围的容器),你需要调用 app.ApplicationServices.GetAutofacRoot()
中的方法 Configure(IApplicationBuilder app)
这将 return 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 开始)!事实上,如果您今天使用这些答案,您将收到以下编译器警告:
Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'.
正确的方法是实现这一点,它在所有版本的 ASP.NET Core 中安全可靠地工作,是实现 IConfigureOptions<TOptions>
interface that's existed since .NET Core 1.0 - but it seems that far too few people know about how it makes things Just Work™。
例如,您想要添加一个依赖于应用程序的其他服务之一的自定义模型验证器。最初它似乎是不可能的 - 没有办法解决 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>
class 的 Configure
方法中.然后,您可以像注册任何其他服务一样注册这些选项,然后就可以开始了!
有关更多详细信息以及幕后工作原理的信息,I refer you to the always-excellent Andrew Lock。
在 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。
是否可以从 Startup 中的 ConfigureServices
方法解析 IOptions<AppSettings>
的实例? The documentation explicitly says:
Don't use
IOptions<TOptions>
orIOptionsMonitor<TOptions>
inStartup.ConfigureServices.
An inconsistent options state may exist due to the ordering of service registrations.
您可以使用 serviceCollection.BuildServiceProvider()
手动创建服务提供者,但这会导致警告:
Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'.
我怎样才能做到这一点?
public void ConfigureServices(IServiceCollection services)
{
services.Configure<AppSettings>(
configuration.GetConfigurationSection(nameof(AppSettings)));
// How can I resolve IOptions<AppSettings> here?
}
您是否在寻找类似以下的内容?大家可以看看我在代码中的注释:
// 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
包获得。
实例化依赖于其他服务的 类 的最佳方法是使用 AddXXX 提供的重载你用 IServiceProvider。这样就不需要实例化中间服务提供者了。
以下示例展示了如何在 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;
});
想帮助其他看起来相同但也在使用 Autofac 的人。
如果你想获得 ILifetimeScope(即当前范围的容器),你需要调用 app.ApplicationServices.GetAutofacRoot()
中的方法 Configure(IApplicationBuilder app)
这将 return 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 开始)!事实上,如果您今天使用这些答案,您将收到以下编译器警告:
Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'.
正确的方法是实现这一点,它在所有版本的 ASP.NET Core 中安全可靠地工作,是实现 IConfigureOptions<TOptions>
interface that's existed since .NET Core 1.0 - but it seems that far too few people know about how it makes things Just Work™。
例如,您想要添加一个依赖于应用程序的其他服务之一的自定义模型验证器。最初它似乎是不可能的 - 没有办法解决 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>
class 的 Configure
方法中.然后,您可以像注册任何其他服务一样注册这些选项,然后就可以开始了!
有关更多详细信息以及幕后工作原理的信息,I refer you to the always-excellent Andrew Lock。
在 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