在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()的成本和可能的副作用是什么 的相关文章

  • 未解决的包含:“cocos2d.h” - Cocos2dx

    当我在 Eclipse 中导入 cocos2dx android 项目时 我的头文件上收到此警告 Unresolved inclusion cocos2d h 为什么是这样 它实际上困扰着我 该项目可以正确编译并运行 但我希望这种情况消失
  • 如何忽略“有符号和无符号整数表达式之间的比较”?

    谁能告诉我必须使用哪个标志才能使 gcc 忽略 有符号和无符号整数表达式之间的比较 警告消息 gcc Wno sign compare 但你确实应该修复它警告你的比较
  • 获取没有非标准端口的原始 url (C#)

    第一个问题 环境 MVC C AppHarbor Problem 我正在调用 openid 提供商 并根据域生成绝对回调 url 在我的本地机器上 如果我点击的话 效果很好http localhost 12345 login Request
  • C 预处理器库

    我的任务是开发源分析工具C程序 并且我需要在分析本身之前预处理代码 我想知道什么是最好的图书馆 我需要一些重量轻 便于携带的东西 与其推出自己的 为什么不使用cpp这是的一部分gcc suite http gcc gnu org onlin
  • Cython 和类的构造函数

    我对 Cython 使用默认构造函数有疑问 我的 C 类 Node 如下 Node h class Node public Node std cerr lt lt calling no arg constructor lt lt std e
  • 增加在 Azure 上运行的 Dockerized ASP.NET Core 站点的最大上传大小限制?

    以下是应用程序的架构 使用 ASP NET Core 编写的 Web API Dockerfile 使用以下命令构建 Web 应用程序microsoft dotnet 2 1 sdk并使用执行 APImicrosoft dotnet asp
  • 指针减法混乱

    当我们从另一个指针中减去一个指针时 差值不等于它们相距多少字节 而是等于它们相距多少个整数 如果指向整数 为什么这样 这个想法是你指向内存块 06 07 08 09 10 11 mem 18 24 17 53 7 14 data 如果你有i
  • 如何返回 json 结果并将 unicode 字符转义为 \u1234

    我正在实现一个返回 json 结果的方法 例如 public JsonResult MethodName Guid key var result ApiHelper GetData key Data is stored in db as v
  • 在数据库中搜索时忽略空文本框

    此代码能够搜索数据并将其加载到DataGridView基于搜索表单文本框中提供的值 如果我将任何文本框留空 则不会有搜索结果 因为 SQL 查询是用 AND 组合的 如何在搜索 从 SQL 查询或 C 代码 时忽略空文本框 private
  • 将自定义元数据添加到 jpeg 文件

    我正在开发一个图像处理项目 C 我需要在处理完成后将自定义元数据写入 jpeg 文件 我怎样才能做到这一点 有没有可用的图书馆可以做到这一点 如果您正在谈论 EXIF 元数据 您可能需要查看exiv2 http www exiv2 org
  • for循环中计数器变量的范围是多少?

    我在 Visual Studio 2008 中收到以下错误 Error 1 A local variable named i cannot be declared in this scope because it would give a
  • Qt表格小部件,删除行的按钮

    我有一个 QTableWidget 对于所有行 我将一列的 setCellWidget 设置为按钮 我想将此按钮连接到删除该行的函数 我尝试了这段代码 它不起作用 因为如果我只是单击按钮 我不会将当前行设置为按钮的行 ui gt table
  • 从库中捕获主线程 SynchronizationContext 或 Dispatcher

    我有一个 C 库 希望能够将工作发送 发布到 主 ui 线程 如果存在 该库可供以下人员使用 一个winforms应用程序 本机应用程序 带 UI 控制台应用程序 没有 UI 在库中 我想在初始化期间捕获一些东西 Synchronizati
  • Discord.net 无法在 Linux 上运行

    我正在尝试让在 Linux VPS 上运行的 Discord net 中编码的不和谐机器人 我通过单声道运行 但我不断收到此错误 Unhandled Exception System Exception Connection lost at
  • 将 unsigned char * (uint8_t *) 转换为 const char *

    我有一个带有 uint8 t 参数的函数 uint8 t ihex decode uint8 t in size t len uint8 t out uint8 t i hn ln for i 0 i lt len i 2 hn in i
  • 实体框架 4 DB 优先依赖注入?

    我更喜欢创建自己的数据库 设置索引 唯一约束等 使用 edmx 实体框架设计器 从数据库生成域模型是轻而易举的事 现在我有兴趣使用依赖注入来设置一些存储库 我查看了 StackOverflow 上的一些文章和帖子 似乎重点关注代码优先方法
  • x86 上未对齐的指针

    有人可以提供一个示例 将指针从一种类型转换为另一种类型由于未对齐而失败吗 在评论中这个答案 https stackoverflow com questions 544928 reading integer size bytes from a
  • ASP.NET MVC 6 (ASP.NET 5) 中的 Application_PreSendRequestHeaders 和 Application_BeginRequest

    如何在 ASP NET 5 MVC6 中使用这些方法 在 MVC5 中 我在 Global asax 中使用了它 现在呢 也许是入门班 protected void Application PreSendRequestHeaders obj
  • 防止索引超出范围错误

    我想编写对某些条件的检查 而不必使用 try catch 并且我想避免出现 Index Out of Range 错误的可能性 if array Element 0 Object Length gt 0 array Element 1 Ob
  • 使用按位运算符相乘

    我想知道如何使用按位运算符将一系列二进制位相乘 但是 我有兴趣这样做来查找二进制值的十进制小数值 这是我正在尝试做的一个例子 假设 1010010 我想使用每个单独的位 以便将其计算为 1 2 1 0 2 2 1 2 3 0 2 4 虽然我

随机推荐