.NET系统学习----Globalization & Resources

8 篇文章 0 订阅
        前言
l          了解资源文件
l          创建资源文件
l          在程序中使用资源文件
l          资源文件的命名和部署
l          参考
 
前言:
在学习如何使用 .NET 资源文件以及如何开发 World-Ready 程序之前,我们先通过一个例子来看看为什么要使用资源文件,以及使用它的好处。
假设要在程序中根据当前的 Culutre 来设置 Form Title Logo
       private void Form1_Load(object sender, System.EventArgs e)   {
              CultureInfo ci = new CultureInfo(Thread.CurrentThread.CurrentUICulture.ToString());
              switch (ci.ToString().ToLower()) {
                     case "zh-cn":  // 中文版本
                            this.Text=FormTitle_ZH_CN;
                            imgLogo.Image = new Bitmap(Application.StartupPath + "/Logo_ZH_CN.jpg");
                            break ;
                     case "en-us":  // 英文版本
                            this.Text=FormTitle_EN_US;
                            imgLogo.Image = new Bitmap(Application.StartupPath + "/Logo_EN_US.jpg");
                            break;
                     default:    // 默认版本
                            this.Text=FormTitle_Neutral;
                            imgLogo.Image = new Bitmap(Application.StartupPath + "/Logo_Neutral.jpg");
                            break;
              }
}
这段代码有两个问题:
首先, Logo 文件是暴露给用户的,而且是以普通文件的格式存储的,这导致其他程序或是用户很容易修改这些文件;节省硬盘空间的用户还可能会选择删除它,这些都可能会导致应用程序出错。确保图片或任何其他文件和代码在一起的唯一的安全方式是将它作为资源文件嵌入在程序集中并加载。
其次,这是一个 World-Ready 程序,如果需要新加入一个新的 Culture ,你可能不得不更改你的源代码,加入新的 case ,然后重新编译来适应新的 Culture 的需要,这对一个 World-Ready 程序来说是不现实的。开发 World-Ready 程序很重要的一点就是要保证程序的逻辑界面和资源界面的隔离。 任何时候加入一个新的 Culture 资源,我们都不应该重新编译源程序,相反,我们只需要把新的资源文件准备好,然后发布给用户并部署在合适的目录下就可以了。应用程序应该能够根据不同的 Culture 来自动寻找合适的资源。
本文的目的就是通过实例来帮助读者了解什么是 Resources ,以及如何使用 Resources 来消除上面所提到的两个问题。
全文分为四部分:
第一部分是一些和资源相关的概念。
第二部分是一个实例程序( ResourceGenerator ),用来说明如何创建资源文件。
第三部分是另外一个实例程序( WorldAPP ),用来说明如何在程序中使用资源文件
第四部分是关于资源文件的命名和部署。分别介绍 .NET 中资源文件的命名方式和如何在 World-Ready 程序中配置资源文件。
 
