Search code examples
c#asp.net-web-apixmlserializer

How to remove xmlns:xsi and xmlns:xsd from Web API XML serializer


For some hours, I have battled with removing the default namespaces from the XML returned from serializing my independent objects (not MVC model) in ASP.NET Web Api. Sample codes for the application is:

Class Definition:

public class PaymentNotificationResponse
{

    [XmlArray("Payments")]
    [XmlArrayItem("Payment", typeof(PaymentResponse))]
    public PaymentResponse[] Payments { get; set; }
}

I then created a Web Api Controler that creates an object of the PaymentNotificationResponse based on some input, and then serialize the object to the requesting party. The Controller is listed below:

public class PaymentController : ApiController
{

    public PaymentNotificationResponse Post()
    {
        //Read request content (only available async), run the task to completion and pick the stream.
        var strmTask = Request.Content.ReadAsStreamAsync();
        while (strmTask.Status != TaskStatus.RanToCompletion) { }
        Stream strm = strmTask.Result;

        //Go back to the beginning of the stream, so that the data can be retrieved. Web Api reads it to the end.
        if (strm.CanSeek)
            strm.Seek(0, SeekOrigin.Begin);

        //Read stream content and convert to string.
        byte[] arr = new byte[strm.Length];
        strm.Read(arr, 0, arr.Length);
        String str = Encoding.UTF8.GetString(arr);

        //Change the default serializer to XmlSerializer from DataContractSerializer, so that I don't get funny namespaces in properties.
        //Then set a new XmlSerializer for the object
        Configuration.Formatters.XmlFormatter.UseXmlSerializer = true;
        Configuration.Formatters.XmlFormatter.SetSerializer<PaymentNotificationResponse>(new XmlSerializer(typeof(PaymentNotificationResponse)));

        //Now call a function that would convert the string to the required object, which would then be serialized when the Web Api is invoked.
        return CreatePaymentNotificationFromString(str);
    }
}

Problem is, when I invoke the Api with valid string parameter, it returns an XML of this format (valid XML, but xmlns is not needed):

<PaymentNotificationResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <Payments>
        <Payment>
            <PaymentLogId>8325</PaymentLogId>
            <Status>0</Status>
        </Payment>
    </Payments>
</PaymentNotificationResponse>

The system I'm sending this to doesn't need the xmlns:xsi and the xmlns:xsd. In fact, it returns an exception when it sees the namespaces.

I tried returning a string with XML tags, it just wrapped the response in a <string></string> and encoded all the < and >. So that was not an option.

I saw this post and this one. While the former is very detailed, it didn't solve my problem. It only just introduced one extra xmlns to the generated XML. I think it's because I didn't explicitly call .Serialize() function in the XmlSerializer.

I figured out a solution, and I thought I should share. So I would state it in the answers.


Solution

  • To fix this, I added a method to serialize the PaymentNotificationResponseand return an XML string, thus (I included this in the definition of PaymentNotificationResponse):

    //I added this method after I tried serialize directly to no avail
        public String SerializeToXml()
        {
            MemoryStream ms = new MemoryStream();
    
            XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
            ns.Add("", "");
    
            new XmlSerializer(typeof(PaymentNotificationResponse)).Serialize(ms, this, ns);
            XmlTextWriter textWriter = new XmlTextWriter(ms, Encoding.UTF8);
    
            ms = (System.IO.MemoryStream)textWriter.BaseStream;
    
            return new UTF8Encoding().GetString(ms.ToArray());
        }
    

    I parsed the string to create an XDocument, then return the root element. This made me change the return type of the Post() method to XElement. The Post() listing would then be:

    public XElement Post()
        {
            //...Same as it was in the question, just the last line that changed.
    
            var pnr = CreatePaymentNotificationFromString(str);
            return XDocument.Parse(pnr.SerializeToXml()).Root;
        }
    

    That would make my response XML this:

    <PaymentNotificationResponse>
        <Payments>
            <Payment>
                <PaymentLogId>8325</PaymentLogId>
                <Status>0</Status>
            </Payment>
        </Payments>
    </PaymentNotificationResponse>
    

    I hope this helps someone.