在学习如何使用.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
-----------------------------------------------