Remove xml namespaces from WCF restful response

前端 未结 9 1658
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-12-05 11:13

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 docum

相关标签:
9条回答
  • 2020-12-05 11:15

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

    0 讨论(0)
  • 2020-12-05 11:18

    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.

    0 讨论(0)
  • 2020-12-05 11:22

    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.

    0 讨论(0)
  • 2020-12-05 11:23

    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.

    0 讨论(0)
  • 2020-12-05 11:27

    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".

    0 讨论(0)
  • 2020-12-05 11:34

    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.

    0 讨论(0)
提交回复
热议问题