gpt4 book ai didi

c# - 在 ConfigureServices() 中调用 BuildServiceProvider() 的成本和可能的副作用是什么

转载 作者:行者123 更新时间:2023-11-30 14:44:58 49 4
gpt4 key购买 nike

有时,在服务注册期间,我需要从 DI 容器解析其他(已经注册的)服务。对于像 Autofac 或 DryIoc 这样的容器,这没什么大不了的,因为您可以在一行上注册服务,在下一行上您可以立即解决它。

但是对于微软的 DI 容器,你需要注册服务,然后建立一个服务提供者,然后你才能从那个 IServiceProvider 解析服务。实例。

请参阅此 SO 问题的已接受答案:ASP.NET Core Model Binding Error Messages Localization

public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization(options => { options.ResourcesPath = "Resources"; });
services.AddMvc(options =>
{
var F = services.BuildServiceProvider().GetService<IStringLocalizerFactory>();
var L = F.Create("ModelBindingMessages", "AspNetCoreLocalizationSample");
options.ModelBindingMessageProvider.ValueIsInvalidAccessor =
(x) => L["The value '{0}' is invalid."];

// omitted the rest of the snippet
})
}

能够本地化 ModelBindingMessageProvider.ValueIsInvalidAccessor消息,答案建议解决 IStringLocalizerFactory通过基于当前服务集合构建的服务提供者。

此时“构建”服务提供者的成本是多少?这样做是否有任何副作用,因为服务提供者将至少再构建一次(在添加所有服务之后)?

最佳答案

每个服务提供者都有自己的缓存。因此,构建多个服务提供者实例会导致一个名为 Torn Lifestyles 的问题。 :

When multiple [registrations] with the same lifestyle map to the same component, the component is said to have a torn lifestyle. The component is considered torn because each [registration] will have its own cache of the given component, which can potentially result in multiple instances of the component within a single scope. When the registrations are torn the application may be wired incorrectly which could lead to unexpected behavior.


这意味着每个服务提供者都有自己的单例实例缓存。从同一个源(即从同一个服务集合)构建多个服务提供者将导致单例实例被创建不止一次——这破坏了给定单例注册最多有一个实例的保证。
但是还有其他一些细微的错误可能会出现。例如,在解析包含作用域依赖项的对象图时。为创建存储在下一个容器中的对象图构建单独的临时服务提供程序可能会导致这些范围依赖项在应用程序的持续时间内保持事件状态。这个问题通常被称为 Captive Dependencies .

With containers like Autofac or DryIoc this was no big deal since you could register the service on one line and on the next line you could immediately resolve it.


此声明意味着在注册阶段仍在进行时尝试从容器解析实例没有问题。然而,这是不正确的——在你已经解决实例之后通过向容器添加新注册来改变容器是一种危险的做法——它可能导致各种难以跟踪的错误,独立于使用的 DI 容器。
尤其是因为那些难以跟踪的错误,DI 容器(例如 Autofac、Simple Injector 和 Microsoft.Extensions.DependencyInjection (MS.DI))首先阻止了您执行此操作。 Autofac 和 MS.DI 通过在“容器构建器”(AutoFac 的 ContainerBuilder 和 MS.DI 的 ServiceCollection )中进行注册来做到这一点。另一方面,Simple Injector 不会进行这种拆分。相反,它会在解析第一个实例后锁定容器,以免进行任何修改。然而,效果是相似的。它会阻止您在解决后添加注册。
Simple Injector 文档实际上包含一些 decent explanation为什么这种注册-解析-注册模式有问题:

Imagine the scenario where you want to replace some FileLogger component for a different implementation with the same ILogger interface. If there’s a component that directly or indirectly depends on ILogger, replacing the ILogger implementation might not work as you would expect. If the consuming component is registered as singleton, for example, the container should guarantee that only one instance of this component will be created. When you are allowed to change the implementation of ILogger after a singleton instance already holds a reference to the “old” registered implementation the container has two choices—neither of which are correct:

  • Return the cached instance of the consuming component that has a reference to the “wrong” ILogger implementation.
  • Create and cache a new instance of that component and, in doing so, break the promise of the type being registered as a singleton and the guarantee that the container will always return the same instance.

