在ConfigureServices()中调用BuildServiceProvider()的成本和可能产生的副作用 [英] What are the costs and possible side effects of calling BuildServiceProvider() in ConfigureServices()

查看:1789
本文介绍了在ConfigureServices()中调用BuildServiceProvider()的成本和可能产生的副作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Sometimes, during service registrations, I need to resolve other (already registered) services from the DI container. 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.

但是使用Microsoft的DI容器,您需要注册服务,然后构建服务提供者,然后您才能从该 IServiceProvider 实例解析服务。

But with Microsoft's DI container you need to register the service, then build a service provider and only then you are able resolve the services from that IServiceProvider instance.

查看此问题的已接受答案: ASP.NET核心模型绑定错误消息本地化

See the accepted answer this SO question: 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

To be able to localize the ModelBindingMessageProvider.ValueIsInvalidAccessor message, the answer suggests to resolve a IStringLocalizerFactory through the service provider built based on the current service collection.

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

What is the cost of "building" the service provider at that point and are there any side effects of doing that, since the service provider will be built at least once more (after all services are added)?

推荐答案

每个服务提供程序具有自己的缓存。因此,构建多个服务提供商实例可能会导致名为 Torn生活方式

Each service provider has its own cache. Building multiple service provider instances can, therefore, lead to a problem called Torn Lifestyles:


当具有相同生活方式的多个[注册]映射到同一组件时,该组件被称为撕裂的生活方式。该组件被认为是已损坏的,因为每个[注册]将拥有其自己的给定组件缓存,这可能会导致在单个作用域内出现该组件的多个实例。当注册被撕毁时,应用程序可能会错误地连接,这可能导致意外行为。

这意味着每个服务提供者将拥有自己的单例实例缓存。从同一来源(即从同一服务集合)构建多个服务提供者将导致多次创建一个单例实例,这打破了对于给定单例注册最多存在一个实例的保证。

This means that each service provider will have its own cache of singleton instances. Building multiple service providers from the same source (i.e. from the same service collection) will cause a singleton instance to be created more than once—this breaks the guarantee that there is at most one instance for a given singleton registration.

但是还有其他一些,就像可能会出现的细微错误一样。例如,当解析包含范围相关性的对象图时。为创建存储在下一个容器中的对象图而创建单独的临时服务提供程序,可能会导致这些范围内的依赖项在应用程序期间保持有效。此问题通常称为圈养依赖性

But there are other, just as subtle bugs that can appear. For instance, when resolving object graphs that contain scoped dependencies. Building a separate temporary service provider for the creation of an object graph that is stored in the next container might cause those scoped dependencies to be kept alive for the duration of the application. This problem is commonly referred to as Captive Dependencies.


使用Autofac或DryIoc这样的容器,这没什么大不了的,因为您可以在一行上注册该服务,而在下一行上可以立即解决该服务。

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.

该语句表示在注册阶段仍在进行中,尝试从容器中解析实例没有问题。但是,这是不正确的-在解决实例后通过在容器中添加新的注册来更改容器是一种危险的做法-可能导致各种难以跟踪的错误。

This statement implies that there are no problems with trying to resolve instances from the container while the registration phase is still in progress. This, however, is incorrect—altering the container by adding new registrations to it after you already resolved instances is a dangerous practice—it can lead to all sorts of hard to track bugs.

尤其是由于那些难以跟踪的错误,DI容器(例如Autofac,Simple Injector和Microsoft.Extensions.DependencyInjection(MS.DI))一开始就阻止您执行此操作。 Autofac和MS.DI通过在容器构建器中进行注册来做到这一点(AutoFac的 ContainerBuilder 和MS.DI的 ServiceCollection )。另一方面,简单注入器不会进行此拆分。相反,它会在解析第一个实例后锁定容器,使其无法进行任何修改。但是,效果是相似的。

It is especially because of those hard to track bugs that DI Containers, such as Autofac, Simple Injector, and Microsoft.Extensions.DependencyInjection (MS.DI) prevent you from doing this in the first place. Autofac and MS.DI do this by having registrations made in a 'container builder' (AutoFac's ContainerBuilder and MS.DI's ServiceCollection). Simple Injector, on the other hand, does not make this split. Instead, it locks the container from any modifications after the first instance is resolved. The effect, however, is similar; it prevents you from adding registrations after you resolve.

