讓ASP.NET Web API的Action方法

joytoy發表於2021-09-09

在今天編輯推薦的《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 即可。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4650/viewspace-2817870/,如需轉載,請註明出處,否則將追究法律責任。

相關文章