【发布时间】: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。
标签: c# multithreading asp.net-core threadpool cultureinfo