(可选)在ASP.NET Core 1.0应用程序中通过URL /路由覆盖请求区域性 [英] Optionally override request culture via url/route in an ASP.NET Core 1.0 Application

查看:74
本文介绍了(可选)在ASP.NET Core 1.0应用程序中通过URL /路由覆盖请求区域性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试覆盖当前请求的区域性。我使用自定义 ActionFilterAttribute 使其部分工作。

I am trying to override the culture of the current request. I got it working partly using a custom 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
{
    ...
}

到目前为止,该方法仍然有效,但是我有两个问题:

This works so far, but I have two issues with it:


  1. 我不想在每个控制器上都声明 {culture} ,因为我将在每条路线上都需要它。

  2. 即使我将其声明为 [Route( api / {culture = en-US} / [controller])] 出于明显的原因。

  1. I don't like having to declare {culture} on every controller, as I am going to need it on every route.
  2. I having a default culture doesn't work with this approach, even if I declare it as [Route("api/{culture=en-US}/[controller]")] for obvious reasons.

设置默认路由结果均无效。

Setting a default route results is not working neither.

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

我还研究了自定义 IRequestCultureProvider 的实现并将其添加到 UseRequestLocalization 方法,例如

I also investigated in a custom IRequestCultureProvider implementation and add it to the UseRequestLocalization method like

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

但后来我无法访问那里的路线(我想是因为路线稍后会完成在管线中)。当然,我也可以尝试解析请求的网址。而且我什至不知道我是否可以更改此处的路线,以使其与上面的路线及其所在的文化相匹配。

but then I don't have access to the routes there (I assume cause the routes are done later in the pipeline). Of course I could also try to parse the requested url. And I don't even know if I could change the route at this place so it would match the above route with the culture in it.

通过查询参数传递区域性或更改路由内参数的顺序不是一种选择。

Passing the culture via query parameter or changing the order of the parameters inside the route is not an option.

两个网址 api / zh-cn / products 正如我们 api / products 应该路由到相同的控制器,前者不会改变区域性。

Both urls api/en-us/products as we as api/products should route to the same controller, where the former don't change the culture.

确定文化的顺序应为


  1. 如果已定义在网址中,输入

  2. 如果未在网址中定义,请检查查询字符串并使用

  3. 如果未在查询中定义,请检查Cookie

  4. 如果未在cookie中定义,请使用 Accept-Language 标头。

  1. If defined in url, take it
  2. If not defined in url, check query string and use that
  3. If not defined in query, check cookies
  4. If not defined in cookie, use Accept-Language header.

2-4是通过 UseRequestLocalization 完成的,并且有效。我也不喜欢当前的方法必须向每个控制器添加两个属性(路由中的 {culture} [ServiceFilter(typeof(LanguageActionFilter ))] )。

2-4 is done via UseRequestLocalization and that works. Also I don't like the current approach having to add two attributes to each controller ({culture} in route and the [ServiceFilter(typeof(LanguageActionFilter))]).

编辑:
我也想限制有效语言环境的数量到 RequestLocalizationOptions SupportedCultures 属性中的一组,传递给 UseRequestLocalization

I also like to limit the number of valid locales to the one set in SupportedCultures property of the RequestLocalizationOptions passed to the UseRequestLocalization.

IOptions< RequestLocalizationOptions>上面 LanguageActionFilter 中的localizationOptions 不起作用,因为它返回了 RequestLocalizationOptions 的新实例其中 SupportedCultures 始终为 null ,而不是传递给它的那个。

IOptions<RequestLocalizationOptions> localizationOptions in the LanguageActionFilter above doesn't work as it returns a new instance of RequestLocalizationOptions where SupportedCultures is always null and not the one passed to the.

FWIW这是一个RESTful WebApi项目。

FWIW it's an RESTful WebApi project.

推荐答案

更新ASP。 Net Core 1.1

新的 RouteDataRequestCultureProvider 作为1.1 版本,其中希望这意味着您不再需要创建自己的请求提供程序。您可能仍然会在这里找到有用的信息(例如路由位),或者可能对创建自己的请求区域性提供程序感兴趣。

A new RouteDataRequestCultureProvider is coming as part of the 1.1 release, which hopefully means you won't have to create your own request provider anymore. You might still find the information here useful (like the routing bits) or you might be interested in creating your own request culture provider.

您可以创建2条路由,通过这些路由,您可以在url中有无文化区域的情况下访问端点。 / api / zh-CN / home / api / home 都将路由到家庭控制器。 (因此 / api / blah / home 不会将路线与文化相匹配,因为blah控制器不存在,因此会得到404)

You can create 2 routes that will let you access your endpoints with and without a culture segment in the url. Both /api/en-EN/home and /api/home will be routed to the home controller. (So /api/blah/home won't match the route with culture and will get 404 since the blah controller doesn't exists)

