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

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

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

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

         static void Main(string[] args)
{
string birthdate = "02/06/2013";
DateTime dateTime;
dateTime = DateTime.Parse(birthdate);
Console.WriteLine(dateTime.ToString("yyyy年MM月dd日"));
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-GB");
dateTime = DateTime.Parse(birthdate);
Console.WriteLine(dateTime.ToString("yyyy年MM月dd日"));
Console.Read();
}

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

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

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

         static void Main(string[] args)
{
string hello;
hello = International.Hello;
Console.WriteLine(hello);
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("ja-JP");
hello = International.Hello;
Console.WriteLine(hello);
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");
hello = International.Hello;
Console.WriteLine(hello);
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-GB");
hello = International.Hello;
Console.WriteLine(hello);
Console.Read();
}

那么在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的控制器中读取到。参考以下例子

         public void Post(Object obj)
{
HttpHeaderValueCollection<StringWithQualityHeaderValue> acceptedLanguages = Request.Headers.AcceptLanguage;
foreach (StringWithQualityHeaderValue language in acceptedLanguages)
{
Debug.WriteLine(language.Value);
Debug.WriteLine(language.Quality);
}
}

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

 namespace HelloWebAPI.Infrastructure
{
public class CultureHandler : DelegatingHandler
{ private List<string> supportedCulture = new List<string>()
{
"zh-cn", "en-us", "ja-jp"
}; protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
HttpHeaderValueCollection<StringWithQualityHeaderValue> acceptedLanguage = request.Headers.AcceptLanguage;
if (acceptedLanguage != null && acceptedLanguage.Count > )
{
StringWithQualityHeaderValue preferredLanguage =
acceptedLanguage.OrderByDescending(e => e.Quality ?? 1.0D)
.Where(e => !e.Quality.HasValue || e.Quality.Value > 0.0D)
.FirstOrDefault(
e => supportedCulture.Contains(e.Value, StringComparer.OrdinalIgnoreCase));
if (preferredLanguage != null)
{
// 如需要,此处也可同时设置CurrentCulture属性
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(preferredLanguage.Value);
} if (acceptedLanguage.Any(e => e.Value == "*" &&(!e.Quality.HasValue || e.Quality.Value > 0.0D)))
{
string selectedCulture =
supportedCulture.FirstOrDefault(e => !acceptedLanguage.Any(
ee =>
ee.Value.Equals(e, StringComparison.OrdinalIgnoreCase) && ee.Quality.HasValue &&
ee.Quality.Value == 0.0D));
if (!string.IsNullOrWhiteSpace(selectedCulture))
{
// 如需要,此处也可同时设置CurrentCulture属性
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(selectedCulture);
}
}
}
return base.SendAsync(request, cancellationToken);
}
}
}

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

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

         public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.MessageHandlers.Add(new CultureHandler());
}

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

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

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

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

为了调试,我们使用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文件中,我们可以直接对其设置。

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

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

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

DataTimeConverter源代码如下:

 namespace HelloWebAPI.Infrastructure
{
public class DateTimeConverter : DateTimeConverterBase
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(((DateTime)value).ToString());
} public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return DateTime.Parse(reader.Value.ToString());
}
}
}

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

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

         public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new HelloWebAPI.Infrastructure.DateTimeConverter());
config.MessageHandlers.Add(new CultureHandler());
}

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

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

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