第一部分 概念
先来了解一些概念:
1.        什么是资源文件
顾名思义,资源文件当然包含的全是资源。不过,什么是资源?这里所谓的资源就是程序中可利用的任何数据,譬如:字符串、图片或任何二进制格式的数据。一个资源文件可以有多种语言文化版本,比如,一个 Culture.resources 文件可以有英语版、简体中文版日文版等。 ResourceManager 可以自动根据 Culture 和资源文件名来确认调用哪个版本。只不过不同的资源版本需要在文件名中加入语言文化信息( .resource 文件有一套严格的命名规范,参考 第四部分:资源文件的命名和部署 )。
2.        资源文件的类型
System.Resources 名称空间支持三种类型的资源:
.txt 文件,只能有字符串资源。因为不能被嵌入到 Assembly 中,所以很容易暴露,被其他程序或用户修改。最大缺点是仅支持字符串资源,不推荐使用。
.resx 文件,由 XML 组成,可以加入任何资源,包括二进制格式的。同样不能被嵌入到 Assembly 中。在 System.Resources 名称空间中有专用读写的类。 VS.NET 中创建的这种文件也是将其转成 .resources 文件然后根据设置将其嵌入到 Assembly 中。
.resources 文件, PE 格式,可以加入任何资源。是唯一可以被嵌入到 Assembly 的文件 ,在 System.Resources 名称空间中有专用读写的类( ResourceManager )。
3.        调用资源文件的几种方法
ResourceManager 可以根据不同的 UICulture 设置返回不同的本地资源,不同 Culture 的资源文件有一套严格的命名规则,只有按照这个规则命名, CRL 才可以根据 Culture 找到这个本地资源。 PS :因为这个很重要,所以才一再出现 J 参考 第四部分:资源文件的命名和部署
.txt 文件:
不可以直接调用,得先将其转换成 .resources 文件才能使用。
.resx 文件:
可以用 ResXResourceReader 来读取,但是这种方法不直观也不安全,不推荐直接调用 .resx 文件。正确的方法是将其转换成 .resources 文件,然后用 ResourceManager 读取。注意,如果是在 VS.NET 中添加的 .resx 文件,那么它们自动被设为 Embedded Resource ,然后被转成 .resources 文件后嵌入到 Assembly 中。
.resources 文件:
分成两种情况:
·         被嵌入或编译成卫星程序集( Satellite Assembly ):
ResourceManager 的各种 constructor 来获得在 Assembly 中的资源。
·         单独文件,没有被编译或嵌入到 Assembly 中:
可以用 ResourceManager.CreateFileBasedResourceManager 来获得资源集( ResourceSet ),就是所有的资源。
特殊情况:
还有一种特殊情况,那就是当你直接嵌入一资源时,也就是说,不通过一个资源文件( .resources )而直接将一资源 Object )嵌入到 Assembly 中。这可以通过 AL.exe Assembly Linker )的参数 /embed:<object> 把资源嵌入在 Assembly 中。在这种情况下 ResourceManager 就没有用了,因为它只能获取 .resources 资源文件(在或不在 Assembly 中)。
调用这类直接嵌入在 Assembly 中的资源,我们就需要利用 Reflection 的一些特性来完成。在 System.Reflection.Assembly 类中有一些相关函数可以帮助我们拿到这些资源。通过 Assembly.GetManifestResourceNames 可以拿到所有的资源的名字,然后我们就可以通过 Assembly.GetManifestResourceStream <object_name> )这个函数拿到对应的资源并以 stream 的方式返回,然后我们可以将这个 stream 转成在 .NET 中可用的对象。比如,如果嵌入资源是一图片,那么我们可以利用 New Bitmap Stream )的 constructor 获得这个图片资源的 Bitmap 对象。
 