要使这些路线起作用,包含文化参数的路径优先级较高,而文化参数包含正则表达式:

For these routes to work, the one that includes the culture parameter has higher preference and the culture parameter includes a regex:

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中的首选方式。

The above routes will work with MVC style controller, but if you are building a rest interface using wb api style of controllers, the attribute routing is the favored way in MVC 6.


  • 一种选择是使用属性路由,但使用您可以设置url的基段吗?

  • One option is to use attribute routing, but use a base class for all your api controllers were you can set the base segments of the url:

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


  • 避免基类的快速方法无需太多自定义代码,即可通过网络api兼容性shim


    • 添加软件包 Microsoft.AspNet.Mvc.WebApiCompatShim: 6.0.0-rc1-final

    • 添加填充规则:

    • Add the package "Microsoft.AspNet.Mvc.WebApiCompatShim": "6.0.0-rc1-final"
    • Add the shim conventions:

    services.AddMvc().AddWebApiConventions();
    


  • 确保您的控制器继承自 ApiController ,由shim程序包添加的

  • 使用te MapApiRoute重载定义包含文化参数的路由:

  • Make sure your controllers inherit from ApiController, which is added by the shim package
  • Define the routes including the culture parameter with te MapApiRoute overload:

    routes.MapWebApiRoute("apiLanguage", 
     "api/{language:regex(^[a-z]{{2}}-[A-Z]{{2}}$)}/{controller}/{id?}");
    
    routes.MapWebApiRoute("DefaultApi", 
     "api/{controller}/{id?}");
    


  • 更干净,更好的选择会创建并应用您自己的 IApplicationModelConvention ,它会在区域性路由中添加区域性前缀。这超出了此问题的范围,但是我已经实现了本地化文章

    The cleaner and better option would be creating and applying your own IApplicationModelConvention which takes care of adding the culture prefix to your attribute routes. This is out of the scope for this question, but I have implemented the idea for this localization article

    然后,您需要创建一个新的 IRequestCultureProvider ,它将查看请求网址并从那里提取区域性(如果提供)。

    Then you need to create a new IRequestCultureProvider that will look at the request url and extract the culture from there (if provided).


    一旦升级到ASP .Net Core 1.1,您可能会避免手动解析请求URL并提取文化区域。

    Once you upgrade to ASP .Net Core 1.1 you might avoid manually parsing the request url and extract the culture segment.

    我已经检查了在ASP.Net Core 1.1中实现 RouteDataRequestCultureProvider ,并且它们使用HttpContext扩展方法 GetRouteValue(string)用于在请求提供程序内部获取url段:

    I have checked the implementation of RouteDataRequestCultureProvider in ASP.Net Core 1.1, and they use an HttpContext extension method GetRouteValue(string) for getting url segments inside the request provider:

    culture = httpContext.GetRouteValue(RouteDataStringKey)?.ToString();
    

    但是我怀疑(我还没有机会尝试)添加中间件作为MVC过滤器。这样,您的中间件将在路由中间件之后运行,后者是将 IRoutingFeature 添加到HttpContext中的过程。作为一项快速测试,在UseMvc之前添加以下中间件将使您没有路由数据:

    However I suspect (I haven't had a chance to try it yet) that this would only work when adding middleware as MVC filters. That way your middleware runs after the Routing middleware, which is the one adding the IRoutingFeature into the HttpContext. As a quick test, adding the following middleware before UseMvc will get you no route data:

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


    为了实施新的 IRequestCultureProvider 您只需要:

    In order to implement the new IRequestCultureProvider you just need to:


    • 在请求url路径中搜索区域性参数。

    • 如果未找到参数,则返回null。 (如果所有提供程序都返回null,则将使用默认区域性。)

    • 如果找到了区域性参数,则返回具有该区域性的新ProviderCultureResult。

    • 本地化中间件如果不是受支持的区域性之一,它将回退到默认区域。

    • Search for the culture parameter in the request url path.
    • If no parameter is found, return null. (If all the providers return null, the default culture will be used)
    • If a culture parameter is found, return a new ProviderCultureResult with that culture.
    • The localization middleware will fallback to the default one if it is not one of the supported cultures.

    实现将如下所示:

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

    最后启用本地化功能,包括新提供商支持的提供程序列表中的第一个。当按顺序评估它们时,第一个返回非空结果的获胜者,您的提供者将优先,下一个将是默认值(查询字符串,cookie和标头)。

    Finally enable the localization features including your new provider as the first one in the list of supported providers. As they are evaluated in order and the first one returning a not null result wins, your provider will take precedence and next will come the default ones (query string, cookie and header).

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

    这篇关于(可选)在ASP.NET Core 1.0应用程序中通过URL /路由覆盖请求区域性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