.NET Core采用的全新配置系统[6]: 深入了解三种针对文件(JSON、XML与INI)的配置源
物理文件是我们最常用到的原始配置的载体,最佳的配置文件格式主要由三种,它们分别是JSON、XML和INI,对应的配置源类型分别是JsonConfigurationSource、XmlConfigurationSource和IniConfigurationSource。 [ 本文已经同步到《ASP.NET Core框架揭秘》之中]
目录
一、FileConfigurationSource & FileConfigurationProvider
二、JsonConfigurationSource &JsonConfigurationProvider
三、XmlConfiguationSource & XmlConfiguationProvider
四、IniConfigurationSource & IniConfigurationSource
一、FileConfigurationSource & FileConfigurationProvider
上述这三个具体的ConfigurationSource类型具有如下一个相同的基类FileConfigurationSource。
1: public abstract class FileConfigurationSource : IConfigurationSource
2: {
3: public IFileProvider FileProvider { get; set; }
4: public bool Optional { get; set; }
5: public string Path { get; set; }
6: public bool ReloadOnChange { get; set; }
7:
8: public abstract IConfigurationProvider Build(IConfigurationBuilder builder);
9: }
如上面的代码片段所示,FileConfigurationSource是一个抽象类,它利用一个FileProvider对象来提供原始的配置文件,而Path属性自然代表配置文件的路径。Optional属性表示明当前的FileConfigurationSource是否是可选的配置源,其默认值为False。当某个FileConfigurationSource的Optional属性为True的时候,如果指定的配置文件路径不存在,将不会有任何异常被抛出来。由于FileProvider具有监控文件变化的能力,它的ReloadOnChange属性表示如果被监控的配置文件发生改变后是否需要重新加载配置。
由于FileConfigurationSource总是利用FileProvider来读取配置文件的内容,所以当我们创建一个具体的FileConfigurationSource对象的时候都需要采用显式或者隐式的方式指定一个FileProvider对象(关于FileProvider,可以参阅我的“文件系统”博文系列)。我们可以调用扩展方法SetFileProvider将一个默认的FileProvider注册到ConfigurationBuilder对象上,从相面的代码片段可以看出注册的FileProvider被保存到Properties属性表示的字典对象上,对应的Key为“FileProvider”。
1: public static class FileConfigurationExtensions
2: {
3: private static string FileProviderKey = "FileProvider";
4:
5: public static IFileProvider GetFileProvider(this IConfigurationBuilder builder)
6: {
7: object obj2;
8: if (builder.Properties.TryGetValue(FileProviderKey, out obj2))
9: {
10: return (builder.Properties[FileProviderKey] as IFileProvider);
11: }
12: return new PhysicalFileProvider(AppContext.BaseDirectory ?? string.Empty);
13: }
14:
15: public static IConfigurationBuilder SetBasePath(this IConfigurationBuilder builder, string basePath)
16: {
17: return builder.SetFileProvider(new PhysicalFileProvider(basePath));
18: }
19:
20: public static IConfigurationBuilder SetFileProvider(this IConfigurationBuilder builder, IFileProvider fileProvider)
21: {
22: builder.Properties[FileProviderKey] = fileProvider;
23: return builder;
24: }
25: }
通过隐式方式提供的FileProvider通过调用ConfigurationBuilder的另一个扩展方法GetFileProvider方法获取。从上面给出的代码片段我们可以看到,它会优先返回我们注册的FileProvider。如果这样的FileProvdier尚未注册,该方法会返回指向当前应用执行目录的PhysicalFileProvider对象。除了上述这两个方法,ConfigurationBuilder还具有另一个名为SetBasePath的方法,该方法采用指定的路径创建一个PhysicalFileProvider对象并对它进行注册。
虽然JsonConfigurationSource、XmlConfigurationSource和IniConfigurationSource这些针对通过文件类型的ConfigurationSource会提供不同类型的ConfigurationProvider来读取对应的配置文件并将读取的内容转换成一个配置字典,但是这些ConfigurationProvider都派生与如下一个FileConfigurationProvider类型。FileConfigurationSource和FileConfigurationProvider都定义在“Microsoft.Extensions.Configuration
.FileExtensions”这个NuGet包中。
1: public abstract class FileConfigurationProvider : ConfigurationProvider
2: {
3:
4: public FileConfigurationSource Source { get; private set; }
5:
6: public FileConfigurationProvider(FileConfigurationSource source)
7: {
8: this.Source = source;
9: if (this.Source.ReloadOnChange)
10: {
11: ChangeToken.OnChange(
12: () => this.Source.FileProvider.Watch(this.Source.Path),
13: this.Load);
14: }
15: }
16:
17: public override void Load()
18: {
19: IFileInfo fileInfo = this.Source.FileProvider.GetFileInfo(this.Source.Path);
20:
21: if ((fileInfo == null) || !fileInfo.Exists)
22: {
23: if (!this.Source.Optional)
24: {
25: throw new FileNotFoundException();
26: }
27: base.Data = new Dictionary<string, string>(
28: StringComparer.OrdinalIgnoreCase);
29: }
30: else
31: {
32: using (Stream stream = fileInfo.CreateReadStream())
33: {
34: this.Load(stream);
35: }
36: }
37: base.OnReload();
38: }
39:
40: public abstract void Load(Stream stream);
41: }
我们通过如上所示的代码片段以简化的形式模拟了FileConfigurationProvider的实现逻辑。它定义了一个抽象方法Load来完成针对配置文件的读取和配置字典的生成,该参数代表读取文件的输出流。在重写的Load方法中,它直接利用FileProvider得到描述配置文件的FileInfo对象,并调用此FileInfo对象的CreateReadStream方法得到这个Stream对象。
上面的这个代码片段还体现了额外一些细节。首先,如果我们将FileConfigurationSource的ReloadOnChange属性设置为True,意味着我们希望当配置文件发生该表的时候重新加载该文件。FileConfigurationProvider直接利用FileProvider的Watch方法监视配置文件的变换,并将Load方法注册为回调从而到达配置数据同步的目的。其次,如果指定的配置文件不存在,并且FileConfigurationSource的Optional属性被设置为True,FileConfigurationProvider是不能抛出FileNotFoundException异常的。
二、JsonConfigurationSource&JsonConfigurationProvider
JsonConfigurationSource代表针对通过JSON文件定义的配置源,该类型定义在NuGet包“Microsoft.Extensions.Configuration.Json”中。如下面的代码片段所示,在重写的Build方法中,如果FileProvider属性没有被显式赋值,它会调用ConfigurationBuilder的扩展方法GetFileProvider得到一个FileProvdier并对该属性赋值。Build方法最终创建并返回的是一个根据自己创建的JsonConfigurationProvider对象。作为FileConfigurationProvider的继承者,JsonConfigurationProvider利用重写的Load方法读取配置文件的内容并将其转换成配置字典。
1: public class JsonConfigurationSource : FileConfigurationSource
2: {
3: public override IConfigurationProvider Build(IConfigurationBuilder builder)
4: {
5: base.FileProvider = base.FileProvider ?? builder.GetFileProvider();
6: return new JsonConfigurationProvider(this);
7: }
8: }
9:
10: public class JsonConfigurationProvider : FileConfigurationProvider
11: {
12: public JsonConfigurationProvider(JsonConfigurationSource source);
13: public override void Load(Stream stream);
14: }
“Microsoft.Extensions.Configuration.Json”这个NuGet包为我们定义了如下所示的一系列针对IConfigurationBuilder接口的扩展方法AddJsonFile来完成针对JsonConfigurationSource的注册。如果调用第一个AddJsonFile方法重载,我们可以利用指定的Action<JsonConfigurationSource>对象对创建的JsonConfigurationSource进行初始化。至于后续的AddJsonFile方法重载,实际上就是通过相应的参数初始化JsonConfigurationSource的Path、Optional和ReloadOnChange属性罢了。
1: public static class JsonConfigurationExtensions
2: {
3: public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, Action<JsonConfigurationSource> configureSource);
4: public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path);
5: public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path, bool optional);
6: public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path, bool optional,bool reloadOnChange);
7: }
当使用JSON文件来定义配置的时候,我们会发现不论对于何种数据结构(复杂对象、集合、数组和字典),我们都能通过JSON格式以一种简单而自然的方式来定义它们。同样以前面定义的Profile类型为例,我们可以利用如下所示的三个JSON文件分别定义一个完整的Profile对象、一个Profile对象的集合以及一个Key和Value类型分别为字符串和Profile的字典。
Profile类型
1: public class Profile
2: {
3: public Gender Gender { get; set; }
4: public int Age { get; set; }
5: public ContactInfo ContactInfo { get; set; }
6: }
7:
8: public class ContactInfo
9: {
10: public string EmailAddress { get; set; }
11: public string PhoneNo { get; set; }
12: }
13:
14: public enum Gender
15: {
16: Male,
17: Female
18: }
Profile对象:
1: {
2: "profile": {
3: "gender" : "Male",
4: "age" : "18",
5: "contactInfo": {
6: "email" : "foobar@outlook.com",
7: "phoneNo" : "123456789"
8: }
9: }
10: }
Profile集合或者数组
1: {
2: "profiles": [
3: {
4: "gender" : "Male",
5: "age" : "18",
6: "contactInfo": {
7: "email" : "foo@outlook.com",
8: "phoneNo" : "123"
9: }
10: },
11: {
12: "gender" : "Male",
13: "age" : "25",
14: "contactInfo": {
15: "email" : "bar@outlook.com",
16: "phoneNo" : "456"
17: }
18: },
19: {
20: "gender" : "Female",
21: "age" : "40",
22: "contactInfo": {
23: "email" : "baz@outlook.com",
24: "phoneNo" : "789"
25: }
26: }
27: ]
28: }
Profile字典
1: {
2: "profiles": {
3: "foo": {
4: "gender" : "Male",
5: "age" : "18",
6: "contactInfo": {
7: "email" : "foo@outlook.com",
8: "phoneNo" : "123"
9: }
10: },
11: "bar": {
12: "gender" : "Male",
13: "age" : "25",
14: "contactInfo": {
15: "email" : "bar@outlook.com",
16: "phoneNo" : "456"
17: }
18: },
19: "baz": {
20: "gender": "Female",
21: "age" : "40",
22: "contactInfo": {
23: "email" : "baz@outlook.com",
24: "phoneNo" : "789"
25: }
26: }
27: }
28: }
三、XmlConfiguationSource & XmlConfiguationProvider
XML也是一种常用的配置定义形式,它对数据的表达能力甚至强于JSON,基于所有类型的数据结构都可以通过XML表示出来。当我们通过一个XML元素表示一个复杂对象的时候,对象的数据成员定义成当前XML元素的子元素。如果数据成员是一个简单数据类型,我们还可以选择将其定义成当前XML元素的属性(Attribute)。针对一个Profile对象,我们可以采用如下两种不同的形式来定义。
1: <Profile>
2: <Gender>Male</Gender>
3: <Age>18</Age>
4: <ContactInfo>
5: <EmailAddress >foobar@outlook.com</Email>
6: <PhoneNo>123456789</PhoneNo>
7: </ContactInfo>
8: </Profile>
或者
1: <Profile Gender="Male" Age="18">
2: <ContactInfo EmailAddress ="foobar@outlook.com" PhoneNo="123456789"/>
3: </Profile>
虽然XML对数据结构的表达能力总体要强于JSON,但是作为配置模型的数据来源却有自己的局限性,比如它们对集合的表现形式有点不尽如人意。举个简单的例子,对于一个元素类型为Profile的集合,我们可以采用具有如下结构的XML来表现。
1: <Profiles>
2: <Profile Gender="Male" Age="18">
3: <ContactInfo EmailAddress ="foobar@outlook.com" PhoneNo="123"/>
4: </Profile>
5: <Profile Gender="Male" Age="25">
6: <ContactInfo EmailAddress ="bar@outlook.com" PhoneNo="456"/>
7: </Profile>
8: <Profile Gender="Male" Age="40">
9: <ContactInfo EmailAddress ="baz@outlook.com" PhoneNo="789"/>
10: </Profile>
11: </Profiles>
但是这段XML却不能正确地转换成配置字典,原因很简单,因为字典的Key必须是唯一的,这必然要求最终构成配置树的每个节点必须具有不同的路径。上面这段XML很明显不满足这个基本的要求,因为表示一个Profile对象的三个XML元素(<Profile>...</Profile>)是“同质”的,对于由它们表示的三个Profile对象来说,分别表示性别、年龄、电子邮箱地址和电话号码的四个叶子节点的路径是完全一样的,所以根据无法作为配置字典的Key。通过前面针对配置绑定的介绍我们知道,如果需要通过配置字典来表示一个Profile对象的集合,我们需要按照如下的方式为每个集合元素加上相应的索引(“foo”、“bar”和“baz”)。
1: foo:Gender
2: foo:Age
3: foo:ContactInfo:EmailAddress
4: foo:ContactInfo:PhoneNo
5:
6: bar:Gender
7: bar:Age
8: bar:ContactInfo:EmailAddress
9: bar:ContactInfo:PhoneNo
10:
11: baz:Gender
12: baz:Age
13: baz:ContactInfo:EmailAddress
14: baz:ContactInfo:PhoneNo
按照这样的结构,如果我们需要以XML的方式来表示一个Profile对象的集合,就不得不采用如下的结构,即采用索引来命名集合元素对应的XML元素。当时这样的定义方式从语义的角度来讲是不合理的,因为同一个集合的所有元素应该是“同质”的,同质的XML元素采用不同的名称有点说不过去。根据配置绑定的规则,这样的结构同样可以表示一个由三个元素组成的Dictionary<string, Profile>对象,Key分别是“Foo”、“Bar”和“Baz”。如果用这样的XML来表示一个字典对象,语义上就完全没有问题了。
1: <Profiles>
2: <Foo Gender="Male" Age="18">
3: <ContactInfo EmailAddress ="foobar@outlook.com" PhoneNo="123"/>
4: </Foo>
5: <Bar Gender="Male" Age="25">
6: <ContactInfo EmailAddress ="foobar@outlook.com" PhoneNo="123"/>
7: </Bar>
8: <Baz Gender="Male" Age="18">
9: <ContactInfo EmailAddress ="baz@outlook.com" PhoneNo="789"/>
10: </Baz>
11: </Profiles>
针对XML文件的配置源类型为XmlConfigurationSource,该类型定义在“Microsoft.Extensions.Configuration.Xml”这个NuGet包中。如下面的代码片段所示,XmlConfigurationSource通过重写的Build方法创建了一个XmlConfigurationProvider对象。作为抽象类型FileConfigurationProvider的继承者,XmlConfigurationProvider通过重写的Load方法完成了针对XML文件的读取和配置字典的初始化。
1: public class XmlConfigurationSource : FileConfigurationSource
2: {
3: public override IConfigurationProvider Build(IConfigurationBuilder builder)
4: {
5: base.FileProvider = base.FileProvider ?? builder.GetFileProvider();
6: return new XmlConfigurationProvider(this);
7: }
8: }
9:
10: public class XmlConfigurationProvider : FileConfigurationProvider
11: {
12: public XmlConfigurationProvider(XmlConfigurationSource source);
13: public override void Load(Stream stream);
14: }
JsonConfigurationSource的注册可以通过调用针对IConfigurationBuilder的扩展方法AddJsonFile来完成。与之类似,“Microsoft.Extensions.Configuration.Xml”这个NuGet包中同样提供了如下一系列名为AddXmlFile的扩展方法重载来根据指定的XML文件创建相应的XmlConfigurationSource并注册到指定的ConfigurationBuilder对象上。AddXmlFile和AddJsonFile方法具有完全一样的声明。
1: public static class XmlConfigurationExtensions
2: {
3: public static IConfigurationBuilder AddXmlFile(this IConfigurationBuilder builder, string path);
4: public static IConfigurationBuilder AddXmlFile(this IConfigurationBuilder builder, string path, bool optional);
5: public static IConfigurationBuilder AddXmlFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange);
6: public static IConfigurationBuilder AddXmlFile(this IConfigurationBuilder builder, IFileProvider provider, string path, bool optional, bool reloadOnChange);
7: }
四、IniConfigurationSource & IniConfigurationSource
“INI”是“Initialization”的缩写,INI文件又被称为初始化文件,它是Windows系统普遍使用的配置文件,同时也被一些Linux和Unix系统所支持。INI文件直接以键值对的形式定义配置项,如下所示的代码片段体现了INI文件的基本格式。总的来说,INI文件以单纯的“{Key}={Value}”的形式定义配置项,{Value}可以定义在可选的双引号中(如果值的前后包括空白字符,必须使用双引号,否则会被忽略)。
1: [Section]
2: key1=value1
3: key2 = " value2 "
4: ; comment
5: # comment
6: / comment
除了以“{Key}={Value}”的定义的原子配置项外,我们还可以采用“[{SectionName}]”的形式定义配置节对它们进行分组。中括号(“[]”)同时作为下一个的配置节开始的标志,同时也作为上一个配置结束的标志,所以采用INI文件定义的配置节并不存在层次化的结构,即没有“子配置节”的概念。除此之外,我们可以在INI中定义相应的注释,注释行前置的字符可以采用“;”、“#”或者“/”。
由于INI文件自身就体现为一个数据字典,所以我们可以采用“路径化”的Key来定义最终绑定为复杂对象、集合或者字典的配置数据。如果采用INI文件来定义一个Profile对象的基本信息,我们就可以采用如下定义形式。
1: Gender = "Male"
2: Age = "18"
3: ContactInfo:EmailAddress = "foobar@outlook.com"
4: ContactInfo:PhoneNo = "123456789"
由于Profile的配置信息具有两个层次(Profile>ContactInfo),我们可以按照如下的形式将EmailAddress和PhoneNo定义在配置节“ContactInfo”中,这个INI文件和上面是完全等效的。
1: Gender = "Male"
2: Age = "18"
3:
4: [ContactInfo]
5: EmailAddress = "foobar@outlook.com"
6: PhoneNo = "123456789"
针对INI文件类型的配置源类型通过如下所示的IniConfigurationSource来表示,该类型定义在“Microsoft.Extensions.Configuration.Ini”这个NuGet包中。IniConfigurationSource在重写的Build方法中会创建的ConfigurationProvdier类型为IniConfigurationProvider。作为抽象类FileConfigurationProvider的继承者,IniConfigurationProvider利用重写的Load方法完成INI文件内容的读取和配置字典的初始化。
1: public class IniConfigurationSource : FileConfigurationSource
2: {
3: public override IConfigurationProvider Build(IConfigurationBuilder builder)
4: {
5: base.FileProvider = base.FileProvider ?? builder.GetFileProvider();
6: return new IniConfigurationProvider(this);
7: }
8: }
9:
10: public class IniConfigurationProvider : FileConfigurationProvider
11: {
12: public IniConfigurationProvider(IniConfigurationSource source);
13: public override void Load(Stream stream);
14: }
既然JsonConfigurationSource和XmlConfigurationSource的注册可以通过调用IConfigurationBuilder接口的扩展方法AddJsonFile和AddXmlFile来完成,“Microsoft.Extensions.Configuration.Ini”这个NuGet包自然会也会为IniConfigurationSource定义如下所示的AddIniFile扩展方法。
1: public static class IniConfigurationExtensions
2: {
3: public static IConfigurationBuilder AddIniFile(this IConfigurationBuilder builder, string path);
4: public static IConfigurationBuilder AddIniFile(this IConfigurationBuilder builder, string path, bool optional);
5: public static IConfigurationBuilder AddIniFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange);
6: public static IConfigurationBuilder AddIniFile(this IConfigurationBuilder builder, IFileProvider provider, string path, bool optional, bool reloadOnChange);
7: }
.NET Core采用的全新配置系统[6]: 深入了解三种针对文件(JSON、XML与INI)的配置源的更多相关文章
- 深入了解三种针对文件(JSON、XML与INI)的配置源
深入了解三种针对文件(JSON.XML与INI)的配置源 物理文件是我们最常用到的原始配置的载体,最佳的配置文件格式主要由三种,它们分别是JSON.XML和INI,对应的配置源类型分别是JsonCon ...
- .NET Core采用的全新配置系统[10]: 配置的同步机制是如何实现的?
配置的同步涉及到两个方面:第一,对原始的配置文件实施监控并在其发生变化之后从新加载配置:第二,配置重新加载之后及时通知应用程序进而使后者能够使用最新的配置.要了解配置同步机制的实现原理,先得从认识一个 ...
- .NET Core采用的全新配置系统[1]: 读取配置数据
提到“配置”二字,我想绝大部分.NET开发人员脑海中会立马浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化的配置定义在这两个文 ...
- .NET Core采用的全新配置系统[7]: 将配置保存在数据库中
我们在<聊聊默认支持的各种配置源>和<深入了解三种针对文件(JSON.XML与INI)的配置源>对配置模型中默认提供的各种ConfigurationSource进行了深入详尽的 ...
- .NET Core采用的全新配置系统[2]: 配置模型设计详解
在<.NET Core采用的全新配置系统[1]: 读取配置数据>中,我们通过实例的方式演示了几种典型的配置读取方式,其主要目的在于使读者朋友们从编程的角度对.NET Core的这个全新的配 ...
- .NET Core采用的全新配置系统[3]: “Options模式”下的配置是如何绑定为Options对象
配置的原子结构就是单纯的键值对,并且键和值都是字符串,但是在真正的项目开发中我们一般不会单纯地以键值对的形式来使用配置.值得推荐的做法就是采用<.NET Core采用的全新配置系统[1]: 读取 ...
- .NET Core采用的全新配置系统[8]: 如何实现配置与源文件的同步
配置的同步涉及到两个方面:第一,对原始的配置文件实施监控并在其发生变化之后从新加载配置:第二,配置重新加载之后及时通知应用程序进而使后者能够使用最新的配置.接下来我们利用一个简单的.NET Core控 ...
- .NET Core采用的全新配置系统[5]: 聊聊默认支持的各种配置源[内存变量,环境变量和命令行参数]
较之传统通过App.config和Web.config这两个XML文件承载的配置系统,.NET Core采用的这个全新的配置模型的最大一个优势就是针对多种不同配置源的支持.我们可以将内存变量.命令行参 ...
- .NET Core采用的全新配置系统[9]: 为什么针对XML的支持不够好?如何改进?
物理文件是我们最常用到的原始配置的载体,最佳的配置文件格式主要由三种,它们分别是JSON.XML和INI,对应的配置源类型分别是JsonConfigurationSource.XmlConfigura ...
随机推荐
- NodeJs之child_process
一.child_process child_process是NodeJs的重要模块.帮助我们创建多进程任务,更好的利用了计算机的多核性能. 当然也支持线程间的通信. 二.child_process的几 ...
- Hibernatel框架关联映射
Hibernatel框架关联映射 Hibernate程序执行流程: 1.集合映射 需求:网络购物时,用户购买商品,填写地址 每个用户会有不确定的地址数目,或者只有一个或者有很多.这个时候不能把每条地址 ...
- Hyper-V2:向VM增加虚拟硬盘
使用Hyper-V创建VM,在VM成功安装OS之后,发现VM只有一个逻辑盘C,用于存储VM的操作系统.在产品环境中,需要向VM增加虚拟硬盘,便于将数据单独存储在不同的逻辑盘符中.在Hyper-V中,分 ...
- HTML5 progress和meter控件
在HTML5中,新增了progress和meter控件.progress控件为进度条控件,可表示任务的进度,如Windows系统中软件的安装.文件的复制等场景的进度.meter控件为计量条控件,表示某 ...
- $.type 怎么精确判断对象类型的 --(源码学习2)
目标: var a = [1,2,3]; console.log(typeof a); //->object console.log($.type(a)); //->ar ...
- .NET Core的日志[5]:利用TraceSource写日志
从微软推出第一个版本的.NET Framework的时候,就在“System.Diagnostics”命名空间中提供了Debug和Trace两个类帮助我们完成针对调试和跟踪信息的日志记录.在.NET ...
- [.NET] C# 知识回顾 - 事件入门
C# 知识回顾 - 事件入门 [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/6057301.html 序 之前通过<C# 知识回顾 - 委托 de ...
- Redis的简单动态字符串实现
Redis 没有直接使用 C 语言传统的字符串表示(以空字符结尾的字符数组,以下简称 C 字符串), 而是自己构建了一种名为简单动态字符串(simple dynamic string,sds)的抽象类 ...
- ASP.NET MVC原理
仅此一文让你明白ASP.NET MVC原理 ASP.NET MVC由以下两个核心组成部分构成: 一个名为UrlRoutingModule的自定义HttpModule,用来解析Controller与 ...
- IT雇员及外包商选择:人品第一
最近,苹果iOS操作系统和智能手机爆出了一个奇葩故障,在播放特定一段五秒钟的视频时能导致手机死机.唯一的解决办法是按住电源键和Home按键进行手机的重启. 第十八届中国国际高新技术成果交易会在深圳举办 ...