软件国际化是在软件设计和文档开发过程中,使得功能和代码设计能处理多种语言和文化习俗,在创建不同语言版本时,不需要重新设计源程序代码的软件工程方法。这在很多成熟的软件开发平台中非常常见。对于.net开发者来说,我们一般可以通过以下两种方式来实现软件的国际化。

  • 语言配置文件
  • 资源文件

在.net平台中,软件的国际化主要依靠工作线程的国际化来完成。在.net框架的的处理线程中,我们通过设置Thread.CurrentCulture属性来实现对日期、时间、数字、货币值、文本的排序顺序,负载约定和字符串比较的默认值的格式确定,默认情况下,这个属性来自于“控制面板”的“区域和语言选项”中的用户区域性。当然,在软件运行过程中也可以通过手动的方式强制改变Thread.CurrentCulture属性值。CurrentUICulture属性则用来确定需要向用户呈现的资源格式,它对软件的操作界面来说最有用,因为它标识了在显示UI元素时应使用的语言。在.net中通常给软件设置不同的UI资源文件,使得软件运行时通过CurrentUICulture属性值来选择不同语言的资源文件渲染软件界面。线程的CurrentUICulture 和 CurrentCulture 属性一般设置为同一个CultureInfo对象,也就是就他它使用相同的语言、国家信息,然而也可将它们设为不同的对象。例如一个美国人在北京借用了一台操作系统是简体中文版的计算机进行工作时,就可以通过这种方式让软件满足美国用户的使用需求。

下面的例子解释了对于不同的Thread.CurrentCulture属性值,同一个日期字符串转换为日期类型时会生成不同结果

 1         static void Main(string[] args)
2 {
3 string birthdate = "02/06/2013";
4 DateTime dateTime;
5 dateTime = DateTime.Parse(birthdate);
6 Console.WriteLine(dateTime.ToString("yyyy年MM月dd日"));
7 Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-GB");
8 dateTime = DateTime.Parse(birthdate);
9 Console.WriteLine(dateTime.ToString("yyyy年MM月dd日"));
10 Console.Read();
11 }

下面的例子解释了对于不同的Thread.CurrentUICulture属性值,框架会选择不同的资源文件读取其中的值,以便向使用不同语言的用户呈现不同的信息。运行前,需要在项目中建立如下的资源文件。

每个资源文件的内容如下:

其中,文件名中的语言代码代表不同的国家及使用的语言,如en-US=美国英语、en-GB=英国英语等等。文件名不带语言代码的为默认资源文件,框架会根据当前计算机所在的区域来调用这个资源文件。由于我在中国,并且使用简体中文的操作系统,所以,会将默认资源文件中的中文字符读出。

 1         static void Main(string[] args)
2 {
3 string hello;
4 hello = International.Hello;
5 Console.WriteLine(hello);
6 Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("ja-JP");
7 hello = International.Hello;
8 Console.WriteLine(hello);
9 Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");
10 hello = International.Hello;
11 Console.WriteLine(hello);
12 Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-GB");
13 hello = International.Hello;
14 Console.WriteLine(hello);
15 Console.Read();
16 }

那么在Web API中,如何确定用户来自哪个国家以及使用哪种语言、文化呢?由于Web API无法主动读取用户所有区域的信息,那么就只能被动地从用户那里获取此类信息。而这部分信息则会被包含在用户提交的HTTP请求头信息中。比如:

Accept-Language: en-us, en-gb;q=0.8, en;q=0.7

在这段HTTP请求头信息中,所传达的信息就是用户可接受的语言和地区文化。每种国家及语言后面的参数q称为相对质量的因素(relative quality factor),代表用户对于该种语言可接受的优先度(取值0.0~1.0,默认值为1.0)。总之通俗一来就讲,上面这段头信息的意思就是用户首选是美式英语,如果不支持的话,没关系,那就英式英语吧,再不行的话,其它类型的英语也可以。

上面的这段请求头信息提交到服务器时,会被封装在HttpRequestMessage类(System.Net.Http命名空间)的Headers属性中,我们可以很轻易地在Web API的控制器中读取到。参考以下例子

