Duncan
Duncan

Reputation: 2513

Remove xml namespaces from WCF restful response

I am using WCF to return a plain old XML (POX) document to the caller. I am using the XML Serializer formatter to turn the objects into XML.

In the returned document I have some extraneous xml namespace references (that weren't there in the ASMX version) for the XML Schema and instance. I have seen various arguments on the web that these shouldn't be removed which I don't buy into for returning a plain XML document.

What is the simplest way of removing these xmlns references from a returned XML document in WCF?

The signature looks like:

public ResponseInfo Process(string input) {
}

Upvotes: 19

Views: 28140

Answers (9)

Qais Khateeb
Qais Khateeb

Reputation: 48

I have the same problem when I work with ASMX clients and For me this solve the problem :

Add to your Service Interface :

[XmlSerializerFormat(Use = OperationFormatUse.Literal, Style = OperationFormatStyle.Document)]

Add to Operations :

[OperationContract(Action = "http://www.YourNameSpace.com/ActionName",ReplyAction = "http://www.YourNameSpace.com/ActionName")]

Upvotes: 1

Redwood
Redwood

Reputation: 69332

I found a good solution to this issue which lets you inject your own XmlSerializer into WCF that is used when serializing and deserializing requests. This XmlSerializer can be set up to omit XML namespaces entirely (including xmlns:i="w3.org/2001/XMLSchema-instance") or any other way you desire.

My solution utilizes WcfRestContrib. You could almost use the included POX formatter but in our case we wanted to support attributes so we wrote our own simple formatter.

Instructions:

1) Reference WcfRestContrib from your project.

2) Create an IWebFormatter implementation:

public class NamespacelessXmlFormatter : IWebFormatter {
    public object Deserialize(WebFormatterDeserializationContext context, Type type) {
        if (context.ContentFormat != WebFormatterDeserializationContext.DeserializationFormat.Xml) {
            throw new InvalidDataException("Data must be in xml format.");
        }

        return NamespacelessXmlSerializer.Deserialize(context.XmlReader, type);
    }

    public WebFormatterSerializationContext Serialize(object data, Type type) {
        using (var stream = NamespacelessXmlSerializer.Serialize(data, type)) {
            using (var binaryReader = new BinaryReader(stream)) {
                byte[] bytes = binaryReader.ReadBytes((int)stream.Length);
                return WebFormatterSerializationContext.CreateBinary(bytes);
            }
        }
    }
}

Which utilizes an XmlSerializer that fits your needs (here's ours which simply omits all namespaces):

public static class NamespacelessXmlSerializer {

    private static readonly XmlSerializerNamespaces _customNamespace = new XmlSerializerNamespaces();

    private static readonly XmlWriterSettings _xmlSettings = new XmlWriterSettings {
        OmitXmlDeclaration = true
    };

    static NamespacelessXmlSerializer() {
        // to make sure .NET serializer doesn't add namespaces
        _customNamespace.Add(String.Empty, String.Empty);
    }

    /// <summary>
    /// Deserializes object from its XML representation.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="stream"></param>
    /// <returns></returns>
    public static T Deserialize<T>(Stream stream) {
        return (T)Deserialize(stream, typeof(T));
    }

    /// <summary>
    /// Deserializes object from its XML representation.
    /// </summary>
    public static object Deserialize(Stream stream, Type type) {
        var ds = new XmlSerializer(type);
        var d = ds.Deserialize(stream);
        return d;
    }

    public static object Deserialize(XmlDictionaryReader xmlReader, Type type) {
        var ds = new XmlSerializer(type);
        var d = ds.Deserialize(xmlReader);
        return d;
    }

    /// <summary>
    /// Serializes object to XML representation.
    /// </summary>
    /// <exception cref="InvalidOperationException">
    /// Is thrown when there was an error generating XML document. This can happen 
    /// for example if the object has string with invalid XML characters:
    /// http://www.w3.org/TR/2004/REC-xml-20040204/#charsets.
    /// See this article for other potential issues:
    /// http://msdn.microsoft.com/en-us/library/aa302290.aspx
    /// </exception>
    public static Stream Serialize<T>(T objectToSerialize) {
        return Serialize(objectToSerialize, typeof(T));
    }

    /// <summary>
    /// Serializes object to XML representation.
    /// </summary>
    /// <exception cref="InvalidOperationException">
    /// Is thrown when there was an error generating XML document. This can happen 
    /// for example if the object has string with invalid XML characters:
    /// http://www.w3.org/TR/2004/REC-xml-20040204/#charsets.
    /// See this article for other potential issues:
    /// http://msdn.microsoft.com/en-us/library/aa302290.aspx
    /// </exception>
    public static Stream Serialize(object objectToSerialize, Type type) {
        var stream = new MemoryStream();

        XmlWriter writer = XmlWriter.Create(stream, _xmlSettings);
        var x = new XmlSerializer(type);
        x.Serialize(writer, objectToSerialize, _customNamespace);

        stream.Position = 0;

        return stream;
    }
}

3) Apply the WebDispatchFormatter... attributes to your service using your custom implementation as the type (based on this documentation):

