如何让ASP.NET Web API的Action方法在希望的Culture下执行

在今天编辑推荐的《Hello Web API系列教程——Web API与国际化》一文中,作者通过自定义的HttpMessageHandler的方式根据请求的Accep-Language报头设置当前线程UI Culture的方式来解决Localization的问题。如果你对ASP.NET Web API的执行机制有足够了解的话,你会发现实际上有很多种解决方案。不过这些解决方案都不够完美,原因很简单:ASP.NET Web API的整个框架均采用基于Task的并行编程模式,所以每个可扩展组件均可以在不同的线程中执行,这样会导致我们没有办法100%控制目标方法真正执行的线程的UI Culture。不过在默认情况下,大部分组件是按照同步的方式执行的,所以我们之需要在目标Action方法执行之前设置当前线程的UI Culture即可。

一、两个辅助的扩展方法

我们针对HttpRequestMessage定义了如下两个扩展方法。SetCurrentUICulture从请求的Accpet-Language报头提取客户端接受的语言并据此设置当前线程的UI Culture。在这之前,它会将当前线程的UI Culture保存到HttpRequestMessage对象中。ResetCurrentUICulture方法将这个CultureInfo对象从HttpRequestMessage其中提取出来,将当前线程的UI Cuilture回复到之前的状态。

 

public static class HttpRequestMessageExtensions
{
    public static void SetCurrentUICulture(this HttpRequestMessage request)
    {
        StringWithQualityHeaderValue acceptCultureHeader = request.Headers.AcceptLanguage.OrderByDescending(header => header.Quality).FirstOrDefault();
        if (null != acceptCultureHeader)
        {
            request.Properties["__CurrentCulture"] = Thread.CurrentThread.CurrentUICulture;
            Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(acceptCultureHeader.Value);
        }
    }

    public static void ResetCurrentUICulture(this HttpRequestMessage request)
    {
        object culture;
        if (request.Properties.TryGetValue("__CurrentCulture", out culture))
        {
            Thread.CurrentThread.CurrentUICulture = (System.Globalization.CultureInfo)culture;
        }
    }
}

 

二、第1种方案:自定义ActionFilter

我想这应该是大家最容易想到的解决方案,因为ActionFilter可以注册一些回调操作在目标Action方法执行前后被自动调用。为此我们定义了如下一个继承自ActionFilterAttribute的UseAcceptCultureAttribute类型。我们分别在重写的OnActionExecuting和OnActionExecuted方法中利用上面定义的两个扩展方法对当前线程的UI Culture进行设置和恢复。

public class UserAcceptCultureAttribute:ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        actionContext.Request.SetCurrentUICulture();
    }
    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        actionExecutedContext.Request.ResetCurrentUICulture();
    }
}

为了验证这个ActionFilterAttribute特性,我们定义了如下一个继承自ApiController的HelloController。唯一的Action方法返回的字符串是从资源文件中提取的(类型Resources为资源文件自动生成的类型),而ActionFilterAttribute就应用在这个Get方法上。

[UserAcceptCulture]
public class HelloController : ApiController
{
    public string Get()
    {
        return Properties.Resources.HelloWorld;
    }
}

我们定义了两个资源文件,一个为语言文化中性的Resources.resx,另一个则是针对中文的Resources.zh.resx。唯一的资源项HelloWorld分别在所在的文件中以英文和中文进行定义,而上面定义的Get方法返回的正式它们的值。

 

在启动之后,我们利用Fiddler来调用定义在HelloController中的Action方法Get,并手工设置Accept-Language报头的值。如下图所示,当请求的Accept-Language报头被分别设置为“en-US;q=1.0, zh-CN;q=0.8”和“en-US;q=0.8, zh-CN;q=1.0”时(即给en-US和zh-CN分配不同的Quality),返回的内容分别是英文和中文。

 

三、第2种方案:自定义HttpActionDescriptor