1         public void Post(Object obj)
2 {
3 HttpHeaderValueCollection<StringWithQualityHeaderValue> acceptedLanguages = Request.Headers.AcceptLanguage;
4 foreach (StringWithQualityHeaderValue language in acceptedLanguages)
5 {
6 Debug.WriteLine(language.Value);
7 Debug.WriteLine(language.Quality);
8 }
9 }

当然,在实际的开发中,上面的方法不是推荐的方法。更加科学的方法是我们新建一个自定义的DelegatingHandler,简单一点来讲,DelegatingHandler是HTTP请求通道中的过滤器。我们可以在这个DelegatingHandler中读取Accept-Language信息,并且设置处理线程的Thread.CurrentCulture属性和CurrentUICulture属性。源码如下:

 1 namespace HelloWebAPI.Infrastructure
2 {
3 public class CultureHandler : DelegatingHandler
4 {
5
6 private List<string> supportedCulture = new List<string>()
7 {
8 "zh-cn", "en-us", "ja-jp"
9 };
10
11 protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
12 {
13 HttpHeaderValueCollection<StringWithQualityHeaderValue> acceptedLanguage = request.Headers.AcceptLanguage;
14 if (acceptedLanguage != null && acceptedLanguage.Count > 0)
15 {
16 StringWithQualityHeaderValue preferredLanguage =
17 acceptedLanguage.OrderByDescending(e => e.Quality ?? 1.0D)
18 .Where(e => !e.Quality.HasValue || e.Quality.Value > 0.0D)
19 .FirstOrDefault(
20 e => supportedCulture.Contains(e.Value, StringComparer.OrdinalIgnoreCase));
21 if (preferredLanguage != null)
22 {
23 // 如需要,此处也可同时设置CurrentCulture属性
24 Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(preferredLanguage.Value);
25 }
26
27 if (acceptedLanguage.Any(e => e.Value == "*" &&(!e.Quality.HasValue || e.Quality.Value > 0.0D)))
28 {
29 string selectedCulture =
30 supportedCulture.FirstOrDefault(e => !acceptedLanguage.Any(
31 ee =>
32 ee.Value.Equals(e, StringComparison.OrdinalIgnoreCase) && ee.Quality.HasValue &&
33 ee.Quality.Value == 0.0D));
34 if (!string.IsNullOrWhiteSpace(selectedCulture))
35 {
36 // 如需要,此处也可同时设置CurrentCulture属性
37 Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(selectedCulture);
38 }
39 }
40 }
41 return base.SendAsync(request, cancellationToken);
42 }
43 }
44 }

上面的这段代码中,假设系统支持的语言有美式英语、简体中文、日文。并且如果用户支持多种语言,该DelegatingHandler也会根据头信息中的相对质量的因素q进行排序以确定首选语言。

打开WebApiConfig.cs文件,注册自定义的DelegatingHandler,在Register静态方法中添加如下的语句。

1         public static void Register(HttpConfiguration config)
2 {
3 config.Routes.MapHttpRoute(
4 name: "DefaultApi",
5 routeTemplate: "api/{controller}/{id}",
6 defaults: new { id = RouteParameter.Optional }
7 );
8 config.MessageHandlers.Add(new CultureHandler());
9 }

应用场景一:根据用户所在的国家和使用的语言,向用户提供不同语言的响应

在项目中,新建一个App_GlobalResources文件夹,该文件为ASP.net的专属文件夹,专门用于存放资源文件。分别建立几个对应语言的资源文件。文件中均有一个同键不同值的字符串“NotFound”,该字符串用于提示用户找不到指定的文件或资源。

在控制器中,建立一个示例动作方法如下:

1     public class EmployeesController : ApiController
2 {
3 public HttpResponseMessage Get(int id)
4 {
5 // 业务逻辑,此处省略
6 return Request.CreateErrorResponse(HttpStatusCode.NotFound, Resources.Message.NotFound);
7 }
8 }

为了调试,我们使用Fiddler来进行,运行项目之后,我们通过设置HTTP请求头信息中的Accept-Lanuage,可以得到不同语言的响应。

应用场景二:根据用户所在的国家和使用的语言,对用户提交的数据进行处理和提交。

