# 跟我一起學WCF(2)——利用.NET Remoting技術開發分布式應用
## 一、引言
上一篇博文分享了消息隊列(MSMQ)技術來實現分布式應用,在這篇博文繼續分享下.NET平臺下另一種分布式技術——.NET Remoting。
## 二、.NET Remoting 介紹
## 2.1 .NET Remoting簡介
.NET REmoting與MSMQ不同,它不支持離線可得,另外只適合.NET平臺的程序進行通信。它提供了一種允許對象通過應用程序域與另一個對象進行交互的框架。.NET 應用程序都在一個主應用程序域中執行的,在一個應用程序域中的代碼不能訪問另一個應用程序域的數據,然而在某些情況下,我們需要跨應用程序域,與另外的應用程序域進行通信,這時候就可以采用.NET Remoting技術來實現與另一個程序域中的對象進行交互。
## 2.2 .NET Remoting基本原理
.NET Remoting技術是通過通道來實現兩個應用程序之間對象的通信的。首先,客戶端通過Remoting技術,訪問通道來獲得服務器端對象,再通過代理解析為客戶端對象,也稱作透明代理,此時獲得客戶端對象只是服務器對象的一個引用。這既保證了客戶端和服務端有關對象的松散耦合,同時優化了通信的性能。在這個過程中,當客戶端通過透明代理來調用遠程對象的方法時,此時會將調用封裝到一個消息對象中,該消息對象包括遠程對象信息,被調用的方法名和參數,然后透明代理會將調用委托給真實代理([RealProxy](http://msdn.microsoft.com/zh-cn/library/system.runtime.remoting.proxies.realproxy.invoke(v=vs.110).aspx)對象)的Invoke方法來生成一個[IMethodCallMessage](http://msdn.microsoft.com/zh-cn/library/system.runtime.remoting.messaging.imethodcallmessage(v=vs.110).aspx),接著通過序列化把這個消息對象序列化成數據流發送到通道,通道會把數據流傳送到服務器端。當服務器接收到經過格式化的數據之后,首先從中通過反序列化來還原消息對象,之后在服務器端來激活遠程對象,并調用對應的方法,而方法的返回結果過程則是按照之前的方法反向重復一遍,具體的實現原理圖如下所示:

## 2.3 .NET Remoting幾個重要概念
上面簡單介紹了下.NET Remoting實現分布式應用程序的基本原理,這里介紹下在.NET Remoting中涉及的幾個重要概念。
1. 遠程對象:是運行在服務器端的對象,客戶端不能直接調用,由于.NET Remoting傳遞的對象是以引用的方式,因此所傳遞的遠程對象必須繼承MarshalByRefObject類,這個類可以使遠程對象在.NET Remoting應用通信中使用,支持對象的跨域邊界訪問。
2. 遠程對象的激活方式:在訪問服務器端的一個對象實例之前,必須通過一個名為Activation的進程創建它并進行初始化。這種客戶端通過通道來創建遠程對象的方式稱為對象的激活。在.NET Remoting中,遠程對象的激活分為兩大類:服務器端激活和客戶端激活。
* 服務器端激活,又叫做WellKnow(知名對象)激活模式,為什么稱為知名對象激活模式呢?是因為服務應用程序在激活對象實例之前會在一個眾所周知的統一資源標示符(URI)上發布這個類型,然后該服務器進行會為此類型配置一個WellKnow對象,并根據指定的端口或地址來發布對象。.NET Remoting把服務器端激活又分為SingleTon模式和SingleCall模式兩種。
SingleTon模式:此為有狀態模式。如果設置為SingleTon激活模式,則.NET Remoting將為所有客戶端建立同一個對象實例。當對象處于活動狀態時,SingleTon實例會處理所有后來的客戶端訪問請求,而不管它們是同一個客戶端,還是其他客戶端。SingleTon實例將在方法調用中一直維護其狀態,類似static成員的概念
SingleCall模式:是一種無狀態模式。一旦設置為SingleCall模式,則當客戶端調用遠程對象的方法時,Remoting會為每一個客戶端建立一個遠程對象實例,對象實例的銷毀則是由GC自動管理。類似實例成員的概念。
* 客戶端激活:與Wellknow模式不同,。NET Remoting在激活每個對象實例的時候,會給每個客戶端激活的類型指派一個URI。客戶端激活模式一旦獲得客戶端的請求,將為每一個客戶端都建立一個實例引用。SingleCall模式與客戶端激活模式的區別有:首先,對象實例創建的時間不同。客戶端激活方式是客戶一旦發出調用請求就實例化,而SingleCall則要等到調用對象方法時再創建。其次,SingleCall模式激活的對象是無狀態的,對象聲明周期由GC管理,而客戶端激活的對象是有狀態的,其生命周期可自定義。第三,兩種激活模式在服務器端和客戶端實現的方法不一樣,尤其是在客戶端,SingleCall模式由GetObject()來激活,它調用對象默認的構造函數,而客戶端激活模式,則通過CreateInstance()來激活,它可以傳遞參數,所以可以調用自定義的構造函數來創建實例。
3\. 通道:在.NET Remoting中時通過通道來實現兩個應用程序域之間對象的通信。.NET Remoting中包括4中通道類型:
1. TcpChannel:Tcp通道使用Tcp協議來跨越.Net Remoting邊界來傳輸序列化的消息流,TcpChannel默認使用二進制格式序列化消息對象,因此具有更高的傳輸性能,但不提供任何內置的安全功能。
2. HttpChannel:Http通道使用Http協議在客戶端和服務器之間發生消息,使其在Internet上穿越防火墻來傳輸序列化的消息流(這里準確講不能說穿越,主要是因為防火墻都開放了80端口,所以使用Http協議可以穿過防火墻進行數據的傳輸,如果防火墻限制了80端口,Http協議也照樣不能穿越防火墻)。默認情況下,HttpChannel使用Soap格式序列化消息對象,因此它具有更好的互操作性,并且可以使用Http協議中的加密機制來對消息進行加密來保證安全性。因此,通常在局域網內,我們更多地使用TcpChannel,如果要穿越防火墻,則使用HttpChannel。
3. IpcChannel:進程間通信,只使用同一個系統進程之間的通信,不需要主機名和端口號。而使用Http通道和Tcp通道都要指定主機名和端口號。
4. 自定義通道:自定義的傳輸通道可以使用任何基本的傳輸協議來進行通信,如UDP協議、SMTP協議等。
## 三、利用.NET Remoting技術開發分布式應用三部曲
前面詳細介紹了.NET Remoting相關內容,下面具體看看如何使用.NET Remoting技術來開發分布式應用程序。開發.NET Remoting應用分三步走。
第一步:創建遠程對象,該對象必須繼承MarshalByRefObject對象。具體的示例代碼如下:
```
1 namespace RemotingObject
2 {
3 // 第一步:創建遠程對象
4 // 創建遠程對象——必須繼承MarshalByRefObject,該類支持對象的跨域邊界訪問
5 public class MyRemotingObject :MarshalByRefObject
6 {
7 // 用來測試Tcp通道
8 public int AddForTcpTest(int a, int b)
9 {
10 return a + b;
11 }
12
13 // 用來測試Http通道
14 public int MinusForHttpTest(int a, int b)
15 {
16 return a - b;
17 }
18
19 // 用來測試IPC通道
20 public int MultipleForIPCTest(int a, int b)
21 {
22 return a * b;
23 }
24 }
25 }
```
遠程對象分別定義3個方法,目的是為了測試3中不同的通道方式的效果。
第二步:創建服務器端,需要添加System.Runtime.Remoting.dll引用,具體實現代碼如下所示:
```
1 using System;
2 using System.Runtime.Remoting;
3 using System.Runtime.Remoting.Channels;
4 using System.Runtime.Remoting.Channels.Http;
5 using System.Runtime.Remoting.Channels.Ipc;
6 using System.Runtime.Remoting.Channels.Tcp;
7
8 namespace RemotingServerHost
9 {
10 // 第二步:創建宿主應用程序
11 class Server
12 {
13 static void Main(string[] args)
14 {
15 // 1.創建三種通道
16
17 // 創建Tcp通道,端口號9001
18 TcpChannel tcpChannel = new TcpChannel(9001);
19
20 // 創建Http通道,端口號9002
21 HttpChannel httpChannel = new HttpChannel(9002);
22
23 // 創建IPC通道,端口號9003
24 IpcChannel ipcChannel = new IpcChannel("IpcTest");
25
26 // 2.注冊通道
27 ChannelServices.RegisterChannel(tcpChannel, false);
28 ChannelServices.RegisterChannel(httpChannel, false);
29 ChannelServices.RegisterChannel(ipcChannel, false);
30
31 // 打印通道信息
32 // 打印Tcp通道的名稱
33 Console.WriteLine("The name of the TcpChannel is {0}", tcpChannel.ChannelName);
34 // 打印Tcp通道的優先級
35 Console.WriteLine("The priority of the TcpChannel is {0}", tcpChannel.ChannelPriority);
36
37 Console.WriteLine("The name of the HttpChannel is {0}", httpChannel.ChannelName);
38 Console.WriteLine("The priority of the httpChannel is {0}", httpChannel.ChannelPriority);
39
40 Console.WriteLine("The name of the IpcChannel is {0}", ipcChannel.ChannelName);
41 Console.WriteLine("The priority of the IpcChannel is {0}", ipcChannel.ChannelPriority);
42
43 // 3\. 注冊對象
44 // 注冊MyRemotingObject到.NET Remoting運行庫中
45 RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemotingObject.MyRemotingObject), "MyRemotingObject", WellKnownObjectMode.Singleton);
46 Console.WriteLine("Press any key to exit");
47 Console.ReadLine();
48 }
49 }
50 }
```
第三步:創建客戶端程序,具體的實現代碼如下所示:
```
1 using RemotingObject;
2 using System;
3
4 namespace RemotingClient
5 {
6 class Client
7 {
8 static void Main(string[] args)
9 {
10 // 使用Tcp通道得到遠程對象
11 //TcpChannel tcpChannel = new TcpChannel();
12 //ChannelServices.RegisterChannel(tcpChannel, false);
13 MyRemotingObject proxyobj1 = Activator.GetObject(typeof(MyRemotingObject), "tcp://localhost:9001/MyRemotingObject") as MyRemotingObject;
14 if (proxyobj1 == null)
15 {
16 Console.WriteLine("連接TCP服務器失敗");
17 }
18
19 //HttpChannel httpChannel = new HttpChannel();
20 //ChannelServices.RegisterChannel(httpChannel, false);
21 MyRemotingObject proxyobj2 = Activator.GetObject(typeof(MyRemotingObject), "http://localhost:9002/MyRemotingObject") as MyRemotingObject;
22 if (proxyobj2 == null)
23 {
24 Console.WriteLine("連接Http服務器失敗");
25 }
26
27 //IpcChannel ipcChannel = new IpcChannel();
28 //ChannelServices.RegisterChannel(ipcChannel, false);
29 MyRemotingObject proxyobj3 = Activator.GetObject(typeof(MyRemotingObject), "ipc://IpcTest/MyRemotingObject") as MyRemotingObject;
30 if (proxyobj3 == null)
31 {
32 Console.WriteLine("連接Ipc服務器失敗");
33 }
34 // 輸出信息
35 Console.WriteLine("This call object by TcpChannel, 100 + 200 = {0}", proxyobj1.AddForTcpTest(100, 200));
36 Console.WriteLine("This call object by HttpChannel, 100 - 200 = {0}", proxyobj2.MinusForHttpTest(100, 200));
37 Console.WriteLine("This call object by IpcChannel, 100 * 200 = {0}", proxyobj1.MultipleForIPCTest(100, 200));
38 Console.WriteLine("Press any key to exit!");
39 Console.ReadLine();
40 }
41 }
42 }
```
經過上面的三步,我們就完成了這個分布式應用的開發工作,下面測試下該程序是否可以正常運行,首先,運行服務器端,你將看到如下界面:

在.NET Remoting中,是允許同時創建多個通道的,但是.NET Remoting要求通道的名字必須不同,因為名字是用來標識通道的唯一標識符。但上面代碼中,我們并沒有指明通道的名字,為什么還可以允許成功呢?從上面圖片可知,當我們創建通道時,如果沒有為其顯式指定通道名,則會使用對應的通道類型作為該通道名,如TcpChannel將會以tcp作為通道名,如果想注冊多個Tcp通道則必須顯式指定其名字。
下面看看運行客戶端所獲得的結果,具體客戶端運行效果如下圖所示:

## 四、使用配置文件來重寫上面的分布式程序
在第三部分中,我們是把服務器的各種通道方式和地址寫死在程序中的,這樣的實現方式部署起來不方便,下面使用配置文件的方式來配置服務器端的通道類型和服務器地址。遠程對象的定義不需要改變,下面直接看服務器端使用配置文件后的實現代碼如下所示:
```
1 using System;
2 using System.Runtime.Remoting;
3 using System.Runtime.Remoting.Channels;
4
5 namespace RemotingServerHostByConfig
6 {
7 class Program
8 {
9 static void Main(string[] args)
10 {
11 RemotingConfiguration.Configure("RemotingServerHostByConfig.exe.config", false);
12
13 foreach (var channel in ChannelServices.RegisteredChannels)
14 {
15 // 打印通道的名稱
16 Console.WriteLine("The name of the Channel is {0}", channel.ChannelName);
17 // 打印通道的優先級
18 Console.WriteLine("The priority of the Channel is {0}", channel.ChannelPriority);
19 }
20 Console.WriteLine("按任意鍵退出……");
21 Console.ReadLine();
22 }
23 }
24 }
```
服務端的配置文件的內容為:
```
1 <?xml version="1.0" encoding="utf-8" ?>
2 <!--服務端App.config的內容-->
3 <configuration>
4 <startup>
5 <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
6 </startup>
7 <system.runtime.remoting>
8 <application>
9 <service>
10 <wellknown mode="Singleton"
11 type="RemotingObject.MyRemotingObject,RemotingObject"
12 objectUri="MyRemotingObject"/>
13 </service>
14 <channels>
15 <channel port="9001" ref="tcp"/>
16 <channel port="9002" ref="http"/>
17 <channel portName="IpcTest" ref="ipc"/> <!--Ipc通道不需要端口號-->
18 </channels>
19 </application>
20 </system.runtime.remoting>
21 </configuration>
```
此時,客戶端程序的實現代碼如下所示:
```
1 using RemotingObject;
2 using System;
3 using System.Runtime.Remoting;
4
5 namespace RemotingClientByConfig
6 {
7 class Program
8 {
9 static void Main(string[] args)
10 {
11 //使用HTTP通道得到遠程對象
12 RemotingConfiguration.Configure("RemotingClientByConfig.exe.config", false);
13 MyRemotingObject proxyobj1 = new MyRemotingObject();
14 if (proxyobj1 == null)
15 {
16 Console.WriteLine("連接服務器失敗");
17 }
18
19 Console.WriteLine("This call object by TcpChannel, 100 + 200 = {0}", proxyobj1.AddForTcpTest(100, 200));
20 Console.WriteLine("This call object by HttpChannel, 100 - 200 = {0}", proxyobj1.MinusForHttpTest(100, 200));
21 Console.WriteLine("This call object by IpcChannel, 100 * 200 = {0}", proxyobj1.MultipleForIPCTest(100, 200));
22 Console.WriteLine("Press any key to exit!");
23 Console.ReadLine();
24 }
25 }
26 }
```
客戶端配置文件為:
```
1 <?xml version="1.0" encoding="utf-8" ?>
2 <configuration>
3 <startup>
4 <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
5 </startup>
6 <system.runtime.remoting>
7 <application>
8 <client>
9 <wellknown type="RemotingObject.MyRemotingObject,RemotingObject"
10 url="http://localhost:9002/MyRemotingObject" />
11 </client>
12 <channels>
13 <channel ref="tcp" port="0"></channel>
14 <channel ref="http" port="0"></channel>
15 <channel ref="ipc" port="0"></channel>
16 </channels>
17 </application>
18 </system.runtime.remoting>
19 </configuration>
```
使用配置文件修改后的分布式程序的運行結果與前面的運行結果一樣,這里就不一一貼圖了。
## 五、總結
到這里,.NET Remoting技術的分享就結束了,本文只是對.NET Remoting技術做了一個基本的介紹,如果想深入了解.NET Remoting技術的話,推薦大家可以看看下面的專題[細細品味C#——.Net Remoting專題](http://www.cnblogs.com/xia520pi/archive/2011/11/02/2233371.html)。在下一篇文章中,繼續為大家分享另一種分布式技術——Web Service。
本文的示例代碼文件下載:[.NETRemotingSample](http://files.cnblogs.com/zhili/NetRemoting.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 領域驅動設計實戰系列總結