HttpActionDescriptor用于描述定义在HttpController中的Action,默认的HttpActionDescriptor类型为ReflectedHttpActionDescriptor。Action方法的执行最终实现在HttpActionDescriptor的ExecuteAsync方法中,我们可以通过自定义的HttpActionDescriptor的方式在目标Action方法执行前后对当前线程的UI Culture进行设置和恢复。为此,我们定义了如下一个ExtendedReflectedHttpActionDescriptor类型。在重写的ExecuteAsync方法中,我们调用基类的同名方法执行目标Action方法,并在这前后分别调用当前HttpRequestMessage的两个扩展方法设置和恢复当前线程的UI Culture。

 

public class ExtendedReflectedHttpActionDescriptor:ReflectedHttpActionDescriptor
{
    public ExtendedReflectedHttpActionDescriptor(ReflectedHttpActionDescriptor actionDescriptor)
        : base(actionDescriptor.ControllerDescriptor,actionDescriptor.MethodInfo)
    { }
    public override Task<object> ExecuteAsync(HttpControllerContext controllerContext, IDictionary<string, object> arguments, CancellationToken cancellationToken)
    {
        controllerContext.Request.SetCurrentUICulture();
        Task<object> task = base.ExecuteAsync(controllerContext, arguments, cancellationToken);
        controllerContext.Request.ResetCurrentUICulture();
        return task;
    }
}

 

ASP.NET Web API利用一个名为HttpActionSelector的对象来选择与当前请求匹配的HttpActionDescriptor,要让我们自定义的ExtendedReflectedHttpActionDescriptor被使用,我们得对应的HttpActionSelector。ASP.NET Web API默认使用的HttpActionSelector类型为ApiControllerActionSelector,我们自定义的ExtentedApiControllerActionSelector就继承于它。如下面的代码片断所示,在重写的SelectAction方法中,我们调用基类的同名方法得到一个ReflectedHttpActionDescriptor 对象,并根据它创建一个ExtendedReflectedHttpActionDescriptor 对象并返回。

public class ExtentedApiControllerActionSelector:ApiControllerActionSelector
{
    public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
    {
        ReflectedHttpActionDescriptor actionDescriptor = (ReflectedHttpActionDescriptor)base.SelectAction(controllerContext);
        return new ExtendedReflectedHttpActionDescriptor(actionDescriptor);
    }
}

自定义的ExtentedApiControllerActionSelector可以在Global.asax中按照如下的方式进行注册。

四、第3种方案:自定义HttpActionInvoker

目标Action的执行是通过一个名为HttpActionInvoker驱动执行的(它调用HttpActionDescriptor的ExecuteAsync方法),默认的HttpActionInvoker类型为ApiControllerActionInvoker。我们可以继承它,并在执行目标Action方法前后设置和恢复当前线程的UI Culture。为此我定义了如下一个ExtendedApiControllerActionInvoker,在重写的InvokeActionAsync方法中,我们调用基类的同名方法执行目标Action方法,并在这前后分别调用当前HttpRequestMessage的两个扩展方法设置和恢复当前线程的UI Culture。

 

public class ExtendedApiControllerActionInvoker:ApiControllerActionInvoker
{
    public override Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
    {
        actionContext.Request.SetCurrentUICulture();
        Task<HttpResponseMessage> task = base.InvokeActionAsync(actionContext, cancellationToken);
        actionContext.Request.ResetCurrentUICulture();
        return task;
    }
}

 

自定义的ExtendedApiControllerActionInvoker可以在Global.asax中按照如下的方式进行注册。

 

五、第4种方案:为HttpController创建一个基类

HttpActionInvoker的最终又是在执行HttpController时被调用的,所以我们可以在执行HttpController上作文章。所以我们定义了如下一个继承自ApiController的ExtendedApiController 类型。在重写的ExecuteAsync方法中,我们调用基类同名方法前后对当前线程的UI Culture进行了设置和恢复。

 

public class ExtendedApiController:ApiController
{
    public override Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
    {
        controllerContext.Request.SetCurrentUICulture();
        Task<HttpResponseMessage> task = base.ExecuteAsync(controllerContext, cancellationToken);
        controllerContext.Request.ResetCurrentUICulture();
        return task;
    }
}

 

那么我们的HelloController只需要继承自ExtendedApiController 即可。

 

--原文链接:http://www.cnblogs.com/artech/p/asp-net-web-api-localization.html

 

posted @ 2016-05-26 17:47  Niko12230  阅读(144)  评论(0)    收藏  举报