文章出處

基于SOAP消息格式的WCF之所以強大原因之一是因為SOAP消息頭的高度擴展性。相應的WS-*協議很多都體現在消息頭封裝的信息上,包括諸如尋址,需要調用方法名,維護Session的信息等等……

 

SOAP示例

復制代碼
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing"
xmlns:s
="http://www.w3.org/2003/05/soap-envelope">


下面就是很長很有內涵的消息頭
<s:Header>
<a:Action s:mustUnderstand="1" u:Id="_2" xmlns:u="http://docs.oasis-
open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
>

http://www.thatindigogirl.com/samples/2006/06/PhotoUploadContract/UploadPhoto
</a:Action>
<a:MessageID u:Id="_3" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-
200401-wss-wssecurity-utility-1.0.xsd"
>

urn:uuid:940d5687-fcb2-44b5-a696-cc7eba22524b</a:MessageID>
<a:ReplyTo u:Id="_4" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-
200401-wss-wssecurity-utility-1.0.xsd"
>

<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<a:To s:mustUnderstand="1" u:Id="_5" xmlns:u="http://docs.oasis-
open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
>

http://localhost:3045/PhotoApplication/PhotoManagerService.svc/Soap12</a:To>
<o:Security .../>
</s:Header>

Body部分省略
<s:Body>........</s:Body>

</s:Envelope>
復制代碼



我們要清楚的是,SOAP協議規定的只是一種帶有固定格式的XML。規定了由消息頭和消息體兩部分組成,并且規定了相應的節點。

WCF中的REST是一種POX(plain old xml),換句話說就是沒有任何規定的xml,沒有強行規定消息頭,沒有消息體。甚至因為沒有規定,所以傳輸的格式用不用xml都不是大問題,Json也可以做為傳輸數據的封裝格式。

沒有消息頭,所以我們需要另外一種方式定位所需調用的方法,傳遞我們需要傳送的參數信息。而我們能依靠的,就是Http協議給我們提供的信息。

總的來說,基于SOAP消息的WCF是對于WS-*規范的一種實現。而REST方式的WCF和采用basicHttpBinding差不多,也就是什么都沒有,相應的規范則由我們自己定義和實現。

 

我們都知道(額,不知道的話以后再說)如果調用基于SOAP消息的WCF服務的客戶端也是WCF的話,那么客戶端就可以使用透明代理,在通過反射把對方法棧的調用攔截并轉換為相應的消息,發送給服務提供者。其中相應的調用方法信息則封裝在action屬性里。服務端通過消息過濾器查找到相應的信息,并實例化service對象(服務對象也有可能已經實例化好了,具體看你的InstanceContextMode),調用相應的方法。

為了方便演示,我直接通過VS自帶的模板新建了一個WCF Liberary項目用來說明上面描述的步驟。

圖一:Contract的定義

    [ServiceContract]
    public interface IService1
    { 
        [OperationContract]
        CompositeType GetDataUsingDataContract(CompositeType composite);

    }


圖2:根據Contract信息生成的元數據,包含相應的Action節點,用來告知客戶端如何序列化相應的SOAP消息頭:

復制代碼
<wsdl:portType name="IService1">
<wsdl:operation name="GetDataUsingDataContract">
<wsdl:input wsaw:Action="http://tempuri.org/IService1/GetDataUsingDataContract" message="tns:IService1_GetDataUsingDataContract_InputMessage"/>
<wsdl:output wsaw:Action="http://tempuri.org/IService1/GetDataUsingDataContractResponse" message="tns:IService1_GetDataUsingDataContract_OutputMessage"/>
</wsdl:operation>
</wsdl:portType>
復制代碼
 

 

圖3:在客戶端調用方法時,透明代理把對方法的調用攔截并轉換SOAP消息發送給服務端,下圖就是轉換后發出的SOAP:

復制代碼
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
  注意下面這一行
<a:Action s:mustUnderstand="1">http://tempuri.org/IService1/GetDataUsingDataContract</a:Action>
<a:MessageID>urn:uuid:2b959c1a-a159-4d25-bbb0-9f644b92fa68</a:MessageID>
<a:ReplyTo>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
</s:Header>
<s:Body>
<GetDataUsingDataContract xmlns="http://tempuri.org/">
<composite xmlns:d4p1="http://schemas.datacontract.org/2004/07/WcfServiceLibrary1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<d4p1:BoolValue>true</d4p1:BoolValue>
<d4p1:StringValue>df</d4p1:StringValue>
</composite>
</GetDataUsingDataContract>
</s:Body>
</s:Envelope>
復制代碼




現在問題出來了,如果我們不是通過相應的序列化器,而是選擇自己發送沒有相應消息頭的XML,比如:

    <GetDataUsingDataContract xmlns="http://tempuri.org/">
