十五天精通WCF——第一天 三種Binding讓你KO80%的業務
轉眼wcf技術已經出現很多年了,也在.net界混的風生水起,同時.net也是一個高度封裝的框架,作為在wcf食物鏈最頂端的我們所能做的任務已經簡單的不能再簡單了,
再簡單的話馬路上的大媽也能寫wcf了,好了,wcf最基本的概念我們放在后面慢慢分析,下面我們來看看神奇的3個binding如何KO我們實際場景中的80%的業務場景。
?
一:basicHttpBinding
作為入門第一篇,也就不深入談談basic中的信道棧中那些啥東西了,你只需要知道有ABC三個要素,注意不是姨媽巾哦,如果需要詳細了解,可以觀賞我以前的系列。在
這里我就不多說了,太簡單的東西沒意思,先看個例子簡單感受了,你只需知道的是basic走的是http協議就好了,傳輸消息為soap。
1. 契約
1 using System.Runtime.Serialization;2 using System.ServiceModel;3 4 namespace MyService5 {6 [ServiceContract]7 public interface IHomeService8 {9 [OperationContract] 10 int GetLength(string name); 11 } 12 }
2. 實現類
1 using System;2 using System.Messaging;3 using System.Threading;4 5 namespace MyService6 {7 public class HomeService : IHomeService8 {9 public int GetLength(string name) 10 { 11 return name.Length; 12 } 13 } 14 }
3. 服務啟動
1 using System;2 using System.ServiceModel;3 4 namespace MyService5 {6 class Program7 {8 static void Main(string[] args)9 { 10 using (ServiceHost host = new ServiceHost(typeof(HomeService))) 11 { 12 try 13 { 14 host.Open(); 15 16 Console.WriteLine("服務開啟!"); 17 18 Console.Read(); 19 } 20 catch (Exception e) 21 { 22 Console.WriteLine(e.Message); 23 } 24 } 25 } 26 } 27 }
4. 配置config文件
<?xml version="1.0" encoding="utf-8" ?> <configuration><system.serviceModel><bindings><netTcpBinding><binding name="IHomeServiceBinding" /></netTcpBinding></bindings><behaviors><serviceBehaviors><behavior name=""><serviceMetadata httpGetEnabled="true" /><serviceDebug includeExceptionDetailInFaults="true" /></behavior></serviceBehaviors></behaviors><services><service name="MyService.HomeService"><endpoint address="http://127.0.0.1:1920/HomeService" binding="basicHttpBinding" contract="MyService.IHomeService"><identity><dns value="localhost" /></identity></endpoint><endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /><host><baseAddresses><add baseAddress="http://127.0.0.1:1920"/></baseAddresses></host></service></services></system.serviceModel> </configuration>
5. 然后通過 servicehost 啟動服務端
using System; using System.ServiceModel;namespace MyService {class Program{static void Main(string[] args){using (ServiceHost host = new ServiceHost(typeof(HomeService))){try{host.Open();Console.WriteLine("服務開啟!");Console.Read();}catch (Exception e){Console.WriteLine(e.Message);}}}} }
?
好了,到現在為止,服務端全部開啟完畢,接下來我們通過“添加服務引用”,來添加對客戶端的引用
1 using System;2 3 namespace ConsoleApplication14 {5 class Program6 {7 static void Main(string[] args)8 {9 HomeServiceReference.HomeServiceClient client = new HomeServiceReference.HomeServiceClient(); 10 11 var s = client.GetLength("12345"); 12 13 Console.WriteLine("長度為:{0}", s); 14 15 Console.Read(); 16 } 17 } 18 }
?
麻蛋,就這么簡單,是的,就這樣簡單的五步,基于http的通信就這樣被不小心的完成了,真不好意思。
?
二:netTcpBinding
有了basic的代碼,現在我們要改成tcp通信,這會通信走的是字節流,很簡單,改一下服務端的config文件就好了,大家也知道這種性能要比basic好。
<?xml version="1.0" encoding="utf-8" ?> <configuration><system.serviceModel><behaviors><serviceBehaviors><behavior name="mxbehavior"><serviceMetadata httpGetEnabled="true" /><serviceDebug includeExceptionDetailInFaults="true" /></behavior></serviceBehaviors></behaviors><services><service name="MyService.HomeService" behaviorConfiguration="mxbehavior"><endpoint address="net.tcp://localhost:19200/HomeService" binding="netTcpBinding" contract="MyService.IHomeService"><identity><dns value="localhost" /></identity></endpoint><endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/><host><baseAddresses><add baseAddress="http://localhost:1920/HomeService"/></baseAddresses></host></service></services></system.serviceModel> </configuration>
?
三:netMsmqBinding
msmq這個玩意,我想大家都清楚,一個物理上的文件,好處呢,你也明白,就是client和service的所有通信都要經過它的手,這樣任何一方出了問題,只要
它在就沒問題了。同樣我們把tcp改成msmq也是非常簡單的,不過要注意,msmqbinding中是不可以讓契約方法有返回值的。所以我們加上isoneway就好了。
using System.Runtime.Serialization; using System.ServiceModel;namespace MyService {[ServiceContract]public interface IHomeService{[OperationContract(IsOneWay = true)]void GetLength(string name);} }
然后我在mmc上新建一個消息隊列,如下:
然后我們再改動以下配置文件
<?xml version="1.0" encoding="utf-8" ?> <configuration><system.serviceModel><behaviors><serviceBehaviors><behavior name="mxbehavior"><serviceMetadata httpGetEnabled="true" /><serviceDebug includeExceptionDetailInFaults="true" /></behavior></serviceBehaviors></behaviors><bindings><netMsmqBinding><binding name="msmqbinding"><security mode="None"/></binding></netMsmqBinding></bindings><services><service name="MyService.HomeService" behaviorConfiguration="mxbehavior"><endpoint address="net.msmq://localhost/private/homequeue" binding="netMsmqBinding"contract="MyService.IHomeService" bindingConfiguration="msmqbinding"><identity><dns value="localhost" /></identity></endpoint><endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /><host><baseAddresses><add baseAddress="http://localhost:19200/HomeService"/></baseAddresses></host></service></services></system.serviceModel> </configuration>
?
縱觀上面的三種binding,配置起來何其簡單,底層的各種通訊協議貌似對我來說都是透明的,其實呢???wcf在底層做了何其多的事情,而我卻沒有挖掘。。。
這對碼農里說也是一種悲哀啊。。。出了問題就只能禱告上天。。。下一篇我會開始深入剖析。
十五天精通WCF——第二天 告別煩惱的config配置
?
經常搞wcf的基友們肯定會知道,當你的應用程序有很多的“服務引用”的時候,是不是有一種瘋狂的感覺。。。從一個環境遷移到另外一個環境,你需要改變的
endpoint會超級tmd的多,簡直就是搞死了人。。。好了,這篇我們來看看如何最小化配置。
?
一:精簡service的config配置
就像上一篇的代碼一樣,我的service端的config配置如下:
1 <?xml version="1.0" encoding="utf-8" ?>2 <configuration>3 <system.servicemodel>4 <behaviors>5 <servicebehaviors>6 <behavior name="mxbehavior">7 <servicemetadata httpgetenabled="true" />8 <servicedebug includeexceptiondetailinfaults="true" />9 </behavior> 10 </servicebehaviors> 11 </behaviors> 12 <services> 13 <service name="myservice.homeservice" behaviorconfiguration="mxbehavior"> 14 <endpoint address="net.tcp://localhost:1920/homeservice" binding="nettcpbinding" contract="myservice.ihomeservice"> 15 <identity> 16 <dns value="localhost" /> 17 </identity> 18 </endpoint> 19 <endpoint address="mex" binding="mexhttpbinding" contract="imetadataexchange" /> 20 <host> 21 <baseaddresses> 22 <add baseaddress="http://localhost:19200/homeservice"/> 23 </baseaddresses> 24 </host> 25 </service> 26 </services> 27 </system.servicemodel> 28 </configuration>
?
通過上面的代碼,你應該知道在system.servicemodel下的所有節點都是wcf專屬的節點,所有的節點數據都會被開啟servicehost這個監聽器時捕獲到,下面我可以
通過servicehost這個監聽器的源碼下面找找相關的讀取config節點的代碼。
?
?
通過上面的截圖,你是不是有一種感覺,就是service的底層也是通過代碼動態的讀取config下面的節點來獲取數據,那就意味著我可以直接將代碼寫入到code中,
對吧,這樣我就可以把我認為該配置的東西配置起來,不該配置的東西全部放到代碼里面去,這樣我的靈活性是不是非常的強大。。。。爽吧,說干就干。。。
1 class Program12 {3 static void Main(string[] args)4 {5 ServiceHost host = new ServiceHost(typeof(HomeService), new Uri("http://localhost:19200/HomeService"));6 7 host.AddServiceEndpoint(typeof(IHomeService), new NetTcpBinding(), "net.tcp://localhost:1920/HomeService");8 9 //公布元數據 10 host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true }); 11 host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex"); 12 13 host.Open(); 14 15 Console.WriteLine("服務已經開啟。。。"); 16 17 Console.Read(); 18 } 19 }
?
有人就要說了,地址的話肯定不能是寫死的,必須變活,簡單啊,我就僅僅把ip地址配置到config里面去不就完事了,對不對。
<configuration><appSettings><add key ="baseurl" value="http://localhost:19200/HomeService"/><add key ="endpoindurl" value="net.tcp://localhost:1920/HomeService"/></appSettings>
1 class Program12 {3 static void Main(string[] args)4 {5 ServiceHost host = new ServiceHost(typeof(HomeService), new Uri(ConfigurationManager.AppSettings["baseurl"]));6 7 host.AddServiceEndpoint(typeof(IHomeService), new NetTcpBinding(), ConfigurationManager.AppSettings["endpoindurl"]);8 9 //公布元數據 10 host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true }); 11 host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex"); 12 13 host.Open(); 14 15 Console.WriteLine("服務已經開啟。。。"); 16 17 Console.Read(); 18 } 19 }
?
現在看的話,是不是清楚多了,如果你覺得我的代碼比較累贅,你可以封裝成一個方法,然后就可以動態的配置nettcp,basic,ws*等等對吧。。。好了,說完服
務端,接下來我們看看client端如何避免。
?
二:精簡client的config配置
就像上一節那樣,如果我用“服務引用”的話,vs會偷偷的用svcutil.exe來給我們生成一個proxy類和一個config文件,proxy類也就是你看到的xxxclient。。。
可惡的是config里面會給我生成一些亂七八糟的東西,如下圖:
1 <?xml version="1.0" encoding="utf-8" ?>2 <configuration>3 <system.serviceModel>4 <bindings>5 <netTcpBinding>6 <binding name="NetTcpBinding_IHomeService" />7 </netTcpBinding>8 </bindings>9 <client> 10 <endpoint address="net.tcp://localhost:1920/HomeService" binding="netTcpBinding" 11 bindingConfiguration="NetTcpBinding_IHomeService" contract="HomeServiceReference.IHomeService" 12 name="NetTcpBinding_IHomeService"> 13 <identity> 14 <dns value="localhost" /> 15 </identity> 16 </endpoint> 17 </client> 18 </system.serviceModel> 19 </configuration>
?
同服務器端一樣,如果我用code做掉,是不是非常的爽呢???那可不可以做掉呢? 我們還得看一下proxy的源碼,首先你會看到其實所謂的proxy只是一個繼承
自clientbase的一個類,如下圖。
?
?
上面的兩幅圖,你會發現,最后的proxy類是通過ChannelFactory<TChannel>類來完成助攻的,那話說回來了,既然底層用了ChannelFactory<TChannel>,
那何不我在代碼里面就用ChannelFactory<TChannel>不是更好嗎???這樣config也省了,對吧,說干就干啦。。。
1 static void Main(string[] args) 2 { 3 ChannelFactory<IHomeService> factory = new ChannelFactory<IHomeService>(new NetTcpBinding(), "net.tcp://localhost:1920/homeservice"); 4 5 var channel = factory.CreateChannel(); 6 7 var result = channel.GetLength("12345"); 8 }
?
好了,代碼就這么簡單,現在是不是感覺自己萌萌大啦~~~
?
十五天精通WCF——第三天 client如何知道server提供的功能清單
?通常我們去大保健的時候,都會找姑娘問一下這里能提供什么服務,什么價格,這時候可能姑娘會跟你口述一些服務或者提供一份服務清單,這樣的話大
家就可以做到童嫂無欺,這樣一份活生生的例子,在wcf中同樣是一個道理,只有client了解service能提供哪些功能,client才可以根據server提供的功能進行
消費,那問題來了,service怎么把功能提供給client進行選擇呢???這個就是我這一篇要聊的wsdl(web service description language)。。。
?
一:wsdl
現在你已經知道了,wsdl就是server提供給client的清單,那下面問題就來了。server是如何提供的呢???你要是比較仔細的話,可能會知道我在上一
篇提到的一個endpoint,如下截圖。
在上面這幅圖中,你可以看到,Homeservice提供了兩個端點,一個是“服務端點“,一個是“元數據端點”。并且你也看到了,元數據的端點地址是
http://192.168.16.16:19200/mex,當client通過svcutil訪問這個地址的時候,就拿到了server能提供的功能清單,然后client就可以根據這些功能生成一
個代理文件,然后的然后,就是你懂得,各種啪啪啪,XXXClient。
?
二:眼見為實
1.見證wsdl
要想看見wsdl,你只需要通過http://localhost:19200打開服務地址、如下圖:
?
然后點擊:http://localhost:19200/?singleWsdl
?
現在你看到的就是server功能清單,太tmd的重量級了,已經完完全全果體在世人前了,下一小節我們再詳細的分析下。
?
2. 見證client端的XXXclient
剛才我也說了,當你用vs做“服務引用”的時候,svcutil會根據http://localhost:19200/mex的地址來查看wsdl,然后生成代理,下面我們具體來看一下。
?
?
點擊確定之后,我們就可以看到在?Service References 文件夾下面生成了一個Reference.cs 文件。
?
然后我們打開Reference.cs,就可以看到一個繼承于ClientBase的HomeServiceClient。
?
?
三:詳細分析wsdl文件
學wcf,你一定要像svcutil一樣能夠看得懂wsdl。
?
1. 首先看下server提供了一個Update操作,參數是一個id,一個Student這個自定義的復雜類型,同時返回也是Student這個
? ? 復雜類型。
1 namespace MyService 2 { 3 [ServiceContract] 4 public interface IHomeService 5 { 6 [OperationContract] 7 Student Update(int id, Student stu); 8 } 9 }
?
?2. wsdl這個xml文件,剛才你也看到了,下面我們一個個節點看看
??<1> portType 和 operation節點
當你看到下面的截圖后,我想你也能猜的出來,portType就是契約(IHomeService),operation就是契約方法(Update),不過有點意思的是,在operation
下面你看到了一個input,一個output,這個就是所謂的 ”輸入消息“,”輸出消息”,那是什么意思呢??? 也就是說client到server的消息叫做“輸入消息”,server到
client端叫做“輸出消息”,到這里你應該似乎明白了,我C#中的Update方法是有入參和出參的,然而這映射到wsdl中就是兩條消息,input和output,這個也就是經典
的“請求-響應“模式。
?
好了,繼續往下看,在wsdl:input和wsdl:output中分別有一個Action屬性,這個非常有意思,wcf的底層就是通過這個地址來找到對應的方法,比如我們看到的代理
類中的Update方法上面就有這么一段。
?
?<2> message 和 types節點
繼續往下看的話,你會發現input和output中還有一個message屬性,對應的為IHomeService_Update_InputMessage和IHomeService_Update_OutputMessage,
這個正好是message節點的引用,如下圖:
從這個圖中,你可以看到input和output下面都有一個wsdl:part節點,這個就是表明input和output中需要攜帶的參數,比如element="tns:Update",就引用了
element中Name=Update的節點,如下圖:
?
好了,最后我再截一張圖,可以看到,傳輸協議為soap,服務地址等等。。。然后就沒什么好說的了。
?
?
十五天精通WCF——第四天 你一定要明白的通信單元Message
轉眼你已經學了三天的wcf了,是不是很好奇wcf在傳輸層上面到底傳遞的是個什么鳥毛東西呢???應該有人知道是soap,那soap這叼毛長得是什么
樣呢?這一篇我們來揭開答案。。。
?
一:soap到底長成什么樣子
為了能看清soap長的啥樣,我可以用強大的Fiddler來監視一下,突然好激動啊!!!
1.Server
1 static void Main(string[] args)2 {3 ServiceHost host = new ServiceHost(typeof(HomeService), new Uri("http://192.168.1.105:19200"));4 5 host.AddServiceEndpoint(typeof(IHomeService), new BasicHttpBinding(), "HomeServie");6 7 //公布元數據8 host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true });9 10 host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex"); 11 12 host.Open(); 13 14 Console.WriteLine("服務已經開啟。。。"); 15 16 Console.Read(); 17 }
2.Client
1 ChannelFactory<IHomeService> factory = new ChannelFactory<IHomeService>(new BasicHttpBinding(), new EndpointAddress("http://192.168.1.105:19200/HomeServie")); 2 3 var client = factory.CreateChannel(); 4 5 client.Update("王八蛋");
?
?
現在我想你大概看清楚了這玩意是個么樣子,一個建立在xml上面的一種消息格式,根元素是envelope,我知道這叼毛翻譯過來就是“信封”,所以就有了”封頭“
和”封體”,就是s:Header 和 s:Body,從這個soap中你可以看到它忽略了header,然后我們繼續往下看,還記得Update的意思嗎???如果你讀懂了上一篇,
你應該知道這是一個Action,也就是所謂的input消息。與之對應的就是UpdateResponse這個output消息,對吧,還記得xmlns="http://tempuri.org/">嗎?
它就是IHomeService的默認命名空間,對吧。。。
下一個我們關注的是Update這個Action中的<str>這個,你也看得到,這個就是上圖中Update方法中的str參數,最后我們來看一下UpdateResponse中
的<UpdateResult xmlns:a="http://schemas.datacontract.org/2004/07/MyService,不知道你是否還記得它就是WSDL中關于Student的XSD結
構,看下圖:
?
好了,wcf中的soap結構我們也大概了解了一下,不知道有沒有引發你對soap更深入的思考呢???
?
二:對soap的更深入思考
通過fiddler觀察,你應該也明白了,不管是客戶端還是服務端,wcf的高層封裝都是僅僅拿出了Envelope中的body節點,而其他節點對我們來說好像并
沒有什么卵用,比如我說的Header節點,這么說來,Header是不是有點浪費呢???那下面有一個問題來了,wcf在底層用什么來構造消息的呢???下面
我們大概找下client端的源碼。。。
?
通過上面的圖,你現在應該也知道了在.net中其實tmd的就是message構造的,所以我想告訴你的是:既然wcf在底層也是用message來構造的,何不我自己
就來構造message消息呢???豈不美哉???這樣我就可以隨意操作message,對吧。。。不然wcf這個高層封裝的叼毛,對我來說就是一種束縛。。。因
為我已經知道了service公布的wsdl,所以我可以輕松構造message。。。
?
三:用message來調用Server端
? 廢話不多說,構造message你一定要知道下圖中的三點:(input這個Action,契約方式 和 服務地址)。
?
好了,下面我先來構造數據契約,指定服務契約的命名空間 和 Action在Soap中的名稱
1 [DataContract(Namespace = "http://tempuri.org/", Name = "Update")] 2 class Test 3 { 4 [DataMember] 5 public string str { get; set; } 6 }
然后,我把這個數據契約塞到envelope中的body中,如下:
1 BasicHttpBinding bingding = new BasicHttpBinding();2 3 BindingParameterCollection param = new BindingParameterCollection();4 5 var u = new Test() { str = "王八蛋" };6 7 Message request = Message.CreateMessage(MessageVersion.Soap11, "http://tempuri.org/IHomeService/Update", u);8 9 IChannelFactory<IRequestChannel> factory = bingding.BuildChannelFactory<IRequestChannel>(param); 10 11 factory.Open(); 12 13 IRequestChannel channel = factory.CreateChannel(new EndpointAddress("http://192.168.1.105:19200/HomeServie")); 14 15 channel.Open(); 16 17 var result = channel.Request(request); 18 19 channel.Close(); 20 21 factory.Close();
接下來,我們跑起來看一下,效果咋樣。。。
?
看沒看到,這個就是我手工構造的Message,是不是太帥了。。。哈哈,太帥的應該在后面,剛才也說了,既然大家玩的都是Message,而你這個幾把wcf卻僅僅把
我的message.body拿出來了,那干脆我直接在契約方法中加message豈不是更好么???自由操作Message還有個什么好處呢??當然啦,我可以在Message的
Header中加一些參數token,client的ip地址,client的身份,client的時間等等這些統計信息,對吧。。。這樣才是最帥的,好了,說干就干,我們修改下server端的
契約方法,只用來接受Message。
?
server端:
1 public class HomeService : IHomeService2 {3 public Message Update(Message message)4 {5 var header = message.Headers;6 7 var ip = header.GetHeader<string>("ip", string.Empty);8 9 var currentTime = header.GetHeader<string>("currenttime", string.Empty); 10 11 //這個就是牛逼的 統計信息。。。 12 Console.WriteLine("客戶端的IP=" + ip + " 當前時間=" + currentTime); 13 14 return Message.CreateMessage(message.Version, message.Headers.Action + "Response", "等我吃完肯德基,再打死你這個傻逼!!!"); 15 } 16 }
?
client端:
1 namespace ConsoleApplication12 {3 class Program4 {5 static void Main(string[] args)6 {7 BasicHttpBinding bingding = new BasicHttpBinding();8 9 BindingParameterCollection param = new BindingParameterCollection(); 10 11 var u = new Test() { str = "王八蛋" }; 12 13 Message request = Message.CreateMessage(MessageVersion.Soap11, "http://tempuri.org/IHomeService/Update", u); 14 15 //在header中追加ip信息 16 request.Headers.Add(MessageHeader.CreateHeader("ip", string.Empty, Dns.GetHostByName(Dns.GetHostName()).AddressList[0].ToString())); 17 request.Headers.Add(MessageHeader.CreateHeader("currenttime", string.Empty, DateTime.Now)); 18 19 IChannelFactory<IRequestChannel> factory = bingding.BuildChannelFactory<IRequestChannel>(param); 20 21 factory.Open(); 22 23 IRequestChannel channel = factory.CreateChannel(new EndpointAddress("http://192.168.1.105:19200/HomeServie")); 24 25 channel.Open(); 26 27 var result = channel.Request(request); 28 29 channel.Close(); 30 31 factory.Close(); 32 } 33 } 34 35 [DataContract(Namespace = "http://tempuri.org/", Name = "Update")] 36 class Test 37 { 38 [DataMember] 39 public string str { get; set; } 40 } 41 }
?
然后我們用Fiddler監視一下結果:
?
現在一切都如我所愿,好了,我想你也大概明白了這個神奇的message,也不要忘了它就是wcf的基本通信單元,我要去吃肯德基了。。。。。。
?
十五天精通WCF——第五天 你需要了解的三個小技巧
?一: 服務是端點的集合
當你在開發wcf的時候,你或許已經注意到了一個service可以公布多個endpoint,確實是這樣,在wcf中有一句很經典的話,叫做“服務是端點的集合",就
比如說一個普普通通的服務,它就公布了一個服務端點,一個元數據端點,對吧。。。
仔細一想,這個問題就好玩了,既然一個service可以公布多個endpoint,而且我還知道wcf中有很多的binding,這些binding對應著很多的傳輸方式,那是不是
說我一個service可以用多種協議方法對外公布,比如說同時以nettcp,basic,msmqbinding,udp等方式公布,對吧,那這樣的話是不是超級好玩,如果對方
是非.net程序,那就可以調用我的basic,如果對方是.net程序,那是不是可以調用我的nettcp,對不對。。。當然啦,wcf無所不能,這是一個史上無比強大的牛
逼框架,牛逼的要死,已經逼得程序員只需隨便改幾個配置就能達到完全不一樣的效果。。。下面我同時用nettcp和basic的方式來同時公布服務,好了,現在我
們就來見證奇跡吧。。。
Service:
1 using System;2 using System.Runtime.Serialization;3 using System.ServiceModel;4 using System.ServiceModel.Channels;5 using System.Threading;6 7 namespace MyService8 {9 public class HomeService : IHomeService 10 { 11 public Student Update(Student message) 12 { 13 return new Student() { Name = "一線碼農" }; 14 } 15 } 16 17 [DataContract] 18 public class Student 19 { 20 [DataMember] 21 public string Name { get; set; } 22 23 [DataMember] 24 public int Age { get; set; } 25 } 26 }
Host :
1 class Program12 {3 static void Main(string[] args)4 {5 ServiceHost host = new ServiceHost(typeof(HomeService), new Uri("http://192.168.1.105:1920"));6 7 host.AddServiceEndpoint(typeof(IHomeService), new BasicHttpBinding(), "HomeServie");8 9 host.AddServiceEndpoint(typeof(IHomeService), new NetTcpBinding(), "net.tcp://192.168.1.105:1921/HomeServieTcp"); 10 11 host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true }); 12 13 host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex"); 14 15 host.Open(); 16 17 Console.Read(); 18 } 19 }
Client端:
1 class Program2 {3 static void Main(string[] args)4 {5 //basic 方式6 ChannelFactory<IHomeService> factory = new ChannelFactory<IHomeService>(new BasicHttpBinding(),7 new EndpointAddress("http://192.168.1.105:1920/HomeServie"));8 9 var client = factory.CreateChannel(); 10 11 var result = client.Update(new Student() { }); 12 13 14 //nettcp方式 15 factory = new ChannelFactory<IHomeService>(new NetTcpBinding(), 16 new EndpointAddress("net.tcp://192.168.1.105:1921/HomeServieTcp")); 17 18 client = factory.CreateChannel(); 19 20 result = client.Update(new Student() { }); 21 } 22 }
?
通過上面的代碼,是不是已經發現,我在client端,既可以用basic的方式調用,又可以用nettcp的方式調用,這個技巧是不是感覺wcf無比強大呢???
?
二:Host寄宿多個Service
我們知道wcf的寄宿方式有很多種,有iis,有windowservice,還有簡單方便的console方式,而默認情況下,我們最通常的方法都是一個service,一個寄宿,
而其實呢??? 其實一個寄宿host可以承載多個service,看起來是不是很好玩,如果說你有10個servcie,現在你只需要用一個console host就能寄宿起來,廢
話不多說,我演示一下給你看就好了。
Service:
1 namespace MyService2 {3 [ServiceContract]4 public interface IHomeService5 {6 [OperationContract]7 Student Update(Student message);8 }9 10 [ServiceContract] 11 public interface IFlyService 12 { 13 [OperationContract] 14 Student Fly(Student stu); 15 } 16 }
Host:
1 class Program12 {3 static void Main(string[] args)4 {5 //第一個: 這是Home服務6 ServiceHost host = new ServiceHost(typeof(HomeService), new Uri("http://192.168.1.105:1920"));7 host.AddServiceEndpoint(typeof(IHomeService), new BasicHttpBinding(), "HomeServie");8 host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true });9 host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex"); 10 host.Open(); 11 12 Console.WriteLine("Home服務開啟。。。。"); 13 14 //第一個: 這是Fly服務 15 var host2 = new ServiceHost(typeof(FlyService), new Uri("http://192.168.1.105:1930")); 16 host2.AddServiceEndpoint(typeof(IFlyService), new BasicHttpBinding(), "FlyServie"); 17 host2.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true }); 18 host2.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex"); 19 host2.Open(); 20 21 Console.WriteLine("Fly服務開啟。。。。"); 22 23 Console.Read(); 24 } 25 }
有沒有看到,現在兩個服務都開啟了,這種方式看起來是不是很爽呀,否則的話,你需要開啟兩個Host,這樣的話,我的手續就精簡了。。。對吧。。
?
三: Tcp中的端口共享
這玩意聽起來大家都懂,端口共享嘛,不就是兩個程序共享一個端口,對吧,在通常情況下,我們肯定會認為這無法做到,其實呢?在Wcf中我們還是可以玩
的,也就是一個PortSharingEnabled的事!!!如果說端口可以共享的話,那我們的service是不是就可以少開辟幾個端口呢?同樣這也方便我們進行service的管
理,下面我給大家繼續演示一下。。。很好玩的,么么噠
?
可以看到,我的兩個host都是用1920的端口,并且現在我真的開啟起來啦。。。。好了,三種技巧都說到了,我想你在現實的wcf開發中,或多或少的都能接
觸的到,希望對你有用~~~~
?
十五天精通WCF——第六天 你必須要了解的3種通信模式
? ? ?wcf已經說到第六天了,居然還沒有說到這玩意有幾種通信模式,慚愧慚愧,不過很簡單啦,單向,請求-響應,雙工模式,其中的第二種“請求-響應“
模式,這個大家不用動腦子都清楚,這一篇我大概來分析下。
?
一:“請求-響應“模式
? 如果你看了我上一篇的博文,你應該非常清楚這種類似“本地調用”的方式,wcf同樣也分為“同步”和“異步”兩種,不過不管是異步還是同步,最終都逃
不過是“請求-響應”這個事實,對吧。
?
1: 同步方式
這種方式我想沒什么好說的,前面幾篇我已經說的非常清楚了,具體使用方法可以參考我的前面幾篇文章。。。謝啦~~~~
?
2: 異步方式
通常我們都有這樣的一個思維,遇到耗時的東西第一反應就想到了多線程,畢竟多線程也是一種負載均衡,在wcf這種”請求-響應“模式,同樣也支持異
步,很神奇吧,而且神奇到可以在“服務引用“界面上做到一鍵生成,什么???你不信!!!!不信你看。。。
?
然后我非常好奇的看下XXXClient給我們生成的是個什么代碼。。。
?
通過client端的proxy代碼,你可以清楚的看到,這雞巴WCF真的不容易,給我們生成了兩種“異步模式”,第一種是最古老的beginXXX,endXXX模式,
還有一種是被Jeffrey Richter 嚴重鄙視的“事件異步模式”。。。沒什么好說的,截圖一下給大家看看。
?
二:“單向“模式
很多時候,我們或許都有這樣的需求,比如說訂單提交成功的時候,我需要給客戶發送郵件,但是你想想,我發送郵件這個任務只是我訂單流程的
一個“額外任務“,也就是說,它的失敗不應該會阻止我的訂單流程,并且它的邏輯時間不應該會阻礙我的下單總時間,對吧。。。這樣的話,我的訂單時
間才會最小化,為了達到不影響下單總時間的效果,我的想法就是,client端直接把消息丟給信道就好了,然后不管server端有沒有真的接收到,處理的
慢不慢,過的好不好,等等,非常開心的是,這些對wcf來說真的是小菜一碟,只需要一個輕輕松松的”IsOneWay=true“屬性就可以了。。。牛逼的要
死。。。還有就是因為是單向的,所以契約方法就沒有存在返回值的必要了,我說的對吧。。。嘿嘿~~~
1 [ServiceContract] 2 public interface IHomeService 3 { 4 [OperationContract(IsOneWay = true)] 5 void Update(Student message); 6 }
1 namespace MyService2 {3 public class HomeService : IHomeService4 {5 public void Update(Student message)6 {7 Console.WriteLine(message.Name);8 }9 } 10 11 [DataContract] 12 public class Student 13 { 14 [DataMember] 15 public string Name { get; set; } 16 17 [DataMember] 18 public int Age { get; set; } 19 } 20 }
為了驗證是否真的是單向通訊,我可以用二種方法驗證下。
?
1. wsdl中是否有output這個message
通過下面的圖,我想你看的很清楚了,你再也沒有找到我們熟悉的“output”這個message,這就說明貌似真的是單向的了,因為wsdl就是web服務的清單。
?
2. 使用fillder監視一下請求消息
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 HomeServiceClient client = new HomeServiceClient(); 6 7 client.Update(new Student() { Name = "hxc" }); 8 } 9 }
正如我在圖中說的那樣,非常奇怪,我的IsOneWay模式,竟然在http模式下行不通,但是你要記住,http模式天生就是“請求-響應”模式,它完全做不了
單向模式,說明白一點就是:“wcf發現你的bingding不支持單向“的時候,它并不會報錯,還是用自己天生的”請求-相應“模式來模擬”單向通信“,這就是你
看到的非常奇怪的Http 202這個http狀態碼,很多人包括我,都不知道http202 是幾個意思,沒關系,我們百科一下就好了。。。下面框框的里面的字,
已經說的非常清楚了,感謝感謝。。。
?
三:“雙向“ 模式
這個通訊其實沒什么好講的,也只有tcp模式才會天生支持,而http模式天生就不支持,就像上面一樣,如果非要用http來支持“雙向通訊“,那又是在
坑"wcf"他爹,這樣就會逼著他爹在底層再建立一個“請求-響應“模式來支持所謂的”雙向通訊“,而且”雙向通訊“這個玩意還不如用兩個單向的”請求-響應”模
式或者兩個“單向模式”來支持,而且兩個”請求-響應“模式比”雙向通訊“有更大的靈活性,反正我是對它不感冒,了解一下即可,如果大家比較感興趣,可以
在wcf官網上看一下:https://msdn.microsoft.com/zh-cn/library/ms735119.aspx。
? 好了,就說到這里,洗洗睡了,晚安~~~~
?
十五天精通WCF——第七天 Close和Abort到底該怎么用才對得起觀眾
?
一:文起緣由
? ? ? ? ? 寫這一篇的目的源自于最近看同事在寫wcf的時候,用特別感覺繁瑣而且云里霧里的嵌套try catch來防止client拋出異常,特別感覺奇怪,就比如下面的代碼。
1 public void StartNormalMarketing(int shopId, List<int> marketingIdList)2 {3 4 using (SendEventMarketingService.DistributeServiceClient client = new SendEventMarketingService.DistributeServiceClient())5 {6 try7 {8 9 client.StartByMarketingIDList(shopId, marketingIdList, SendEventMarketingService.MarketingType.NormalMarketing); 10 11 } 12 catch (Exception ex) 13 { 14 LogHelper.WriteLog("常規營銷活動開啟服務", ex); 15 } 16 finally 17 { 18 try 19 { 20 client.Close(); 21 } 22 catch (Exception) 23 { 24 client.Abort(); 25 } 26 } 27 } 28 }
看完上面的代碼,不知道你是否有什么感想?而且我還問了同事,為什么try catch要寫成這樣,同事說是根據什么書上來的什么最佳實踐,這話一說,我也不敢輕易
懷疑了,只能翻翻源代碼看看這話是否有道理,首先我來說說對這段代碼的第一感覺。。。
?
1. 代碼特別繁瑣
我們寫代碼,特別不喜歡繁瑣,上面的代碼就是一例,你try catch就try catch,還在finally中嵌套一個try catch,真的有點感覺像吃了兩只癩蛤蟆一樣。。。
?
2. 混淆close和abort的用法
這種代碼給人的感覺就是為什么不精簡一下呢???比如下面這樣,起碼還可以少寫一對try catch,對吧。
1 public void StartNormalMarketing(int shopId, List<int> marketingIdList)2 {3 4 using (SendEventMarketingService.DistributeServiceClient client = new SendEventMarketingService.DistributeServiceClient())5 {6 try7 {8 9 client.StartByMarketingIDList(shopId, marketingIdList, SendEventMarketingService.MarketingType.NormalMarketing); 10 11 client.Close(); 12 } 13 catch (Exception ex) 14 { 15 LogHelper.WriteLog("常規營銷活動開啟服務", ex); 16 17 client.Abort(); 18 } 19 } 20 }
而且乍一看這段代碼和文中開頭那一段代碼貌似實現一樣,但是某些人的“最佳實踐”卻不是這樣,所以確實會導致我這樣的后來人犯迷糊,對吧。。。反正我就是頭暈,
簡直就是弄糊涂到什么時候該用close,什么時候該用abort。。。
二:探索原理
為了弄明白到底可不可以用一個try catch來替代之,下面我們一起研究一下。
?
1. ?從代碼注釋角度甄別
從類庫的注釋中,可以比較有意思的看出,abort方法僅僅比close多一個“立即”,再無其他,有意思,不過這對我來說并沒有什么卵用,因為這個注釋太
籠統了,為了讓自己更加徹底的明白,只能來翻看下close和abort的源代碼。
?
2. ?從源碼角度甄別
為了方便讓ILSpy調試Client代碼,現在我決定用ChannelFactory來代替,如下圖:
1 namespace ConsoleApplication12 {3 class Program4 {5 static void Main(string[] args)6 {7 ChannelFactory<IHomeService> factory = new ChannelFactory<IHomeService>();8 9 try 10 { 11 var channel = factory.CreateChannel(); 12 13 factory.Close(); 14 } 15 catch (Exception ex) 16 { 17 factory.Abort(); 18 } 19 } 20 } 21 }
為了讓大家更好的理解,我把close方法的源碼提供如下:
1 // System.ServiceModel.Channels.CommunicationObject2 [__DynamicallyInvokable]3 public void Close(TimeSpan timeout)4 {5 if (timeout < TimeSpan.Zero)6 {7 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("timeout", SR.GetString("SFxTimeoutOutOfRange0")));8 }9 using ((DiagnosticUtility.ShouldUseActivity && this.TraceOpenAndClose) ? this.CreateCloseActivity() : null) 10 { 11 CommunicationState communicationState; 12 lock (this.ThisLock) 13 { 14 communicationState = this.state; 15 if (communicationState != CommunicationState.Closed) 16 { 17 this.state = CommunicationState.Closing; 18 } 19 this.closeCalled = true; 20 } 21 switch (communicationState) 22 { 23 case CommunicationState.Created: 24 case CommunicationState.Opening: 25 case CommunicationState.Faulted: 26 this.Abort(); 27 if (communicationState == CommunicationState.Faulted) 28 { 29 throw TraceUtility.ThrowHelperError(this.CreateFaultedException(), Guid.Empty, this); 30 } 31 goto IL_174; 32 case CommunicationState.Opened: 33 { 34 bool flag2 = true; 35 try 36 { 37 TimeoutHelper timeoutHelper = new TimeoutHelper(timeout); 38 this.OnClosing(); 39 if (!this.onClosingCalled) 40 { 41 throw TraceUtility.ThrowHelperError(this.CreateBaseClassMethodNotCalledException("OnClosing"), Guid.Empty, this); 42 } 43 this.OnClose(timeoutHelper.RemainingTime()); 44 this.OnClosed(); 45 if (!this.onClosedCalled) 46 { 47 throw TraceUtility.ThrowHelperError(this.CreateBaseClassMethodNotCalledException("OnClosed"), Guid.Empty, this); 48 } 49 flag2 = false; 50 goto IL_174; 51 } 52 finally 53 { 54 if (flag2) 55 { 56 if (DiagnosticUtility.ShouldTraceWarning) 57 { 58 TraceUtility.TraceEvent(TraceEventType.Warning, 524292, SR.GetString("TraceCodeCommunicationObjectCloseFailed", new object[] 59 { 60 this.GetCommunicationObjectType().ToString() 61 }), this); 62 } 63 this.Abort(); 64 } 65 } 66 break; 67 } 68 case CommunicationState.Closing: 69 case CommunicationState.Closed: 70 goto IL_174; 71 } 72 throw Fx.AssertAndThrow("CommunicationObject.BeginClose: Unknown CommunicationState"); 73 IL_174:; 74 } 75 }
然后我提供一下Abort代碼:
1 // System.ServiceModel.Channels.CommunicationObject2 [__DynamicallyInvokable]3 public void Abort()4 {5 lock (this.ThisLock)6 {7 if (this.aborted || this.state == CommunicationState.Closed)8 {9 return; 10 } 11 this.aborted = true; 12 this.state = CommunicationState.Closing; 13 } 14 if (DiagnosticUtility.ShouldTraceInformation) 15 { 16 TraceUtility.TraceEvent(TraceEventType.Information, 524290, SR.GetString("TraceCodeCommunicationObjectAborted", new object[] 17 { 18 TraceUtility.CreateSourceString(this) 19 }), this); 20 } 21 bool flag2 = true; 22 try 23 { 24 this.OnClosing(); 25 if (!this.onClosingCalled) 26 { 27 throw TraceUtility.ThrowHelperError(this.CreateBaseClassMethodNotCalledException("OnClosing"), Guid.Empty, this); 28 } 29 this.OnAbort(); 30 this.OnClosed(); 31 if (!this.onClosedCalled) 32 { 33 throw TraceUtility.ThrowHelperError(this.CreateBaseClassMethodNotCalledException("OnClosed"), Guid.Empty, this); 34 } 35 flag2 = false; 36 } 37 finally 38 { 39 if (flag2 && DiagnosticUtility.ShouldTraceWarning) 40 { 41 TraceUtility.TraceEvent(TraceEventType.Warning, 524291, SR.GetString("TraceCodeCommunicationObjectAbortFailed", new object[] 42 { 43 this.GetCommunicationObjectType().ToString() 44 }), this); 45 } 46 } 47 }
?
仔細觀察完這兩個方法,你會發現什么呢???至少我可以提出下面四個問題:
?
1:Abort是Close的子集嗎?
?是的,因為如果你看懂了Close,你會發現Close只針對Faulted 和Opened做了判斷,而其中在Faulted的枚舉下會調用原生的Abort方法。。。如下圖
?
2:我能監視Client的各種狀態嗎?比如Created,Opening,Fault,Closed等等。。。
當然可以了,wcf的信道老祖宗就是ICommunicationObject,而它就有5種監聽事件,這些就可以隨時監聽,懂伐???
1 static void Main(string[] args)2 {3 ChannelFactory<IHomeService> factory = new ChannelFactory<IHomeService>(new BasicHttpBinding(), new EndpointAddress("http://localhost:1920/HomeServie"));4 5 try6 {7 factory.Opened += (o, e) =>8 {9 Console.WriteLine("Opened"); 10 }; 11 12 factory.Closing += (o, e) => 13 { 14 Console.WriteLine("Closing"); 15 }; 16 17 factory.Closed += (o, e) => 18 { 19 Console.WriteLine("Closed"); 20 }; 21 22 var channel = factory.CreateChannel(); 23 24 var result = channel.Update(new Student() { }); 25 26 factory.Close(); 27 } 28 catch (Exception ex) 29 { 30 factory.Abort(); 31 } 32 }
?
3:Abort會拋出異常嗎?
從這個截圖中可以看到非常有意思的一段,那就是居然abort活生生的把異常給吞了。。。骨頭都不給吐出來。。。真tmd的神奇到家了,想想也有道理,因為只有
這樣,我們上層的代碼在catch中才不會二次拋出“未處理異常”了,對吧,再轉念看一下Close方法。
?
從上面圖中可以看到,Close在遇到Faulted之后調用Abort方法,如果說Abort方法調用失敗,Close方法會再次判斷狀態,如果還是Faulted的話,就會向上拋出
異常。。。這就是為什么Abort不會拋異常,Close會的原因,所以Close千萬不要放在Catch塊中。
?
4. Abort代碼大概都干了些什么
這個問題問的好,要能完美解決的話,我們看下代碼,如下圖,從圖中可以看到,Abort的大目的就是用來關閉信道,具體會經過closeing,abort和closed這
三個方法,同時,這三個事件也會被老祖宗ICommunicationObject監聽的到。
?
?
好了,最后我們關注的一個問題在于下面這條語句是否應該放在Try塊中???
1 ChannelFactory<IHomeService> factory = new ChannelFactory<IHomeService>(new BasicHttpBinding(), new EndpointAddress("http://localhost:1920/HomeServie"));
很簡單,我們簡要的看一下代碼,看里面是否會有“異常”拋出即可。。。。
?
可以看到,在new的過程中可能,或許會有異常的產生,所以最好把try catch改成下面這樣。。。
1 class Program2 {3 static void Main(string[] args)4 {5 ChannelFactory<IHomeService> factory = null;6 try7 {8 factory = new ChannelFactory<IHomeService>(new BasicHttpBinding(), new EndpointAddress("http://localhost:1920/HomeServie"));9 10 var channel = factory.CreateChannel(); 11 12 var result = channel.Update(new Student() { }); 13 14 factory.Close(); 15 16 throw new Exception(); 17 } 18 catch (Exception ex) 19 { 20 if (factory != null) 21 factory.Abort(); 22 } 23 } 24 }
?
好了,綜合我上面所說的一切,我個人覺得最好的方式應該是上面這樣,夜深了,睡覺了,晚安。
?
十五天精通WCF——第八天 對“綁定”的最后一點理解
轉眼已經中斷10幾天沒有寫博客了,也不是工作太忙,正好碰到了端午節,然后最近看天津臺的愛情保衛戰入迷了。。。太好看了,一直都是回味無窮。。。而且
涂磊老師話說的真是tmd的經典,然后就這樣耽擱了,好了,話不多說,這篇我們看看binding中最后一點需要知道的東西。
?
一:信道棧
我在之前的文章中多次提到信道棧,不知道大家對它的概念是否有了解,其實想想也還是蠻簡單的,既然是棧,那么這個棧肯定就不止一個元素了,對吧,第二個
的話,既然是棧,那么肯定就遵循FILO的原則,可能你會說,這個還是蠻抽象的,能給個具體的例子么???恭喜你,wcf中還真有一個方法CreateBindingElements,
下面我們具體看看。。。
?
1. ?簡單看看各種binding的棧中都有些什么
看到上面的監控窗口,是不是有點意思,在BasicHttpBinding的信道棧中有兩個元素,分別是HttpTransportBindingElement和TextMessageEncodingBindingEl
ement,通過名字也能很容易的判斷出來,一個是“http傳輸協議”,一個是“文本消息編碼協議”,然后再看看復雜一點的WSHttpBinding,你會發現,他不光有Basic
的所有東西,還包括SymmetricSecurityBindingElement(安全協議) 和?TransactionFlowBindingElement(事務流),現在你心中是不是有底了,起碼我知道各
種Binding里面都有些啥,為了更好的理解,我來畫一張簡圖。
上面這個圖,大概也就表達了我的意思,當我們Client在走WSHttpBinding這個協議的時候,Client端的InputMessage會先走?TransactionFlow,SymmetricSec
urity,TextMessageEncoding,最后走HttpTransport,然后Service端就按照客戶端進行“反向處理”,通過一陣禁臠之后,我們就拿到了安全的OutputMessage。
?
二:BindingElement的跨綁定性
你要是很仔細的話,你肯定會發現,其實Binding就是一個預先默認配置好的信道棧,對不對,你也看到了,每一種Binding都有屬于自己的BindingElements,
恰恰這些Elements是可以跨Binding的,也就是說我可以自由組合Elements,這樣是不是可以給我們這些寒酸的碼農最大的靈活性,對吧,舉個簡單的例子,
BasicHttpBinding有兩個綁定元素,其中對soap消息進行的是TextMessageEncoding編碼對吧,而netTcpBinding對soap進行的BinaryMessageEncoding,
然后你也應該知道了,我想做一個自定義的Binding,其中消息編碼是BinaryMessage,傳輸協議是HttpTransport,那怎么做呢????
Host文件:
1 class Program12 {3 static void Main(string[] args)4 {5 ServiceHost host = new ServiceHost(typeof(HomeService), new Uri("http://192.168.1.105:1920"));6 7 var customBinding = new CustomBinding();8 9 customBinding.Elements.Add(new BinaryMessageEncodingBindingElement()); 10 customBinding.Elements.Add(new HttpTransportBindingElement()); 11 12 host.AddServiceEndpoint(typeof(IHomeService), customBinding, "HomeServie"); 13 14 host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true }); 15 16 host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex"); 17 18 host.Open(); 19 20 Console.WriteLine("服務已經開啟!!!"); 21 22 Console.Read(); 23 } 24 }
?
Client調用:
1 static void Main(string[] args)2 {3 ServiceReference1.HomeServiceClient client = new ServiceReference1.HomeServiceClient();4 5 var result = client.Update("你好");6 7 Console.WriteLine("server value:" + result);8 9 Console.Read(); 10 }
最后我們用Fiddler監視一下,最后我們看看,都是些亂碼。
?
這篇就說到這里了,希望對你有幫助,下一篇我們看看WCF中的Behavior,很好玩的哦~~~
?
十五天精通WCF——第九天 高級玩法之自定義Behavior
?
終于我又看完了二期愛情保衛戰,太酸爽了,推薦鏈接:http://www.iqiyi.com/a_19rrgublqh.html?vfm=2008_aldbd,不多說,誰看誰入迷,下面言歸正傳,
看看這個很有意思的Behavior。
?
一: Behavior這個潑婦的厲害
? 在前面的文章中,我也清楚的說明了整個wcf通信流,而Behavior這個潑婦可以在wcf通信流中的任何地方插上一腳,蠻狠無比,利用的好,讓你上天堂,利用的不
好,讓你下地獄。。。下面讓你看看behavior到底有哪些可以注入的點???先畫個簡圖:
上面的圖,大概就是wcf的通信簡圖,所有藍色字體都是Behavior注入的點,其中Client和Service端都可以注入,如果按照功能分的話,又可以分為“操作級別”和
”端點級別“,下面我來簡要的分解下。
?
二:端點級別Behavior
從圖中你也可以看到,消息檢查器是放在Channel這個級別的,也就是說它可以監視Client和Server的入站請求,也就是說所有的請求都需要通過它轉發,如果
這樣的話,那我是不是可以在這個注入點上自由的修改,變更,攔截入站和出站請求,而且利用這個特性我還可以做很多的事情,比如日志記錄,記錄統計等等,下
面我們來看看這個怎么使用??? 只需要extends?IEndpointBehavior ?和?IDispatchMessageInspector,然后加入EndpointBehaviors即可。。。
?1.?IDispatchMessageInspector
1 public class MyDispatchMessageInspector : IDispatchMessageInspector2 {3 public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)4 {5 Console.WriteLine(request.ToString());6 return request;7 }8 9 public void BeforeSendReply(ref Message reply, object correlationState) 10 { 11 Console.WriteLine(reply.ToString()); 12 } 13 }
2.?IEndpointBehavior
1 public class MyEndpointBehavior : IEndpointBehavior2 {3 public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)4 {5 }6 7 public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)8 {9 } 10 11 public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher) 12 { 13 endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new MyDispatchMessageInspector()); 14 } 15 16 public void Validate(ServiceEndpoint endpoint) 17 { 18 } 19 }
3. 將MyEndpointBehavior加入到Host中
1 static void Main(string[] args)2 {3 ServiceHost host = new ServiceHost(typeof(HomeService), new Uri("http://127.0.0.1:1920"));4 5 host.AddServiceEndpoint(typeof(IHomeService), new BasicHttpBinding(), "HomeServie");6 7 host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true });8 9 host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex"); 10 11 host.Description.Endpoints[0].EndpointBehaviors.Add(new MyEndpointBehavior()); 12 13 host.Open(); 14 15 Console.WriteLine("服務已經開啟!!!"); 16 17 Console.Read(); 18 }
4. 最后我們看一下服務方法
1 public class HomeService : IHomeService 2 { 3 public string Update(string message) 4 { 5 Console.WriteLine("我在Action方法:" + message); 6 7 return "my reply!!!"; 8 } 9 }
?
下面看看效果。。。在效果圖中,你應該看到了。在我的Action中的方法前后各有一段“入站消息”和“出站消息”,是不是很爽???
?
三:操作級別Behavior
從文章開頭的簡圖中,你應該看到了,Operation級別的Behavior比較多,有“操作啟動器(IOperationInvoker)","參數檢查(IParameterInspector)“,
“消息格式化器(IDispatchMessageFormatter)”等等。。。 為什么說等等這個詞,很簡單啊,,,其實還有很多系統內置的,既然是Operation,那就必
然是針對方法的,還記得OperationContract是怎么套在方法上的嗎??? 是特性,對吧,,,同樣的道理,OperationBehavior也是一樣,那怎么用呢??
同樣也是很簡單的,繼承幾個接口即可。。。
?<1>?IParameterInspector 的玩法
? ?其實沒什么好說的,既然是屬于Operation下面的Behavior,那都是通過特性注入的,而這個IParameterInspector,可以做到類似Mvc的Model驗證,下面
我做個簡單的Action參數長度驗證(長度不超過8個字符)。
1.?IParameterInspector
1 public class MyIParameterInspector : IParameterInspector2 {3 public int MaxLength { get; set; }4 5 public MyIParameterInspector(int MaxLength)6 {7 this.MaxLength = MaxLength;8 }9 10 /// <summary> 11 /// 出站的操作 12 /// </summary> 13 /// <param name="operationName"></param> 14 /// <param name="outputs"></param> 15 /// <param name="returnValue"></param> 16 /// <param name="correlationState"></param> 17 public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState) 18 { 19 20 } 21 22 /// <summary> 23 /// 入站的參數 24 /// </summary> 25 /// <param name="operationName"></param> 26 /// <param name="inputs"></param> 27 /// <returns></returns> 28 public object BeforeCall(string operationName, object[] inputs) 29 { 30 foreach (var item in inputs) 31 { 32 if (Convert.ToString(item).Length > MaxLength) 33 { 34 throw new Exception("碼單,長度不能超過 " + MaxLength + " 個長度"); 35 } 36 } 37 38 return null; 39 } 40 }
2.?IOperationBehavior
1 public class MyOperationBehavior : Attribute, IOperationBehavior2 {3 public int MaxLength { get; set; }4 5 public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)6 {7 8 }9 10 public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) 11 { 12 13 } 14 15 public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation) 16 { 17 dispatchOperation.ParameterInspectors.Add(new MyIParameterInspector(MaxLength)); 18 } 19 20 public void Validate(OperationDescription operationDescription) 21 { 22 23 } 24 }
3. 在Action在加上MyOperationBehavior 這個 Attribute
1 public class HomeService : IHomeService2 {3 [MyOperationBehavior(MaxLength = 5)]4 public string Update(string message)5 {6 Console.WriteLine("我在Action方法:" + message);7 8 return "my reply!!!";9 } 10 }
4. 然后我在客戶端故意輸入大于5的字符,看看效果怎么樣???
1 public class Program12 {3 static void Main(string[] args)4 {5 HomeServiceClient client = new HomeServiceClient();6 7 client.Update("我故意輸入了很多的字符,哈哈。。。。。");8 9 Console.Read(); 10 } 11 }
5. 最后看看效果圖,可以看到,最終的入站消息會拋出一個異常。。。
?
?
<2>?MessageFormatter,IOperationInvoker?的玩法
剩下的這兩個玩法都差不多,你只需要extends一下,然后加入到OperationBehavior即可,有了上面的思想,我想下面這些使用起來都不是問題吧。。。
?
十五天精通WCF——第十天 學會用SvcConfigEditor來簡化配置
? ? ? ?我們在玩wcf項目的時候,都是自己手工編寫system.serviceModel下面的配置,雖然在webconfig中做wcf的服務配置的時候,vs提供大多
數的代碼提示,但對于不太熟悉服務配置的小鳥們來說,有些困難,而且一些服務配置也容易遺漏,大多情況下,我們都是copy一份服務配置,然
后在服務配置上面修修改改,對吧。。。其實呢,.net給我們提供了一個強大的scvconfigeditor這個工具化的軟件來幫助我們生成wcf的配置,是
不是很神奇???
?
一:工具在何處
當然在無比牛逼的Microsoft SDK下面啦,在C:\Program Files (x86)\Microsoft SDKs\Windows下面,你會找到很多的版本,如下圖:
對吧,你已經看到了很多的版本,當然啦,我肯定要找最新的啦,一禁臠,我進去了v8.0A,如下圖:
C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools
?
你應該也看到了,各種牛逼的工具,很眼饞吧,不過這一篇我們還是看重SvcConfigEditor。
?
二: 如何使用SvcConfigEditor
1. ? 雙擊打開,選擇“文件” => “新建配置”。
?
2. ?然后我們選擇 “新建服務” => “填寫服務名”
?
3. ?然后我們給service定義一個host, 點擊 "主機" => "新建“ => "填寫基址"。
?
4.??到這一步,你是不是特別想看一看生成的config配置是咋樣的???好啊,滿足你的虛榮心,我們只需要點
? ? ?擊"保存“,選擇一個路徑即可。。。
5. ?好了,你的虛榮心得到滿足了,下面我們來定義endpoint了,其實也是非常非常簡單的, 點擊”終結點"
? ? => "新建服務終結點",然后我們就象征性的填寫一些Address,Contract,Binding即可,如下圖:
?
6. 上面我們就已經定義了一個basichttpbinding了,下一步的話,我們還記得要公布一個mexhttpbinding,
? ? 這樣我的svcutil才能服務引用,對吧,所以方法也是很簡單,繼續“新建終結點”,如下圖:
7. 最后我還記得mex需要有一個behavior,讓http的get可以訪問,有了這個神器,同樣簡單,我們可以
? ? 點擊“高級” => "服務行為" => "新建"。
?
8. 最后我們保存來看一下生成的appconfig是啥樣的???
則么樣???我不需要寫一個字的config配置就完成了基本的服務配置,如果你還想玩高級的,可以自己試著琢磨琢磨SvcConfigEditor。
?
好了,差不多可以睡了,下一篇我們來研究研究 SvcConfigEditor中的診斷工具,很好玩的啦~~~~~
?
十五天精通WCF——第十一天 如何對wcf進行全程監控
說點題外話,我們在玩asp.net的時候,都知道有一個叼毛玩意叫做“生命周期”,我們可以用httpmodule在先于頁面的page_load中
做一些攔截,這樣做的好處有很多,比如記錄日志,參數過濾,全局登錄驗證等等。。。在wcf里面的話也是有類似的功能,第一種就是在
endpoint中加上runtime的behavior,這樣的話就可以先于“服務方法”做攔截,第二種方法呢,也就是我們這一篇所說的全程監控,俗稱
”診斷功能”。
?
一:診斷
我也說了,“診斷”這是wcf的一個專業術語,意思也就是監控wcf的所有動向,如果往下說的話,可以分為監控 wcf的message 和 wcf
本身的服務狀態信息和端對端的流轉消息。
1. 端對端的流轉消息
在玩wcf之前,不知道有多少人熟悉Diagnostics,對的,它就是.net自帶的日志類,當然在這個年代,記錄日志的組件有很多,比如
log4net,Nlog等等。。。不過話說回來,Diagnostics這個叼毛用起來還比較另類,它由“跟蹤源” 和 “監聽器”組成。分別就是TraceSource
來指定跟蹤源,用TraceListener來指定跟蹤源的監聽器,所以理所當然,TraceSource的所有蹤跡都會被TraceListener監聽到,下面我們
看看怎么玩。
<?xml version="1.0" encoding="utf-8"?> <configuration><system.diagnostics><sources><source name="System.ServiceModel" switchValue="ActivityTracing"><listeners><add name="mylisteners" type="System.Diagnostics.XmlWriterTraceListener" initializeData="E:\1.txt" /></listeners></source></sources><trace autoflush="true"/></system.diagnostics><system.serviceModel><behaviors><serviceBehaviors><behavior><serviceMetadata httpGetEnabled="true" /><serviceDebug includeExceptionDetailInFaults="false" /></behavior></serviceBehaviors></behaviors><services><service name="MyService.HomeService"><endpoint address="HomeService" binding="wsHttpBinding"contract="MyService.IHomeService"><identity><dns value="localhost" /></identity></endpoint><endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /><host><baseAddresses><add baseAddress="http://192.168.1.107:1920" /></baseAddresses></host></service></services></system.serviceModel></configuration>
?從上面的配置中可以看到,你有沒有發現我在配置system.diagnostics的時候和wcf一點關系都沒有,我并沒有在system.ServiceModel
下對diagnostics有一丁點的配置,對吧,這說明什么,說明“蹤跡跟蹤”功能和wcf一點關系都沒有,但卻可以完整的記錄wcf的蹤跡信息,然
后我稍微解釋下listeners節點,在這里我配置了一個XmlWriterTraceListener的監聽器,然后把輸出文件的路徑配置在initializeData屬性下,
其實都是diagnostics本身的知識范疇,和wcf一點關系都沒有,好了,下面我開啟下程序,看看到底都追蹤到什么?
有沒有看到,當我的服務啟動之后,追蹤信息就全部來了。。。但是接下來有一個問題來了,這個很雜亂的xml該怎么看才能最舒舒服服的
呢???不用著急啦,wcf同樣給我們提供了一個叫做SvcTraceView的工具,專門就是用來查找這個“蹤跡信息”的,工具的路徑在:
C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools
?
下面的事情就是打開它,附加一下1.txt文件就好了,如下圖:
從左邊的“活動圖”中大概可以看到HomeService這個服務啟動到運行經歷了一些什么樣的悲慘故事。。。有興趣的話,大家可以自己動
手試試啦。
?
2. 監控input和ouput的message
如果要監控message的話,我們需要再定義一個TraceSource 和 TraceListener即可,不過這次監聽的是System.ServiceModel.
MessageLogging跟蹤源,然后在System.ServiceModel下面配置一下message的參數,如下:
1 <?xml version="1.0" encoding="utf-8"?>2 <configuration>3 4 <system.diagnostics>5 <sources>6 <source name="System.ServiceModel" switchValue="ActivityTracing">7 <listeners>8 <add name="mylisteners" type="System.Diagnostics.XmlWriterTraceListener" initializeData="E:\1.txt" />9 </listeners> 10 </source> 11 <source name="System.ServiceModel.MessageLogging" switchValue="ActivityTracing"> 12 <listeners> 13 <add name="messagelogging" type="System.Diagnostics.XmlWriterTraceListener" initializeData="E:\2.txt"/> 14 </listeners> 15 </source> 16 </sources> 17 <trace autoflush="true"/> 18 </system.diagnostics> 19 20 <system.serviceModel> 21 22 <diagnostics> 23 <messageLogging logEntireMessage="true" logMalformedMessages="true" logMessagesAtTransportLevel="true" /> 24 </diagnostics> 25 26 <behaviors> 27 <serviceBehaviors> 28 <behavior> 29 <serviceMetadata httpGetEnabled="true" /> 30 <serviceDebug includeExceptionDetailInFaults="false" /> 31 </behavior> 32 </serviceBehaviors> 33 </behaviors> 34 35 <services> 36 <service name="MyService.HomeService"> 37 <endpoint address="HomeService" binding="basicHttpBinding" 38 contract="MyService.IHomeService"> 39 <identity> 40 <dns value="localhost" /> 41 </identity> 42 </endpoint> 43 <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /> 44 <host> 45 <baseAddresses> 46 <add baseAddress="http://192.168.1.107:1920" /> 47 </baseAddresses> 48 </host> 49 </service> 50 </services> 51 52 </system.serviceModel> 53 54 </configuration>
?
這次我準備來跑一下客戶端,調用Server端的Update方法,看看能抓到啥樣的Messsage。
?
?
現在我迫不及待的想用SvcTraceView打開下2.txt,看看都拿到了什么追蹤信息。。。
?
?
好了,這篇我也只是引路式的介紹下SvcTraceView,具體更深入的玩法,大家可以琢磨琢磨,對了,如果大家想對Source和Listener的
一些參數需要進一步了解,可以參考下SvcConfigEditor,比如下面這樣,一目了然,你懂的。。。
?
十五天精通WCF——第十二天 說說wcf中的那幾種序列化
我們都知道wcf是由信道棧組成的,在我們傳輸的參數走到傳輸信道層之前,先需要經過序列化的過程,也就是將參數序列化為message,這篇
我們就來說說這里的序列化,蠻有意思的,可能初學者也明白,在wcf中默認的序列化是DataContractSerializer,確實是這樣,不過wcf在信道中
其實不僅僅支持DataContractSerializer,它還支持其他類型的序列化,比如XmlSerializer,NetDataContractSerializer以及DataContractJson
Serializer,下面我們一起來見證下。
?
1.?XmlSerializer
???要了解XmlSerializer,我們先來簡單看看NetDataContractSerializer,在前面的文章中,我也說過DataContract就是將我們的model序列化為
XSD,第二點就是使用DataContract的原則就是你必須在Model上加DataContract,而且在你要序列化的字段上加DataMember。這樣才能夠正確的序列
化,為了演示,我們先看看默認的序列化Model會變成啥樣?
1 [DataContract]2 public class Student3 {4 [DataMember]5 public int ID { get; set; }6 7 [DataMember]8 public string Name { get; set; }9 10 [DataMember] 11 public string SNS { get; set; } 12 }
但是在有些情況下,你可能并不適合用DataContract,比如Model是第三方提供的,那么這個時候你的Model可能就不會有DataContract標記,那這樣的
話wcf就無法進行序列化,那我如果非要保證wcf能正常跑起來的話,還有其他好的辦法嗎???當然了,肯定有辦法,這就好比談戀愛一樣,總不能
在一棵樹上吊死吧,沒人誰離不開誰,也不會誰離開了誰會死,天涯何處無芳草,男兒何患無妻,對吧。Wcf中也一樣,既然DataContract用不了,自
然會有替代它的人,那這個人就是XmlSerializer,使用起來也很簡單,就是在契約方法上面加上XmlSerializerFormat即可,然后我們把Model的
DataContract全部去掉。
?
是不是很簡單,下面我們就要驗證一下,看看這個Format是否進入到了這個Operation的Behavior中,
?
從上面的圖中,你也看到了,?XmlSerializerFormat?已經被注入到Behavior中,并且是由類XmlSerializerOperationBehavior代為處理。
?
接下來,我們用fiddler監視一下,看看Message中的Body是否真的按照XmlSerializer?序列化了。
有沒有看到,這次Message的Body已經和文章開頭處的Message不一樣了。
?
2. NetDataContract
? ? ? ?這個玩意也沒什么好說的,光從表面上看,它和DataContract唯一不同的地方就是多了一個Net,所以你大概也能猜到,這個功能大概和DataCont
ract一樣,只不過比DataContract多了一個程序集保存,那這句話是什么意思呢???就是NetDataContract會把程序集的命名空間和類名都保存到XSD中,
在反序列化的過程中必須要用同樣的程序集才能解開,其實不管我們是做SOA或者面向對象編程都講究接口編程,而NetDataContract給你的印象就是面
向對象編程,當然這也有好處,比如說如果把程序集帶進去就好像秘鑰一樣,必須有它才能解開,對吧,所以導致wcf項目組并不對NetDataContract感冒
,所以在實際應用上也不建議使用。
?
3.?DataContractJsonSerializer
? ?看到上面這個帶有Json的字樣,我想大家都知道這玩意是干什么的???沒錯,他就是將我們的Model序列化成Json,這在wcf的rest編碼使用的很廣,
如果大家有興趣的話,我在下一篇會詳細描述,這里我們先簡單看一看。
?
好了,這一篇就說這些了,洗洗睡了。。。
?
十五天精通WCF——第十三天 用WCF來玩Rest
? ? ? ? 在我們玩wcf的時候,都會潛意識的覺得wcf就是通過soap協議交換消息的,并且可以在basic,tcp,msmq等等綁定中任意切換,
牛逼的一塌糊涂,但是呢,如果說哪一天wcf不再使用soap協議,而是采用json格式的字符串,是不是有一點顛覆你對wcf的認識的???
從傳統意義上說,wcf是非常重量級的,很明白的一個例子就是太多太多的配置,尤其是Behavior的配置,而且behavior對wcf來說又是重
中之重,它對wcf的擴展和性能又是最重要的,可恨的是wcf在binding,behavior,contract之中的配置又是非常非常的保守,可以說用
wcf來玩分布式,這些默認配置是完全做不到的,就比如說basicbinding的基類HttpBindingBase。
?
抱怨的話我也不說了,可能微軟也覺得這個問題是個不小的問題,然后就有了輕量級的 asp.net web api,你可以看到它和wcf比起來精
簡多了,也許讓我們這些碼農更加的專注于業務吧,既然wcf帶了這玩意,我也得必須約談一下。
?
一:UriTemplate
要說rest,還得先說UriTemplate,因為wcf用UriTemplate來做rest中的uri模板匹配,然后用WebInvoke這個OperationBehavior
插入到wcf的心臟中,說的玄乎一點,這個就有點像mvc中的路由匹配機制,下面我舉個例子:
?
1. 用UriTemplate來告知可以監視的完整Url
從下面的圖中,可以看到三個元素:服務地址,模板,入參(這里面的”1“),這三個元素組合在一起,就構成了完整的remote url,
然后這個完整的url就是我模板(/User/{id})監視的對象。
?
2. 通過UriTemplate來解析url中的參數。
既然可以構建url,那當然可以解析url啦,對吧,下面這張圖可以很清晰的告知你,當外來的url=http://127.0.1:1920/HomeService
/User/1過來的時候應該被哪個uriTemplate所接收。
?
正是因為UriTemplate具有這樣的url構建和解析能力,所以wcf就把UriTemplate作為WebInvoke和WebGet這兩個屬性的參數來動態
解析外來的url,然后根據這個url分配到具體的服務方法上,下面我們具體看一看。
?
二:WebGet,WebInvoke的使用
剛才也說了,WebGet和WebInvoke正是用了UriTemplate,才具有了路由轉向的功能,還有就是默認返回的是xml,這里就用json
值作為服務返回的格式
1 [ServiceContract]2 public interface IHomeService3 {4 [OperationContract]5 [WebGet(UriTemplate = "Get/{id}", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]6 Student Get(string id);7 8 [OperationContract]9 [WebInvoke(Method = "POST", UriTemplate = "Add", RequestFormat = WebMessageFormat.Json, 10 ResponseFormat = WebMessageFormat.Json)] 11 string Add(Student stu); 12 }
對了,Rest推薦使用Http協議中的Get,Post,Delete,Put來作為CURD的狀態機制,然后就是你如果看懂了UriTemplate,那你現在應
該知道這個Template在監視什么類型的url。做完了上面的coding,下面我們需要在webconfig中通過behavior來指定啟動“web編程模型”,
就比如下面這樣。
1 <?xml version="1.0" encoding="utf-8"?>2 <configuration>3 4 <system.diagnostics>5 <sources>6 <source name="System.ServiceModel" switchValue="ActivityTracing">7 <listeners>8 <add name="mylisteners" type="System.Diagnostics.XmlWriterTraceListener" initializeData="E:\1.txt" />9 </listeners> 10 </source> 11 <source name="System.ServiceModel.MessageLogging" switchValue="ActivityTracing"> 12 <listeners> 13 <add name="messagelogging" type="System.Diagnostics.XmlWriterTraceListener" initializeData="E:\2.txt"/> 14 </listeners> 15 </source> 16 </sources> 17 <trace autoflush="true"/> 18 </system.diagnostics> 19 20 <system.serviceModel> 21 22 <diagnostics> 23 <messageLogging logEntireMessage="true" logMalformedMessages="true" logMessagesAtTransportLevel="true" /> 24 </diagnostics> 25 26 <behaviors> 27 <serviceBehaviors> 28 <behavior> 29 <serviceMetadata httpGetEnabled="true" /> 30 <serviceDebug includeExceptionDetailInFaults="true" /> 31 </behavior> 32 </serviceBehaviors> 33 <endpointBehaviors> 34 <behavior name="webbehavior"> 35 <webHttp /> 36 </behavior> 37 </endpointBehaviors> 38 </behaviors> 39 40 <services> 41 <service name="MyService.HomeService"> 42 <endpoint address="HomeService" binding="webHttpBinding" behaviorConfiguration="webbehavior" 43 contract="MyService.IHomeService"> 44 <identity> 45 <dns value="localhost" /> 46 </identity> 47 </endpoint> 48 <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /> 49 <host> 50 <baseAddresses> 51 <add baseAddress="http://127.0.0.1:1920" /> 52 </baseAddresses> 53 </host> 54 </service> 55 </services> 56 57 </system.serviceModel> 58 59 </configuration>
?
其實呢?也就是代碼中的WebHttpBehavior類
?
好了,我現在服務地址也出來了:http://127.0.0.1:1920 ,然后服務方法的template也指定了。只要http.sys監控到了template
匹配的url,服務方法就會被執行,比如我現在在瀏覽器里面輸入:http://127.0.0.1:1920/HomeService/Get/1 ?來測試下Get操作。
可以看到,get方法成功了,也正確的匹配了我的服務方法Get。
1 public class HomeService : IHomeService2 {3 public Student Get(string id)4 {5 return new Student() { ID = Convert.ToInt32(id), Name = "hxc", SNS = "001" };6 }7 8 public string Add(Student stu)9 { 10 return "hello"; 11 } 12 }
?
然后我們看看Add方法,我在HttpWebRequest中模擬測試如下。

?
?
好了,大概就說這么多了,如果說你不嫌麻煩,你可以用WCF Rest,還有就是不要忘了很多的默認配置,如果你覺得太繁瑣,
可以用用asp.net web api。
?
十五天精通WCF——第十四天 一起聊聊FaultException
?我們在玩web編程的時候,可能你會不經意的見到一些http500的錯誤,我想你應該不會陌生的,原因你應該也知道,服務器異常嘛,
這時候clr會把這個未處理的異常拋給iis并且包裝成http500的錯誤返回到客戶端,就比如下面這樣。
?
?
從這張圖中,我故意輸入了xss字符,然后的然后,web程序自爆異常,其實我想表達的意思就是,雖然說web程序拋異常了,但不代表iis就
掛了,所以iis還是需要給客戶端做出反饋,這就有了http header,和body信息,同樣的道理,wcf的服務器異常機制也是這樣。。。service
拋出了異常,不代表console就掛了,console要做的事情就是把這個異常包裝起來丟給調用方,而wcf是怎么包裝的呢???就是用了這篇所
說的FaultException。。。
?
一:FaultException
1. faultexception是干什么的?
剛才我也說了,這個異常就是wcf來包裝遠程錯誤的,具體的類含義就是表示“SOAP錯誤“,如果你夠細心的話,你還會發現到它有個屬性
叫Serializable,有了它,這個叼毛就可以序列化到Soap消息中,對伐???
?
2. 如果挖出faultexception?
挖出這個exception的方法有很多,比如我來造一個“除以0”的異常,如下所示:
Service:
1 public class HomeService : IHomeService2 {3 public Student Get(string id)4 {5 //這里必然會拋出異常。。。6 var result = Convert.ToInt32(id) / Convert.ToInt32("0");7 8 return new Student() { ID = Convert.ToInt32(id), Name = "hxc", SNS = "001" };9 } 10 }
Client:
1 public class Program12 {3 static void Main(string[] args)4 {5 using (HomeServiceClient client = new HomeServiceClient())6 {7 try8 {9 var result = client.Get("1"); 10 } 11 catch (Exception ex) 12 { 13 14 } 15 } 16 } 17 }
?
看到了沒有,雖然wcf的service已經拋出異常了,但是還是被clr用Faultexception包裝起來了,正如你看到了s:Fault節點,仔細往下看的話,
你還會看到faultcode,faultstring,detail等等屬性節點,那下面有個問題就來了,我們平時在Client端都習慣這么寫。
1 using (HomeServiceClient client = new HomeServiceClient())2 {3 try4 {5 var result = client.Get("1");6 }7 catch (Exception ex)8 {9 client.Abort(); 10 } 11 }
但是這么寫有個什么問題呢???就是不管客戶端拋出什么異常,我們都習慣用基類異常Exception捕獲,但是wcf有一點非常惡心的就是,
它的異常信息非常的少,第一眼根本看不出個一二三,這是因為所有的異常你都用頂級的exception捕獲,自然你能知道的信息就非常少,
這也很正常,如果你想要更詳細的信息,你是不是應該在Client端寫上更具體的異常捕獲類呢???就比如你現在已經知道的FaultException
是因為服務器的錯誤都是由它處理的。
?
如果現在你按照上圖中所coding的那樣,你是不是對異常信息可以了解的更深,起碼你知道這個異常的拋出,絕逼是因為通道是正常的,只是
servcie拋出異常了而已。。。那你可能要問了,我這話的言外之意就是還有其他異常類也會捕獲wcf拋出的異常,對的,比如說你的信道出現
故障,這時候會拋出一個“通信異常(CommunicationException)”。
?
三:如何挖出“通信異常”
?挖出這個異常,也是很簡單的,現在我們需要使用”會話級別“的binding,比如說nettcpbinding,wshttpbinding,這里的話,我選擇
后者,因為是這樣的,第一次服務器拋異常以后,客戶端和服務器端通信信道就會關閉,如果你在客戶端不重新new一個client,那么這時候你
第二次再使用client的話,這個時候就會產生“信道故障“,拋出CommunicationException,而當你看到CommunicationException的時候,
你可以非常有自信的說,老子的wcf根本就沒有連接到service,而是在client端就被殺死了。。。下面我演示一下。
?
四:自定義FaultException
現在你應該知道了,只要是Servcie的Exception都會拋出 FaultException,對吧,而且你用Fiddler觀察的話,也看的出其中的faultcode
和faultstring貌似都不是很詳細,那我就有一個想法了,既然wcf會自己給我包裝個FaultException,那何不我自己就在發生異常的時候自己包
裝一個自定義的FaultException,然后我可以包裝一些我自己想要告訴客戶端的信息,這樣的話是不是靈活性非常的大呢???想法很不錯,wcf
也是恩準這么做的,下面我把service的get方法更改如下,在FaultException中自定義Reason,Code,Action等等自定義信息。
1 public class HomeService : IHomeService2 {3 public Student Get(string id)4 {5 try6 {7 //這里必然會拋出異常。。。8 var result = Convert.ToInt32(id) / Convert.ToInt32("0");9 10 return new Student() { ID = Convert.ToInt32(id), Name = "hxc", SNS = "001" }; 11 } 12 catch (Exception ex) 13 { 14 var reason = new FaultReason("你這個戰斗力只有五的渣渣。。。 這么簡單的錯誤都出來了,搞個雞巴毛"); 15 16 var code = new FaultCode("500"); 17 18 var faultException = new FaultException(reason, code, "是Get這個王八蛋"); 19 20 throw faultException; 21 } 22 } 23 }
好了,大概就說這么多了,我的目的也很簡單,在寫wcf的client的時候,盡量做到異常越具體越好,這樣方便我們盡可能快的排查問題,因為
wcf的異常信息真的太tmd坑爹了!!!減輕痛苦,從小做起~~~
?
十五天精通WCF——終結篇 那些你需要注意的坑
? ? ? ? ? 終于一路走來,到了本系列的最后一篇了,這一篇也沒什么好說的,整體知識框架已經在前面的系列文章中講完了,wcf的配置眾多,如果
不加一些指定配置,你可能會遇到一些災難性的后果,快來一睹為快吧。
?
一: 第一個大坑 【數據傳輸量】
我們使用wcf的目的,就是用來進行分布式的數據交互,既然是交互,就一定要進行數據交換,可能一些新人并沒有注意到wcf在數據傳輸量上
面做了一個大小限制,比如我現在要傳輸一個2m的txt給service,會出現什么情況???
1 static void Main(string[] args)2 {3 try4 {5 var txt = File.ReadAllText("E:\\1.txt");6 7 HomeServiceClient client = new HomeServiceClient();8 9 client.Get(txt); 10 11 int i = 10; 12 13 } 14 catch (Exception ex) 15 { 16 17 throw; 18 } 19 }
?
可是的可是,我們在玩aspnet的時候,再大的傳輸量都見過,但為什么這玩意就拋異常了呢???下面一個問題就來了,這個傳輸默認值到底
是多少??? 接下來我們就用ILSpy翻翻看。
?
可以看到,這個叼毛玩意居然只有 64k。。。沒錯,你看到的就是64k,也就說明你的傳輸量不能大于64k,否則請求就會在client端拒絕,
知道了原因,我們現在就可以這么修改config了。
<bindings><netTcpBinding><binding name="MySessionBinding" maxReceivedMessageSize="2147483647"/></netTcpBinding></bindings>
?
有很多資料在配置這個坑的時候,也會使用MaxBufferSize 和?MaxBufferPoolSize,就是用來增加緩沖區和緩沖池的大小。
?
一: 第二個大坑 【并發量太低】
說起這個大坑,還得先從一段代碼說起,下面是一段對服務進行2w次并發調用,然后我們看看效果。
public class Program1{static void Main(string[] args){try{for (int i = 0; i < 200000; i++){try{Task.Factory.StartNew((obj) =>{try{HomeServiceClient client = new HomeServiceClient();Console.WriteLine("第 {0} 個請求開始。。。", obj);client.Get("12312");Console.WriteLine("第 {0} 個請求結束。。。", obj);}catch (Exception ex){Console.WriteLine(ex.Message);}}, i);}catch (Exception ex){Console.WriteLine(ex.Message);}}Console.Read();}catch (Exception ex){throw;}}}
?
? ? 從上面你可以看到,當并發數達到800左右的時候,servcie端就開始拒絕client端過來的請求了,并且之后的1min的時間里,client端
開始出現超時異常,這肯定不是我想看到的,?那有人就要說了,我的并發達到800多很正常啊,如果提高這個并發呢???其實在wcf里面
有一個叫做ServiceThrottlingElement綁定元素,它就是用來控制服務端的并發數。
?
這三個屬性的大概意思,我想大家都看的明白,不過有點奇怪的是,這三個屬性的默認值 和 ILSpy中看到的不一樣。。。
?
也懶的研究源碼了,不管怎么樣,反正這三個屬性值都是int類型的,所以我將他們設置為int.maxValue就好了。
<system.serviceModel><behaviors ><serviceBehaviors ><behavior name="nettcpBehavior"><serviceMetadata httpGetEnabled="false" /><!--是否在錯誤中包含有關異常的詳細信息--><serviceDebug includeExceptionDetailInFaults="True" /><serviceThrottling maxConcurrentCalls="2147483647" maxConcurrentInstances="2147483647" maxConcurrentSessions="2147483647" /></behavior></serviceBehaviors></behaviors><bindings><netTcpBinding><binding name="MySessionBinding" /></netTcpBinding></bindings><services><service behaviorConfiguration="nettcpBehavior" name="MyService.HomeService"><endpoint address="net.tcp://127.0.0.1:19200/HomeService" binding="netTcpBinding"bindingConfiguration="MySessionBinding" contract="MyService.IHomeService" /><endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /><host><baseAddresses><add baseAddress="http://127.0.0.1:1920" /></baseAddresses></host></service></services></system.serviceModel>
?
然后我們再把程序跑起來看一看。。。
?
? ? ? 現在你可以發現并發早已突破800了,不過你要記住,如果并發數太多,容易造成系統資源耗盡,導致崩潰,這時候負載均衡就來
了,對吧,wcf需要修改的配置還有很多,正因為wcf框架龐大,很多默認配置不符合生產需求,所以大家在工作中需要注意,這個系列
就到此打住了,希望對你有幫助。