# 跟我一起学WCF(8)——WCF中Session、实例管理详解
## 一、引言
由前面几篇博文我们知道,WCF是微软基于SOA建立的一套在分布式环境中各个相对独立的应用进行交流(Communication)的框架,它实现了最新的基于WS-*规范。按照SOA的原则,相对独自的业务逻辑以Service的形式进行封装,调用者通过消息(Messaging)的方式来调用服务。对于承载某个业务功能实现的服务应该具有上下文(Context)无关性,意思就是说构造服务的操作(Operation)不应该绑定到具体的调用上下文,对于任何的调用,具有什么的样输入就会对应怎样的输出。因为SOA一个最大的目标是尽可能地实现重用,只有具有Context无关性,服务才能最大限度的重用。即从软件架构角度理解为,一个模块只有尽可能的独立,即具有上下文无关性,才能被最大限度的重用。软件体系一直在强调低耦合也是这个道理。
但是在某些场景下,我们却希望系统为我们创建一个Session来保留Client和Service的交互的状态,如Asp.net中Session的机制一样,WCF也提供了对Session的支持。下面就具体看看WCF中对Session的实现。
## 二、WCF中Session详细介绍
## 2.1 Asp.net的Session与WCF中的Session
在WCF中,Session属于Service Contract的范畴,并在Service Contract定义中通过[SessionModel](http://msdn.microsoft.com/zh-cn/library/system.servicemodel.servicecontractattribute.sessionmode(v=vs.90).aspx)参数来实现。WCF中会话具有以下几个重要的特征:
* Session都是由Client端显示启动和终止的。
在WCF中Client通过创建的代理对象来和服务进行交互,在支持Session的默认情况下,Session是和具体的代理对象绑定在一起,当Client通过调用代理对象的某个方法来访问服务时,Session就被初始化,直到代理的关闭,Session则被终止。我们可以通过两种方式来关闭Proxy:一是调用[ICommunicationObject.Close 方法](http://msdn.microsoft.com/zh-cn/library/ms195520(v=vs.110).aspx),二是调用[ClientBase<TChannel>.Close 方法](http://msdn.microsoft.com/zh-cn/library/ms575273(v=vs.110).aspx) 。我们也可以通过服务中的某个操作方法来初始化、或者终止Session,可以通过OperationContractAttribute的IsInitiating和[IsTerminating](http://msdn.microsoft.com/zh-cn/library/system.servicemodel.operationcontractattribute.isterminating(v=vs.110).aspx)参数来指定初始化和终止Session的Operation。
* 在WCF会话期间,传递的消息按照它发送的顺序被接收。
* WCF并没有为Session的支持保存相关的状态数据。
讲到Session,做过Asp.net开发的人,自然想到的就是Asp.net中的Session。它们只是名字一样,在实现机制上有很大的不同。Asp.net中的Session具有以下特性:
* Asp.net的Session总是由服务端启动的,即在服务端进行初始化的。
* Asp.net中的Session是无需,不能保证请求处理是有序的。
* Asp.net是通过在服务端以某种方式保存State数据来实现对Session的支持,例如保存在Web Server端的内存中。
## 2.2 WCF中服务实例管理
对于Client来说,它实际上不能和Service进行直接交互,它只能通过客户端创建的Proxy来间接地和Service进行交互,然而真正的调用而是通过服务实例来进行的。我们把通过Client的调用来创建最终的服务实例过程称作激活,在.NET Remoting中包括Singleton模式、SingleCall模式和客户端激活方式,WCF中也有类似的服务激活方式:单调服务(PerCall)、会话服务(PerSession)和单例服务(Singleton)。
* **单调服务(Percall)**:为每个客户端请求分配一个新的服务实例。类似.NET Remoting中的SingleCall模式
* **会话服务(Persession)**:在会话期间,为每次客户端请求共享一个服务实例,类似.NET Remoting中的客户端激活模式。
* **单例服务(Singleton)**:所有客户端请求都共享一个相同的服务实例,类似于.NET Remoting的Singleton模式。但它的激活方式需要注意一点:当为对于的服务类型进行Host的时候,与之对应的服务实例就被创建出来,之后所有的服务调用都由这个服务实例进行处理。
WCF中服务激活的默认方式是PerSession,但不是所有的Bingding都支持Session,比如BasicHttpBinding就不支持Session。你也可以通过下面的方式使ServiceContract不支持Session.
```
[ServiceContract(SessionMode = SessionMode.NotAllowed)]
```
下面分别介绍下这三种激活方式的实现。
## 三、WCF中实例管理的实现
WCF中服务激活的默认是PerSession的方式,下面就看看PerSession的实现方式。我们还是按照前面几篇博文的方式来实现使用PerSession方式的WCF服务程序。
第一步:自然是实现我们的WCF契约和契约的服务实现。具体的实现代码如下所示:
```
1 // 服务契约的定义
2 [ServiceContract]
3 public interface ICalculator
4 {
5 [OperationContract(IsOneWay = true)]
6 void Increase();
7
8 [OperationContract]
9 int GetResult();
10 }
11
12 // 契约的实现
13 public class CalculatorService : ICalculator, IDisposable
14 {
15 private int _nCount = 0;
16
17 public CalculatorService()
18 {
19 Console.WriteLine("CalulatorService object has been created");
20 }
21
22 // 为了看出服务实例的释放情况
23 public void Dispose()
24 {
25 Console.WriteLine("CalulatorService object has been Disposed");
26 }
27
28 #region ICalulator Members
29 public void Increase()
30 {
31 // 输出Session ID
32 Console.WriteLine("The Add method is invoked and the current session ID is: {0}", OperationContext.Current.SessionId);
33 this._nCount++;
34 }
35
36 public int GetResult()
37 {
38 Console.WriteLine("The GetResult method is invoked and the current session ID is: {0}", OperationContext.Current.SessionId);
39 return this._nCount;
40 }
41 #endregion
42 }
```
为了让大家对服务对象的创建和释放有一个直观的认识,我特意对服务类实现了构造函数和IDisposable接口,同时在每个操作中输出当前的Session ID。
第二步:实现服务宿主程序。这里还是采用控制台程序作为服务宿主程序,具体的实现代码如下所示:
```
1 // 服务宿主程序
2 class Program
3 {
4 static void Main(string[] args)
5 {
6 using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
7 {
8 host.Opened += delegate
9 {
10 Console.WriteLine("The Calculator Service has been started, begun to listen request...");
11 };
12
13 host.Open();
14 Console.ReadLine();
15 }
16 }
17 }
```
对应的配置文件为:
```
<!--服务宿主的配置文件-->
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name ="CalculatorBehavior">
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="WCFContractAndService.CalculatorService" behaviorConfiguration="CalculatorBehavior">
<endpoint address="" binding="basicHttpBinding" contract="WCFContractAndService.ICalculator"/>
<host>
<baseAddresses>
<add baseAddress="http://localhost:9003/CalculatorPerSession"/>
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>
```
第三步:实现完了服务宿主程序,接下来自然是实现客户端程序来访问服务操作。这里的客户端也是控制台程序,具体的实现代码如下所示:
```
1 // 客户端程序实现
2 class Program
3 {
4 static void Main(string[] args)
5 {
6 // Use ChannelFactory<ICalculator> to create WCF Service proxy
7 ChannelFactory<ICalculator> calculatorChannelFactory = new ChannelFactory<ICalculator>("HttpEndPoint");
8 Console.WriteLine("Create a calculator proxy :proxy1");
9 ICalculator proxy1 = calculatorChannelFactory.CreateChannel();
10 Console.WriteLine("Invoke proxy1.Increate() method");
11 proxy1.Increase();
12 Console.WriteLine("Invoke proxy1.Increate() method again");
13 proxy1.Increase();
14 Console.WriteLine("The result return via proxy1.GetResult() is: {0}", proxy1.GetResult());
15
16 Console.WriteLine("Create another calculator proxy: proxy2");
17 ICalculator proxy2 = calculatorChannelFactory.CreateChannel();
18 Console.WriteLine("Invoke proxy2.Increate() method");
19 proxy2.Increase();
20 Console.WriteLine("Invoke proxy2.Increate() method again");
21 proxy2.Increase();
22 Console.WriteLine("The result return via proxy2.GetResult() is: {0}", proxy2.GetResult());
23
24 Console.ReadLine();
25 }
26 }
```
客户端对应的配置文件内容如下所示:
```
<!--客户端配置文件-->
<configuration>
<system.serviceModel>
<client>
<endpoint address="http://localhost:9003/CalculatorPerSession"
binding="basicHttpBinding"
contract="WCFContractAndService.ICalculator" name="HttpEndPoint"/>
</client>
</system.serviceModel>
</configuration>
```
经过上面三步,我们就完成了PerSession方式的WCF程序了,下面看看该程序的运行结果。
首先,以管理员权限运行服务寄宿程序,运行成功后,你将看到如下图所示的画面:
![](https://box.kancloud.cn/2016-01-23_56a2eb48b3006.png)
接下来,运行客户端对服务操作进行调用,运行成功后,你将看到服务宿主的输出和客户端的输出情况如下图所示:
![](https://box.kancloud.cn/2016-01-23_56a2eb48c191d.png)
从客户端的运行结果可以看出,虽然我们两次调用了Increase方法来增加_nCount的值,但是最终的运行结果仍然是0。这样的运行结果好像与我们之前所说的WCF默认Session支持矛盾,因为如果WCF默认的方式PerSession的话,则服务实例是和Proxy绑定在一起,当Proxy调用任何一个操作的时候Session开始,从此Session将会与Proxy具有一样的生命周期。按照这个描述,客户端运行的结果应该是2而不是0。这里,我只能说运行结果并没有错,因为有图有真相嘛,那到底是什么原因导致客户端获得_nCount值是0呢?其实在前面已经讲到过,并不是所有的绑定都是支持Session的,上面程序的实现我们使用的basicHttpBinding,而basicHttpBinding是不支持Session方式的,所以WCF会采用PerCall的方式创建Service Instance,所以在服务端中对于每一个Proxy都有3个对象被创建,两个是对Increase方法的调用会导致服务实例的激活,另一个是对GetResult方法的调用导致服务实例的激活。因为是PerCall方式,所以每次调用完之后,就会对服务实例进行释放,所以对应的就有3行服务对象释放输出。并且由于使用的是不支持Session的binding,所以Session ID的输出也为null。所以,上面WCF程序其实是PerCall方式的实现。
既然,上面的运行结果是由于使用了不支持Session的basicHttpBinding导致的,下面看看使用一个支持Session的Binding:wsHttpBinding来看看运行结果是怎样的,这里的修改很简单,只需要把宿主和客户端的配置文件把绑定类型修改为wsHttpBinding就可以了。
```
<!--客户端配置文件-->
<configuration>
<system.serviceModel>
<client>
<endpoint address="http://localhost:9003/CalculatorPerSession"
binding="**wsHttpBinding**"
contract="WCFContractAndService.ICalculator" name="HttpEndPoint"/>
</client>
</system.serviceModel>
</configuration>
<!--服务宿主的配置文件-->
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name ="CalculatorBehavior">
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="WCFContractAndService.CalculatorService" behaviorConfiguration="CalculatorBehavior">
<endpoint address="" binding="**wsHttpBinding**" contract="WCFContractAndService.ICalculator"/>
<host>
<baseAddresses>
<add baseAddress="http://localhost:9003/CalculatorPerSession"/>
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>
```
现在我们再运行下上面的程序来看看此时的执行结果,具体的运行结果如下图所示:
![](https://box.kancloud.cn/2016-01-23_56a2eb48d9d9e.png)
从上面的运行结果可以看出,此时两个Proxy的运行结果都是2,可以看出此时服务激活方式采用的是PerSession方式。此时对于服务端就只有两个服务实例被创建了,并且对于每个服务实例具有相同的Session ID。 另外由于Client的Proxy还依然存在,服务实例也不会被回收掉,从上面服务端运行的结果也可以证实这点,因为运行结果中没有对象呗Disposable的输出。你可以在客户端显式调用ICommunicationObject.Close方法来显式关闭掉Proxy,在客户端添加对Proxy的显示关闭代码,此时客户端的代码修改为如下所示:
```
1 // 客户端程序实现
2 class Program
3 {
4 static void Main(string[] args)
5 {
6 // Use ChannelFactory<ICalculator> to create WCF Service proxy
7 ChannelFactory<ICalculator> calculatorChannelFactory = new ChannelFactory<ICalculator>("HttpEndPoint");
8 Console.WriteLine("Create a calculator proxy :proxy1");
9 ICalculator proxy1 = calculatorChannelFactory.CreateChannel();
10 Console.WriteLine("Invoke proxy1.Increate() method");
11 proxy1.Increase();
12 Console.WriteLine("Invoke proxy1.Increate() method again");
13 proxy1.Increase();
14 Console.WriteLine("The result return via proxy1.GetResult() is: {0}", proxy1.GetResult());
15 **(proxy1 as ICommunicationObject).Close(); // 显示关闭Proxy**
16
17 Console.WriteLine("Create another calculator proxy: proxy2");
18 ICalculator proxy2 = calculatorChannelFactory.CreateChannel();
19 Console.WriteLine("Invoke proxy2.Increate() method");
20 proxy2.Increase();
21 Console.WriteLine("Invoke proxy2.Increate() method again");
22 proxy2.Increase();
23 Console.WriteLine("The result return via proxy2.GetResult() is: {0}", proxy2.GetResult());
24 **(proxy2 as ICommunicationObject).Close();** 25
26 Console.ReadLine();
27 }
28 }
```
此时,服务对象的Dispose()方法将会调用,此时服务端的运行结果如下图所示:
![](https://box.kancloud.cn/2016-01-23_56a2eb48edc84.png)
上面演示了默认支持Session的情况,下面我们修改服务契约使之不支持Session,此时只需要知道ServiceContract的SessionMode为NotAllowed即可。
```
[ServiceContract**(SessionMode= SessionMode.NotAllowed)**] // 是服务契约不支持Session
public interface ICalculator
{
[OperationContract(IsOneWay = true)]
void Increase();
[OperationContract]
int GetResult();
}
```
此时,由于服务契约不支持Session,此时服务激活方式采用的仍然是PerCall。运行结果与前面采用不支持Session的绑定的运行结果一样,这里就不一一贴图了。
除了通过显式修改ServiceContract的SessionMode来使服务契约支持或不支持Session外,还可以定制操作对Session的支持。定制操作对Session的支持可以通过OperationContract的IsInitiating和InTerminating属性设置。
```
1 // 服务契约的定义
2 [ServiceContract(SessionMode= SessionMode.Required)] // 显式使服务契约支持Session
3 public interface ICalculator
4 {
5 // IsInitiating:该值指示方法是否实现可在服务器上启动会话(如果存在会话)的操作,默认值是true
6 // IsTerminating:获取或设置一个值,该值指示服务操作在发送答复消息(如果存在)后,是否会导致服务器关闭会话,默认值是false
7 [OperationContract(IsOneWay = true, IsInitiating =true, IsTerminating=false )]
8 void Increase();
9
10 [OperationContract(IsInitiating = true, IsTerminating = true)]
11 int GetResult();
12 }
```
在上面代码中,对两个操作都设置InInitiating的属性为true,意味着调用这两个操作都会启动会话,而把GetResult操作的IsTerminating设置为true,意味着调用完这个操作后,会导致服务关闭掉会话,因为在Session方式下,Proxy与Session有一致的生命周期,所以关闭Session也就是关闭proxy对象,所以如果后面再对proxy对象的任何一个方法进行调用将会导致异常,下面代码即演示了这种情况。
```
1 // 客户端程序实现
2 class Program
3 {
4 static void Main(string[] args)
5 {
6 // Use ChannelFactory<ICalculator> to create WCF Service proxy
7 ChannelFactory<ICalculator> calculatorChannelFactory = new ChannelFactory<ICalculator>("HttpEndPoint");
8 Console.WriteLine("Create a calculator proxy :proxy1");
9 ICalculator proxy1 = calculatorChannelFactory.CreateChannel();
10 Console.WriteLine("Invoke proxy1.Increate() method");
11 proxy1.Increase();
12 Console.WriteLine("Invoke proxy1.Increate() method again");
13 proxy1.Increase();
14 Console.WriteLine("The result return via proxy1.GetResult() is: {0}", proxy1.GetResult());
15 **try
16 {
17 proxy1.Increase(); // session关闭后对proxy1.Increase方法调用将会导致异常
18 }
19 catch (Exception ex) // 异常捕获
20 {
21 Console.WriteLine("在Session关闭后调用Increase方法失败,错误信息为:{0}", ex.Message);
22 }** 23
24 Console.WriteLine("Create another calculator proxy: proxy2");
25 ICalculator proxy2 = calculatorChannelFactory.CreateChannel();
26 Console.WriteLine("Invoke proxy2.Increate() method");
27 proxy2.Increase();
28 Console.WriteLine("Invoke proxy2.Increate() method again");
29 proxy2.Increase();
30 Console.WriteLine("The result return via proxy2.GetResult() is: {0}", proxy2.GetResult());
31
32 Console.ReadLine();
33 }
34 }
```
此时运行结果也验证我们上面的分析,客户端和服务端的运行结果如下图所示:
![](https://box.kancloud.cn/2016-01-23_56a2eb490d13e.png)
上面演示了PerSession和PerCall的两种服务对象激活方式,下面看看Single的激活方式运行的结果。首先通过ServiceBehavior的InstanceContextMode属性显式指定激活方式为Single,由于[ServiceBehaviorAttribute](http://msdn.microsoft.com/zh-cn/library/System.ServiceModel.ServiceBehaviorAttribute(v=vs.110).aspx)特性只能应用于类上,所以把该特性应用于CalculatorService类上,此时服务实现的代码如下所示:
```
// 契约的实现
// ServiceBehavior属性只能应用在类上
**[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] // 显示指定PerSingle方式**
public class CalculatorService : ICalculator, IDisposable
{
private int _nCount = 0;
public CalculatorService()
{
Console.WriteLine("CalulatorService object has been created");
}
// 为了看出服务实例的释放情况
public void Dispose()
{
Console.WriteLine("CalulatorService object has been Disposed");
}
#region ICalulator Members
public void Increase()
{
// 输出Session ID
Console.WriteLine("The Add method is invoked and the current session ID is: {0}", OperationContext.Current.SessionId);
this._nCount++;
}
public int GetResult()
{
Console.WriteLine("The GetResult method is invoked and the current session ID is: {0}", OperationContext.Current.SessionId);
return this._nCount;
}
#endregion
}
```
此时运行服务宿主的输出结果如下图所示:
![](https://box.kancloud.cn/2016-01-23_56a2eb49265d7.png)
从运行结果可以看出,对于Single方式,服务实例在服务类型被寄宿的时候就已经创建了,对于PerCall和PerSession方式而是在通过Proxy调用相应的服务操作之后,服务实例才开始创建的。下面运行客户端程序,你将看到如下图所示的运行结果:
![](https://box.kancloud.cn/2016-01-23_56a2eb493b301.png)
此时,第二个Proxy返回的结果是4而不是2,这是因为采用Single方式只存在一个服务实例,所有的调用状态都将保留,所以_nCount的值在原来的基础上继续累加。
## 四、总结
到这里,本文的分享就结束了,本文主要分享了WCF中实例管理的实现。从WCF的实例实现可以看出,WCF实例实现是借鉴了.NET Remoting中实例实现,然后分别分享了服务实例三种激活方式在WCF中的实现,并通过对运行结果进行对比来让大家理解它们之间的区别。
本文所以源码:[WCFInstanceManager.zip](http://files.cnblogs.com/zhili/WCFInstanceManager.zip)
- C# 基础知识系列
- C# 基础知识系列 专题一:深入解析委托——C#中为什么要引入委托
- C# 基础知识系列 专题二:委托的本质论
- C# 基础知识系列 专题三:如何用委托包装多个方法——委托链
- C# 基础知识系列 专题四:事件揭秘
- C# 基础知识系列 专题五:当点击按钮时触发Click事件背后发生的事情
- C# 基础知识系列 专题六:泛型基础篇——为什么引入泛型
- C# 基础知识系列 专题七: 泛型深入理解(一)
- C# 基础知识系列 专题八: 深入理解泛型(二)
- C# 基础知识系列 专题九: 深入理解泛型可变性
- C#基础知识系列 专题十:全面解析可空类型
- C# 基础知识系列 专题十一:匿名方法解析
- C#基础知识系列 专题十二:迭代器
- C#基础知识 专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
- C# 基础知识系列 专题十四:深入理解Lambda表达式
- C# 基础知识系列 专题十五:全面解析扩展方法
- C# 基础知识系列 专题十六:Linq介绍
- C#基础知识系列 专题十七:深入理解动态类型
- 你必须知道的异步编程 C# 5.0 新特性——Async和Await使异步编程更简单
- 全面解析C#中参数传递
- C#基础知识系列 全面解析C#中静态与非静态
- C# 基础知识系列 C#中易混淆的知识点
- C#进阶系列
- C#进阶系列 专题一:深入解析深拷贝和浅拷贝
- C#进阶系列 专题二:你知道Dictionary查找速度为什么快吗?
- C# 开发技巧系列
- C# 开发技巧系列 使用C#操作Word和Excel程序
- C# 开发技巧系列 使用C#操作幻灯片
- C# 开发技巧系列 如何动态设置屏幕分辨率
- C# 开发技巧系列 C#如何实现图片查看器
- C# 开发技巧 如何防止程序多次运行
- C# 开发技巧 实现属于自己的截图工具
- C# 开发技巧 如何使不符合要求的元素等于离它最近的一个元素
- C# 线程处理系列
- C# 线程处理系列 专题一:线程基础
- C# 线程处理系列 专题二:线程池中的工作者线程
- C# 线程处理系列 专题三:线程池中的I/O线程
- C# 线程处理系列 专题四:线程同步
- C# 线程处理系列 专题五:线程同步——事件构造
- C# 线程处理系列 专题六:线程同步——信号量和互斥体
- C# 多线程处理系列专题七——对多线程的补充
- C#网络编程系列
- C# 网络编程系列 专题一:网络协议简介
- C# 网络编程系列 专题二:HTTP协议详解
- C# 网络编程系列 专题三:自定义Web服务器
- C# 网络编程系列 专题四:自定义Web浏览器
- C# 网络编程系列 专题五:TCP编程
- C# 网络编程系列 专题六:UDP编程
- C# 网络编程系列 专题七:UDP编程补充——UDP广播程序的实现
- C# 网络编程系列 专题八:P2P编程
- C# 网络编程系列 专题九:实现类似QQ的即时通信程序
- C# 网络编程系列 专题十:实现简单的邮件收发器
- C# 网络编程系列 专题十一:实现一个基于FTP协议的程序——文件上传下载器
- C# 网络编程系列 专题十二:实现一个简单的FTP服务器
- C# 互操作性入门系列
- C# 互操作性入门系列(一):C#中互操作性介绍
- C# 互操作性入门系列(二):使用平台调用调用Win32 函数
- C# 互操作性入门系列(三):平台调用中的数据封送处理
- C# 互操作性入门系列(四):在C# 中调用COM组件
- CLR
- 谈谈: String 和StringBuilder区别和选择
- 谈谈:程序集加载和反射
- 利用反射获得委托和事件以及创建委托实例和添加事件处理程序
- 谈谈:.Net中的序列化和反序列化
- C#设计模式
- UML类图符号 各种关系说明以及举例
- C#设计模式(1)——单例模式
- C#设计模式(2)——简单工厂模式
- C#设计模式(3)——工厂方法模式
- C#设计模式(4)——抽象工厂模式
- C#设计模式(5)——建造者模式(Builder Pattern)
- C#设计模式(6)——原型模式(Prototype Pattern)
- C#设计模式(7)——适配器模式(Adapter Pattern)
- C#设计模式(8)——桥接模式(Bridge Pattern)
- C#设计模式(9)——装饰者模式(Decorator Pattern)
- C#设计模式(10)——组合模式(Composite Pattern)
- C#设计模式(11)——外观模式(Facade Pattern)
- C#设计模式(12)——享元模式(Flyweight Pattern)
- C#设计模式(13)——代理模式(Proxy Pattern)
- C#设计模式(14)——模板方法模式(Template Method)
- C#设计模式(15)——命令模式(Command Pattern)
- C#设计模式(16)——迭代器模式(Iterator Pattern)
- C#设计模式(17)——观察者模式(Observer Pattern)
- C#设计模式(18)——中介者模式(Mediator Pattern)
- C#设计模式(19)——状态者模式(State Pattern)
- C#设计模式(20)——策略者模式(Stragety Pattern)
- C#设计模式(21)——责任链模式
- C#设计模式(22)——访问者模式(Vistor Pattern)
- C#设计模式(23)——备忘录模式(Memento Pattern)
- C#设计模式总结
- WPF快速入门系列
- WPF快速入门系列(1)——WPF布局概览
- WPF快速入门系列(2)——深入解析依赖属性
- WPF快速入门系列(3)——深入解析WPF事件机制
- WPF快速入门系列(4)——深入解析WPF绑定
- WPF快速入门系列(5)——深入解析WPF命令
- WPF快速入门系列(6)——WPF资源和样式
- WPF快速入门系列(7)——深入解析WPF模板
- WPF快速入门系列(8)——MVVM快速入门
- WPF快速入门系列(9)——WPF任务管理工具实现
- ASP.NET 开发
- ASP.NET 开发必备知识点(1):如何让Asp.net网站运行在自定义的Web服务器上
- ASP.NET 开发必备知识点(2):那些年追过的ASP.NET权限管理
- ASP.NET中实现回调
- 跟我一起学WCF
- 跟我一起学WCF(1)——MSMQ消息队列
- 跟我一起学WCF(2)——利用.NET Remoting技术开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(4)——第一个WCF程序
- 跟我一起学WCF(5)——深入解析服务契约 上篇
- 跟我一起学WCF(6)——深入解析服务契约 下篇
- 跟我一起学WCF(7)——WCF数据契约与序列化详解
- 跟我一起学WCF(8)——WCF中Session、实例管理详解
- 跟我一起学WCF(9)——WCF回调操作的实现
- 跟我一起学WCF(10)——WCF中事务处理
- 跟我一起学WCF(11)——WCF中队列服务详解
- 跟我一起学WCF(12)——WCF中Rest服务入门
- 跟我一起学WCF(13)——WCF系列总结
- .NET领域驱动设计实战系列
- .NET领域驱动设计实战系列 专题一:前期准备之EF CodeFirst
- .NET领域驱动设计实战系列 专题二:结合领域驱动设计的面向服务架构来搭建网上书店
- .NET领域驱动设计实战系列 专题三:前期准备之规约模式(Specification Pattern)
- .NET领域驱动设计实战系列 专题四:前期准备之工作单元模式(Unit Of Work)
- .NET领域驱动设计实战系列 专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
- .NET领域驱动设计实战系列 专题六:DDD实践案例:网上书店订单功能的实现
- .NET领域驱动设计实战系列 专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
- .NET领域驱动设计实战系列 专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现
- .NET领域驱动设计实战系列 专题九:DDD案例:网上书店AOP和站点地图的实现
- .NET领域驱动设计实战系列 专题十:DDD扩展内容:全面剖析CQRS模式实现
- .NET领域驱动设计实战系列 专题十一:.NET 领域驱动设计实战系列总结