asp.net 核心 2.1 中的共享资源本地化和简单注入器

SharedResource Localization and Simple Injector in asp.net core 2.1

我在 ASP.NET 核心 2.1 中有一个 ASP.NET web api,并且我已经按照 here 的解释实现了共享资源。这很好用。

行:

services.AddLocalization()

向内置 IOC 容器添加本地化。 (我认为这至少是魔法发生的地方)

现在我已经为我自己的 classes 添加了 Simple Injector,并且我有一个 class,我已经注册为 Async Scoped,它被注入了共享资源(通过 IStringLocalizer) .但是,IStringLocalizer 的范围是瞬态的,这与 Async Scoped 不兼容(因为它的范围更长)。我显然可以通过将选项 "SuppressLifestyleMismatchVerification" 设置为 true 来解决问题,但这闻起来不对。 (在这种情况下,它可能无关紧要,但通过使用此选项,我掩盖了我可能遇到的任何其他问题)有没有办法解决这个问题?例如,我可以更改共享资源的范围吗?

你看到的是两个宇宙的碰撞;两个 DI 库都有自己的、不兼容的定义 transient:

  • Simple Injector 将具有 Transient 生活方式的组件视为短暂的,或 brief。这就是为什么 Simple Injector 不允许将瞬变注入作用域组件;示波器的寿命可能比 brief.
  • 长得多
  • ASP.NET 核心根本不认为瞬态组件是短暂的。相反,.NET Core 中 Transient 组件的预期生命周期是 与其消费者的预期生命周期一样长 docs 甚至声明“此生命周期最适合轻量级、无状态服务。”

更新 使用 Simple Injector v5,此行为已有所放松,现在(默认情况下)可以将瞬变注入作用域组件.然而,transient 含义的定义保持不变,这意味着当您尝试将 transient 注入单例时,Simple Injector 仍然认为这是一个错误。

正是由于这种行为,Microsoft.Extensions.DependencyInjection (MS.DI) 容器允许将瞬变注入到单例和作用域消费者中。

我什至会争辩说 Microsoft 错误地命名了他们的生活方式,因为实际行为是每个消费者的依赖项都有一个实例。 Microsoft 似乎从 Autofac 复制了此行为。然而,Autofac 确实命名了同样的生活方式 InstancePerDependency,如果你问我,这是一个更明显的名字。

不过,奇怪的是 Microsoft 的 AddLocalization 扩展方法 registers StringLocalizer<T> as transient. This is weird, because, apart from the wrapped IStringLocalizer, StringLocalizer<T> doesn't have any state. And not only that, the IStringLocalizer that it wraps is produced by the injected IStringLocalizerFactory and can be expected to be the same instance (which is enforced by the fact that the ResourceManagerStringLocalizerFactory caches 返回实例)。

如上所述,在 MS.DI 内,Transient 意味着“我会和我的消费者一样长寿。 “这实际上意味着 StringLocalizer<T> 实例可以像单例一样存在,意思是:在整个应用程序的持续时间内。

从这方面来说,本地化团队选择StringLocalizer<T>过一种短暂的生活方式,实际上很奇怪,即使在MS.DI。 Transient 仅意味着创建了更多的实例,并且 IStringLocalizerFactory 的调用频率超过了要求。我发现 Singleton 这种注册的生活方式要明显得多。

长话短说,我建议覆盖默认注册为单例,因为无论如何这样做都是安全的:

services.AddLocalization();
services.AddSingleton(typeof(IStringLocalizer<>), typeof(StringLocalizer<>));

谢谢@Steven 清晰的解释。 我写了一个本地化的扩展方法,并与 IStringLocalizer 的生活方式混淆。

public static class LocalizerExtensions
{
    private static IStringLocalizer<Localizer> _localizer =  default!;

    public static IServiceCollection AddLocalizer(this IServiceCollection services)
    {
        services.Configure<RequestLocalizationOptions>(options =>
        {
            string enUSCulture = "en";
            var supportedCultures = new[]
            {
                new CultureInfo(enUSCulture),
                new CultureInfo("vi"),
            };
            options.DefaultRequestCulture = new RequestCulture(enUSCulture, enUSCulture);
            options.SupportedCultures = supportedCultures;
            options.SupportedUICultures = supportedCultures;
            options.RequestCultureProviders.Add(new CustomRequestCultureProvider(context =>
            {
                var lang = GetLang(context);
                return Task.FromResult(new ProviderCultureResult(lang, lang));
            }));
        });
        services.AddSingleton<ILocalizer, Localizer>();
        services.AddLocalization(options => options.ResourcesPath = "Resources");
        return services;
    }

    public static IApplicationBuilder UseLocalizer(this IApplicationBuilder app)
    {
        _localizer = app.ApplicationServices.GetRequiredService<IStringLocalizer<Localizer>>();
        app.UseRequestLocalization();
        return app;
    }

    public static string ToLocalizer(this string name)
    {
        return _localizer[name];
    }

    private static string GetLang(HttpContext context)
    {
        var lang = context.Request.Headers["Language"];
        if (string.IsNullOrWhiteSpace(lang))
        {
            lang = context.Request.Query["Language"];
        }
        if (string.IsNullOrWhiteSpace(lang))
        {
            lang = "vi";
        }
        return lang;
    }
}

在方法ToLocalizer中,我使用了IStringLocalizer<Localizer>的静态实例,这意味着单例生活方式,并担心是否正确使用它。有了你的解释,我就可以放心使用了

我们需要为 IStringLocalizer 使用 DI 很奇怪,因为它不会根据请求更改。因此,如果我们有它的单例实现会更好。我找到了另一种将 IStringLocalizer 实现为单例的方法。我的资源文件位于“LocalizeResources”文件夹中,“MessageResources”是一个虚拟 class,我传递它是为了提供对我的资源文件的引用。

“LocalizationFactoryService”是一个静态 class,它创建一个 IStringLocalizer 的实例。

将下面的代码添加到 Startup.cs

中的 ConfigureService 方法
public void ConfigureServices(IServiceCollection services)
{
   ....
   
   services.AddLocalization(opts =>
        {
            opts.ResourcesPath = "LocalizeResources";
        });

        services.AddMvc()
            .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
            .AddDataAnnotationsLocalization(options =>
            {
                options.DataAnnotationLocalizerProvider = (type, factory) =>
                    factory.Create(typeof(MessageResources));
            });
    var options = Options.Create(new LocalizationOptions { ResourcesPath = 
                  "LocalizeResources" });
    var factory = new ResourceManagerStringLocalizerFactory(options, 
            NullLoggerFactory.Instance);

   LocalizationFactoryService.SetLocalization(new 
            StringLocalizer<MessageResources>(factory));

}

“LocalizationFactoryService”是一个静态的 class,我们可以使用 GetLocalizer() 方法在任何 controller/Service class.

中获取 IStringLocalizer 的实例
public static class LocalizationFactoryService 
    {
        private static IStringLocalizer<MessageResources> _stringLocalizer;

        public static void SetLocalization(IStringLocalizer<MessageResources> stringLocalizer)
        {
            _stringLocalizer = stringLocalizer;
        }

        public static IStringLocalizer<MessageResources> GetLocalizer()
        {
            return _stringLocalizer;
        }
   }