我试图覆盖当前请求的文化。我使用自定义ActionFilterAttribute
部分工作。
public sealed class LanguageActionFilter : ActionFilterAttribute
{
private readonly ILogger logger;
private readonly IOptions<RequestLocalizationOptions> localizationOptions;
public LanguageActionFilter(ILoggerFactory loggerFactory, IOptions<RequestLocalizationOptions> options)
{
if (loggerFactory == null)
throw new ArgumentNullException(nameof(loggerFactory));
if (options == null)
throw new ArgumentNullException(nameof(options));
logger = loggerFactory.CreateLogger(nameof(LanguageActionFilter));
localizationOptions = options;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
string culture = context.RouteData.Values["culture"]?.ToString();
if (!string.IsNullOrWhiteSpace(culture))
{
logger.LogInformation($"Setting the culture from the URL: {culture}");
#if DNX46
System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture);
#else
CultureInfo.CurrentCulture = new CultureInfo(culture);
CultureInfo.CurrentUICulture = new CultureInfo(culture);
#endif
}
base.OnActionExecuting(context);
}
}
在控制器上,我使用LanguageActionFilter
。
[ServiceFilter(typeof(LanguageActionFilter))]
[Route("api/{culture}/[controller]")]
public class ProductsController : Controller
{
...
}
到目前为止这有效,但我有两个问题:
{culture}
,因为我将在每条路线上都需要它。 [Route("api/{culture=en-US}/[controller]")]
。 设置默认路由结果也不起作用。
app.UseMvc( routes =>
{
routes.MapRoute(
name: "DefaultRoute",
template: "api/{culture=en-US}/{controller}"
);
});
我还在自定义IRequestCultureProvider
实施中进行了调查,并将其添加到UseRequestLocalization
方法,如
app.UseRequestLocalization(new RequestLocalizationOptions
{
RequestCultureProviders = new List<IRequestCultureProvider>
{
new UrlCultureProvider()
},
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("de-de"),
new CultureInfo("en-us"),
new CultureInfo("en-gb")
},
SupportedUICultures = new List<CultureInfo>
{
new CultureInfo("de-de"),
new CultureInfo("en-us"),
new CultureInfo("en-gb")
}
}, new RequestCulture("en-US"));
然后我无法访问那里的路线(我假设路由是在管道中稍后完成的)。当然我也可以尝试解析请求的URL。我甚至不知道我是否可以改变这个地方的路线,这样就可以将上述路线与其中的文化相匹配。
通过查询参数传递文化或更改路径内参数的顺序不是一种选择。
我们api/en-us/products
的两个网址api/products
应该路由到同一个控制器,前者不会改变文化。
确定文化的顺序应为
Accept-Language
标头。 2-4是通过UseRequestLocalization
完成的,并且有效。另外,我不喜欢当前的方法必须为每个控制器添加两个属性(路由中的{culture}
和[ServiceFilter(typeof(LanguageActionFilter))]
)。
修改
我还想将有效区域设置的数量限制为传递给SupportedCultures
的{{1}} RequestLocalizationOptions
属性中的一个。
UseRequestLocalization
中的 IOptions<RequestLocalizationOptions> localizationOptions
不起作用,因为它返回LanguageActionFilter
的新实例,其中RequestLocalizationOptions
始终为SupportedCultures
而不是通过的null
到了。
FWIW这是一个RESTful WebApi项目。
答案 0 :(得分:22)
更新ASP.Net Core 1.1
新的RouteDataRequestCultureProvider
将成为1.1 release的一部分,希望这意味着您不再需要创建自己的请求提供商。您仍然可以在此处找到有用的信息(例如路由位),或者您可能有兴趣创建自己的请求文化提供程序。
您可以创建2条路线,让您可以在网址中使用和不包含文化细分来访问您的终结点。 /api/en-EN/home
和/api/home
都将路由到家庭控制器。 (所以/api/blah/home
与文化路线不匹配,因为blah控制器不存在而得到404
要使这些路由起作用,包含culture参数的路由具有更高的首选项,culture参数包含正则表达式:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "apiCulture",
template: "api/{culture:regex(^[a-z]{{2}}-[A-Z]{{2}}$)}/{controller}/{action=Index}/{id?}");
routes.MapRoute(
name: "defaultApi",
template: "api/{controller}/{action=Index}/{id?}");
});
上述路由适用于MVC样式控制器,但如果使用wb api样式的控制器构建rest接口,则属性路由是MVC 6中最受欢迎的方式。
一个选项是使用属性路由,但是如果您可以设置网址的基本段,则为所有api控制器使用基类:
[Route("api/{language:regex(^[[a-z]]{{2}}-[[A-Z]]{{2}}$)}/[controller]")]
[Route("api/[controller]")]
public class BaseApiController: Controller
{
}
public class ProductController : BaseApiController
{
//This will bind to /api/product/1 and /api/en-EN/product/1
[HttpGet("{id}")]
public IActionResult GetById(string id)
{
return new ObjectResult(new { foo = "bar" });
}
}
"Microsoft.AspNet.Mvc.WebApiCompatShim": "6.0.0-rc1-final"
添加垫片约定:
services.AddMvc().AddWebApiConventions();
ApiController
,由shim软件包使用te MapApiRoute重载定义包含culture参数的路由:
routes.MapWebApiRoute("apiLanguage",
"api/{language:regex(^[a-z]{{2}}-[A-Z]{{2}}$)}/{controller}/{id?}");
routes.MapWebApiRoute("DefaultApi",
"api/{controller}/{id?}");
更清晰,更好的选择是创建并应用您自己的IApplicationModelConvention
,它负责为您的属性路由添加区域性前缀。这超出了这个问题的范围,但我已经实现了这个localization article
然后,您需要创建一个新的IRequestCultureProvider
,它将查看请求网址并从中提取文化(如果提供)。
升级到ASP .Net Core 1.1后,您可以避免手动解析请求URL并提取文化段。
我已经检查了ASP.Net Core 1.1中的implementation of
RouteDataRequestCultureProvider
,他们使用HttpContext扩展方法GetRouteValue(string)
来获取请求提供程序中的url段:culture = httpContext.GetRouteValue(RouteDataStringKey)?.ToString();
但是我怀疑(我还没有机会尝试它),这只会在添加middleware as MVC filters时起作用。这样你的中间件就可以在路由中间件之后运行,这是将
IRoutingFeature
添加到HttpContext中的中间件。作为快速测试,在UseMvc之前添加以下中间件将不会获得路由数据:app.Use(async (context, next) => { //always null var routeData = context.GetRouteData(); await next(); });
为了实现新的IRequestCultureProvider
,您只需:
实施将如下所示:
public class UrlCultureProvider : IRequestCultureProvider
{
public Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
{
var url = httpContext.Request.Path;
//Quick and dirty parsing of language from url path, which looks like "/api/de-DE/home"
//This could be skipped after 1.1 if using the middleware as an MVC filter
//since you will be able to just call the method GetRouteValue("culture")
//on the HttpContext and retrieve the route value
var parts = httpContext.Request.Path.Value.Split('/');
if (parts.Length < 3)
{
return Task.FromResult<ProviderCultureResult>(null);
}
var hasCulture = Regex.IsMatch(parts[2], @"^[a-z]{2}-[A-Z]{2}$");
if (!hasCulture)
{
return Task.FromResult<ProviderCultureResult>(null);
}
var culture = parts[2];
return Task.FromResult(new ProviderCultureResult(culture));
}
}
最后启用本地化功能,包括新提供商作为支持提供商列表中的第一个。当按顺序评估它们并且第一个返回非空结果时,您的提供者将优先,接下来将{{3}}(查询字符串,cookie和标题)。
var localizationOptions = new RequestLocalizationOptions
{
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("de-DE"),
new CultureInfo("en-US"),
new CultureInfo("en-GB")
},
SupportedUICultures = new List<CultureInfo>
{
new CultureInfo("de-DE"),
new CultureInfo("en-US"),
new CultureInfo("en-GB")
}
};
//Insert this at the beginning of the list since providers are evaluated in order until one returns a not null result
localizationOptions.RequestCultureProviders.Insert(0, new UrlCultureProvider());
//Add request localization middleware
app.UseRequestLocalization(localizationOptions, new RequestCulture("en-US"));