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

2023-12-13

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

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

请参阅此问题的已接受答案:ASP.NET Core 模型绑定错误消息本地化

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通过基于当前服务集合构建的服务提供者。

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


每个服务提供商都有自己的缓存。因此,构建多个服务提供者实例可能会导致一个称为撕裂的生活方式:

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

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

但还可能出现其他同样微妙的错误。例如,在解析包含范围依赖项的对象图时。构建一个单独的临时服务提供程序来创建存储在下一个容器中的对象图可能会导致这些范围依赖项在应用程序的持续时间内保持活动状态。这个问题通常被称为强制依赖项.

对于像 Autofac 或 DryIoc 这样的容器来说,这没什么大不了的,因为您可以在一行上注册服务,然后在下一行上立即解决它。

此声明意味着在注册阶段仍在进行时尝试从容器解析实例不会出现任何问题。然而,这是不正确的——在已经解析实例之后通过添加新注册来更改容器是一种危险的做法——它可能导致各种难以跟踪的错误,与使用的 DI 容器无关。

特别是由于那些难以跟踪的错误,DI 容器(例如 Autofac、Simple Injector 和 Microsoft.Extensions.DependencyInjection (MS.DI))从一开始就阻止您执行此操作。 Autofac 和 MS.DI 通过在“容器构建器”(AutoFac 的ContainerBuilder和 MS.DI 的ServiceCollection)。另一方面,简单注入器不会进行这种分割。相反,它会在第一个实例解析后锁定容器以防止任何修改。然而,效果是相似的。它会阻止您在解决后添加注册。

简单注入器文档实际上包含一些体面的解释为什么这种“注册-解析-注册”模式存在问题:

想象一下您想要替换某些内容的场景FileLogger具有相同组件的不同实现ILogger界面。如果有一个组件直接或间接依赖于ILogger,替换ILogger实施可能不会如您所期望的那样进行。例如,如果使用组件注册为单例,则容器应保证仅创建该组件的一个实例。当您被允许更改实施时ILogger在单例实例已经拥有对“旧”注册实现的引用之后,容器有两种选择 - 两者都不正确:

  • 返回引用“错误”的消费组件的缓存实例ILogger执行。
  • 创建并缓存该组件的新实例,这样做会破坏将类型注册为单例的承诺以及容器始终返回相同实例的保证。

出于同样的原因,您会看到 ASP.NET CoreStartup类定义了两个单独的阶段:

  • “添加”阶段(ConfigureServices方法),您可以在其中将注册添加到“容器构建器”(又名:IServiceCollection)
  • “使用”阶段(Configure方法),您在其中声明要通过设置路由来使用 MVC。在此阶段期间,IServiceCollection已经变成了IServiceProvider这些服务甚至可以通过方法注入到Configure method.

因此,一般的解决方案是推迟解析服务(例如您的IStringLocalizerFactory)直到“使用”阶段,并随之推迟依赖于服务解析的事物的最终配置。

不幸的是,这似乎导致了先有鸡还是先有蛋配置时的因果困境ModelBindingMessageProvider因为:

  • 配置ModelBindingMessageProvider需要使用MvcOptions class.
  • The MvcOptions课程仅在“添加”期间可用(ConfigureServices) phase.
  • 在“添加”阶段,无法访问IStringLocalizerFactory并且无法访问容器或服务提供商,并且无法通过使用创建此类值来推迟解决该问题Lazy<IStringLocalizerFactory>.
  • 在“使用”阶段,IStringLocalizerFactory可用,但此时还没有MvcOptions您可以使用任何时间来配置ModelBindingMessageProvider.

解决这一僵局的唯一方法是使用内部的私有字段Startup类并在闭包中使用它们AddOptions。例如:


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");
}
  

该解决方案的缺点是这会导致时间耦合,这是它自己的代码味道。

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

  • 虽然ResourceManagerStringLocalizerFactory,这是默认的本地化器工厂,不包含任何状态,它确实依赖于其他服务,即IOptions<LocalizationOptions> and ILoggerFactory。两者都配置为单例。
  • 默认ILoggerFactory实施(即LoggerFactory),由服务提供商创建,并且ILoggerProvider之后可以将实例添加到该工厂。如果你的第二个ResourceManagerStringLocalizerFactory取决于它自己ILoggerFactory执行?这样做会正确吗?
  • 同样适用于IOptions<T>——实施者OptionsManager<T>。它是一个单例,但是OptionsManager<T>本身取决于IOptionsFactory<T>并包含自己的私有缓存。如果有第二次会发生什么OptionsManager<T>对于一个特定的T?未来这种情况会改变吗?
  • What if ResourceManagerStringLocalizerFactory被替换为不同的实现?这种情况并非不可能发生。依赖图会是什么样子?如果生活方式被破坏,这会带来麻烦吗?
  • 一般来说,即使您现在可以得出结论,效果很好,您确定这在 ASP.NET Core 的任何未来版本中都适用吗?不难想象,对 ASP.NET Core 未来版本的更新将以极其微妙和奇怪的方式破坏您的应用程序,因为您隐式依赖于这种特定行为。这些错误将很难追踪。

