如何在 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】:

如果您需要使用服务提供者手动解析服务,您可以使用此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 答案更好,应该是公认的答案。【参考方案2】:

实例化依赖于其他服务的类的最佳方式是使用 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)【参考方案3】:

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

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

正确的方法是实现这一点,在所有版本的 ASP.NET Core 中都安全可靠地工作,是实现自 .NET Core 1.0 以来就存在的 IConfigureOptions&lt;TOptions&gt; 接口 - 但它似乎很少有人知道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&lt;TOptions&gt; 的“魔力”让它变得如此简单:

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&lt;TOptions&gt; 类的Configure 方法。然后,您以与注册任何其他服务相同的方式注册选项,然后离开!

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

【讨论】:

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

您是否正在寻找类似以下的内容?大家可以看一下我的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

【讨论】:

【参考方案5】:

想帮助其他看起来相同但也在使用 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 前缀谷歌搜索这个问题来到这里的,不幸的是没有找到任何特定的主题。所以我希望其他也会遇到这个问题的人也能找到答案。【参考方案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 中的请求标头?