如何在 ConfigureServices 中解析 IOptions 实例?
Posted
技术标签:
【中文标题】如何在 ConfigureServices 中解析 IOptions 实例?【英文标题】:How to resolve IOptions instance inside ConfigureServices? 【发布时间】:2015-10-30 02:05:11 【问题描述】:是否可以从 Startup 中的 ConfigureServices
方法解析 IOptions<AppSettings>
的实例? The documentation explicitly says:
不要在
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?
【问题讨论】:
【参考方案1】:您是否正在寻找类似以下的内容?大家可以看一下我的cmets的代码:
// 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
【讨论】:
【参考方案2】:如果您需要使用服务提供者手动解析服务,您可以使用此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),但如果在应用程序代码中使用 BuildServiceProvider
会导致警告例如ConfigureServices
,因为它会导致创建单例服务的附加副本。 Ehsan Mirsaeedi 的回答 here 是此类情况下最理想的解决方案。
这个答案是错误的,好像有单例注册一样,可能会导致创建多次单例。 @ehsan-mirsaeedi 答案更好,应该是公认的答案。【参考方案3】:
实例化依赖于其他服务的类的最佳方式是使用 AddXXX 重载,它为您提供 IServiceProvider em>。这样您就不需要实例化中间服务提供者。
以下示例展示了如何在 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)【参考方案4】:想帮助其他看起来相同但也在使用 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 前缀谷歌搜索这个问题来到这里的,不幸的是没有找到任何特定的主题。所以我希望其他也会遇到这个问题的人也能找到答案。【参考方案5】:告诉您手动构建IServiceProvider
以获取IOptions<T>
实例的所有其他答案都是危险,因为它们是错误(至少在 ASP .NET Core 3.0)!实际上,如果您今天使用这些答案,您将收到以下编译器警告:
从应用程序代码调用“BuildServiceProvider”会导致创建一个额外的单例服务副本。考虑将依赖注入服务等替代方案作为“配置”的参数。
正确的方法是实现这一点,在所有版本的 ASP.NET Core 中都安全可靠地工作,是实现自 .NET Core 1.0 以来就存在的 IConfigureOptions<TOptions>
接口 - 但它似乎很少有人知道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>
类的Configure
方法。然后,您以与注册任何其他服务相同的方式注册选项,然后离开!
如需更多详细信息,以及有关其在幕后如何运作的信息,请I refer you to the always-excellent Andrew Lock。
【讨论】:
如果在调用其他库的扩展方法时只需要读取一个配置对象怎么办?有什么方法可以渲染/解析配置以通知您在注册其他服务时提供的值? @AlexanderTrauzzi 只需将 IConfiguration 传递给 ConfigureServices - IOC 将提供它。 作为图书馆作者,有什么办法可以解决这个问题,因为它将采用扩展方法? @AlexanderTrauzzi 我建议您问一个问题,详细说明您要完成的工作。 假设您使用这种方法配置MvcOptions
,但您也可以正常配置它。你的方式是“添加”到现有配置,还是“替换”它?【参考方案6】:
在 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 传递选项。它可能对某人有所帮助。当我使用 *** 时,我通常会找到针对我的问题的特定答案或从其他答案中找到的代码 sn-p,而这些答案并不总是被接受的答案。因此,即使它们与公认的答案相似,也可以通过不同的示例获得更多答案。以上是关于如何在 ConfigureServices 中解析 IOptions 实例?的主要内容,如果未能解决你的问题,请参考以下文章
如何正确解析要在 ASP.NET Core 3.1 的 ConfigureServices() 中使用的服务?
在另一个类文件中编写 ConfigureServices 方法
如何将记录器实例传递给我在ConfigureServices中的自定义ModelBinderProvider?
在 ConfigureServices 方法中访问 IHostingEnvironment
在 Startup.cs 中使 ConfigureServices 方法异步
在 WebApi Core ConfigureServices 中访问 services.AddScoped 中的请求标头?