不幸的是,当涉及到配置时ModelBindingMessageProvider,似乎没有简单的出路。在我看来,这是 ASP.NET Core MVC 中的一个设计缺陷。希望微软能够在未来的版本中解决这个问题。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在ConfigureServices()中调用BuildServiceProvider()的成本和可能的副作用是什么 的相关文章

  • WCF RIA 服务 - 加载多个实体

    我正在寻找一种模式来解决以下问题 我认为这很常见 我正在使用 WCF RIA 服务在初始加载时将多个实体返回给客户端 我希望两个实体异步加载 以免锁定 UI 并且我想利用 RIA 服务来执行此操作 我的解决方案如下 似乎有效 这种方法会遇到
  • asp.net core / kestrel中的线程管理

    我正在解决我们已迁移到 asp net core 2 0 的 asp net 应用程序的性能 可扩展性问题 我们的应用程序作为应用程序服务托管在 azure 上 并且在任何中等流量的情况下都很容易崩溃 让我困惑的一件事是如何处理多个并发请求
  • ASP.NET MVC:这个业务逻辑应该放在哪里?

    我正在开发我的第一个真正的 MVC 应用程序 并尝试遵循一般的 OOP 最佳实践 我正在将控制器中的一些简单业务逻辑重构到我的域模型中 我最近一直在阅读一些内容 很明显我应该将逻辑放在域模型实体类中的某个位置 以避免出现 贫血域模型 反模式
  • 查找c中结构元素的偏移量

    struct a struct b int i float j x struct c int k float l y z 谁能解释一下如何找到偏移量int k这样我们就可以找到地址int i Use offsetof 找到从开始处的偏移量z
  • 为什么当实例化新的游戏对象时,它没有向它们添加标签? [复制]

    这个问题在这里已经有答案了 using System Collections using System Collections Generic using UnityEngine public class Test MonoBehaviou
  • 嵌套接口:将 IDictionary> 转换为 IDictionary>?

    我认为投射一个相当简单IDictionary
  • 用于登录 .NET 的堆栈跟踪

    我编写了一个 logger exceptionfactory 模块 它使用 System Diagnostics StackTrace 从调用方法及其声明类型中获取属性 但我注意到 如果我在 Visual Studio 之外以发布模式运行代
  • 如何从 appsettings.json 文件中的对象数组读取值

    我的 appsettings json 文件 StudentBirthdays Anne 01 11 2000 Peter 29 07 2001 Jane 15 10 2001 John Not Mentioned 我有一个单独的配置类 p
  • 不同枚举类型的范围和可转换性

    在什么条件下可以从一种枚举类型转换为另一种枚举类型 让我们考虑以下代码 include
  • 使用 Bearer Token 访问 IdentityServer4 上受保护的 API

    我试图寻找此问题的解决方案 但尚未找到正确的搜索文本 我的问题是 如何配置我的 IdentityServer 以便它也可以接受 授权带有 BearerTokens 的 Api 请求 我已经配置并运行了 IdentityServer4 我还在
  • SolrNet连接说明

    为什么 SolrNet 连接的容器保持静态 这是一个非常大的错误 因为当我们在应用程序中向应用程序发送异步请求时 SolrNet 会表现异常 在 SolrNet 中如何避免这个问题 class P static void M string
  • 如何在整个 ASP .NET MVC 应用程序中需要授权

    我创建的应用程序中 除了启用登录的操作之外的每个操作都应该超出未登录用户的限制 我应该添加 Authorize 每个班级标题前的注释 像这儿 namespace WebApplication2 Controllers Authorize p
  • 如何查看网络连接状态是否发生变化?

    我正在编写一个应用程序 用于检查计算机是否连接到某个特定网络 并为我们的用户带来一些魔力 该应用程序将在后台运行并执行检查是否用户请求 托盘中的菜单 我还希望应用程序能够自动检查用户是否从有线更改为无线 或者断开连接并连接到新网络 并执行魔
  • 覆盖子类中的字段或属性

    我有一个抽象基类 我想声明一个字段或属性 该字段或属性在从该父类继承的每个类中具有不同的值 我想在基类中定义它 以便我可以在基类方法中引用它 例如覆盖 ToString 来表示 此对象的类型为 property field 我有三种方法可以
  • 向现有 TCP 和 UDP 代码添加 SSL 支持?

    这是我的问题 现在我有一个 Linux 服务器应用程序 使用 C gcc 编写 它与 Windows C 客户端应用程序 Visual Studio 9 Qt 4 5 进行通信 是什么very在不完全破坏现有协议的情况下向双方添加 SSL
  • 如何从两个不同的项目中获取文件夹的相对路径

    我有两个项目和一个共享库 用于从此文件夹加载图像 C MainProject Project1 Images 项目1的文件夹 C MainProject Project1 Files Bin x86 Debug 其中有project1 ex
  • 如何将带有 IP 地址的连接字符串放入 web.config 文件中?

    我们当前在 web config 文件中使用以下连接字符串 add name DBConnectionString connectionString Data Source ourServer Initial Catalog ourDB P
  • 如何在Xamarin中删除ViewTreeObserver?

    假设我需要获取并设置视图的高度 在 Android 中 众所周知 只有在绘制视图之后才能获取视图高度 如果您使用 Java 有很多答案 最著名的方法之一如下 取自这个答案 https stackoverflow com a 24035591
  • C++ 中类级 new 删除运算符的线程安全

    我在我的一门课程中重新实现了新 删除运算符 现在我正在使我的代码成为多线程 并想了解这些运算符是否也需要线程安全 我在某处读到 Visual Studio 中默认的 new delete 运算符是线程安全的 但这对于我的类的自定义 new
  • 如何防止用户控件表单在 C# 中处理键盘输入(箭头键)

    我的用户控件包含其他可以选择的控件 我想实现使用箭头键导航子控件的方法 问题是家长控制拦截箭头键并使用它来滚动其视图什么是我想避免的事情 我想自己解决控制内容的导航问题 我如何控制由箭头键引起的标准行为 提前致谢 MTH 这通常是通过重写

随机推荐