[WebDispatchFormatterConfiguration("application/xml")]
[WebDispatchFormatterMimeType(typeof(NamespacelessXmlFormatter), "application/xml")]

4) Apply the WebDispatchFormatter attribute to all of your service methods (based on this documentation).

5) That's it. Test your service and confirm it now behaves as expected.

Upvotes: 3

Steven King
Steven King

Reputation: 580

Not sure if this will help, but we had a similar issue. Instead of decorating thousands of data elements with DataContract/DataMember attributes and using the (default) DataContractSerializer, we found that if our WCF service used the XmlSerializerFormat instead, we could easily deserialize our objects.

[System.ServiceModel.ServiceContract]
public interface IRestService
{
    [System.ServiceModel.OperationContract]
    // Added this attribute to use XmlSerializer instead of DataContractSerializer
    [System.ServiceModel.XmlSerializerFormat(
        Style=System.ServiceModel.OperationFormatStyle.Document)]
    [System.ServiceModel.Web.WebGet(
        ResponseFormat = System.ServiceModel.Web.WebMessageFormat.Xml,
        UriTemplate = "xml/objects/{myObjectIdentifier}")]
    MyObject GetMyObject(int myObjectIdentifier);
}

This is how we're deserializing the objects:

public static T DeserializeTypedObjectFromXmlString<T>(string input)
{
    T result;

    try
    {
        System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(typeof(T));
        using (System.IO.TextReader textReader = new System.IO.StringReader(input))
        {
            result = (T)xs.Deserialize(textReader);
        }
    }
    catch
    {
        throw;
    }

    return result;
}

Upvotes: 2

user565923
user565923

Reputation: 101

I had the same problem. Adding BodyStyle:=WebMessageBodyStyle.Bare to WebInvoke worked for me. Response is no longer wrapped in a metadata.

Upvotes: 7

Simon Gibbs
Simon Gibbs

Reputation: 4818

Just to give the other perspective, if the namespace is unique to your project, e.g:

http://mycompany.com/myapi/

then it should be retained.

Such namespaces are best practice, they'll add less than 1 line of boilerplate to any XPath calls (which you can turn into a helper method) and require about 15 lines of helper code to generate a prefix/URI map, but that is the ONLY downside and you won't always encounter it.

In exchange you get unambiguous names for every element and that means you can compose third party namespaces with impunity e.g. in theory you could return XHTML directly without application level encoding.

Other namespaces turning up is unlikely to be an issue, as they are unlikely to be used on any of the tags that you defined in your project. In fact, if they are, then there is a bug somewhere. The likely explanaintion for there existence is that the framework added them just in case it needed to add an element somewhere below the spot where they are declared, which may not be a location you have to care about.

Another answer mentioned http://www.w3.org/2001/XMLSchema-instance which is a bit special, perhaps you could say what namespaces were added.

Upvotes: 2