Hello Web API系列教程——Web API与国际化的更多相关文章

  1. ASP.NET Web API系列教程目录

    ASP.NET Web API系列教程目录 Introduction:What's This New Web API?引子:新的Web API是什么? Chapter 1: Getting Start ...

  2. ASP.NET Web API系列教程(目录)(转)

    注:微软随ASP.NET MVC 4一起还发布了一个框架,叫做ASP.NET Web API.这是一个用来在.NET平台上建立HTTP服务的Web API框架,是微软的又一项令人振奋的技术.目前,国内 ...

  3. [转]ASP.NET Web API系列教程(目录)

    本文转自:http://www.cnblogs.com/r01cn/archive/2012/11/11/2765432.html 注:微软随ASP.NET MVC 4一起还发布了一个框架,叫做ASP ...

  4. Web攻防系列教程之文件上传攻防解析(转载)

    Web攻防系列教程之文件上传攻防解析: 文件上传是WEB应用很常见的一种功能,本身是一项正常的业务需求,不存在什么问题.但如果在上传时没有对文件进行正确处理,则很可能会发生安全问题.本文将对文件上传的 ...

  5. SpringBoot系列教程web篇之过滤器Filter使用指南扩展篇

    前面一篇博文介绍了在 SpringBoot 中使用 Filter 的两种使用方式,这里介绍另外一种直接将 Filter 当做 Spring 的 Bean 来使用的方式,并且在这种使用方式下,Filte ...

  6. SpringBoot系列教程Web篇之开启GZIP数据压缩

    本篇可以归纳在性能调优篇,虽然内容非常简单,但效果可能出乎预料的好: 分享一个真实案例,我们的服务部署在海外,国内访问时访问服务时,响应有点夸张:某些返回数据比较大的接口,耗时在 600ms+上,然而 ...

  7. SpringBoot系列教程web篇Listener四种注册姿势

    java web三要素Filter, Servlet前面分别进行了介绍,接下来我们看一下Listener的相关知识点,本篇博文主要内容为SpringBoot环境下,如何自定义Listener并注册到s ...

  8. SpringBoot系列教程web篇Servlet 注册的四种姿势

    原文: 191122-SpringBoot系列教程web篇Servlet 注册的四种姿势 前面介绍了 java web 三要素中 filter 的使用指南与常见的易错事项,接下来我们来看一下 Serv ...

  9. SpringBoot系列教程web篇之过滤器Filter使用指南

    web三大组件之一Filter,可以说是很多小伙伴学习java web时最早接触的知识点了,然而学得早不代表就用得多.基本上,如果不是让你从0到1写一个web应用(或者说即便从0到1写一个web应用) ...

随机推荐

  1. NodeJs之log4js

    log4js log4js是一个管理,记录日志的工具. 其实与morgan的作用类似. 安装 npm install -g log4js log4js的6个日志级别 分别是:trace(蓝色).deb ...

  2. FFmpeg学习6:视音频同步

    在上一篇文章中,视频和音频是各自独立播放的,并不同步.本文主要描述了如何以音频的播放时长为基准,将视频同步到音频上以实现视音频的同步播放的.主要有以下几个方面的内容 视音频同步的简单介绍 DTS 和 ...

  3. 再讲IQueryable<T>,揭开表达式树的神秘面纱

    接上篇<先说IEnumerable,我们每天用的foreach你真的懂它吗?> 最近园子里定制自己的orm那是一个风生水起,感觉不整个自己的orm都不好意思继续混博客园了(开个玩笑).那么 ...

  4. Js new到底发生了什么

    在Js中,我们使用了new关键字来进行实例化 那么在这个new的过程中到底发生了什么? 关于构造函数的return 正常来讲构造函数中是不用写return语句的,因为它会默认返回新创建的对象. 但是, ...

  5. angular实现统一的消息服务

    后台API返回的消息怎么显示更优雅,怎么处理才更简洁?看看这个效果怎么样? 自定义指令和服务实现 自定义指令和服务实现消息自动显示在页面的顶部,3秒之后消失 1. 显示消息 这种显示消息的方式是不是有 ...

  6. ExtJS 4.2 业务开发(三)数据添加和修改

    接上面的船舶管理业务,这里介绍添加和修改操作. 目录 1. 添加操作 2. 修改操作 3. 在线演示 1. 添加操作 1.1 创建AddShipWindow.js 在业务中的view目录下创建一个Ad ...

  7. Android线程管理之ThreadLocal理解及应用场景

    前言: 最近在学习总结Android的动画效果,当学到Android属性动画的时候大致看了下源代码,里面的AnimationHandler存取使用了ThreadLocal,激起了我很大的好奇心以及兴趣 ...

  8. 模仿Linux内核kfifo实现的循环缓存

    想实现个循环缓冲区(Circular Buffer),搜了些资料多数是基于循环队列的实现方式.使用一个变量存放缓冲区中的数据长度或者空出来一个空间来判断缓冲区是否满了.偶然间看到分析Linux内核的循 ...

  9. 自己实现简单Spring Ioc

    IoC则是一种 软件设计模式,简单来说Spring通过工厂+反射来实现IoC. 原理简单说明: 其实就是通过解析xml文件,通过反射创建出我们所需要的bean,再将这些bean挨个放到集合中,然后对外 ...

  10. BI分析受阻?FineBI推出SPA螺旋式分析新功能!

    过去,企业级的数据分析通常会有这么几种场景,业务部门托信息部门分析数据,结果报表一出,唇枪舌剑争论你我高低,数据不准,指标不对.信息部门欠缺业务概念,业务部门不懂技术逻辑,数据分析之路,暂时搁浅. 后 ...