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

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

问题描述

我正在尝试覆盖当前请求的文化.我使用自定义 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]")] for明显的原因.
  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"));

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

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/en-us/products 和我们作为 api/products 的两个 url 都应该路由到同一个控制器,前者不会改变文化.

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. 如果在url中定义,就拿去
  2. 如果未在 url 中定义,请检查查询字符串并使用它
  3. 如果未在查询中定义,请检查 cookie
  4. 如果 cookie 中没有定义,则使用 Accept-Language 标头.

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))]).

我还想将有效语言环境的数量限制为传递给 UseRequestLocalizationRequestLocalizationOptionsSupportedCultures 属性中的一组.

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 中的本地化选项 不起作用,因为它返回 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 release,希望这意味着您不必再创建自己的请求提供程序.您可能仍然会发现此处的信息很有用(例如路由位),或者您可能对创建自己的请求文化提供者感兴趣.

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/en-EN/home/api/home 都将被路由到家庭控制器.(所以 /api/blah/home 不会将路线与文化匹配,并且会得到 404,因为 blah 控制器不存在)

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 的基本段,请为所有 api 控制器使用基类:

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

  • 在不需要太多自定义代码的情况下避免使用基类的快速方法是通过 web 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 包添加
  • 使用 MapApiRoute 重载定义包含culture参数的路由:

  • 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,它将查看请求 url 并从那里提取文化(如果提供).

    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.

    我已经检查了 实施RouteDataRequestCultureProvider 在 ASP.Net Core 1.1 中,他们使用 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路径中搜索culture参数.
    • 如果没有找到参数,返回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/route 覆盖请求区域性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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