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

时间:2016-03-06 16:31:48

标签: routes asp.net-core asp.net-core-mvc 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);
    }
}

在控制器上,我使用LanguageActionFilter

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

到目前为止这有效,但我有两个问题:

  1. 我不想在每个控制器上声明{culture},因为我将在每条路线上都需要它。
  2. 我有一个默认文化不适用于这种方法,即使我出于显而易见的原因将其声明为[Route("api/{culture=en-US}/[controller]")]
  3. 设置默认路由结果也不起作用。

    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应该路由到同一个控制器,前者不会改变文化。

    确定文化的顺序应为

    1. 如果在网址中定义,请将其取消
    2. 如果未在网址中定义,请检查查询字符串并使用
    3. 如果未在查询中定义,请检查Cookie
    4. 如果未在Cookie中定义,请使用Accept-Language标头。
    5. 2-4是通过UseRequestLocalization完成的,并且有效。另外,我不喜欢当前的方法必须为每个控制器添加两个属性(路由中的{culture}[ServiceFilter(typeof(LanguageActionFilter))])。

      修改 我还想将有效区域设置的数量限制为传递给SupportedCultures的{​​{1}} RequestLocalizationOptions属性中的一个。

      上面UseRequestLocalization中的

      IOptions<RequestLocalizationOptions> localizationOptions不起作用,因为它返回LanguageActionFilter的新实例,其中RequestLocalizationOptions始终为SupportedCultures而不是通过的null到了。

      FWIW这是一个RESTful WebApi项目。

1 个答案:

答案 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" });
        }
    } 
    
  • 通过web api compatibility shim

    快速避免基类而不需要太多自定义代码
    • 添加包"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,您只需:

  • 在请求网址路径中搜索culture参数。
  • 如果未找到参数,则返回null。 (如果所有提供程序都返回null,则将使用默认区域性文件)
  • 如果找到了culture参数,则返回具有该文化的新ProviderCultureResult。
  • 如果localization middleware不是受支持的文化之一,the default ones将回退到默认文件。

实施将如下所示:

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"));