.NET的资源并不限于.resx文件
为了构建一个轻量级的资源管理框架以满足简单的本地化(Localization)的需求,我试图直接对现有的Resource
编程模型进行扩展。虽然最终没能满足我们的需求,但是这两天也算对.NET如何进行资源的存取进行了深入的学习。在本篇文章中,我会通过自定义ResourceManager
让资源的存储形式不仅仅局限于.ResX
文件,你可以根据需要实现任意的存储方式,比如结构化的XML、数据库表,甚至是通过远程访问获取资源。
一、从添加资源文件(.resx文件)说起
说起资源,你首先想到的肯定是通过VS添加的扩展名为.resx的资源文件。在这个资源文件中,你不但可以添加单纯的文本资源条目,也可以添加图片、图标、文本文件以及其它类型文件。 不但如此,当你在.resx文件中定义任意类型资源条目的时候,默认定义的代码生成器会为你生成对应的托管代码,让你可以采用强类型编程
的方式获取某个条目。
比如说,如果你在一个名称为Resources.resx
的资源文件中定义了两个字符串资源条目,默认的代码生成器或为你生成如下的代码。
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Demo.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
internal static string Greeting4Chris {
get {
return ResourceManager.GetString("Greeting4Chris", resourceCulture);
}
}
internal static string Greeting4NewYear {
get {
return ResourceManager.GetString("Greeting4NewYear", resourceCulture);
}
}
}
那么你就可以通过生成的这个Resources
类(和资源文件同名)的对应的静态只读属性获取对应的值。
1: var greeting4Chris = Resources.Greeting4Chris;
2: var greeting4NewYear = Resources.Greeting4NewYear;
从通过代码生成器生成出来的Resources代码,我们可以看出Greeting4Chris
和Greeting4NewYear
这两个属性的实现是直接通过一个类型为ResourceManager
对象的GetString
方法获取的。
那么ResourceManager
在背后是通过怎样的机制进行资源文件的读取的呢?
二、ResourceManager、ResourceSet、ResourceReader与ResourceWriter
ResourceManager
应该是.NET资源编程模型的核心,也可以说是整个资源编程模型的外观类(Facade Class),它提供资源条目提取的API。ResourceManager
定义在System.Resources
命名空间下,我们不防先来看看ResourceManager
的定义。
1: public class ResourceManager
2: {
3: public ResourceManager(Type resourceSource);
4: public ResourceManager(string baseName, Assembly assembly);
5: public ResourceManager(string baseName, Assembly assembly, Type usingResourceSet);
6:
7: public virtual object GetObject(string name);
8: public virtual object GetObject(string name, CultureInfo culture);
9: public virtual string GetString(string name);
10: public virtual string GetString(string name, CultureInfo culture);
11:
12: public virtual ResourceSet GetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents);
13: protected virtual ResourceSet InternalGetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents);
14: //Others...
15: }
虽然我们将相应的条目定义在.resx
资源文件中(该文件实际上就是一个XML),但是该文件在编译的时候会变成.resources
文件(二进制文件)被内嵌到程序集中,所以ResourceManager
操作的实际上是内嵌在某个程序集中的.resources
文件,这也是为什么在构造函数中需要指定Assembly
的原因。构造函数的另一个参数BaseName
表示不包括扩展名和Culture Code的.resources
文件名,比如说资源文件名为Foo.en-US.resoures对应的BaseName
就是Foo。
对于字符串类型的资源条目,通过GetString
方法获取,其他类型的文件则通过GetObject
获取。而ResourceManager
的核心实际上是一个叫做GetResourceSet
的方法,方法将所有的资源条目读取出来保存到一个类型为ResourceSet
的对象中(该方法最终会调用受保护的方法InternalGetResourceSet
)。而ResourceSet
在整个资源体系中是一个重要的对象,它充当ResourceManager
和物理存储的中介,下面是ResourceSet
的定义。
1: public class ResourceSet : IDisposable, IEnumerable
2: {
3: public ResourceSet(Stream stream);
4: public ResourceSet(IResourceReader reader);
5: public ResourceSet(string fileName);
6:
7: public virtual Type GetDefaultReader();
8: public virtual Type GetDefaultWriter();
9:
10: public virtual object GetObject(string name);
11: public virtual object GetObject(string name, bool ignoreCase);
12: public virtual string GetString(string name);
13: public virtual string GetString(string name, bool ignoreCase);
14:
15: IEnumerator IEnumerable.GetEnumerator();
16: public virtual IDictionaryEnumerator GetEnumerator();
17: public void Dispose();
18: //Others...
19: }
以持久化文件方式存储的资源最终需要加载到ResourceSet
对象中,肯定需要IO操作,所以ResourceSet构造函数中参数分别是Stream、文件名和一个IResourceReader的对象。GetObject和GetString方法,不用多说你也知道是用于某个命名资源条目。由于资源条目实际上就是简单Key-Value
对,所以ResourceSet仅仅需要为ResourceManager提供针对每个资源条目的迭代功能,所以ResourceSet的核心应该是返回类型为IDictionaryEnumerator
虚方法GetEmunerator
方法。
而ResourceSet得两个GetDefaultReader
和GetDefaultWriter
方法则涉及到另外两个重要的对象ResourceReader
和ResourceWriter
,故名思义它们分别负责资源的读取和写入。在System.Resources
命名空间下,它们各自具有相应的接口:IResourceReader
和IResourceWriter
,定义如下:
1: public interface IResourceReader : IEnumerable, IDisposable
2: {
3: void Close();
4: IDictionaryEnumerator GetEnumerator();
5: }
1: public interface IResourceWriter : IDisposable
2: {
3: void AddResource(string name, object value);
4: void AddResource(string name, string value);
5: void AddResource(string name, byte[] value);
6: void Close();
7: void Generate();
8: }
到这里我们介绍了资源体系下四个重要的对象ResourceManager、ResourceSet、ResourceReader与ResourceWriter
,至于他们是如何相互协作以实现对资源的读取和写入的,相信下面会给你答案。
三、自定义BinaryResourceManager管理单独二进制资源文件(.resources文件)
我们说过上述的ResourceManager
仅仅提供对内嵌于某个程序集中的.resources
文件的操作,如果我们直接将资源定义在一个独立的.resources
文件、.resx
文件甚至是自定义结构的XML文件呢?
在这种情况下,我们可通过自定义ResourceManager
的方式来解决这个问题。为此我定义了如下一个抽象类FileResourceManager
作为基于文件的ResourceManager
的基类。
1: public abstract class FileResourceManager: ResourceManager
2: {
3: private string baseName;
4: public string Directory { get; private set; }
5: public string Extension { get; private set; }
6:
7: public override string BaseName
8: {
9: get{ return baseName;}
10: }
11:
12: public FileResourceManager(string directory, string baseName, string extension)
13: {
14: this.Directory = directory;
15: this.baseName = baseName;
16: this.Extension = extension;
17: }
18:
19: protected override string GetResourceFileName(CultureInfo culture)
20: {
21: string fileName = string.Format("{0}.{1}.{2}", this.baseName, culture, this.Extension.TrimStart('.'));
22: string path = Path.Combine(this.Directory, fileName);
23: if (File.Exists(path))
24: {
25: return path;
26: }
27: return Path.Combine(this.Directory, string.Format("{0}.{1}", baseName, this.Extension.TrimStart('.')));
28: }
29: }
属性Directory
、BaseName
和Extension
分别表示资源文件所在的目录、不包括Culture Code和扩展名的文件名以及扩展名。FileResourceManager
集成自ResourceManager类,并重写了GetResourceFileName
方法用于获取基于某种Culture的资源文件路径。
现在我们定义如下一个BinaryResourceManager
用于操作单独存在的.resources
文件。我自需要重写InternalGetResourceSet
,返回的是基于.resources
文件名创建的ResourceSet
对象。
1: public class BinaryResourceManager : FileResourceManager
2: {
3: public BinaryResourceManager(string directory, string baseName)
4: : base(directory, baseName, ".resources")
5: {}
6:
7: protected override ResourceSet InternalGetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents)
8: {
9: return new ResourceSet(this.GetResourceFileName(culture));
10: }
11: }
现在我们来看看如何使用我们创建的BinaryResourceManager
。由于它直接操作ResourceSet
来维护资源条目列表,当我们通过指定资源文件名创建ResourceSet
的时候,系统会创建一个类型为System.Resources.ResourceReader
的对象来读取二进制的.resources
文件并将内容写入ResourceSet
对象。而.resources文件具有默认的ResourceWrtier
,即System.Resources.ResourceWriter
。
为了让我们的Demo能够适用于后续的自定义ResourceManager,我写了一些辅助方法,首先是预先创建资源文件的方法PrepareFiles
方法。通过传入的BaseName和扩展名,我会创建三个资源文件:<BaseName>.<Extension>、<BaseName>.en-US.<Extension>
和<BaseName>.zh-CN.<Extension>
,第一个代码语言文化中性,后者则基于美国英语和简体中文。
1: static void PrepareFiles(string baseName, string extension)
2: {
3: var fileNames = new string[]{
4: baseName + "." + extension,
5: baseName + ".en-US." + extension,
6: baseName + ".zh-CN." + extension };
7:
8: Array.ForEach(fileNames, fileName =>{
9: if (!File.Exists(fileName)) File.Create(fileName).Dispose();});
10: }
然后是用于资源写入操作的AddResource
方法,该方法两个参数createWriter
和culture
表示创建IResourceWriter
的委托和对应的语言文化。
1: static void AddResource(Func<IResourceWriter> createWriter, CultureInfo culture)
2: {
3: using (IResourceWriter resourceWriter = createWriter())
4: {
5: if (culture.Name.StartsWith("en"))
6: {
7: resourceWriter.AddResource("Greeting4Chris", "Merry Christmas!");
8: resourceWriter.AddResource("Greeting4NewYear", "Happy Chinese New Year!");
9: }
10: if (culture.Name.StartsWith("zh"))
11: {
12: resourceWriter.AddResource("Greeting4Chris", "圣诞快乐!");
13: resourceWriter.AddResource("Greeting4NewYear", "新年快乐!");
14: }
15: resourceWriter.Generate();
16: }
17: }
最后是用于资源读取和输出的DisplayResource
方法,该方法通过指定的ResourceManager
读取当前需要文化资源并输出。而我指定了三种不同的语言文化环境:en-US、zh-CN和ja-JP。
1: static void DisplayResource(ResourceManager resourceManager)
2: {
3: Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");
4: Console.WriteLine(CultureInfo.CurrentUICulture.EnglishName);
5: Console.WriteLine("\t"+resourceManager.GetString("Greeting4Chris"));
6: Console.WriteLine("\t" + resourceManager.GetString("Greeting4NewYear") + "\n");
7:
8: Thread.CurrentThread.CurrentUICulture = new CultureInfo("zh-CN");
9: Console.WriteLine(CultureInfo.CurrentUICulture.EnglishName);
10: Console.WriteLine("\t" + resourceManager.GetString("Greeting4Chris"));
11: Console.WriteLine("\t" + resourceManager.GetString("Greeting4NewYear") + "\n");
12:
13: Thread.CurrentThread.CurrentUICulture = new CultureInfo("ja-JP");
14: Console.WriteLine(CultureInfo.CurrentUICulture.EnglishName);
15: Console.WriteLine("\t" + resourceManager.GetString("Greeting4Chris"));
16: Console.WriteLine("\t" + resourceManager.GetString("Greeting4NewYear") + "\n");
17: }
最后我们的程序是这样的:
1: PrepareFiles("GreetingMessages", "resources");
2:
3: AddResource(() => new ResourceWriter("GreetingMessages.resources"), new CultureInfo("en-US"));
4: AddResource(() => new ResourceWriter("GreetingMessages.en-US.resources"), new CultureInfo("en-US"));
5: AddResource(() => new ResourceWriter("GreetingMessages.zh-CN.resources"), new CultureInfo("zh-CN"));
6:
7: DisplayResource(new BinaryResourceManager("", "GreetingMessages"));
最终的输出为:
1: English (United States)
2: Merry Christmas!
3: Happy Chinese New Year!
4:
5: Chinese (Simplified, PRC)
6: 圣诞快乐!
7: 新年快乐!
8:
9: Japanese (Japan)
10: Merry Christmas!
11: Happy Chinese New Year!
在《下篇》中,我将介绍如何通过自定义ResourceManager操作定义在.resx、XML和数据库表的资源。
.NET的资源并不限于.resx文件的更多相关文章
- .NET的资源并不限于.resx文件(二)
ResourceManager在默认的情况下只能提供对内嵌于程序集的.resources资源文件的存取. 为了实现对独立二进制.resources资源文件的支持,我们自定义了BinaryResoruc ...
- resx文件在X64位编译,提示“未能加载文件或程序集”的问题?
原文:resx文件在X64位编译,提示"未能加载文件或程序集"的问题? resx文件在X64位编译,提示"未能加载文件或程序集"的问题? 解答: 错误现象如下 ...
- .resources文件转.resx 文件
最近在进行.net winform应用程序的反向工程,资源文件反向出来后都是.resources文件,工程编译和运行都没有问题,但.resources文件为二级制文件,无法在Visual Studio ...
- C#窗体的resx文件
这些图片在项目文件中没找到,原来都存在了resx文件中. 属性界面的Image.BackgroundImage属性手动选择的图片会自动存储到resx文件中,之后这些图片源文件就可以删除了.resx中的 ...
- C#的Winform多语言实现(resx文件)
1. 简体中文 2. 繁体中文 3. 英文 下面子丰介绍一下实现的过程: 1. 为每个窗口创建相应语言的resx文件.子丰以英文为例,右键->添加->新建项->资源文件,文件名为窗口 ...
- WPF 的另类资源方式 Resources.resx
类似Winform的搞法,可以把资源放到Resources.resx中. 1.字符串 打开这个编辑器后,输入Name和Value就可以了. CS代码里面,很简单的调用: var title = W ...
- C#Winform中resx文件无效 找不到路径
问题由来 笔者因为更改了添加的图片的路径,再把路径改成图片所在的路径还是报resx文件无效,未能找到路径 问题原因 其实这个问题是因为对对象的引用修改了,但是resx文件中的应用还是没有修改.因为re ...
- Java中用ClassLoader载入各种资源(类、文件、web资源)的方法
lassLoader主要对类的请求提供服务,当JVM需要某类时,它根据名称向ClassLoader要求这个类,然后由ClassLoader返回这个类的class对象. ClassLoader负责载入系 ...
- 【转载】IIS7.5(经典模式)访问静态资源(.css和.js文件)提示:未能执行 URL
IIS7.5(经典模式)静态资源(.css和.js文件)提示:未能执行 URL “/”应用程序中的服务器错误. 未能执行 URL. 说明: 执行当前 Web 请求期间,出现未处理的异常.请检查堆栈跟踪 ...
随机推荐
- Win10下JDK环境搭建的两种方法
jdk1.8--64位官网下载的百度网盘 https://pan.baidu.com/s/1A7jYfupwMWZawb5z_RSdJg 提取码: 92eu 第一种方法(建议) 变量名: ...
- Parentheses Balance (括号平衡)---栈
题目链接:https://vjudge.net/contest/171027#problem/E Yes的输出条件: 1. 空字符串 2.形如()[]; 3.形如([])或者[()] 分析: 1.设置 ...
- Soulwail
XMLHttpRequest 属性解读 首先在 Chrome console 下创建一个 XMLHttpRequest 实例对象 xhr.如下所示: inherit 试运行一下代码. var xhr ...
- first-child和first-of-type
我想实现的效果:将第一个article字体颜色设置为红色 123456 <div? <h1>logo</h1> <article>article1</a ...
- 初学qt——数据库连接
连接数据库我们需要有相应的dll文件,不同的数据库用不同的文件,对应的dll这里就不提供了,网上一搜一堆,就只说下这些文件的存放位置吧. 找到对应的dll文件后打开自己安装的qt的文件目录,将dll文 ...
- jstack的使用
一.概述 有些时候我们需要查看下jvm中的线程执行情况,比如,发现服务器的CPU的负载突然增高了.出现了死锁.死循环等,我们该如何分析呢? 由于程序是正常运行的,没有任何的输出,从日志方面也看不出什么 ...
- Keras在MNIST实现LeNet-5模型训练时的错误?
当使用Keras API 训练模型时,训练时报错? UnknownError (see above for traceback): Failed to get convolution algorith ...
- 网络|Trojan 网络代理服务搭建
Trojan 网络代理服务搭建 前言 本文目的在于帮助相同困惑的网友,让使用更加简单. Trojan为Trojan-GFW开源的一款新思路网络代理软件, 前期准备 [x] 服务器:系统CentOS 7 ...
- angular -——组件样式修改不成功
angular组件样式修改不成功! 自己定义的css可以成功 组件的不行 style在模板字符串里 直接没有 class 是显示的 但是样式不生效 加上面 即可,为什么?我也不太清楚.有知道答案的请回 ...
- python虚拟环境安装使用
# 安装 pip install virtualenv pip install virtualenvwrapper-win # win环境下 liunx下不需要-win #创建一个文件夹并cd进去mk ...