如何讓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即可。
目錄
一、兩個輔助的擴展方法
二、第1種方案:自定義ActionFilter
三、第2種方案:自定義HttpActionDescriptor
四、第3種方案:自定義HttpActionInvoker
五、第4種方案:為HttpController創建一個基類
一、兩個輔助的擴展方法
我們針對HttpRequestMessage定義了如下兩個擴展方法。SetCurrentUICulture從請求的Accpet-Language報頭提取客戶端接受的語言並據此設置當前線程的UI Culture。在這之前,它會將當前線程的UI Culture保存到HttpRequestMessage對象中。ResetCurrentUICulture方法將這個CultureInfo對象從HttpRequestMessage其中提取出來,將當前線程的UI Cuilture回複到之前的狀態。
1: public static class HttpRequestMessageExtensions
2: {
3: public static void SetCurrentUICulture(this HttpRequestMessage request)
4: {
5: StringWithQualityHeaderValue acceptCultureHeader = request.Headers.AcceptLanguage.OrderByDescending(header => header.Quality).FirstOrDefault();
6: if (null != acceptCultureHeader)
7: {
8: request.Properties["__CurrentCulture"] = Thread.CurrentThread.CurrentUICulture;
9: Thread.CurrentThread.CurrentUICulture = new CultureInfo(acceptCultureHeader.Value);
10: }
11: }
12:
13: public static void ResetCurrentUICulture(this HttpRequestMessage request)
14: {
15: object culture;
16: if (request.Properties.TryGetValue("__CurrentCulture", out culture))
17: {
18: Thread.CurrentThread.CurrentUICulture = (CultureInfo)culture;
19: }
20: }
21: }
二、第1種方案:自定義ActionFilter
我想這應該是大家最容易想到的解決方案,因為ActionFilter可以注冊一些回調操作在目標Action方法執行前後被自動調用。為此我們定義了如下一個繼承自ActionFilterAttribute的UseAcceptCultureAttribute類型。我們分別在重寫的OnActionExecuting和OnActionExecuted方法中利用上麵定義的兩個擴展方法對當前線程的UI Culture進行設置和恢複。
1: public class UseAcceptCultureAttribute: ActionFilterAttribute
2: {
3: public override void OnActionExecuting(HttpActionContext actionContext)
4: {
5: actionContext.Request.SetCurrentUICulture();
6: }
7:
8: public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
9: {
10: actionExecutedContext.Request.ResetCurrentUICulture();
11: }
12: }
為了驗證這個ActionFilterAttribute特性,我們定義了如下一個繼承自ApiController的HelloController。唯一的Action方法返回的字符串是從資源文件中提取的(類型Resources為資源文件自動生成的類型),而ActionFilterAttribute就應用在這個Get方法上。
1: public class HelloController : ApiController
2: {
3:
[UseAcceptCulture]
4: public string Get()
5: {
6: return Resources.HelloWorld;
7: }
8: }
我們定義了兩個資源文件,一個為語言文化中性的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。
1: public class ExtendedReflectedHttpActionDescriptor : ReflectedHttpActionDescriptor
2: {
3: public ExtendedReflectedHttpActionDescriptor(ReflectedHttpActionDescriptor actionDescriptor)
4: : base(actionDescriptor.ControllerDescriptor, actionDescriptor.MethodInfo)
5: { }
6: public override Task<object> ExecuteAsync(HttpControllerContext controllerContext, IDictionary<string, object> arguments, CancellationToken cancellationToken)
7: {
8: controllerContext.Request.SetCurrentUICulture();
9: Task<object> task = base.ExecuteAsync(controllerContext, arguments, cancellationToken);
10: controllerContext.Request.ResetCurrentUICulture();
11: return task;
12: }
13: }
ASP.NET Web API利用一個名為HttpActionSelector的對象來選擇與當前請求匹配的HttpActionDescriptor,要讓我們自定義的ExtendedReflectedHttpActionDescriptor被使用,我們得對應的HttpActionSelector。ASP.NET Web API默認使用的HttpActionSelector類型為ApiControllerActionSelector,我們自定義的ExtentedApiControllerActionSelector就繼承於它。如下麵的代碼片斷所示,在重寫的SelectAction方法中,我們調用基類的同名方法得到一個ReflectedHttpActionDescriptor 對象,並根據它創建一個ExtendedReflectedHttpActionDescriptor 對象並返回。
1: public class ExtentedApiControllerActionSelector: ApiControllerActionSelector
2: {
3: public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
4: {
5: ReflectedHttpActionDescriptor actionDescriptor = (ReflectedHttpActionDescriptor) base.SelectAction(controllerContext);
6: return new ExtendedReflectedHttpActionDescriptor(actionDescriptor);
7: }
8: }
自定義的ExtentedApiControllerActionSelector可以在Global.asax中按照如下的方式進行注冊。
1: public class WebApiApplication : System.Web.HttpApplication
2: {
3: protected void Application_Start()
4: {
5: GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpActionSelector), new ExtentedApiControllerActionSelector());
6: //...
7: }
8: }
四、第3種方案:自定義HttpActionInvoker
目標Action的執行是通過一個名為HttpActionInvoker驅動執行的(它調用HttpActionDescriptor的ExecuteAsync方法),默認的HttpActionInvoker類型為ApiControllerActionInvoker。我們可以繼承它,並在執行目標Action方法前後設置和恢複當前線程的UI Culture。為此我定義了如下一個ExtendedApiControllerActionInvoker,在重寫的InvokeActionAsync方法中,我們調用基類的同名方法執行目標Action方法,並在這前後分別調用當前HttpRequestMessage的兩個擴展方法設置和恢複當前線程的UI Culture。
1: public class ExtendedApiControllerActionInvoker: ApiControllerActionInvoker
2: {
3: public override Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
4: {
5: actionContext.Request.SetCurrentUICulture();
6: Task < HttpResponseMessage > task = base.InvokeActionAsync(actionContext, cancellationToken);
7: actionContext.Request.ResetCurrentUICulture();
8: return task;
9: }
10: }
自定義的ExtendedApiControllerActionInvoker可以在Global.asax中按照如下的方式進行注冊。
1: public class WebApiApplication : System.Web.HttpApplication
2: {
3: protected void Application_Start()
4: {
5: GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpActionInvoker), new ExtendedApiControllerActionInvoker());
6: //...
7: }
8: }
五、第4種方案:為HttpController創建一個基類
HttpActionInvoker的最終又是在執行HttpController時被調用的,所以我們可以在執行HttpController上作文章。所以我們定義了如下一個繼承自ApiController的ExtendedApiController 類型。在重寫的ExecuteAsync方法中,我們調用基類同名方法前後對當前線程的UI Culture進行了設置和恢複。
1: public abstract class ExtendedApiController : ApiController
2: {
3: public override Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
4: {
5: controllerContext.Request.SetCurrentUICulture();
6: Task < HttpResponseMessage > task = base.ExecuteAsync(controllerContext, cancellationToken);
7: controllerContext.Request.ResetCurrentUICulture();
8: return task;
9: }
10: }
那麼我們的HelloController隻需要繼承自ExtendedApiController 即可。
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-25 15:34:12