(可选)通过ASP.NET Core 1.0应用程序中的url/route覆盖请求文化

Tse*_*eng 10 routes asp.net-core-mvc asp.net-core asp.net-core-1.0

我试图覆盖当前请求的文化.我使用自定义部分工作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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在控制器上我使用LanguageActionFilter.

[ServiceFilter(typeof(LanguageActionFilter))]
[Route("api/{culture}/[controller]")]
public class ProductsController : Controller
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

这项工作到目前为止,但我有两个问题:

  1. 我不喜欢{culture}在每个控制器上声明,因为我将在每条路线上都需要它.
  2. 我有一个默认文化不适用于这种方法,即使我声明它是[Route("api/{culture=en-US}/[controller]")]显而易见的原因.

设置默认路由结果也不起作用.

app.UseMvc( routes =>
{
    routes.MapRoute(
        name: "DefaultRoute",
        template: "api/{culture=en-US}/{controller}"
    );
});
Run Code Online (Sandbox Code Playgroud)

我还调查了自定义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"));
Run Code Online (Sandbox Code Playgroud)

但后来我无法访问那里的路线(我假设路由是在管道中稍后完成的).当然我也可以尝试解析请求的URL.我甚至不知道我是否可以改变这个地方的路线,这样就可以将上述路线与其中的文化相匹配.

通过查询参数传递文化或更改路径内参数的顺序不是一种选择.

api/en-us/products我们的两个网址api/products应该路由到同一个控制器,前者不会改变文化.

确定文化的顺序应该是

  1. 如果在网址中定义,请接受它
  2. 如果未在url中定义,请检查查询字符串并使用它
  3. 如果未在查询中定义,请检查cookie
  4. 如果未在cookie中定义,请使用Accept-Language标头.

2-4通过UseRequestLocalization并且有效.另外,我不喜欢当前的方法必须为每个控制器添加两个属性({culture}在路由和中[ServiceFilter(typeof(LanguageActionFilter))]).

编辑: 我还想将有效语言环境的数量限制SupportedCulturesRequestLocalizationOptions传递给的属性中的一个UseRequestLocalization.

IOptions<RequestLocalizationOptions> localizationOptionsLanguageActionFilter上述方法无效,因为它返回一个新的实例RequestLocalizationOptions,其中SupportedCultures始终null,而不是一个传递给.

FWIW它是一个RESTful WebApi项目.

Dan*_*.G. 23

更新ASP.Net Core 1.1

新版本RouteDataRequestCultureProvider将作为1.1 版本的一部分发布,这有望意味着您不再需要创建自己的请求提供程序.您仍然可以在此处找到有用的信息(如路由位),或者您可能有兴趣创建自己的请求文化提供程序.


您可以创建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?}");                

});
Run Code Online (Sandbox Code Playgroud)

上述路由将与MVC样式控制器一起使用,但如果使用wb api样式的控制器构建休息接口,则属性路由是MVC 6中的首选方式.

  • 一种选择是使用属性路由,但是如果你可以设置url的基本段,则为所有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" });
        }
    } 
    
    Run Code Online (Sandbox Code Playgroud)
  • 避免基类而不需要太多自定义代码的快速方法是通过web api兼容性垫片:

    • 添加包 "Microsoft.AspNet.Mvc.WebApiCompatShim": "6.0.0-rc1-final"
    • 添加垫片约定:

      services.AddMvc().AddWebApiConventions();
      
      Run Code Online (Sandbox Code Playgroud)
    • 确保您的控制器继承自ApiControllershim包添加的控制器
    • 使用te MapApiRoute重载定义包含culture参数的路由:

      routes.MapWebApiRoute("apiLanguage", 
       "api/{language:regex(^[a-z]{{2}}-[A-Z]{{2}}$)}/{controller}/{id?}");
      
      routes.MapWebApiRoute("DefaultApi", 
       "api/{controller}/{id?}");
      
      Run Code Online (Sandbox Code Playgroud)
  • 更清晰,更好的选择是创建和应用您自己的选项IApplicationModelConvention,负责为您的属性路由添加区域性前缀.这超出了这个问题的范围,但我已经实现了本本地化文章的想法

然后你需要创建一个新的IRequestCultureProvider,它将查看请求URL并从那里提取文化(如果提供).

升级到ASP .Net Core 1.1后,您可以避免手动解析请求URL并提取文化段.

我检查了ASP.Net Core 1.1中的实现RouteDataRequestCultureProvider,他们使用HttpContext扩展方法GetRouteValue(string)获取请求提供程序中的url段:

culture = httpContext.GetRouteValue(RouteDataStringKey)?.ToString();
Run Code Online (Sandbox Code Playgroud)

但是我怀疑(我还没有机会尝试它),这只有在将中间件添加为MVC过滤器时才有效.这样你的中间件就可以在路由中间件之后运行,这是将其添加IRoutingFeature到HttpContext中的中间件.作为快速测试,在UseMvc之前添加以下中间件将不会获得路由数据:

app.Use(async (context, next) =>
{
    //always null
    var routeData = context.GetRouteData();
    await next();
});
Run Code Online (Sandbox Code Playgroud)

为了实现新的IRequestCultureProvider你需要:

  • 在请求URL路径中搜索culture参数.
  • 如果未找到任何参数,则返回null.(如果所有提供程序都返回null,则将使用默认区域性)
  • 如果找到culture参数,则返回具有该文化的新ProviderCultureResult.
  • 如果本地化中间件不是受支持的文化之一,则它将回退到默认中间件.

实现将如下所示:

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

最后启用本地化功能,包括新的提供程序,作为支持的提供程序列表中的第一个.当它们按顺序进行评估并且第一个返回非空结果时,您的提供程序将优先,接下来将是默认值(查询字符串,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"));
Run Code Online (Sandbox Code Playgroud)