<composite>
<d4p1:BoolValue>true</d4p1:BoolValue>
<d4p1:StringValue>df</d4p1:StringValue>
</composite>
</GetDataUsingDataContract>



或者干脆就是在瀏覽器輸入一個地址,當然也就沒有action,會出現什么問題?

 我們最直接的反應應該是,服務端找不到相應的信息,報錯:

<Fault><Code><Value>Sender</Value><Subcode><Value>a:ActionNotSupported</Value></Subcode></Code><Reason><Text xml:lang="zh-CN">由于 ContractFilter 在 EndpointDispatcher 不匹配,因此 Action 為“”的消息無法在接收方處理。這可能是由于協定不匹配(發送方和接收方 Action 不匹配)或發送方和接收方綁定/安全不匹配。請檢查發送方和接收方是否具有相同的協定和綁定(包括安全要求,如 Message、Transport、None)。</Text></Reason></Fault>


因此我們的OperationContract特性需要修改一下,處理那些找不到調用信息的請求:

為了方便說明,因此我新寫了一個方法。

        //把[OperationContract] 注釋掉,改成下面的寫法
[OperationContract(Action = "*", ReplyAction = "*")]
Message Resource(Message input);

這樣所有由客戶端發送過來,沒有被處理的消息都會被送到這里進行處理,因此我們就可以處理那些不帶消息頭的信息了。以Message類作為參數是為了方便使用Http中所包含的信息,因為可以方便的轉換為HttpRequestMessageProperty 類型。下面這個方法實現的簡單功能就是根據參數信息獲取相應的文件。比如訪問http://localhost:9527/Service?job ,獲取job.xml的信息,此時沒有消息頭并且也不再需要消息頭篩選:

        public Message Resource(Message input)
        {
            HttpRequestMessageProperty httpRequestProperty = (HttpRequestMessageProperty)input.Properties[HttpRequestMessageProperty.Name];
            string query = httpRequestProperty.QueryString;
            string fileName = string.Format("{0}.xml", query);
            switch (httpRequestProperty.Method)
            {
                case "GET":
                    Message message = null;
                    if (File.Exists(fileName))
                    {
                        XmlDocument document = new XmlDocument();
                        document.Load(fileName);
                        message = Message.CreateMessage(
                        MessageVersion.None,
                        "*",
                        new XmlNodeReader(document.DocumentElement));
                    }
                    else
                    {
                        message = Message.CreateMessage(
                        MessageVersion.None,
                        "*");
                    }
                    return message;
                default:
                    return Message.CreateMessage(
                    MessageVersion.None, "*");
            }
        }

相應的配置文件,不用多說了吧,既然采用的是REST,那么編碼方式肯定就是文本,傳輸方式肯定就是Http。注意MessageVersion = None意味著咱們不需要對輸入的數據的格式有要求
復制代碼
    <services>
<service name="Service.SimpleServer">
<endpoint address="Service" binding="customBinding" bindingConfiguration="RESTPOXBinding"
contract
="IService.ISimpleResource" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:9527/" />
</baseAddresses>
</host>
</service>
</services>
<bindings>
<customBinding>
<binding name="RESTPOXBinding">
<textMessageEncoding messageVersion="None"/>
<httpTransport/>
</binding>
</customBinding>
</bindings>
復制代碼

當然,這么寫是有點小麻煩,WCF的設計者肯定會考慮到這點,幫我們采用URI模板方式封裝好了,對比:

原始方式:
[OperationContract(Action = "*", ReplyAction = "*")]
Message Resource(Message input);
新的方式:
[OperationContract]
[WebGet(UriTemplate = “/{query}”, BodyStyle = WebMessageBodyStyle.Bare)]
Message Resource(string query);

這樣我們在實現方法的時候就不用再自己手動調用 HttpRequestMessageProperty 類提取信息,也不再判斷Method類型,因此重寫后的Resource方法看起來也清爽了很多。
        public Message Resource(String query)
        {
            string fileName = string.Format("{0}.xml", query);
                    Message message = null;
                    if (File.Exists(fileName))
                    {
                        XmlDocument document = new XmlDocument();
                        document.Load(fileName);
                        message = Message.CreateMessage(
                        MessageVersion.None,
                        "*",
                        new XmlNodeReader(document.DocumentElement));
                    }
                    else
                    {
                        message = Message.CreateMessage(
                        MessageVersion.None,
                        "*");
                    }
                    return message;
        }

同樣的,系統給了我們一個webHttpBinding套餐,也是對上面傳輸協議和編碼的封裝。


關于OperationContract 和 WebGet 特性的說明
這兩個特性主要是給Operation的描述添加了一些元數據信息,給一個Operation添加了這兩個特性以后,該Operation仍然可以通過SOAP消息的方式來訪問。

文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()