第二部分 创建资源文件
创建资源文件有两种方式,一种是使用 .NET SDK 自带的 resgen 工具来创建,另外一种是自己写 code 来创建。分别来介绍:
1.        Resgen
这个工具是 .NET 自带的,它可以把 .txt .resX ,转换为 .resources 文件。 .resources 文件是以一种以键 - 值方式对应存储的 XML 格式文件,每一个键 <data> 对应一个值 <value> ,这个 <value> 可以是任何的二进制格式。如果是格式为(键 = 值)对应得 .txt 文件, resgen 会自动生成键 - 值对应的 XML 文件。但是 resgen 有一个局限性,它不能直接嵌入其他格式的文件,比如你就不能把 .bmp 以键 - 值得方式对应起来,因为你首先不能很容易得把 .bmp 以(键 = 值)对应的格式储存在 .txt 文件中。所以 resgen 主要是针对 txt 文件使用。
一个例子: company1.txt 文件内容为:
Title = Company1
Address = Company1 Address
Phone = 12345678
----------------------------------------------------------------
Resgen company.txt <outputfilename>.resources
如果不指定 <outputfilename> ,默认会生成 company1.resources
然后就可以通过 ResourceManager 来使用了。
还可以再进一步,通过 AL.exe resources 文件变为一个 assembly (使用 assembly 有很多好处(比如可以加入版本信息和 Culture 信息等)详见( .NET 系统学习 ----Assembly )。
Al /out:company1.dll /embed:company1.resources
通过设置 ResourcesManager 的不同的 constructor 就可以访问 Assembly 中包含的 .resources 文件(下面的例子会讲到)。
2.        通过编程使用 IResourcsWrite来生成资源文件
上面的方法的一个最大的缺点是不能很方便的嵌入其他格式的资源,因为把其他格式的资源变为键 - 值对应得 txt 文件并不是一件很容易的事。所以我们介绍另一种方法,通过编程,使用 .NET 提供的 IResourcesWrite 类来实现把任何资源嵌入到 resources 文件中。
 
ResourceGenerator 就是用这种方式实现的。
程序的主界面:
 
用到的主要方法就是:
private void OnGenerateResource(object sender, System.EventArgs e)
{
IResourceWriter rw = new ResourceWriter( “C:/test.resources”);
       switch (sType)
       {
              case "system.string":
              rw.AddResource(sKey,sValue);
              break;
              case "system.drawing.bitmap":
              Bitmap bmp = new Bitmap(sValue);
              rw.AddResource(sKey,bmp);
              break;
              case "system.drawing.image":
              Image img= new Bitmap(sValue);
              rw.AddResource(sKey,img);
              break;
}
}
根据资源的类型,如果不是 string 类型的,我们就把它分实例化为相应的 stream ,然后加入到 resoruces 中即可( string 类型可以直接加入)。生成的就是 .NET 可以直接使用的 .resources 文件。但是这样生成的资源 CLR 并不能根据不同的 Culture 自动识别。要想 CRL 自动识别并加载正确的资源文件,首先必须把 .resources 转换为 Assembly 并根据严格的命名方式命名(参考第四部分:资源文件的命名和部署),并部署到正确的目录下,然后 CLR 就可以根据不同的 Culture 来加载正确的资源。
 
第三部分 在程序中使用资源文件
WorldApp.cs 是一个 World-Ready 的程序,它的逻辑界面和资源界面是分开的,可以实现逻辑界面只 Bulid 一次,运行时根据当前的 Culture 调用相应的 Satellite Assembly (卫星资源程序集)来实现本地化。添加一个新的 Culture 资源不需要重新 Build 源程序,只需要把相应的资源程序集部署到合适的目录就可以了。
下面说明 WorldApp 的实现方式:
程序主界面:
程序在启动的时候会根据当前的 CurrentUICulutre 去加载相应的资源文件。
读取资源文件的代码为:
       private void SetCulture( CultureInfo ci )
       {
              // Change current UI culture
              Thread.CurrentThread.CurrentUICulture = ci;
              // Load culture resources.
              String AssemblyPath = Application.StartupPath + "//Culture.dll";
              Assembly asm = Assembly.LoadFrom(AssemblyPath);
// ResoruceManager constructor will load different resources acording to the
// CurrentUICulture. which means, if CurrentUICulutre is "en-US", rm will load
// "Culture.en-US.resources" automaticly.
// When loaded, give the resource name only.
              ResourceManager rm = new ResourceManager("Culture", asm);             
              // Set title, culture info and logo.
              this.lblTitle.Text = rm.GetString("Title");
              this.lblCulture.Text = rm.GetString("Culture");
              this.lblLogo.Text = rm.GetString("LogoTitle");
              this.imgLogo.Image=(Bitmap)rm.GetObject("Logo");
}
如果当前的 UICulture 改变,可以通过显式调用 SetCulture ( CultureInfo ci ) 来加载相应的 Culture 资源。
现在如果我们有了一个新的 Culture 资源版本,我们只需要把它部署在对应的 Culture 目录下, WorldApp.exe 就可以自动加载, WorldApp.exe 程序本身并不用做任何更改(不需要编译)。
你可以通过上面制作的小工具 ResoruceGenerator 来生成对应不同 Culture 的资源,然后把生成的 Assembly 正确部署就可以了。 WorldApp 就又有了一个新的 Culture 版本。哈 !!
 
第四部分 资源文件的命名和部署
这部分说明资源文件的部署方式和 CLR 是如何识别并加载不同的 Culture 资源的。
·         资源文件的命名方式
假设我们的应用程序名为 WorldApp.exe ,默认的资源文件为 culture.resources ,根据这个资源文件生成的 Assembly culutre.dll (这个是默认版本的资源文件)。然后我们有了一个 en-US Culture 版本的资源文件,则 en-US 的资源文件得名称必须为 culture.en-US.resources ,根据这个资源文件生成的 en-US 版本的 Assembly 必须命名为 culture.resources.dll 且必须加入 Culture 信息(把一个 .resources 生成一个 Assembly resgen /out:cluture.resources.dll /c:en-US /embedcluture.en-US.resources),生成的Assembly必须放在程序运行目录下的en-US目录下,这样CLR才能自动找到。同样,如果我们有了一个zh-CN版本的资源文件,则资源文件的名称必须为culture.zh-CN.resources,生成的Assembly必须为culture.resources.dll,并放在zh-CN目录下。
重要 :因为生成的 .resources 文件本身并不包含 Culture 信息,它的 Culture 信息就体现在它的文件名上,所以 .resources 的命名必须加入 Cluture 信息(如果不加的话,生成的就是默认版本)。从 .resources 生成 Assembly 时,因为 Assembly 可以指定 Culture 信息(通过 /c:<culture> 来指定),所以 Assembly 的名称中不需要加入 Culture 信息,但是 Assembly 的名字必须是:默认版本名 +<.resources>.dll ,就是: culture.[resources].dll
·         资源文件的部署方式
应用程序正确的部署方式(目录结构)应该是:
<WorldApp>                              (应用程序主目录)
WorldApp.exe                            (主程序)
Culture.dll                          (包含 culture.resources 资源文件)
              <en-US>                     en-US 资源目录)
              Culture.resources.dll   (包含 culture.en-US.resources 资源文件)
               <zh-CN>                     zh -CN 资源目录)
              Culture.resources.dll   (包含 culture.zh-CN.resources 资源文件)
              <new-Culture>           net-Culture 资源目录)
              Culture.resources.dll   (包含 cluture.new-Culture.resources
                     <…>                           
有了上面的部署, App.exe 在运行时,会首先根当前 Thread CurrentUICuluture 到对应的目录去寻找资源文件,比如当前的 CurrentUICulture =”en-US” ,则 en-US 目录下的 Culture.resources.dll Assembly 中的 culture.en-US.resources 会被加载。如果 CLR 遍历整个目录还没有找到对应的资源文件,则默认的资源文件版本就被加载( MSDN 中称为 Hub and Spoke model 方式 详见: ms-help://MS.MSDNQTR.2004APR.1033/cpguide/html/cpconPackagingDeployingResources.htm )。
·         CLR 如何加载资源文件
重要: CLR 在匹配资源文件的时候,不是按文件来匹配的,它是按照 <data> 字段一个 key 一个 key 的去匹配。举个例子:
默认版本的 Culture 资源文件中包含四个 key Title, Culture, LogoTitle,Logo
中文版本的 Culture 资源文件中包含只有三个 Key Title, Culture, Logo 。(没有 LogoTitle
如果当前的 Culture ”zh-CN” ,则 zh -CN 版本的 Title, Culture, Logo 都会被加载,但是因为 zh -CN 版本没有 LogoTitle ,所以 CLR 会自动加载和 zh -CN 文化最匹配的一个资源版本的 LogoTitle 。如果都没有,最后才会去加载默认版本的资源文件。
这样做有一个很大的好处:就是说并不是所有资源都必须要有对应 Culture 的版本,我们可以把共通的资源放在默认版本中,只把和特定 Culture 相关的资源隔离就可以了。
重要:关于 Culture
Culture 信息是由主标记(文化)和次标记(地域)两部分组成的。举个例子:
en-US     (英语 - 美国)
en-GB     (英语 - 英国)
en-AU     (英语 - 澳大利亚)
主标记是 en ,表示 Culture 都是英语文化,次标记(地域)区分了它们分别是哪个地区的英语。
说这个有什么用呢?
因为 CLR 在寻找资源的时候是以一种回退的方式来寻找的,就是说,他会首先去寻找最批的那个资源文件,如果没有,则会搜索文化层次结构,以查找最接近于请求的匹配资源文件,并把生成异常作为最后一种手段。比如 CLR 在寻找 en-US 资源的时候没有找到, CLR 不会立即就去用默认版本匹配,而是会首先搜索文化层次结构,以查找最接近于 en-US 的资源(可能是 en-GB 或别的),如果找到,运行时就使用这个资源,如果还找不到,则会继续搜索下一层,最后才会用默认版本匹配(如果默认版本也没有,则会抛出一个异常)。
参考资料:
l          Applied Microsoft .NET Framework Programming ---- Jeffrey Richter
l          MSND Library
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值