Transferred from: Http://vanacosmin.ro/Articles/Read/WCFEnvelopeNamespacePrefix
Posted on January 8th by Vana Genica Cosmin
Introduction
WCF allows customize the response using DataContract, MessageContract or XmlSerializer. With DataContract you has access only to the body of the message while MessageContract'll let you control the headers O f the message too. However, you don ' t has any control over namespace prefixes or on the SOAP envelope tag from the response message.
When generating the response, the WCF uses some default namespaces prefixes and we cannot control this from the configuration. For example, <s:envelope xmlns:s= "http://schemas.xmlsoap.org/soap/envelope/";. Normally, the prefix should not being a problem according to SOAP standards. However, there is times when we need to support old clients who manually parse the response and they expect fixed format Or look for specific prefixes.
Real World Example
Recently, I was asked to rewrite a old service using. NET and WCF, keeping compatibility with existing clients. The response from WCF-looked like this:
<s:envelope xmlns:s= "http://schemas.xmlsoap.org/soap/envelope/" >
<s:Body>
<getcardinforesponse xmlns= "https://vanacosmin.ro/WebService/soap/" xmlns:i= "http://www.w3.org/2001/ Xmlschema-instance ">
<control_area>
<source>OVF</source>
<ns1:source_send_date>2014-01-06T14:15:37.1505943+01:00</ns1:source_send_date>
<api_key/>
<message_id>27970411614463393270</message_id>
<correlation_id>1</correlation_id>
</control_area>
<chip_uid>1111</chip_uid>
<tls_engraved_id>************1111</tls_engraved_id>
<reference_id/>
<is_blocked>false</is_blocked>
<is_useable>false</is_useable>
<registration_date>2013-12-13T13:06:39.75</registration_date>
<last_modification>2013-12-20T15:48:52.307</last_modification>
</GetCardInfoResponse>
</s:Body>
</s:Envelope>
The expected response for existing clients is:
<soap-env:envelope xmlns:soap-env= "http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1= "https:// Vanacosmin.ro/webservice/soap/">
<SOAP-ENV:Body>
<ns1:getcardinforesponse xmlns:i= "Http://www.w3.org/2001/XMLSchema-instance" >
<ns1:control_area>
<ns1:source>OVF</ns1:source>
<ns1:source_send_date>2014-01-06T14:15:37.1505943+01:00</ns1:source_send_date>
<ns1:api_key/>
<ns1:message_id>27970411614463393270</ns1:message_id>
<ns1:correlation_id>1</ns1:correlation_id>
</ns1:control_area>
<ns1:chip_uid>1111</ns1:chip_uid>
<ns1:tls_engraved_id>************1111</ns1:tls_engraved_id>
<ns1:reference_id/>
<ns1:is_blocked>false</ns1:is_blocked>
<ns1:is_useable>false</ns1:is_useable>
<ns1:registration_date>2013-12-13T13:06:39.75</ns1:registration_date>
<ns1:last_modification>2013-12-20T15:48:52.307</ns1:last_modification>
</ns1:GetCardInfoResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
The messages is equivalent from SOAP or XML perspective. What I needed to does in order to obtain the expected result is:
- The namespace prefix for SOAP envelope
- Add another namespace with the prefix ns1 in the SOAP envelope
- Remove the namespace from S:body (because it is moved on a top level)
Possible Solutions
There is multiple extension points where the message can be altered
Custom Messageencoder
You can alter the XML output using a messageencoder. A Good example about this can is found here.
This approach have several disadvantages, as the author also pointed out in the end of the article:
- The message encoder is activated late in the WCF pipeline. IF you have message security, a hash is already included in the message. Changing the message would invalidate the hash.
- You can build a custom channel so, the changing of the message takes place before the hash is calculated. This is complicated, and the next drawback applies.
- If you is using a message inspector (e.g. for logging), you'll log the message in its initial state, and not in the for M sent to the customer.
- If you make big changes to the message, your WSDL contract won't work, so you need to doing additional work for metadata E Xchange, if you want your service to being consumed by new clients without manually parsing your message.
Custom Messageformatter and a derived Message class
The messageformatter is used to transform the result of your method into an instance of Message class. This instance are then passed in the WCF pipeline (message inspectors, channels, encoders). The right place to transform your message because all the other extension points would work with the exact same MES Sage that is sending to your clients
The following diagram shows how the message was sent accross different layers in WCF pipeline. You can see that the messageformatter are just before the messageinspector when you send a message from server to client, W Hile the Messageencoder is a side component which are activated right before the transport layer.
Additional information about the diagram can is found here
Creating a custom Messageformatter
First, you need to create a IDispatchMessageFormatter class. This is the message formatter. The Serializereply method would return an instance of your custom Message class.
public class Mycustommessageformatter:idispatchmessageformatter {
Private ReadOnly IDispatchMessageFormatter Formatter;
Public Mycustommessageformatter (IDispatchMessageFormatter formatter)
{
This.formatter= formatter;
}
public void deserializerequest (Message message, object[]parameters)
{
This.formatter.DeserializeRequest (message,parameters);
}
Public Message serializereply (MessageVersion messageversion, object[]parameters, object result)
{
var message = this.formatter.SerializeReply (messageversion, parameters, result);
return new Mycustommessage (message);
}
}
Inherit from message Classcustom message class
This is the class that would allow you to alter the output of the Your service.
public class Mycustommessage:message
{
Private readonly message message;
Public mycustommessage (Message message)
{
This.message= message;
}
public override Messageheaders Headers
{
get {return this.message.Headers;}
}
public override Messageproperties Properties
{
get {return this.message.Properties;}
}
public override MessageVersion Version
{
get {return this.message.Version;}
}
protected override void Onwritestartbody (XmlDictionaryWriter writer)
{
Writer. WriteStartElement ("Body", "http://schemas.xmlsoap.org/soap/envelope/");
}
protected override void Onwritebodycontents (XmlDictionaryWriter writer)
{
This.message.WriteBodyContents (writer);
}
protected override void Onwritestartenvelope (XmlDictionaryWriter writer)
{
Writer. WriteStartElement ("Soap-env", "Envelope", "http://schemas.xmlsoap.org/soap/envelope/");
Writer. WriteAttributeString ("xmlns", "ns1", NULL, "https://vanacosmin.ro/WebService/soap/");
}
}
Custom Message Class Explained
The derived Message class has several overrides so can use to obtain the required XML:
- All the methods would use the same xmldictionarywriter. This means so if you add a namespace with a prefix, that prefix'll be used for the next elements so you add in the S Ame namespace.
- Onwritestartenvelope Method is called first. By default this method would write the S prefix and would add namespaces according to the SOAP version. I Use this method to change the prefix to ' soap-env ' and also to add the Ns1 namespace.
- Onwritestartbody is called second. By default, this method would still use the prefix s for body. This is what I had to override it and write the Body element using the XmlDictionaryWriter.
- onwritebodycontents is called last in this sequence. By calling Writebodycontents on the original message, I'll get the expected result because I have declared the namespace NS1 at the top level
- There is other methods so can override if you need more flexibility.
Activate the Messageformatter
To activate the messageformatter we'll create a OperationBehavior attribute that must being applied to the methods (on The interface) that is we want to use this messageformatter.
[AttributeUsage(AttributeTargets.Method)]
public class MobilityProviderFormatMessageAttribute : Attribute, IOperationBehavior
{
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters) { }
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) { }
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
var serializerBehavior =operationDescription.Behaviors.Find<DataContractSerializerOperationBehavior>();
Customize WCF Envelope and Namespace Prefix