Hoe soap headers in WCF toe te passen

In need of an English translation? Please drop me a note.

Een http bericht kan uit twee delen bestaan, een header en een soap envelop. In deze http header staat bv. dat  de aanvrager het antwoord gecomprimeer terug wilt hebben (Accept-encoding: gzip, deflate).

De soap is de daadwerkelijke payload van het bericht. WCF plaatst in de body van de envelop direct alle informatie.

Maar zoals de titel al doet vermoeden, is het ook mogelijk om in de soap envelop een header op te nemen. Dit is een interessante optie om aan het bericht een context mee te geven. Er kan bijvoorbeeld in de header informatie over de verzender opgenomen worden. Ook kan bv. een volgnummer opgenomen worden zodat vraag en antwoord beter getraceerd kunnen worden.

Er zijn dus twee uiteindelijk twee soorten headers: die op het HTTP bericht en die op het soap bericht. Meer informatie over soap is ook te vinden op http://www.syncfusion.com/resources/techportal/ebooks/http .

In deze blog richten we ons op het meesturen van een header van client naar de WCF service. Als voorbeeld gebruik ik het standaard WCF contract zoals Microsoft die aanbiedt als een WCF service net nieuw aangemaakt is.

We bekijken eens een standaard aanvraag:

var client =
  new ServiceReference1.
             Service1Client();

string data = client.GetData(42);

Deze geeft als Request:

POST http://localhost:52568/Service1.svc   HTTP/1.1Content-Type: text/xml;   charset=utf-8VsDebuggerCausalityData:   uIDPo/nwpA5i4l1IifBRBjolEk0AAAAAsmDH0cCjakSxrkG/OfV+SitX6PLpY7pFoZKEmtJtaY8ACQAASOAPAction:   “http://tempuri.org/IService1/GetData”Host: localhost:52568Content-Length: 158Expect: 100-continueAccept-Encoding: gzip, deflateConnection: Keep-Alive<s:Envelope   xmlns:s=”http://schemas.xmlsoap.org/soap/envelope/”><s:Body><GetData   xmlns=”http://tempuri.org/”><value>42</value></GetData></s:Body></s:Envelope&gt;

De soap envelop bevat dus alleen een body.

We sturen nu een simpele string in de soap header mee met de volgende aanroep vanuit de cliënt:

using (var client = new ServiceReference1.Service1Client())
{
  using (new System.ServiceModel.
               OperationContextScope(client.InnerChannel))
  {
    var header = MessageHeader.CreateHeader
                      ("Key",
                       "http://somenamespace.com/",
"Value");

    OperationContext.Current.
                          OutgoingMessageHeaders.Add(header);

    // Do the call within the using
    string data = client.GetData(42);

    Console.WriteLine("Data: " + data);
  }
}

De request wordt dan:

POST http://localhost:52568/Service1.svc   HTTP/1.1Content-Type: text/xml;   charset=utf-8VsDebuggerCausalityData:   uIDPo2Vd4ZtREjVIsNbfCTIikcIAAAAAsmDH0cCjakSxrkG/OfV+SitX6PLpY7pFoZKEmtJtaY8ACQAASOAPAction:   “http://tempuri.org/IService1/GetData”Host: localhost:52568Content-Length: 229Expect: 100-continueAccept-Encoding: gzip, deflateConnection: Keep-Alive<s:Envelope   xmlns:s=”http://schemas.xmlsoap.org/soap/envelope/”><s:Header><Key   xmlns=”http://somenamespace.com/”>Value</Key></s:Header><s:Body><GetData   xmlns=”http://tempuri.org/”><value>42</value></GetData></s:Body></s:Envelope&gt;

We zien nu dus de soap envelop uitgebreid worden met een header met daarin een ”key” element.

Nu maken we het wat interessanter. Er kan ook een object doorgegeven worden dus we definiëren een extra class die zowel op de cliënt als op de server bekent is:

public class SoapHeader
{
  public int ValueOne { get; set; }
  public string ValueTwo { get; set; }
}

De aanroep wordt dan als volgt:

using (new System.ServiceModel.
         OperationContextScope(client.InnerChannel))
{
  var soapHeader = new SoapHeader
  {
    ValueOne = 123,
    ValueTwo = "Two"
  };

  var header = MessageHeader.CreateHeader
              ("Key",
               "http://somenamespace.com/",
               soapHeader);

  OperationContext.Current.OutgoingMessageHeaders.Add(header);

  // Do the call within the using
  string data = client.GetData(42);

  Console.WriteLine("Data: " + data);
}

De Request wordt dan uiteindelijk:

POST http://localhost:52568/Service1.svc   HTTP/1.1Content-Type: text/xml;   charset=utf-8VsDebuggerCausalityData:   uIDPoxZdul7FztlGiQ39MXH6mwoAAAAAsmDH0cCjakSxrkG/OfV+SitX6PLpY7pFoZKEmtJtaY8ACQAASOAPAction:   “http://tempuri.org/IService1/GetData”Host: localhost:52568Content-Length: 448Expect: 100-continueAccept-Encoding: gzip, deflateConnection: Keep-Alive<s:Envelope   xmlns:s=”http://schemas.xmlsoap.org/soap/envelope/”><s:Header><Key   xmlns=”http://somenamespace.com/&#8221;   xmlns:i=”http://www.w3.org/2001/XMLSchema-instance”><ValueOne   xmlns=”http://schemas.datacontract.org/2004/07/ClassLibrary1″>123</ValueOne><ValueTwo   xmlns=”http://schemas.datacontract.org/2004/07/ClassLibrary1″>Two</ValueTwo></Key></s:Header><s:Body><GetData   xmlns=”http://tempuri.org/”><value>42</value></GetData></s:Body></s:Envelope&gt;

In de soap header zien we ons samengestelde object als “key” terugkeren met de twee waarden. Maar hoe kan de doorgegeven informative weer uitgelezen worden? De OutgoingMessageHeaders heeft een tegenganger IncomingMessageHeaders:

var soapHeader = OperationContext.Current.
    IncomingMessageHeaders.
      GetHeader<SoapHeader>("Key",
                            "http://somenamespace.com/");

Dit geeft de correcte waarden door aan de Wcf Service:

Zo is het dus wel heel eenvoudig geworden om aan een Soap envelop extra contextinformatie mee te geven.

Om vanuit de server weer informatie richting de client te sturen kan de volgende code gebruikt worden:

var header = MessageHeader.
  CreateHeader("Key",
               "http://somenamespace.com/",
               soapHeader);
OperationContext.Current.
  OutgoingMessageHeaders.Add(header);

Op de client lees je deze waarden weer terug met:

var soapHeaderReturned =
  OperationContext.Current.
    IncomingMessageHeaders.GetHeader<SoapHeader>
    (
      "Returned",
      "http://servicenamespace.com/"
);

Extra: In bovenstaande code heb ik IISExpress in combinatie met Fiddler toegepast om de soapberichten te kunnen afvangen. Hiervoor heb ik in de  app.config als adres voor de webservice gebruikt: http://localhost.fiddler:52568/Service1.svc

Hiermee forceer ik dat Fiddler de berichten kan afluisteren. Helaas verlies ik hierdoor wel de mogelijkheid om vanuit een test applicatie direct in een breakpoint op de service over te stappen.

Update: toevoeging van code om een header terug te sturen naar de client.

Advertenties