【问题标题】:How to resolve IOptions instance inside ConfigureServices?如何在 ConfigureServices 中解析 IOptions 实例?
【发布时间】: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?
}

【问题讨论】:

    标签: c# asp.net-core inversion-of-control asp.net-core-mvc


    【解决方案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&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,但您也可以正常配置它。你的方式是“添加”到现有配置,还是“替换”它?
      【解决方案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 传递选项。它可能对某人有所帮助。当我使用 Stackoverflow 时,我通常会找到针对我的问题的特定答案或从其他答案中找到的代码 sn-p,而这些答案并不总是被接受的答案。因此,即使它们与公认的答案相似,也可以通过不同的示例获得更多答案。
      猜你喜欢
      • 1970-01-01
      • 2020-08-30
      • 2019-02-14
      • 1970-01-01
      • 1970-01-01
      • 2019-06-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多