回想文章开头的例子,对于一个相同的时间日期字符串,不同国家的用户可能会有不同的理解。比如"05/06/2013",类似于这种格式的日期在计算机中很常见。但恰恰是这样一种表达方式在美国人看来是2013年5月6日,因为美国习惯于MM/dd/yyyy这种日期格式;但在英国人看来则会是2013年6月5日,因为他们所理解的日期格式是 dd/MM/yyyy。因此,对于Web API来说,如果我们面对的文化背景如此复杂的用户,那么在处理用户的数据时不得不倍加细心,否则将会造成难以挽回的损失。如何应对这个问题呢?

首先要知道,在Web API框架中,关于JSON的序列化和反序列化,微软已经将这个任务委托给第三方类库JSON.net。经过查阅文档,发现JSON.net对于日期格式都使用了日期转换器进行转换,并且内置了两个常用的日期转换器,JavaScriptDateTimeConverter和IsoDateTimeConverter,这两个日期转换器运行时,通过读取SerializerSettings类的Culture属性来设置线程的Culture。

所以,我们通过设置JSON.net的SerializerSettings类中的Culture属性也可以实现国际化。因此,在WebApiConfig.cs文件中,我们可以直接对其设置。

1         public static void Register(HttpConfiguration config)
2 {
3 config.Routes.MapHttpRoute(
4 name: "DefaultApi",
5 routeTemplate: "api/{controller}/{id}",
6 defaults: new { id = RouteParameter.Optional }
7 );
8 config.Formatters.JsonFormatter.SerializerSettings.Culture = new System.Globalization.CultureInfo("en-US");
9 }

但是,这根本不是最好的解决方案。因为它没办法根据HTTP请求头信息来设置Culture属性,WebApiConfig类只是一个配置类,不是一个过滤器,它没办法访问HTTP请求。

所以,问题的最终解决方案是我们应该避开SerializerSettings类,自己编写DataTimeConverter类对日期进行转换,设置线程的Cultrue工作交由之前自定义的CultureHandler去完成。

DataTimeConverter源代码如下:

 1 namespace HelloWebAPI.Infrastructure
2 {
3 public class DateTimeConverter : DateTimeConverterBase
4 {
5 public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
6 {
7 writer.WriteValue(((DateTime)value).ToString());
8 }
9
10 public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
11 {
12 return DateTime.Parse(reader.Value.ToString());
13 }
14 }
15 }

同时,我们还需要将CultureHandler源码中的Thread.CurrentThread.CurrentUICulture改为Thread.CurrentThread.CurrentCulture。

最后,我们需要在WebApiConfig.cs中对日期转换器进行注册,为了避免与框架中内置的DateTimeConverter冲突,此处使用了类的完全限定名。

 1         public static void Register(HttpConfiguration config)
2 {
3 config.Routes.MapHttpRoute(
4 name: "DefaultApi",
5 routeTemplate: "api/{controller}/{id}",
6 defaults: new { id = RouteParameter.Optional }
7 );
8 config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new HelloWebAPI.Infrastructure.DateTimeConverter());
9 config.MessageHandlers.Add(new CultureHandler());
10 }

启动项目,同时使用Fiddler提交示例的JSON数据如下:

{id:123,firstName:"Gates", lastName:"Bill", age:58, birthdate:"05/06/1955"}

分别设置不同的Accept-Language头信息进行调试