Simple Injector文档实际上包含一些关于此Register-Resolve-Register模式为何存在问题的详细解释

The Simple Injector documentation actually contains some decent explanation on why this Register-Resolve-Register pattern is problematic:


想象一下这种情况您想在其中用相同的 ILogger 接口替换一些 FileLogger 组件以实现不同的实现。如果存在直接或间接依赖于 ILogger 的组件,则替换 ILogger 的实现可能无法按预期工作。例如,如果使用方组件注册为单例,则容器应保证将仅创建此组件的一个实例。在单例实例已经拥有对旧注册实现的引用之后,如果允许您更改 ILogger 的实现,则容器有两个选择-都不正确:

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:


  • 返回引用错误 <$ c $的消费组件的缓存实例c> ILogger 实现。

  • 创建并缓存该组件的新实例,从而违反了类型被注册为单例,并保证容器将始终返回同一实例。

  • 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 类定义了两个单独的阶段:

For this same reason you see that the ASP.NET Core Startup class defines two separate phases:


  • 添加阶段( ConfigureServices 方法),在该阶段中,您将注册添加到容器构建器(又名 IServiceCollection

  • 使用阶段(配置方法),您可以通过设置路由来声明要使用MVC。在此阶段, IServiceCollection 已转换为 IServiceProvider ,并且甚至可以通过方法将这些服务注入配置方法。

  • The "Add" phase (the ConfigureServices method), where you add registrations to the "container builder" (a.k.a. IServiceCollection)
  • The "Use" phase (the Configure method), where you state you want to use MVC by setting up routes. During this phase, the IServiceCollection has been turned into a IServiceProvider and those services can even be method injected into the Configure method.

因此,一般的解决方案是推迟解决服务(例如您的 IStringLocalizerFactory ),直到使用阶段为止,并以此来推迟取决于服务解析的事物的最终配置。

The general solution, therefore, is to postpone resolving services (like your IStringLocalizerFactory) until the "Use" phase, and with it postpone the final configuration of things that depend on the resolving of services.

不幸的是,在配置 ModelBindingMessageProvider 时,这似乎导致鸡肉或鸡蛋因果关系的困境,因为:

This, unfortunately, seems to cause a chicken or the egg causality dilemma when it comes to configuring the ModelBindingMessageProvider because:


  • 配置 ModelBindingMessageProvider 要求使用 MvcOptions 类。

  • MvcOptions 类仅在添加( ConfigureServices )阶段。

  • 在添加阶段,无法访问 IStringLocalizerFactor y ,并且无法通过使用 Lazy< IStringLocalizerFactory> 创建这样的值来延迟对容器或服务提供商的访问并解决该问题。 / li>
  • 在使用阶段,可以使用 IStringLocalizerFactory ,但那时还没有 MvcOptions ,可以再用于配置 ModelBindingMessageProvider

  • Configuring the ModelBindingMessageProvider requires the use of the MvcOptions class.
  • The MvcOptions class is only available during the "Add" (ConfigureServices) phase.
  • During the "Add" phase there is no access to an IStringLocalizerFactory and no access to a container or service provider and resolving it can’t be postponed by creating such value using a Lazy<IStringLocalizerFactory>.
  • During the "Use" phase, IStringLocalizerFactory is available, but at that point, there is no MvcOptions any longer that you can use to configure the ModelBindingMessageProvider.

解决这种僵局的唯一方法是使用 Startup 类中的私有字段,并在 AddOptions 。例如:

The only way around this impasse is by using private fields inside the Startup class and use them in the closure of AddOptions. For instance:

public void ConfigureServices(IServiceCollection services)
{
    services.AddLocalization();
    services.AddMvc(options =>
    {
        options.ModelBindingMessageProvider.SetValueIsInvalidAccessor(
            _ => this.localizer["The value '{0}' is invalid."]);
    });
}

private IStringLocalizer localizer;

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    this.localizer = app.ApplicationServices
        .GetRequiredService<IStringLocalizerFactory>()
        .Create("ModelBindingMessages", "AspNetCoreLocalizationSample");
}

