创建自定义 RequestCultureProvider - 为啥没有初始化 Options 属性?

Posted

技术标签:

【中文标题】创建自定义 RequestCultureProvider - 为啥没有初始化 Options 属性?【英文标题】:Creating a custom RequestCultureProvider - why isn't the Options property initialised?创建自定义 RequestCultureProvider - 为什么没有初始化 Options 属性? 【发布时间】:2020-10-09 22:48:40 【问题描述】:

我正在使用 Razor 页面实现一个 ASP.NET Core 3.1 WebApp,它需要支持多种文化。它将在 Azure 上运行。它还需要支持异步方法

我正在创建一个自定义 RequestCultureProvider,因此如果登录,我可以从用户的 claimPrincipal 中获取文化,正如 Matteo 所建议的那样。如果用户没有登录,我想从默认的RequestCultureProviders 之一获取文化:

    URL 中的查询字符串 饼干.AspNetCore.Culture 接受语言浏览器设置

从任何来源获取用户文化后,我想将其保存在 ".AspNetCore.Culture" cookie 中。这样,用户的文化可以在我的 Razor 页面中使用

var culture = page.Request.Cookies[".AspNetCore.Culture"];

注意:设置响应 cookie 值会导致请求 cookie 中可用的值与Microsoft中记录的相同

这似乎是比尝试从当前线程获取文化更好的方法,因为使用 GitHub 和 SO - Keep CurrentCulture in async/await 中描述的异步方法执行此操作时似乎存在许多问题。

本质上,这些问题似乎是由于 Default RequestCultureProviders 用于设置区域性的初始线程可能与用于运行我的异步方法的线程不同,因为无法保证这些不同的线程具有相同的文化作为初始线程我无法确定通过阅读在我的页面中获得用户的文化:

var culture = Thread.CurrentThread.CurrentCulture.

因此,我决定编写一个 custom RequestCultureProvider,尝试从用户的 claimPrincipal 或 default RequestCultureProviders 获取文化,然后将其设置为文化 cookie,在返回所需的 ProviderCultureResult 之前。我的实现如下:

public class MyRequestCultureProvider : RequestCultureProvider

   public override async Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
   
      string culture = "en";

      if (httpContext == null)
         throw new ArgumentNullException();

      if (httpContext.User.Identity.IsAuthenticated)
         culture = httpContext.User.GetCulture() ?? "en";
      else
      
         var count = Options?.RequestCultureProviders?.Count ?? -1;
         for (var x = 1; x < count; x++)
         
            var provider = Options.RequestCultureProviders[x]; //don't invoke initial provider as that's our one
            var val = await provider.DetermineProviderCultureResult(httpContext);
            if (val?.Cultures?.Count > 0)
             
               culture = val.Cultures[0].ToString() ?? "en";
               break;
            
         
      
      if (httpContext.Request.Cookies[".AspNetCore.Culture"] != culture)
         httpContext.Response.Cookies.Append(".AspNetCore.Culture", culture, new CookieOptions  Expires = DateTime.Now.AddDays(3), IsEssential = true );
      return new ProviderCultureResult(culture, culture );
   


自定义RequestCultureProvider在Startup中添加到中间件如下:

var supportedCultures = new[]

    new CultureInfo("en"),
    new CultureInfo("en-US"),
    new CultureInfo("de-CH"),
;

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

   ...
   app.UseRequestLocalization();

   app.UseHttpsRedirection();
   app.UseStaticFiles();
   ...


public void ConfigureServices(IServiceCollection services)

   services.Configure<RequestLocalizationOptions>(options =>
   
      options.DefaultRequestCulture = new RequestCulture(culture: supportedCultures[0].Name, uiCulture: supportedCultures[0].Name);
      options.SupportedCultures = supportedCultures;
      options.SupportedUICultures = supportedCultures;
      options.AddInitialRequestCultureProvider(new MyRequestCultureProvider());
   );
   services.AddRazorPages();


问题:

    为什么当我在我的覆盖DetermineProviderCultureResult() 方法中访问选项时是null?它是Microsoft 记录的基类的属性,那么我该如何初始化它? 我还能如何获得由默认 RequestCultureProviders 设置的区域性值?您会注意到我的DetermineProviderCultureResult() 方法是异步的,因此简单地读取线程中设置的文化值似乎不是一个好主意。 我还能如何获取用户的文化以在我的异步页面方法中使用?

我的代码中的任何 cmets 也是受欢迎的。

【问题讨论】:

为了帮助#3:AcceptHeadersRequestCultureProvider 应该从浏览器的 Accept-Language 标头中获取用户的首选文化,这对于首次请求可能很有用。我使用的自定义提供程序会根据请求域的查找设置默认区域性,而不管用户在首次加载时的浏览器设置如何,但它继承自 CookieRequestCultureProvider,因此使用 cookie 来选择跨浏览会话的替代区域性粘性。如果您出于某种原因想要查询字符串中的文化,还有QueryStringRequestCultureProvider 【参考方案1】:

我只需要使用 RequestLocalizationOptions 参数为MyRequestCultureProvider 创建一个ctor,这样我就可以像这样设置选项:

public MyRequestCultureProvider(RequestLocalizationOptions options)

   Options = options;

Startup.ConfigureServices 中的代码然后需要更改以在将 Options 对象添加到 RequestCultureProviders 集合时传递它,如下所示:

public void ConfigureServices(IServiceCollection services)

   services.Configure<RequestLocalizationOptions>(options =>
   
      options.DefaultRequestCulture = new RequestCulture(culture: supportedCultures[0].Name, uiCulture: supportedCultures[0].Name);
      options.SupportedCultures = supportedCultures;
      options.SupportedUICultures = supportedCultures;
      options.AddInitialRequestCultureProvider(new MyRequestCultureProvider(options));
   );
   services.AddRazorPages();

我仍然有兴趣听听 cmets 了解这种使用异步 Razor 页面方法为 ASP.NET Core WebApp 获取用户文化的方法

【讨论】:

我最初在这个答案中使用了你的方法(谢谢)。但是,我的应用程序是一个服务于许多域的平台,每个域都有一组可能不同的受支持文化,这些文化由通过另一个服务的数据库调用确定。服务实例在ConfigureServices(..) 中尚不可用,因此要将一个添加到MyRequestCultureProvider 的构造函数中,我必须实现IConfigureOptions&lt;MyOptions&gt;,它将服务放在其ctor 中,其中MyOptions 继承自RequestLocalizationOptions。我基于这个答案成功了:***.com/a/46963194/3626160

以上是关于创建自定义 RequestCultureProvider - 为啥没有初始化 Options 属性?的主要内容,如果未能解决你的问题,请参考以下文章

创建自定义视图的步骤

vue创建自定义组件并监听原生事件

创建自定义事件

如何从自定义集合视图单元(使用 xib 创建的单元)到 tabBar 控制器创建自定义 segue

如何创建自定义 MKAnnotationView 和自定义注释标题和副标题

为Acumatica创建自定义用户控件