出于同样的原因,您会看到 ASP.NET Core Startup类定义了两个独立的阶段:
  • “添加”阶段(ConfigureServices 方法),在此您将注册添加到“容器构建器”(又名 IServiceCollection)
  • “使用”阶段(Configure 方法),您可以通过设置路由来声明要使用 MVC。在此阶段,IServiceCollection已变成IServiceProvider这些服务甚至可以被方法注入(inject)到Configure方法。

  • 因此,一般的解决方案是将解析服务(如您的 IStringLocalizerFactory)推迟到“使用”阶段,并随之推迟依赖于服务解析的事物的最终配置。
    不幸的是,当涉及到配置 ModelBindingMessageProvider 时,这似乎会导致先有鸡还是先有蛋的因果困境。因为:
  • 配置 ModelBindingMessageProvider需要使用 MvcOptions类(class)。
  • MvcOptions类仅在“添加”( ConfigureServices )阶段可用。
  • 在“添加”阶段,无法访问 IStringLocalizerFactory并且无法访问容器或服务提供者,并且不能通过使用 Lazy<IStringLocalizerFactory> 创造这样的值(value)来推迟解决它。 .
  • 在“使用”阶段,IStringLocalizerFactory可用,但此时没有 MvcOptions不再可以用来配置 ModelBindingMessageProvider .

  • 解决这个僵局的唯一方法是在 Startup 中使用私有(private)字段。类并在 AddOptions 的闭包中使用它们.例如:
    public void ConfigureServices(IServiceCollection 服务)
    {
    services.AddLocalization();
    services.AddMvc(选项=>
    {
    options.ModelBindingMessageProvider.SetValueIsInvalidAccessor(
    _ => this.localizer["值 '{0}' 无效。"]);
    });
    }

    私有(private) IStringLocalizer 本地化器;

    公共(public)无效配置(IApplicationBuilder 应用程序,IHostingEnvironment 环境)
    {
    this.localizer = app.ApplicationServices
    .GetRequiredService ()
    .Create("ModelBindingMessages", "AspNetCoreLocalizationSample");
    }

    此解决方案的缺点是这会导致 Temporal Coupling ,这是它自己的代码味道。
    当然,您可以争辩说,对于在处理 IStringLocalizerFactory 时甚至可能不存在的问题,这是一种丑陋的解决方法。 ;在这种特殊情况下,创建一个临时服务提供者来解决本地化工厂可能会正常工作。然而,实际上很难分析您是否会遇到麻烦。例如:
  • 即使 ResourceManagerStringLocalizerFactory ,这是默认的本地化工厂,不包含任何状态,它依赖于其他服务,即 IOptions<LocalizationOptions>ILoggerFactory .两者都配置为单例。
  • 默认ILoggerFactory实现(即 LoggerFactory),由服务提供者创建,而 ILoggerProvider之后可以将实例添加到该工厂。如果您的第二个 ResourceManagerStringLocalizerFactory 会发生什么取决于自己ILoggerFactory执行?这样做会正确吗?
  • 同样适用于 IOptions<T> — 由 OptionsManager<T> 实现.它是一个单例,但是 OptionsManager<T>本身取决于 IOptionsFactory<T>并包含自己的私有(private)缓存。如果有第二个 OptionsManager<T> 会发生什么对于特定的 T ?这在 future 会改变吗?
  • 如果ResourceManagerStringLocalizerFactory怎么办被替换为不同的实现?这是一个不太可能发生的情况。依赖关系图会是什么样子,如果生活方式被破坏会引起麻烦吗?
  • 一般而言,即使您可以得出结论现在工作得很好,您确定这将适用于 ASP.NET Core 的任何 future 版本吗?不难想象,对 ASP.NET Core future 版本的更新会以非常微妙和奇怪的方式破坏您的应用程序,因为您隐式地依赖于这种特定行为。这些错误将很难追踪。

  • 不幸的是,在配置 ModelBindingMessageProvider 时,似乎没有捷径可走。这是 IMO ASP.NET Core MVC 中的一个设计缺陷。希望微软会在 future 的版本中解决这个问题。

    关于c# - 在 ConfigureServices() 中调用 BuildServiceProvider() 的成本和可能的副作用是什么,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56042989/

    49 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com