wcf精通1-15

十五天精通WCF——第一天 三種Binding讓你KO80%的業務

 

  

  轉眼wcf技術已經出現不少年了,也在.net界混的風生水起,同時.net也是一個高度封裝的框架,做爲在wcf食物鏈最頂端的咱們所能作的任務已經簡單的不能再簡單了,html

再簡單的話馬路上的大媽也能寫wcf了,好了,wcf最基本的概念咱們放在後面慢慢分析,下面咱們來看看神奇的3個binding如何KO咱們實際場景中的80%的業務場景。程序員

 

一:basicHttpBindingweb

  做爲入門第一篇,也就不深刻談談basic中的信道棧中那些啥東西了,你只須要知道有ABC三個要素,注意不是姨媽巾哦,若是須要詳細瞭解,能夠觀賞我之前的系列。在編程

這裏我就很少說了,太簡單的東西沒意思,先看個例子簡單感覺了,你只需知道的是basic走的是http協議就行了,傳輸消息爲soap。json

1. 契約windows

複製代碼
 1 using System.Runtime.Serialization;
 2 using System.ServiceModel;
 3 
 4 namespace MyService
 5 {
 6     [ServiceContract]
 7     public interface IHomeService
 8     {
 9         [OperationContract]
10         int GetLength(string name);
11     }
12 }
複製代碼

2. 實現類api

複製代碼
 1 using System;
 2 using System.Messaging;
 3 using System.Threading;
 4 
 5 namespace MyService
 6 {
 7     public class HomeService : IHomeService
 8     {
 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 MyService
 5 {
 6     class Program
 7     {
 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 ConsoleApplication1
 4 {
 5     class Program
 6     {
 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 Program1
 2     {
 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 Program1
 2     {
 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 : IHomeService
 2     {
 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 ConsoleApplication1
 2 {
 3     class Program
 4     {
 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 MyService
 8 {
 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 Program1
 2     {
 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 Program
 2     {
 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 MyService
 2     {
 3         [ServiceContract]
 4         public interface IHomeService
 5         {
 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 Program1
 2     {
 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 MyService
 2 {
 3     public class HomeService : IHomeService
 4     {
 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                 try
 7                 {
 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                 try
 7                 {
 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 ConsoleApplication1
 2 {
 3     class Program
 4     {
 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.CommunicationObject
 2 [__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.CommunicationObject
 2 [__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             try
 6             {
 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 Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             ChannelFactory<IHomeService> factory = null;
 6             try
 7             {
 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 Program1
 2     {
 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 : IDispatchMessageInspector
 2     {
 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 : IEndpointBehavior
 2     {
 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 : IParameterInspector
 2     {
 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, IOperationBehavior
 2     {
 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 : IHomeService
 2     {
 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 Program1
 2     {
 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 Student
 3     {
 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 IHomeService
 3     {
 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 : IHomeService
 2     {
 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中模擬測試以下。

  View Code

 

 

好了,大概就說這麼多了,若是說你不嫌麻煩,你能夠用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 : IHomeService
 2     {
 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 Program1
 2     {
 3         static void Main(string[] args)
 4         {
 5             using (HomeServiceClient client = new HomeServiceClient())
 6             {
 7                 try
 8                 {
 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                 try
 4                 {
 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 : IHomeService
 2     {
 3         public Student Get(string id)
 4         {
 5             try
 6             {
 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             try
 4             {
 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框架龐大,不少默認配置不符合生產需求,因此你們在工做中須要注意,這個系列

就到此打住了,但願對你有幫助。

相關文章
相關標籤/搜索