此解决方案的缺点是,这会导致临时耦合,它本身就是一种代码味道。

The downside of this solution is that this causes Temporal Coupling, which is a code smell of its own.

您当然可以说这是一个丑陋的解决方法,可以解决在处理 IStringLocalizerFactory 时甚至不存在的问题;在这种情况下,创建一个临时服务提供商来解决本地化工厂可能就可以了。然而,实际上很难分析您是否会遇到麻烦。例如:

You could, of course, argue that this a ugly workaround for a problem that might not even exist when dealing with IStringLocalizerFactory; creating a temporary service provider to resolve the localization factory might work just fine in that particular case. Thing is, however, that it is actually pretty hard to analyze whether or not you’re going to run in trouble. For instance:


  • 即使 ResourceManagerStringLocalizerFactory (默认的本地化器工厂)也可以不包含任何状态,它确实依赖于其他服务,即 IOptions< LocalizationOptions> ILoggerFactory

  • 默认的 ILoggerFactory 实现(即 LoggerFactory ),由服务提供商创建,然后可以将 ILoggerProvider 实例添加到该工厂。如果您的第二个 ResourceManagerStringLocalizerFactory 依赖于它自己的 ILoggerFactory 实现,将会发生什么?会正确解决吗?

  • IOptions< T> 持有相同的所有权-由 OptionsManager< T>实现。它是一个单例,但 OptionsManager< T> 本身取决于 IOptionsFactory< T> 并包含其自己的专用缓存。如果对于特定的 T 还有第二个 OptionsManager< T> ,会发生什么?

  • 如果将 ResourceManagerStringLocalizerFactory 替换为其他实现,该怎么办?这是一个不太可能的情况。依赖图会是什么样子,如果生活方式被破坏会造成麻烦?

  • 通常,即使您现在可以得出结论说现在效果还不错,您确定吗?在将来的任何ASP.NET Core版本中都可以使用?不难想象,对ASP.NET Core未来版本的更新将以完全微妙和怪异的方式破坏您的应用程序,因为您隐式地依赖于此特定行为。

  • Even though ResourceManagerStringLocalizerFactory, which is the default localizer factory, does not contain any state, it does takes a dependency on other services, namely IOptions<LocalizationOptions> and ILoggerFactory. Both of which are configured as singletons.
  • The default ILoggerFactory implementation (i.e. LoggerFactory), is created by the service provider, and ILoggerProvider instances can be added afterwards to that factory. What will happen if your second ResourceManagerStringLocalizerFactory depends on its own ILoggerFactory implementation? Will that work out correctly?
  • Same holds for IOptions<T>—implemented by OptionsManager<T>. It is a singleton, but OptionsManager<T> itself depends on IOptionsFactory<T> and contains its own private cache. What will happen if there is a second OptionsManager<T> for a particular T? And could that change in the future?
  • What if ResourceManagerStringLocalizerFactory is replaced with a different implementation? This is a not-unlikely scenario. What would the dependency graph than look like and would that cause trouble if lifestyles get torn?
  • In general, even if you would be able to conclude that works just fine right now, are you sure that this will hold in any future version of ASP.NET Core? It is not that hard to imagine that an update to a future version of ASP.NET Core will break your application in utterly subtle and weird ways because you implicitly depend on this specific behavior. Those bugs will be pretty hard to track down.

不幸的是,当涉及到配置 ModelBindingMessageProvider 时code>,似乎没有简单的出路。这是IMO在ASP.NET Core MVC中的一个设计缺陷。希望Microsoft在将来的版本中解决此问题。

Unfortunately, when it comes to configuring the ModelBindingMessageProvider, there seems no easy way out. This is IMO a design flaw in the ASP.NET Core MVC. Hopefully Microsoft will fix this in a future release.

这篇关于在ConfigureServices()中调用BuildServiceProvider()的成本和可能产生的副作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