.NET系統(tǒng)學(xué)習(xí)----Globalization & Resources
l 前言
l 了解資源文件
l 創(chuàng)建資源文件
l 在程序中使用資源文件
l 資源文件的命名和部署
l 參考
前言:
在學(xué)習(xí)如何使用.NET資源文件以及如何開(kāi)發(fā)World-Ready程序之前,我們先通過(guò)一個(gè)例子來(lái)看看為什么要使用資源文件,以及使用它的好處。
假設(shè)要在程序中根據(jù)當(dāng)前的Culutre來(lái)設(shè)置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: // 默認(rèn)版本
this.Text=FormTitle_Neutral;
imgLogo.Image = new Bitmap(Application.StartupPath + "/Logo_Neutral.jpg");
break;
}
}
這段代碼有兩個(gè)問(wèn)題:
首先,Logo文件是暴露給用戶的,而且是以普通文件的格式存儲(chǔ)的,這導(dǎo)致其他程序或是用戶很容易修改這些文件;節(jié)省硬盤空間的用戶還可能會(huì)選擇刪除它,這些都可能會(huì)導(dǎo)致應(yīng)用程序出錯(cuò)。確保圖片或任何其他文件和代碼在一起的唯一的安全方式是將它作為資源文件嵌入在程序集中并加載。
其次,這是一個(gè)World-Ready程序,如果需要新加入一個(gè)新的Culture,你可能不得不更改你的源代碼,加入新的case,然后重新編譯來(lái)適應(yīng)新的Culture的需要,這對(duì)一個(gè)World-Ready程序來(lái)說(shuō)是不現(xiàn)實(shí)的。開(kāi)發(fā)World-Ready程序很重要的一點(diǎn)就是要保證程序的邏輯界面和資源界面的隔離。任何時(shí)候加入一個(gè)新的Culture資源,我們都不應(yīng)該重新編譯源程序,相反,我們只需要把新的資源文件準(zhǔn)備好,然后發(fā)布給用戶并部署在合適的目錄下就可以了。應(yīng)用程序應(yīng)該能夠根據(jù)不同的Culture來(lái)自動(dòng)尋找合適的資源。
本文的目的就是通過(guò)實(shí)例來(lái)幫助讀者了解什么是Resources,以及如何使用Resources來(lái)消除上面所提到的兩個(gè)問(wèn)題。
全文分為四部分:
第一部分是一些和資源相關(guān)的概念。
第二部分是一個(gè)實(shí)例程序(ResourceGenerator),用來(lái)說(shuō)明如何創(chuàng)建資源文件。
第三部分是另外一個(gè)實(shí)例程序(WorldAPP),用來(lái)說(shuō)明如何在程序中使用資源文件
第四部分是關(guān)于資源文件的命名和部署。分別介紹.NET中資源文件的命名方式和如何在World-Ready程序中配置資源文件。
第一部分 概念
先來(lái)了解一些概念:
1. 什么是資源文件
顧名思義,資源文件當(dāng)然包含的全是資源。不過(guò),什么是資源?這里所謂的資源就是程序中可利用的任何數(shù)據(jù),譬如:字符串、圖片或任何二進(jìn)制格式的數(shù)據(jù)。一個(gè)資源文件可以有多種語(yǔ)言文化版本,比如,一個(gè)Culture.resources 文件可以有英語(yǔ)版、簡(jiǎn)體中文版日文版等。ResourceManager可以自動(dòng)根據(jù)Culture和資源文件名來(lái)確認(rèn)調(diào)用哪個(gè)版本。只不過(guò)不同的資源版本需要在文件名中加入語(yǔ)言文化信息(.resource文件有一套嚴(yán)格的命名規(guī)范,參考第四部分:資源文件的命名和部署)。
2. 資源文件的類型
System.Resources名稱空間支持三種類型的資源:
.txt文件,只能有字符串資源。因?yàn)椴荒鼙磺度氲紸ssembly中,所以很容易暴露,被其他程序或用戶修改。最大缺點(diǎn)是僅支持字符串資源,不推薦使用。
.resx文件,由XML組成,可以加入任何資源,包括二進(jìn)制格式的。同樣不能被嵌入到Assembly中。在System.Resources 名稱空間中有專用讀寫的類。VS.NET中創(chuàng)建的這種文件也是將其轉(zhuǎn)成.resources 文件然后根據(jù)設(shè)置將其嵌入到Assembly中。
.resources文件,PE格式,可以加入任何資源。是唯一可以被嵌入到Assembly的文件,在System.Resources名稱空間中有專用讀寫的類(ResourceManager)。
3. 調(diào)用資源文件的幾種方法
ResourceManager可以根據(jù)不同的UICulture設(shè)置返回不同的本地資源,不同Culture的資源文件有一套嚴(yán)格的命名規(guī)則,只有按照這個(gè)規(guī)則命名,CRL才可以根據(jù)Culture找到這個(gè)本地資源。PS:因?yàn)檫@個(gè)很重要,所以才一再出現(xiàn)J。參考第四部分:資源文件的命名和部署)
.txt 文件:
不可以直接調(diào)用,得先將其轉(zhuǎn)換成 .resources 文件才能使用。
.resx 文件:
可以用ResXResourceReader來(lái)讀取,但是這種方法不直觀也不安全,不推薦直接調(diào)用.resx文件。正確的方法是將其轉(zhuǎn)換成.resources文件,然后用ResourceManager讀取。注意,如果是在VS.NET中添加的.resx文件,那么它們自動(dòng)被設(shè)為 Embedded Resource,然后被轉(zhuǎn)成.resources文件后嵌入到Assembly中。
.resources 文件:
分成兩種情況:
· 被嵌入或編譯成衛(wèi)星程序集(Satellite Assembly):
用ResourceManager的各種constructor來(lái)獲得在Assembly中的資源。
· 單獨(dú)文件,沒(méi)有被編譯或嵌入到Assembly中:
可以用ResourceManager.CreateFileBasedResourceManager來(lái)獲得資源集(ResourceSet),就是所有的資源。
特殊情況:
還有一種特殊情況,那就是當(dāng)你直接嵌入一資源時(shí),也就是說(shuō),不通過(guò)一個(gè)資源文件(.resources)而直接將一資源(Object)嵌入到 Assembly 中。這可以通過(guò)AL.exe(Assembly Linker)的參數(shù)/embed:<object>把資源嵌入在Assembly中。在這種情況下ResourceManager就沒(méi)有用了,因?yàn)樗荒塬@取.resources資源文件(在或不在Assembly中)。
調(diào)用這類直接嵌入在Assembly中的資源,我們就需要利用Reflection的一些特性來(lái)完成。在System.Reflection.Assembly類中有一些相關(guān)函數(shù)可以幫助我們拿到這些資源。通過(guò)Assembly.GetManifestResourceNames可以拿到所有的資源的名字,然后我們就可以通過(guò)Assembly.GetManifestResourceStream(<object_name>)這個(gè)函數(shù)拿到對(duì)應(yīng)的資源并以stream的方式返回,然后我們可以將這個(gè)stream轉(zhuǎn)成在.NET中可用的對(duì)象。比如,如果嵌入資源是一圖片,那么我們可以利用New Bitmap(Stream)的constructor獲得這個(gè)圖片資源的Bitmap對(duì)象。
第二部分 創(chuàng)建資源文件
創(chuàng)建資源文件有兩種方式,一種是使用.NET SDK自帶的resgen工具來(lái)創(chuàng)建,另外一種是自己寫code來(lái)創(chuàng)建。分別來(lái)介紹:
1. Resgen:
這個(gè)工具是.NET自帶的,它可以把.txt,.resX,轉(zhuǎn)換為.resources文件。.resources文件是以一種以鍵-值方式對(duì)應(yīng)存儲(chǔ)的XML格式文件,每一個(gè)鍵<data>對(duì)應(yīng)一個(gè)值<value>,這個(gè)<value>可以是任何的二進(jìn)制格式。如果是格式為(鍵=值)對(duì)應(yīng)得.txt文件,resgen會(huì)自動(dòng)生成鍵-值對(duì)應(yīng)的XML文件。但是resgen有一個(gè)局限性,它不能直接嵌入其他格式的文件,比如你就不能把.bmp以鍵-值得方式對(duì)應(yīng)起來(lái),因?yàn)槟闶紫炔荒芎苋菀椎冒?bmp以(鍵=值)對(duì)應(yīng)的格式儲(chǔ)存在.txt文件中。所以resgen主要是針對(duì)txt文件使用。
一個(gè)例子:company1.txt文件內(nèi)容為:
Title = Company1
Address = Company1 Address
Phone = 12345678
----------------------------------------------------------------
Resgen company.txt <outputfilename>.resources
如果不指定<outputfilename>,默認(rèn)會(huì)生成company1.resources。
然后就可以通過(guò)ResourceManager來(lái)使用了。
還可以再進(jìn)一步,通過(guò)AL.exe把resources文件變?yōu)橐粋(gè)assembly(使用assembly有很多好處(比如可以加入版本信息和Culture信息等)詳見(jiàn)(.NET系統(tǒng)學(xué)習(xí)----Assembly)。
Al /out:company1.dll /embed:company1.resources
通過(guò)設(shè)置ResourcesManager的不同的constructor就可以訪問(wèn)Assembly中包含的.resources文件(下面的例子會(huì)講到)。
2. 通過(guò)編程使用IResourcsWrite來(lái)生成資源文件
上面的方法的一個(gè)最大的缺點(diǎn)是不能很方便的嵌入其他格式的資源,因?yàn)榘哑渌袷降馁Y源變?yōu)殒I-值對(duì)應(yīng)得txt文件并不是一件很容易的事。所以我們介紹另一種方法,通過(guò)編程,使用.NET提供的IResourcesWrite類來(lái)實(shí)現(xiàn)把任何資源嵌入到resources文件中。
ResourceGenerator就是用這種方式實(shí)現(xiàn)的。
程序的主界面:
用到的主要方法就是:
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;
}
}
根據(jù)資源的類型,如果不是string類型的,我們就把它分實(shí)例化為相應(yīng)的stream,然后加入到resoruces中即可(string類型可以直接加入)。生成的就是.NET可以直接使用的.resources文件。但是這樣生成的資源CLR并不能根據(jù)不同的Culture自動(dòng)識(shí)別。要想CRL自動(dòng)識(shí)別并加載正確的資源文件,首先必須把.resources轉(zhuǎn)換為Assembly,并根據(jù)嚴(yán)格的命名方式命名(參考第四部分:資源文件的命名和部署),并部署到正確的目錄下,然后CLR就可以根據(jù)不同的Culture來(lái)加載正確的資源。
第三部分 在程序中使用資源文件
WorldApp.cs是一個(gè)World-Ready的程序,它的邏輯界面和資源界面是分開(kāi)的,可以實(shí)現(xiàn)邏輯界面只Bulid一次,運(yùn)行時(shí)根據(jù)當(dāng)前的Culture調(diào)用相應(yīng)的Satellite Assembly(衛(wèi)星資源程序集)來(lái)實(shí)現(xiàn)本地化。添加一個(gè)新的Culture資源不需要重新Build源程序,只需要把相應(yīng)的資源程序集部署到合適的目錄就可以了。
下面說(shuō)明WorldApp的實(shí)現(xiàn)方式:
程序主界面:
程序在啟動(dòng)的時(shí)候會(huì)根據(jù)當(dāng)前的CurrentUICulutre去加載相應(yīng)的資源文件。
讀取資源文件的代碼為:
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");
}
如果當(dāng)前的UICulture改變,可以通過(guò)顯式調(diào)用SetCulture( CultureInfo ci )來(lái)加載相應(yīng)的Culture資源。
現(xiàn)在如果我們有了一個(gè)新的Culture資源版本,我們只需要把它部署在對(duì)應(yīng)的Culture目錄下,WorldApp.exe就可以自動(dòng)加載,WorldApp.exe程序本身并不用做任何更改(不需要編譯)。
你可以通過(guò)上面制作的小工具ResoruceGenerator來(lái)生成對(duì)應(yīng)不同Culture的資源,然后把生成的Assembly正確部署就可以了。WorldApp就又有了一個(gè)新的Culture版本。哈!!
第四部分 資源文件的命名和部署
這部分說(shuō)明資源文件的部署方式和CLR是如何識(shí)別并加載不同的Culture資源的。
· 資源文件的命名方式
假設(shè)我們的應(yīng)用程序名為WorldApp.exe,默認(rèn)的資源文件為culture.resources,根據(jù)這個(gè)資源文件生成的Assembly為culutre.dll(這個(gè)是默認(rèn)版本的資源文件)。然后我們有了一個(gè)en-US Culture版本的資源文件,則en-US的資源文件得名稱必須為culture.en-US.resources,根據(jù)這個(gè)資源文件生成的en-US版本的Assembly必須命名為culture.resources.dll且必須加入Culture信息(把一個(gè).resources生成一個(gè)Assembly:resgen /out:cluture.resources.dll /c:en-US /embedcluture.en-US.resources),生成的Assembly必須放在程序運(yùn)行目錄下的en-US目錄下,這樣CLR才能自動(dòng)找到。同樣,如果我們有了一個(gè)zh-CN版本的資源文件,則資源文件的名稱必須為culture.zh-CN.resources,生成的Assembly必須為culture.resources.dll,并放在zh-CN目錄下。
重要:因?yàn)樯傻?resources文件本身并不包含Culture信息,它的Culture信息就體現(xiàn)在它的文件名上,所以.resources的命名必須加入Cluture信息(如果不加的話,生成的就是默認(rèn)版本)。從.resources生成Assembly時(shí),因?yàn)锳ssembly可以指定Culture信息(通過(guò)/c:<culture>來(lái)指定),所以Assembly的名稱中不需要加入Culture信息,但是Assembly的名字必須是:默認(rèn)版本名+<.resources>.dll,就是:culture.[resources].dll。
· 資源文件的部署方式
應(yīng)用程序正確的部署方式(目錄結(jié)構(gòu))應(yīng)該是:
<WorldApp> (應(yīng)用程序主目錄)
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在運(yùn)行時(shí),會(huì)首先根當(dāng)前Thread的CurrentUICuluture到對(duì)應(yīng)的目錄去尋找資源文件,比如當(dāng)前的CurrentUICulture=”en-US”,則en-US目錄下的Culture.resources.dll Assembly中的culture.en-US.resources會(huì)被加載。如果CLR遍歷整個(gè)目錄還沒(méi)有找到對(duì)應(yīng)的資源文件,則默認(rèn)的資源文件版本就被加載(MSDN中稱為Hub and Spoke model方式 詳見(jiàn):ms-help://MS.MSDNQTR.2004APR.1033/cpguide/html/cpconPackagingDeployingResources.htm)。
· CLR如何加載資源文件
重要:CLR在匹配資源文件的時(shí)候,不是按文件來(lái)匹配的,它是按照<data>字段一個(gè)key一個(gè)key的去匹配。舉個(gè)例子:
默認(rèn)版本的Culture資源文件中包含四個(gè)key:Title, Culture, LogoTitle,Logo。
中文版本的Culture資源文件中包含只有三個(gè)Key:Title, Culture, Logo。(沒(méi)有LogoTitle)
如果當(dāng)前的Culture是”zh-CN”,則zh-CN版本的Title, Culture, Logo都會(huì)被加載,但是因?yàn)閦h-CN版本沒(méi)有LogoTitle,所以CLR會(huì)自動(dòng)加載和zh-CN文化最匹配的一個(gè)資源版本的LogoTitle。如果都沒(méi)有,最后才會(huì)去加載默認(rèn)版本的資源文件。
這樣做有一個(gè)很大的好處:就是說(shuō)并不是所有資源都必須要有對(duì)應(yīng)Culture的版本,我們可以把共通的資源放在默認(rèn)版本中,只把和特定Culture相關(guān)的資源隔離就可以了。
重要:關(guān)于Culture:
Culture信息是由主標(biāo)記(文化)和次標(biāo)記(地域)兩部分組成的。舉個(gè)例子:
en-US (英語(yǔ)-美國(guó))
en-GB (英語(yǔ)-英國(guó))
en-AU (英語(yǔ)-澳大利亞)
主標(biāo)記是en,表示Culture都是英語(yǔ)文化,次標(biāo)記(地域)區(qū)分了它們分別是哪個(gè)地區(qū)的英語(yǔ)。
說(shuō)這個(gè)有什么用呢?
因?yàn)镃LR在尋找資源的時(shí)候是以一種回退的方式來(lái)尋找的,就是說(shuō),他會(huì)首先去尋找最批的那個(gè)資源文件,如果沒(méi)有,則會(huì)搜索文化層次結(jié)構(gòu),以查找最接近于請(qǐng)求的匹配資源文件,并把生成異常作為最后一種手段。比如CLR在尋找en-US資源的時(shí)候沒(méi)有找到,CLR不會(huì)立即就去用默認(rèn)版本匹配,而是會(huì)首先搜索文化層次結(jié)構(gòu),以查找最接近于en-US的資源(可能是en-GB或別的),如果找到,運(yùn)行時(shí)就使用這個(gè)資源,如果還找不到,則會(huì)繼續(xù)搜索下一層,最后才會(huì)用默認(rèn)版本匹配(如果默認(rèn)版本也沒(méi)有,則會(huì)拋出一個(gè)異常)。
參考資料:
l Applied Microsoft .NET Framework Programming ---- Jeffrey Richter
l MSND Library
|