如何让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方法返回的正式它们的值。

image

在启动之后,我们利用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),返回的内容分别是英文和中文。

image

三、第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
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
1月前
|
开发框架 前端开发 JavaScript
ASP.NET Web Pages - 教程
ASP.NET Web Pages 是一种用于创建动态网页的开发模式,采用HTML、CSS、JavaScript 和服务器脚本。本教程聚焦于Web Pages,介绍如何使用Razor语法结合服务器端代码与前端技术,以及利用WebMatrix工具进行开发。适合初学者入门ASP.NET。
|
3月前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
207 3
|
17天前
|
运维 前端开发 C#
一套以用户体验出发的.NET8 Web开源框架
一套以用户体验出发的.NET8 Web开源框架
一套以用户体验出发的.NET8 Web开源框架
|
22小时前
|
开发框架 数据可视化 .NET
.NET 中管理 Web API 文档的两种方式
.NET 中管理 Web API 文档的两种方式
23 14
|
2月前
|
开发框架 .NET 程序员
驾驭Autofac,ASP.NET WebApi实现依赖注入详细步骤总结
Autofac 是一个轻量级的依赖注入框架,专门为 .NET 应用程序量身定做,它就像是你代码中的 "魔法师",用它来管理对象的生命周期,让你的代码更加模块化、易于测试和维护
驾驭Autofac,ASP.NET WebApi实现依赖注入详细步骤总结
|
1月前
|
开发框架 .NET PHP
ASP.NET Web Pages - 添加 Razor 代码
ASP.NET Web Pages 使用 Razor 标记添加服务器端代码,支持 C# 和 Visual Basic。Razor 语法简洁易学,类似于 ASP 和 PHP。例如,在网页中加入 `@DateTime.Now` 可以实时显示当前时间。
|
3月前
|
开发框架 .NET API
Windows Forms应用程序中集成一个ASP.NET API服务
Windows Forms应用程序中集成一个ASP.NET API服务
114 9
|
3月前
|
存储 开发框架 .NET
.NET 8 实现无实体库表 API 部署服务
【10月更文挑战第12天】在.NET 8中,可通过以下步骤实现无实体库表的API部署:首先安装.NET 8 SDK及开发工具,并选用轻量级Web API框架如ASP.NET Core;接着创建新项目并设计API,利用内存数据结构模拟数据存储;最后配置项目设置并进行测试与部署。此方法适用于小型项目或临时解决方案,但对于大规模应用仍需考虑持久化存储以确保数据可靠性与可扩展性。
|
3月前
|
监控 安全 API
Docker + .NET API:简化部署和扩展
Docker + .NET API:简化部署和扩展
51 1
|
3月前
|
监控 安全 API
最完美的扩展Docker + .NET API:简化部署和扩展
最完美的扩展Docker + .NET API:简化部署和扩展
102 0