轉眼微軟的WCF已走過十個年頭,它是微軟通信框架的集大成者,將之前微軟所有的通信框架進行了整合,提供了統一的應用方式。記得從自己最開始做MFC時,就使用過Named Pipe命名管道,之後做Winform時,使用過Remoting,再之後做B/S架構時,就會經常使用.NET平台下的Web Service,直到使用上WCF。看上去有了一些WCF的使用經驗,實則不然,比如對安全、分布式事務、可靠會話等主題仍然接觸甚少,因而決定重新回顧學習一下相關知識,尤其是對WCF框架的理解(已於2015年開源,可下載源碼,https://github.com/dotnet/wcf/)。很多大公司都構建了自己的SOA框架,不過基本上都是以WCF框架為基礎,對其進行了相應的簡化和微調。因此學習該框架,可以觸類旁通,對應用和搭建自有的SOA架構也有很大的幫助。當然,個人認為WCF已足夠強大,並且其管道模式有極強的擴展性,可以通過自定義綁定滿足絕大部分的需求。整個學習過程將參考蔣金楠大師的《WCF全面解析》一書,本章主要介紹WCF的基本概念和傳說中的"ABC",Let go。
在介紹WCF之前,不得不提一個稱為SOA(Service Orientation Architecture)的概念,也就是我們常說的面向服務的架構,這是一個很老的概念了。即使如此,如果要以SOA為題,寫一遍2000字的論文,感覺仍然很難下手,說明對概念理解還不夠深刻(之後打算專門撰文一篇,為軟考做准備)。實際上,其是構建大型軟件應用的一種重要理念,並不是什麼具體的技術或者平台。這個提法的出現其實有一個過程,就是在過去軟件的架構說到底是基於數據庫的(至於什麼基於組件、基於領域等概念,其實是在應用范疇的,而不是架構范疇的概念),比如不同的兩個系統的交互,往往是通過公用同一個數據庫,或者通過Job等方式同步兩個應用各自的數據,最終都是以數據為中心的。這種架構的優點是開發快速,與數據庫緊密相連,事務性很好,適用於中小系統;缺點是因為各個系統都可以直接和數據庫連接,層次不清晰,當系統越來越龐大時,運維成本越來越大,此外,其可控性、安全性、擴展性也相對較差。而SOA是以上缺點的一個很合適的解決方案,比如:基於開放的標准,使得可以跨平台調用(.NET, J2EE…);基於自治的服務,便於安全性的控制和服務限流;基於契約,將各個子系統解耦。
接下來,詳細回顧一下微軟的所有分布式通信技術,包括如下4種具體技術。
COM和DCOM:COM基於組件設計,通過GUID唯一標識、IKnown與其他接口進行互操作,例如ActiveX,DCOM是COM的分布式版本,提供了可靠傳輸、安全等支持。
.NET Remoting:其基於信道棧的"管道式"消息處理和傳輸機制,支持TCP,UDP等傳輸協議。
Web Service:其提供跨平台的互操作性,構建在ASP.NET平台上,基於一系列開放的標准,包括XML、XSD、SOAP和WSDL等。此外,微軟還通過WSE(Web Service Enhancement)組件為Web服務提供WS-*規范的支持。
MSMQ(Message Queuing):MSMQ通過異步通信的方式,解耦了服務的提供者和調用者,為系統提供了可觀的伸縮性和可用性,並支持可靠信息傳輸、錯誤處理和對事務的支持。
Tip:
J2EE架構其實也有相對應的技術,例如官方的Java RPC,WebService,JMS,第三方的Axis,RabbitMQ等。
本節最後通過一個非常簡單的自寄宿的WCF示例來熟悉WCF的應用以及引入傳說中的三要素"ABC",Address服務地址、Binding服務綁定、Contract服務契約,之後將分節進行詳細介紹
Contract: [ServiceContract] public interface IAddService { [OperationContract] CompositeType Add(CompositeType a, CompositeType b); } [DataContract] public class CompositeType { [DataMember] public int PartA { get; set; } [DataMember] public string PartB { get; set; } } public class AddService : IAddService { public CompositeType Add(CompositeType a, CompositeType b) { return new CompositeType() { PartA = a.PartA + b.PartA, PartB = a.PartB + b.PartB }; } } Host: static void Main(string[] args) { using (var host = new ServiceHost(typeof(AddService))) { host.Opened += (target, eventArgs) => Console.WriteLine("AddService已經啟動,請按任意鍵終止服務!"); host.Open(); Console.Read(); } } Config: <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="metadataBehavior"> <serviceMetadata httpGetEnabled="True" httpGetUrl="http://127.0.0.1:9901/addservice/metadata"/> </behavior> </serviceBehaviors> </behaviors> <services> <service name="Sory.Entertainment.WCF.AddService" behaviorConfiguration="metadataBehavior"> <endpoint address="http://127.0.0.1:9901/addservice" binding="wsHttpBinding" contract="Sory.Entertainment.WCF.IAddService"/> </service> </services> </system.serviceModel> Client: static void Main(string[] args) { using (var client = new WcfService.AddServiceClient()) { var result = client.Add(new WcfService.CompositeType { PartA = 1, PartB = "Hello, " }, new WcfService.CompositeType { PartA = 2, PartB = "World!" }); Console.WriteLine(string.Format("PartA: {0}, PartB: {1}", result.PartA, result.PartB)); } Console.Read(); }View Code
本節將介紹URI、端口共享、請求監聽和消息分發等概念。正如之前所說的,WCF服務是通過終結點EndPoint發布,而終結點由地址、綁定和契約三要素組成,其中地址用於定位服務,並提供額外的尋址信息和認證信息。既然是服務定位,首先引入URI的概念,URI的全稱為Uniform Resource Identifier統一資源標識,其形式是,[Schema傳輸協議]://[主機名|域名|IP地址]:[端口號]/[資源路經],其中支持的協議類型如下表所示。
協議類型
解釋
HTTP/HTTP
前者是互聯網時代的核心--超文本傳輸協議,其是建立在TCP/IP協議簇上應用層協議。特點無狀態、無連接、提供簡單請求-回復消息傳輸方式;後者是采用了SSL(TLS)的HTTP,提供數據加密,實際上,大部分主流網站已實現全站HTTPS。
Net.TCP
TCP全稱傳輸控制協議,屬於傳輸層協議,基於網絡層IP協議,是應用層HTTP協議的基礎。其特點是有狀態、支持全雙工、支持可靠通信,其是基於連接的協議,在數據傳輸前通過3次"握手"創建連接,在傳輸結束後,通過4次"握手"終止連接。
Net.Pipe
命名管道是Windows等操作系統實現跨進程通信(Inter Process Communication, IPC)的標准實現方式,雖然命名管道本身可以跨機器通信,不過WCF中的命名管道專注於同一台機器中的跨進程通信,因此其主機名為localhost,此外由於基於同一台機器,端口變得沒有意義。
Net.Msmq
消息隊列提供了支持離線的通信機制,其包括公共消息隊列和私有消息隊列兩種方式,前者需要注冊到AD域中。此外,除了存儲業務數據消息的普通隊列之外,還有存儲消息拷貝的日志隊列、存儲確認消息的管理隊列、存儲回復消息的回復隊列和存儲死信消息的死信隊列等。
其URI格式為: net.msmq://sory.com/private/xxxservice
之前提及的核心概念終結點在WCF中,通過System.ServiceModel.Description.ServiceEndpoint類表示,其包括Address、Binding、Contract三個核心屬性。其中的Address是EndpointAddress的實現類,其包含Uri、Headers、Identity三個屬性,Uri即是服務的唯一標識,也是服務的目標地址,且這個地址可以使物理的,也可以是邏輯的。這兒的Headers其實就是SOAP消息中的消息頭(類似於Http協議的,也包括消息頭和消息體,前者主要提供一些控制信息,後者存放數據部分),它默認通過DataContractSerializer進行序列化和反序列化,最終轉化為SOAP消息的MessageHeader,相應配置如下所示,添加了服務端消息頭後,在客戶端也需要增加相應消息頭,否則會被地址過濾器給過濾掉(之後的客戶端通過ChannelFactory調用服務的示例中可以看到)。
<endpoint address="http://127.0.0.1:9901/addservice" binding="wsHttpBinding" contract="Sory.Entertainment.WCF.IAddService"> <headers> <authentication xmlns="http://www.sory.com/">{12345678}</authentication> </headers> </endpoint>View Code
補充一點的是,可以通過將服務的ServiceBehavior特性中的AddressFilterModel屬性設置為Any,跳過消息頭的檢驗。
在基礎概念一節的代碼示例中,可以看到WCF通過ServiceHost完成服務寄宿,其中通過AddServiceEndpoint實現終結點的添加,當然也可以通過配置文件的方式添加終結點,在配置文件的<system.serviceModel>模塊的<service>子節點中添加<endpoint>,並補全address、binding、contract屬性,注意在IIS寄宿的情況下,無需提供address,因為.svc文件的地址就是服務的地址。同時,可以通過ServiceHost的Description屬性(.NET中習慣使用Description獲取元數據相關信息,無論是哪一種框架)獲取終結點和服務行為的相關信息。
此外,除了使用絕對地址來指定某個服務的終結點地址外,還可以通過"基地址+相對地址"的方式,其配置形式如下,需要注意一種類型的協議只能有一個基地址,並且當一個服務實現類同時實現了多個服務接口時,該終結點地址可以共享。
<service name="XXX" behaviorConfiguration="XXX"> <host> <baseAddresses> <add baseAddress="net.tcp://127.0.0.1/baseservice"/> </baseAddresses> </host> </service>View Code
客戶端通過服務代理實現對服務的調用,包括兩種方式:通過服務引用或者借助SvcUtil.exe工具來生成服務代理類,該生成類繼承自ClientBase<TChannel>;直接通過ChannelFactory<TChannel>創建服務代理。前者比較簡單,只需要在<system.serviceModel>的子節點<client>中添加對應的<endpoint>節點,然後直接生成的對應的Client類即可,後者如下所示。
var uri = new Uri("http://127.0.0.1:9901/addservice"); var header = AddressHeader.CreateAddressHeader("authentication", "http://www.sory.com/", "{12345678}"); var address = new EndpointAddress(uri, header); var binding = new WSHttpBinding(); var contract = ContractDescription.GetContract(typeof(IAddService)); var endpoint = new ServiceEndpoint(contract, binding, address); using (var factory = new ChannelFactory<IAddService>(endpoint)) { var channel = factory.CreateChannel(); var result = channel.Add(new CompositeType { PartA = 1, PartB = "Hello, " }, new CompositeType { PartA = 2, PartB = "World!" }); Console.WriteLine(string.Format("PartA: {0}, PartB: {1}", result.PartA, result.PartB)); }View Code
在Windows系統,為了安全,常常只開發少量端口,當有大量應用需要使用不同端口時,會顯得捉襟見肘,因此多個應用共享同一個端口顯得很有必要。對於Http/Https協議來說,由於其可以通過IIS來管理應用,其自身通過HTTP.SYS已經實現了80|443端口的共享。而對於TCP協議來說,其通過一個Windows服務(名稱為Net.Tcp Port Sharing Service)來管理,可以通過如下方式實現其共享。
<bindings> <netTcpBinding> <binding name="portSharingBinding" portSharingEnabled="true"></binding> </netTcpBinding> </bindings>View Code
之前在EndpointAddress中提及的Uri屬性表示服務的邏輯地址,而物理地址對於服務端來說是監聽地址,對於客戶端來說是消息真正發送的目標地址。默認情況下,兩個地址是統一的,但在需要中介進行消息轉發的場景下,需要將兩者分離。
對於服務端,可以設置終結點的ListenUri的屬性和ListenUriMode屬性(包括Explicit和Unique,前者嚴格使用ListenUri作為最終的監聽地址,後者將通過不同的策略保證監聽地址的唯一性,如針對端口共享的情況,將在默認Uri後加GUID以作識別),共同完成該需求,示例如下。
示例如下。
<endpoint address="http://127.0.0.1:9901/addservice" listenUri="http://127.0.0.1:9900/addservice" listenUriMode="Unique" …/>
對於客戶端,需要借助ClientViaBehavior這一終結點行為來實現,示例如下。
<behaviors> <endpointBehaviors> <behavior name="clientViaBehavior"> <clientVia viaUri="http://127.0.0.1:9900/addservice"/> </behavior> </endpointBehaviors> </behaviors> <client > <endpoint behaviorConfiguration="clientViaBehavior"></endpoint> </client>View Code
補充:行為這個概念在WCF中非常的重要,很多的功能都是通過相應的行為實現的,接下來進行簡要介紹。如果說契約是客戶端和服務端達成的某種共識,是雙邊協議,而行為則是客戶端或服務端在本地實現某個功能的一種方式,是一種單邊行為。WCF提供了4種類型的行為,包括服務行為、契約行為、終結點行為和操作行為,它們一般可以通過特性或者配置文件的方式進行設置。
這部分內容涉及到整個WCF服務端的架構,下圖展示了一個最簡單的請求分發過程。
在整個消息監聽和分發體系中,信道分發器和終結點分發器是兩個核心的對象,前者負責請求監聽、消息接收並通過消息篩選器選擇正確的終結點,後者完成消息的處理。終結點分發器具有兩個消息消息篩選器,分別是AddressFilter和ContractFilter,均是MessageFilter類型,前者對應的AddressFilterMode包含Exact、Prefix、Any三種枚舉類型。WCF提供6種典型的消息篩選器,包括:ActionMessageFilter,判斷請求消息(SOAP)的<Action>報頭是否和終結點契約中任意操作的Action屬性相匹配(Match);EndpointAddressMessageFilter判斷<To>報頭是否和終結點地址相匹配;MatchAllMessageFilter,表示全匹配;以及不常用的XPathMessageFilter、MatchNoneMeesageFilter和PrefixEndpointAddressMessageFilter。
從基礎架構的角度上看,WCF可以分為服務模型層和信道層兩個層次,服務模型層建立在信道層的基礎是上,而信道層就是通過本節即將介紹的binding綁定創建,注意這兒的綁定與.NET很多地方的綁定概念不同(例如最常見的數據綁定),注意理解。那麼binding是如何創建信道層的呢?它通過組合不同的信道,將其整合為一個指定的信道棧,這個過程其實就是一個職責鏈模式的實現,每個信道都只處理自己的一部分內容,最基本的有傳輸、編碼,復雜一些的包括事務流轉、安全傳輸和可靠傳輸,使得整個框架足夠靈活,已於擴展,一個支持WS-*的信道棧如下圖所示。
其中傳輸信道實現了基於某種協議的消息傳輸,消息編碼信道實現了消息的編碼(例如XML、Binary、MTOM),而WS-AT(WS-Atomic Transaction)實現了分布式的事務支持,WS-RM(WS-Reliable Messaging)實現了信息的可靠傳輸,WS-Security實現了消息的傳輸安全,他們都可以被稱為協議信道。接下來通過一個簡單的例子來演示通過綁定進行消息通信,在其中將引入信道、信道監聽器、信道工廠等主要對象。
服務端: static void Main(string[] args) { var listenUri = new Uri("http://127.0.0.1:9902/listener"); var binding = new BasicHttpBinding(); //創建和開啟信道監聽器 var channelListener = binding.BuildChannelListener<IReplyChannel>(listenUri); channelListener.Open(); //創建、開啟回復信道 var channel = channelListener.AcceptChannel(TimeSpan.MaxValue); channel.Open(); //開始監聽 while (true) { //接受輸入請求信息 var requestContext = channel.ReceiveRequest(TimeSpan.MaxValue); Console.WriteLine(requestContext.RequestMessage); requestContext.Reply(CreateReplyMessage(binding)); } } private static Message CreateReplyMessage(Binding binding) { var action = "http://www.sory.com/addservice/AddResponse"; XNamespace ns = "http://www.sory.com"; XElement body = new XElement(new XElement(ns + "AddResponse", new XElement(ns + "AddResult", 3))); return Message.CreateMessage(binding.MessageVersion, action, body); } 客戶端: static void Main(string[] args) { var listenUri = new Uri("http://127.0.0.1:9902/listener"); var binding = new BasicHttpBinding(); //創建和開啟信道工廠 var channelFactory = binding.BuildChannelFactory<IRequestChannel>(); channelFactory.Open(); //創建、開啟請求信道 var channel = channelFactory.CreateChannel(new EndpointAddress(listenUri)); channel.Open(); //發送請求消息,接受回復消息 var replyMessage = channel.Request(CreateRequestMessage(binding)); Console.WriteLine(replyMessage); Console.Read(); } private static Message CreateRequestMessage(Binding binding) { var action = "http://www.sory.com/addservice/Add"; XNamespace ns = "http://www.sory.com"; XElement body = new XElement(new XElement(ns + "Add", new XElement(ns + "x", 1), new XElement(ns + "y", 2))); return Message.CreateMessage(binding.MessageVersion, action, body); }View Code
通過這個例子看起來很像以前的Window網絡編程中的Socket編程形式,首先服務端監聽,然後客戶端請求,服務端接收並綁定Socket(這兒是綁定信道),之後就可以在此基礎上進行通訊了。這部分涉及到的類型很多,接下來通過一個表格簡述部分主要類,浏覽即可。
類別
介紹
信道與信道棧
最基礎的ICommunicationObject接口,提供統一管理通信對象的狀態機,可以作為一種設計范例用於實際項目中;DefaultCommunicationTimeouts類負責控制超時時限;IChannel和ChannelBase用於表示信道;ISession和ISessionChannel<TSession>用於表示會話信道。此外,支持3種消息交換模式。
數據報Datagram模式:一般使一部的消息發送方式,支持1或多個接收者,對應IOutputChannel, IInputChannel
請求-回復模式:對應IRequestChannel、IReplyChannel
雙工模式:對應IDuplexChannel
信道監聽器(Server)
IChannelListener, ChannelListenerBase
信道工廠(Client)
IChannelFactory, ChannelFactoryBase
最後,進入綁定元素與綁定的介紹,之前提到過,綁定是用於創建信道棧的,而它其中的綁定元素則是用於創建具體的信道的。常見的系統綁定包括:BasicHttpBinding、WSHttpBinding、WS2007HttpBinding、WSDualHttpBinding、NetTcpBinding、NetNamedPipeBinding和NetMsmqBinding。其中BasicHttpBinding最為基礎,在構建類似web服務形式的應用中使用最多,所有帶Net前綴的綁定將局限於.NET平台,不同的綁定的運行效率有不小差異。一般來說,企業內部的服務推薦使用RPC類型的服務,如NetTcpBinding,而對外服務推薦使用WSHttpBinding,當然實際項目中,對外服務一般不會使用WCF框架,而是使用Restful風格的WebAPI。此外,也可以建立自定義的綁定,將框架提供的綁定元素進行重新組合,更有甚者,可以自定義綁定元素,不過這部分內容使用的場景非常的少。最後,提供一個簡單自定義綁定配置作為參考,其組合了傳輸、編碼和安全3個綁定元素,前兩者是必選項,且必須按照順序構建。
<bindings > <customBinding> <binding name="testBinding"> <security></security> <textMessageEncoding></textMessageEncoding> <tcpTransport></tcpTransport> </binding> </customBinding> </bindings>View Code
契約其實就是一個生活中的概念,是一種雙邊和多邊的協議,在WCF中,其保證了無論服務的實現有任何的改變,而服務的消費者始終可以通過契約約定方式來調用服務。由於整個WCF都是基於SOAP以及WS-*的,因此其XML是數據格式標准,通過XSD控制XML的數據結構,用WSDL(web服務描述語言)來提供跨平台的描述服務。
服務契約的定義通過ServiceContractAttribute和OperationContractAttribute兩個特性來定義,前者定義整個服務,後者定義服務中具體的方法,接下來具體介紹一下這兩個類。ServiceContractAttribute類,比較重要的屬性包括:Name,可以定義服務的名稱,默認為接口名;Namespace定義服務的命名空間,可以使用自己的公司名和項目名的組合來設定,其和之前的Name在wsdl文件中均是對<portType>元素的修飾;ConfigurationName實際上就對應配置中的Contract名稱;SessionMode表示契約的會話模式,比如Allowed、Required等;ProtectionLevel表示消息的保護級別;CallbackContract在雙工通信時指定回調操作的接口類型。OperationContractAttribute類,其屬性Name、Namespace、ProtectionLevel與之前相似,值得一提的屬性包括:Action/ReplyAction用於控制某個操作請求/回復信息的<Action>頭,其默認通過命名空間、服務契約、操作名稱組成,後者默認添加Response;IsOneWay控制消息交換的模式。提到消息交換的模式,記得之前提到過主要的三種請求-回復、單向和雙工,前兩項之前的例子中已有展示,之後的示例將展示雙工模式。
服務端: public interface IAddCallback { [OperationContract] void DisplayResult(CompositeType result, CompositeType a, CompositeType b); } [ServiceContract(CallbackContract=typeof(IAddCallback))] public interface IAddService { [OperationContract] void Add(CompositeType a, CompositeType b); } [DataContract] public class CompositeType { [DataMember] public int PartA { get; set; } [DataMember] public string PartB { get; set; } } public class AddCallbackService : IAddCallback { public void DisplayResult(CompositeType result, CompositeType a, CompositeType b) { Console.WriteLine("x + y = {2} when x= {0} and y = {1}", a.PartA, b.PartA, result.PartA); } } public class AddService : IAddService { public void Add(CompositeType a, CompositeType b) { var result = new CompositeType() { PartA = a.PartA + b.PartA, PartB = a.PartB + b.PartB }; IAddCallback callback = OperationContext.Current.GetCallbackChannel<IAddCallback>(); callback.DisplayResult(result, a, b); } } 配置: <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="metadataBehavior"> <serviceMetadata httpGetEnabled="True" httpGetUrl="http://127.0.0.1:9901/addservice/metadata"/> <serviceDebug includeExceptionDetailInFaults="true"/> </behavior> </serviceBehaviors> </behaviors> <services> <service name="Sory.Entertainment.WCF.AddService" behaviorConfiguration="metadataBehavior"> <endpoint address="net.tcp://127.0.0.1:1001/addservice" binding="netTcpBinding" contract="Sory.Entertainment.WCF.IAddService"/> </service> </services> </system.serviceModel> 客戶端: InstanceContext callback = new InstanceContext(new AddCallbackService()); using (DuplexChannelFactory<IAddService> channelFactory = new DuplexChannelFactory<IAddService>(callback, "addservice")) { var addChannel = channelFactory.CreateChannel(); addChannel.Add(new CompositeType { PartA = 1 }, new CompositeType { PartA = 2 }); } 配置: <system.serviceModel> <client> <endpoint name="addservice" address="net.tcp://127.0.0.1:1001/addservice" binding="netTcpBinding" contract="Sory.Entertainment.WCF.IAddService"/> </client> </system.serviceModel>View Code
當調用以上示例的服務時,會拋出一個關於死鎖的異常,原因是其在並發場景下會造成回調死鎖的情況,可以通過將請求或回調方法設置為單向即可。
此外,服務契約是不支持繼承的,而操作契約支持繼承,不過這部分也不太常用,而與契約相關的元數據描述類也非常簡單,這兒就不展開介紹了。
在《CLR via C#》中,將操作分為計算限制的和I/O限制的,一般來說,WCF中主要涉及到I/O限制的操作,這種類型的操作主要是通過異步模型來提高其並發性。談到異步操作,在SOA這類應用中包含3個不同異步場景,這部分知識比較有意思,曾經困到鄙人多年。這3中場景包括:異步的信道調用,客戶端可以通過代理對象異步的調用信道;單向消息交換,客戶端的信道通過單向的消息交換模式向服務端發送消息,發送立刻返回;異步服務實現,服務端在具體實現服務操作時,采用異步調用的方式。
異步服務代理的創建,可以通過在添加服務引用時通過高級選項添加生成異步操作選項,之後可以通過使用BeginXX/EndXX方法、回調和事件注冊等方式使用異步服務代理類。而異步的服務實現可以在服務接口中將原有方法修改為BeginXXX/EndXXX形式的異步方法名,並將OperationContract契約的AsyncPattern屬性設置為true即可。
之前提及的契約描述類中的Operations列表只包含了被OperationContractAttribute特性修飾的服務操作,而運行時的操作是通過DispatchOperation和ClientOperation兩個類型表示。DispatchOperation在服務端的終結點分發器初始化時建立一個DispatchRuntime類,其通過一個SynchronizedKeyedCollection<string, DispatchOperation>集合類型來管理所有的運行時分發操作,OperationSelector用於操作選擇,IOperationInvoker用於操作執行。ClientOperation和前者的結構基本一致,只不過它用於客戶端而已。
Tip:在實際中,很多公司選用ServiceStack的開源架構來構建的自身的SOA服務,此外,過去也常常以通過WebService搭建企業服務總線ESB的方式構建SOA服務。這部分推薦兩位大神的博文,寒江獨釣的http://www.cnblogs.com/yangecnu/p/Introduce-ServiceStack.html和張善友的http://www.cnblogs.com/shanyou/p/3348347.html。
最後,分享一個好玩的東西,就是在微信中可以搜索微軟的"小冰"(剛截稿前對面的程序媛告訴我的,挺逗的,能挖掘你的內心哦),然後就可以在編碼無聊、寂寞空虛時…你懂得,哈哈!
參考資料:
[1]蔣金楠. WCF全面解析[M]. 上海:電子工業出版社, 2012.