Web API与国际化的更多相关文章

  1. Hello Web API系列教程——Web API与国际化

    软件国际化是在软件设计和文档开发过程中,使得功能和代码设计能处理多种语言和文化习俗,在创建不同语言版本时,不需要重新设计源程序代码的软件工程方法.这在很多成熟的软件开发平台中非常常见.对于.net开发 ...

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

    在今天编辑推荐的<Hello Web API系列教程--Web API与国际化>一文中,作者通过自定义的HttpMessageHandler的方式根据请求的Accep-Language报头 ...

  3. 使用 jQuery.i18n.properties 实现 Web 前端的国际化

    jQuery.i18n.properties 简介 在介绍 jQuery.i18n.properties 之前,我们先来看一下什么是国际化.国际化英文单词为:Internationalization, ...

  4. 在一个空ASP.NET Web项目上创建一个ASP.NET Web API 2.0应用

    由于ASP.NET Web API具有与ASP.NET MVC类似的编程方式,再加上目前市面上专门介绍ASP.NET Web API 的书籍少之又少(我们看到的相关内容往往是某本介绍ASP.NET M ...

  5. bootstrap + requireJS+ director+ knockout + web API = 一个时髦的单页程序

    也许单页程序(Single Page Application)并不是什么时髦的玩意,像Gmail在很早之前就已经在使用这种模式.通常的说法是它通过避免页面刷新大大提高了网站的响应性,像操作桌面应用程序 ...

  6. ASP.NET Web API 跨域访问(CORS)

    一.客户端用JSONP请求数据 如果你想用JSONP来获得跨域的数据,WebAPI本身是不支持javascript的callback的,它返回的JSON是这样的: {"YourSignatu ...

  7. Web Api 入门实战 (快速入门+工具使用+不依赖IIS)

    平台之大势何人能挡? 带着你的Net飞奔吧!:http://www.cnblogs.com/dunitian/p/4822808.html 屁话我也就不多说了,什么简介的也省了,直接简单概括+demo ...

  8. Web APi之认证(Authentication)两种实现方式【二】(十三)

    前言 上一节我们详细讲解了认证及其基本信息,这一节我们通过两种不同方式来实现认证,并且分析如何合理的利用这两种方式,文中涉及到的基础知识,请参看上一篇文中,就不再叙述废话. 序言 对于所谓的认证说到底 ...

  9. angular2系列教程(八)In-memory web api、HTTP服务、依赖注入、Observable

    大家好,今天我们要讲是angular2的http功能模块,这个功能模块的代码不在angular2里面,需要我们另外引入: index.html <script src="lib/htt ...

随机推荐

  1. 近乎(Spacebuilder)移动端 V2.2 发布,SNS 社区开源软件

    本次新版本是在以往版本基础上的进一步优化,尤其是架构和操作体验方面. 架构方面: 近乎团队在此次改版中摒弃了原有的缓存框架,启用更加清晰.易于维护的近乎V2.2结构框架,提升了产品的开发效率和拓展性, ...

  2. JVM内存格局总结

    最近一次面试,面试官让我讲讲JVM的内存这一块的理解.我回答的不满意,今天做一个总结. 做一个产品,最终要做到高并发.高可靠.归根结底,是对CPU.内存等资源受限所作出的解决方案.就内存而言,我们写的 ...

  3. 泛函编程(37)-泛函Stream IO:通用的IO处理过程-Free Process

    在上两篇讨论中我们介绍了IO Process:Process[I,O],它的工作原理.函数组合等.很容易想象,一个完整的IO程序是由 数据源+处理过程+数据终点: Source->Process ...

  4. java 用 jxl poi 进行excel 解析 *** 最爱那水货

    /** * 解析excel文件 ,并把数据放入数组中 格式 xlsx xls * @param path 从ftp上下载到本地的文件的路径 * @return 数据数组集合 */ public Lis ...

  5. 通过“回文字算法”复习C++语言。

    一.什么是回文字 给定一个字符串,从前往后读和从后往前读,字符串序列不变.例如,河北省农村信用社的客服电话是“96369”,无论从后往前读,还是从前后往后读,各个字符出现的位置不变. 二.功能实现 ( ...

  6. HTTP协议(转)

    HTTP是一个属于应用层的面向对象的协议,由于其简捷.快速的方式,适用于分布式超媒体信息系统.它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展.目前在WWW中使用的是HTTP/1.0的第 ...

  7. sql server 数据误删找回

    /****** Object: StoredProcedure [dbo].[Recover_Deleted_Data_Proc] Script Date: 04/23/2014 22:11:59 * ...

  8. 使用 github + jekyll 搭建个人博客

    github + jekyll 本地写markdown,然后push到github,就成了博客 其实我一早就知道这两者可以搭建个人博客,因为本人有个很好的习惯——每天都会去看看一些热门文章,了解行业最 ...

  9. Papa Parse – 超强大的多线程 CSV 文本解析库

    Papa Parse 是一个与众不同的,在网页上运行的第一个多线程的 CSV 解析器.它可以解析千兆字节大小文件而不会导致浏览器崩溃.它能够正确地处理格式不正确或边缘的情况下的 CSV 文本.它可以分 ...

  10. 移动Web开发的bug及解决方案

    我目前移动Web开发遇到的bug以及解决方案(慢慢补充当中). 1.android4.0以上一部分手机的webview中,当canvas小于屏幕大小时,绘图时会出现重影,就是说一个图只绘制了一遍,却出 ...