转自:http://vanacosmin.ro/Articles/Read/WCFEnvelopeNamespacePrefix

Posted on January 8th 2014 by Vana Genica Cosmin

Introduction

WCF allows you to customize the response using DataContract, MessageContract or XmlSerializer. With DataContract you have access only to the body of the message while MessageContract will let you control the headers of the message too. However, you don't have any control over namespace prefixes or on the soap envelope tag from the response message.

When generating the response, WCF uses some default namespaces prefixes and we cannot control this from configuration. For example, <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">. Normally, the prefix should not be a problem according to SOAP standards. However, there are 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 an 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 was:

<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 two messages are equivalent from SOAP or XML perspective. What I needed to do in order to obtain the expected result was:

  • Change 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 was moved on a top level)

Possible solutions

There are 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 be found here.

This approach has 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 will invalidate the hash.
  • You can build a custom channel so that the changing of the message takes place before the hash is calculated. This is complicated, and the next drawback applies.
  • If you are using a message inspector (e.g. for logging), you will log the message in its initial state, and not in the form sent to the customer.
  • If you make big changes to the message, your wsdl contract will not work, so you need to do additional work for metadata exchange, if you want your service to be 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 is then passed in the WCF pipeline (message inspectors, channels, encoders). This is the right place to transform your message because all the other extension points will work with the exact same message that you are sending to your clients

The following diagram shows how the message is sent accross different layers in WCF pipeline. You can see that the MessageFormatter is just before the MessageInspector when you send a message from server to client, while the MessageEncoder is a side component which is activated right before the transport layer.

Additional information about the diagram can be found here

Creating a custom MessageFormatter

First, you need to create a IDispatchMessageFormatter class. This is the message formatter. The SerializeReply method will 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 class

Custom message class

This is the class that will allow you to alter the output of 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 that you can use to obtain the required XML:

  • All the methods will use the same XmlDictionaryWriter. This means that if you add a namespace with a prefix, that prefix will be used for the next elements that you add in the same namespace.
  • OnWriteStartEnvelope Method is called first. By default this method will write the s prefix and will 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 will still use the prefix s for body. This is why 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 will get the expected result because I have declared the namespace ns1 at the top level
  • There are other methods that you can override if you need more flexibility.

Activate the MessageFormatter

To activate the MessageFormatter we will create an OperationBehavior attribute that must be applied to the methods (on the interface) that 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的更多相关文章

  1. 错误“Unexpected namespace prefix "xmlns" found for tag LinearLayout”的解决方法(转)

    (转自:http://blog.csdn.net/bombzhang/article/details/12676789) 有一次升级开发工具后发现xml脚本出现错误“Unexpected namesp ...

  2. Unexpected namespace prefix "xmlns" found for tag Linear Layout

    原文地址http://blog.csdn.net/taxuexumei/article/details/41523419 今天遇到的问题,,,保存到博客里,下回遇到找博客就行了,,,,,, 今天在制作 ...

  3. 错误“Unexpected namespace prefix "xmlns" found for tag LinearLayout”的解决方法

    编写布局代码时发现xml脚本出现错误“Unexpected namespace prefix "xmlns" found for tag LinearLayout”,原来是一个na ...

  4. android常见错误-Unexpected namespace prefix "xmlns" found for tag LinearLayout

    有一次升级android开发工具后发现xml脚本出现错误“Unexpected namespace prefix "xmlns" found for tag LinearLayou ...

  5. error:“Unexpected namespace prefix "xmlns" found for tag LinearLayout”

    错误“Unexpected namespace prefix "xmlns" found for tag LinearLayout”的解决方法 androidUnexpected ...

  6. Android:Attribute is missing the Android namespace prefix

    今天编写XML文件时,出现了Attribute is missing the Android namespace prefix的错误,开始一直找没找出原因,后来仔细一看原来只是一个很简单的单词书写错误 ...

  7. SVG报错error on line 39 at column 26: Namespace prefix xlink for href on script is not defined

    转自:http://stackoverflow.com/questions/3561270/error-on-line-39-at-column-26-namespace-prefix-xlink-f ...

  8. org.dom4j.IllegalAddException: No such namespace prefix: *** is in scope on: org.dom4j.tree.DefaultElement (dom4j写入XML文件,标签中带【:】(冒号)解决办法)

    用dom4j操作写XML文件,标签中含有冒号,会报 org.dom4j.IllegalAddException: No such namespace prefix: *** is in scope o ...

  9. [Android开发常见问题-14] Unexpected namespace prefix "abc" found for tag SomeThing

    很多开发者朋友可能会自己定义一些控件,以此来扩展现有控件的功能,在扩展空间的时候通常会自己定义一些属性,例如: [html]  view plain copy   <android.alex.v ...

随机推荐

  1. UnicodeDecodeError: 'utf-8' codec can't decode byte

    for line in open('u.item'): #read each line whenever I run this code it gives the following error: U ...

  2. Ubuntu下设置开机后自动运行命令(转)

    从道理上来讲,Ubuntu开机应该是能够设置执行一些脚本的,事实上确实如此,网上给出了很多解决的方案,基本上是分为两种, 第一种是编辑/etc/下的rc.local脚本, 然后把对应的需要执行的脚本写 ...

  3. JAVA中Set集合--HashSet的使用

    一.使用HashSet添加一个String类型的值: public static void hashSet1(){ HashSet<String> hashSet = new HashSe ...

  4. go 从入门到精通(二)基本数据类型和操作符

    一.文件名&关键字&标识符 所有go源码都是以.go结尾 标识符以字母或下划线开头,大小写敏感 下划线_是特殊标识符,用户忽略结果 保留关键字 导入包时可以设置别名 下面是保留关键字: ...

  5. Java常考面试题(三)

    序言 说说今天遇到的一件小事吧,在遇到问题,查找答案时,看到很多人的博客里面都有提到关键字眼,可让人觉得可恨的是,大多数人写的博文中,基本上都是照着书上的语言发表的,看了跟没看一样,没有一点通俗的语言 ...

  6. iOS求职之C语言面试题

    1.static有什么用途?(请至少说明两种) 1)限制变量的作用域 2)设置变量的存储域(堆,主动分配内存也是堆) 1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变.
 2)  ...

  7. 虚拟机vmware如何进入bios设置,设置启动项(装系统时用)

    方法/步骤   1 打开你要进入Bios的虚拟系统界面(处于关机状态).我这里的是server2003. 2 鼠标点击上部的 虚拟机 菜单. 3 在弹出的菜单中鼠标点击 电源 . 4 在弹出的下一级菜 ...

  8. Logcat + money 笔记

    如下命令:将过滤后的日志按照指定格式输出到指定的文件中 adb logcat -v time -s Test_Tag:v > logcat_local.txt A:其中 -v time 用来指定 ...

  9. html5自定义数字键盘

    原理:使用div模拟输入框,避免手机原生键盘弹出,键盘使用div模拟,点击事件使用js控制,光标闪烁使用css模拟,具体代码如下: <!doctype html> <html lan ...

  10. 转:在eclipse中 使用7.0及以上手机进行测试时logcat不打印日志的解决办法

    解决办法 替换ADT中的ddmlib.jar文件. 下载ADT对应的zip包,解压出ddmlib.jar文件 放到eclipse\configuration\org.eclipse.osgi\bund ...