Bernie
Bernie

Reputation: 286

If you want to change Xml, one of the ways is to use an XslTransform. I had a similar case, where I needed to remove the xmlns attributes from an Xml Post request.

In WCF you can 'intercept' the Xml messages before the go out, or before they are processed on the way in, by implementing either the IClientMessageInspector or the IDispatchMessageInspector, depending on whether you need this at the client or the server side.

For instance, to strip the namespace attributes from an outgoing Xml message to a web service, I implemented the IClientMessageInspector, using the following code:

#region IClientMessageInspector Members
    public void AfterReceiveReply(ref Message reply, object correlationState)
    {   
        //Console.WriteLine(reply.ToString());
    }

    private XslCompiledTransform xt = null;

    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        Console.WriteLine(request.ToString());
        if (!request.IsEmpty)
        {
            XmlReader bodyReader =
                request.GetReaderAtBodyContents().ReadSubtree();

            MemoryStream ms = new MemoryStream();
            XmlWriter xw = XmlWriter.Create(ms);

            if (xt == null)
            {
                xt = new XslCompiledTransform(true);
                xt.Load("StripXmlnsi.xslt");
            }
            xt.Transform(bodyReader, xw);

            ms.Flush();
            ms.Seek(0, SeekOrigin.Begin);

            bodyReader = XmlReader.Create(ms);

            Message changedMessage = Message.CreateMessage(request.Version, null, bodyReader);
            changedMessage.Headers.CopyHeadersFrom(request.Headers);
            changedMessage.Properties.CopyProperties(request.Properties);
            request = changedMessage;
        }
        return null;
    }
    #endregion

and used the following transform:

    <?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="*">
    <!-- remove element prefix (if any) -->
    <xsl:element name="{local-name()}">
      <!-- process attributes -->
      <xsl:for-each select="@*">
        <!-- remove attribute prefix (if any) -->
        <xsl:attribute name="{local-name()}">
          <xsl:value-of select="." />
        </xsl:attribute>
      </xsl:for-each>
      <xsl:apply-templates />
    </xsl:element>
  </xsl:template>
</xsl:stylesheet>

Hope this is helpfull.

Upvotes: 4

Kevin Babcock
Kevin Babcock

Reputation: 10247

You can remove the XML namespace by setting the Namespace parameter of the DataContract attribute to an empty string, like so:

[DataContract(Namespace = "")]
public class ResponseInfo
{
    // ...
}

I hope this helps...

Upvotes: 19

christophercotton
christophercotton

Reputation: 5879

I assume you are trying instead of getting something like this at the beginning of your xml:

<ResponseInfo 
   xmlns="http://schemas.datacontract.org/2004/07/ResponseInfo"
   xmlns:i="http://www.w3.org/2001/XMLSchema-instance" >

You want just:

<ResponseInfo>

Sadly, I haven't seen an easy way yet to remove those fields. I was googling for solutions and most of the options for removing it require creating your own Message inspector, or your own encoder.

Upvotes: 6

Sailing Judo
Sailing Judo

Reputation: 11243

In my RESTful WCF service which I wrote prior to the WCF RESTful starter kit I did the following which gives me nice, clean results.

First, make sure the webHttp endpoint behavior is set:

  <endpointBehaviors>
    <behavior name="Web">
      <webHttp/>
    </behavior>
  </endpointBehaviors>

Your endpoint should look something like this (minus the contract for simplicity):

<endpoint address="" binding="webHttpBinding" behaviorConfiguration="Web" />

My service contract has these operation contracts in it:

    [WebGet(UriTemplate="tasks", ResponseFormat=WebMessageFormat.Xml )]
    [OperationContract]
    Task[] GetTasks();

    [WebGet(UriTemplate="tasks/{id}", ResponseFormat=WebMessageFormat.Xml)]
    [OperationContract]
    Task GetTask(string id);

The service implementation itself has nothing special about it. You can try changing up the WebMessageFormat but the only other item in the enumeration is "json".

Upvotes: 1

Related Questions