39

How do I remove the namespace from the xml response below using Web API?

<ApiDivisionsResponse xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/GrassrootsHoops.Models.Api.Response">
<Divisions xmlns:d2p1="http://schemas.datacontract.org/2004/07/GrassrootsHoops.Data.Entities">
<d2p1:Page>1</d2p1:Page>
<d2p1:PageSize>10</d2p1:PageSize>
<d2p1:Results xmlns:d3p1="http://schemas.datacontract.org/2004/07/GrassrootsHoops.Models.Api.Response.Divisions"/>
<d2p1:Total>0</d2p1:Total>
</Divisions>
</ApiDivisionsResponse>
1

7 Answers 7

44

Option 1 is to switch to using XmlSerializer in GlobalConfiguration:

config.Formatters.XmlFormatter.UseXmlSerializer = true;

Option 2 is to decorate your models with

[DataContract(Namespace="")]

(and if you do so, you'd need to decorate the members with [DataMember] attributes).

6
  • 2
    I did the UseXmlSerializer and now it just uses JSON. Commented Sep 25, 2012 at 22:59
  • 6
    [DataContract()] attribute requires a reference to the System.Runtime.Serialization library Commented Nov 26, 2012 at 17:26
  • @MikeFlynn I ran into the same issue. If the XmlSerializer can not serialize the object it will try Json instead. Not really an expected default behavior IMO. Especially when NetDataContractSerializer throws errors. Commented Feb 14, 2013 at 22:49
  • 7
    I'm setting UseXmlSerializer = true; but the format is the same as the OP wrote Commented Sep 12, 2013 at 12:02
  • Don't know why, but I tried both methods and none work for me. Konamiman's answer did the job. Commented May 19, 2016 at 6:35
22

If you're willing to decorate your model with XmlRoot, here's a nice way to do it. Suppose you have a car with doors. The default WebApi configuration will return something like :

<car 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <doors>
        <door>
            <color>black</color>
        </door>
    </doors>
</car>

This is what you want:

<car>
    <doors>
        <door>
            <color>black</color>
        </door>
    </doors>
</car>

Here's the model:

[XmlRoot("car")]
public class Car
{
    [XmlArray("doors"), XmlArrayItem("door")]
    public Door[] Doors { get; set; }
}

What you have to do is create a custom XmlFormatter that will have an empty namespace if there are no namespaces defined in the XmlRoot attribute. For some reason, the default formatter always adds the two default namespaces.

public class CustomNamespaceXmlFormatter : XmlMediaTypeFormatter
{
    public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content,
                                            TransportContext transportContext)
    {
        try
        {
            var xns = new XmlSerializerNamespaces();
            foreach (var attribute in type.GetCustomAttributes(true))
            {
                var xmlRootAttribute = attribute as XmlRootAttribute;
                if (xmlRootAttribute != null)
                {
                    xns.Add(string.Empty, xmlRootAttribute.Namespace);
                }
            }

            if (xns.Count == 0)
            {
                xns.Add(string.Empty, string.Empty);
            }

            var task = Task.Factory.StartNew(() =>
                {
                    var serializer = new XmlSerializer(type);
                    serializer.Serialize(writeStream, value, xns);
                });

            return task;
        }
        catch (Exception)
        {
            return base.WriteToStreamAsync(type, value, writeStream, content, transportContext);
        }
    }
}

Last thing to do is add the new formatter in the WebApiContext. Be sure to remove (or clear) the old XmlMediaTypeFormatter

public static class WebApiContext
{
    public static void Register(HttpConfiguration config)
    {
        ...
        config.Formatters.Clear();
        config.Formatters.Add(new CustomNamespaceXmlFormatter{UseXmlSerializer=true});
        ...
    }
}   
6
  • Hi, I like your solution, however I don't get the point in calling the base implementation if an exception occurs. Can you explain why have you done that? Thanks. Commented Nov 5, 2013 at 9:09
  • Unfortunately, it's been a while since I've hacked on this. From what I remember, I think that it was intended as a way to simply skip the removal of XML namespaces and simply call the "normal" formatter. Commented Nov 7, 2013 at 17:15
  • 3
    Great work. But, a word of caution: config.Formatters.Add(new IgnoreNamespacesXmlMediaTypeFormatter { UseXmlSerializer = true }); should be set otherwise while sending POST data, FromBody will not be able to serialize it. Commented Jun 4, 2014 at 15:49
  • 1
    This is the only solution that worked for me, after 5 hours searching. I ended up using Konamiman's version of this. Commented May 19, 2016 at 6:32
  • Note: config.Formatters.Clear(); eliminates all other formatters, including json. Commented May 19, 2016 at 6:57
6

I like pobed2's answer. But I needed the CustomNamespaceXmlFormatter to allow me to specify a default root namespace to be used when the XmlRoot attribute is missing and also when it is present and has no value in the Namespace property (that is, the attribute is used to set the root element name only). So I created an improved version, here it is in case it's useful for someone:

public class CustomNamespaceXmlFormatter : XmlMediaTypeFormatter
{
    private readonly string defaultRootNamespace;

    public CustomNamespaceXmlFormatter() : this(string.Empty)
    {
    }

    public CustomNamespaceXmlFormatter(string defaultRootNamespace)
    {
        this.defaultRootNamespace = defaultRootNamespace;
    }

    public override Task WriteToStreamAsync(
        Type type, 
        object value, 
        Stream writeStream,
        HttpContent content,
        TransportContext transportContext)
    {
        var xmlRootAttribute = type.GetCustomAttribute<XmlRootAttribute>(true);
        if(xmlRootAttribute == null)
            xmlRootAttribute = new XmlRootAttribute(type.Name)
            {
                Namespace = defaultRootNamespace
            };
        else if(xmlRootAttribute.Namespace == null)
            xmlRootAttribute = new XmlRootAttribute(xmlRootAttribute.ElementName)
            {
                Namespace = defaultRootNamespace
            };

        var xns = new XmlSerializerNamespaces();
        xns.Add(string.Empty, xmlRootAttribute.Namespace);

        return Task.Factory.StartNew(() =>
        {
            var serializer = new XmlSerializer(type, xmlRootAttribute);
            serializer.Serialize(writeStream, value, xns);
        });
    }
}
2
  • 1
    As explained in this answer, to avoid a severe memory leak, an XmlSerializer constructed with a non-default constructor must be statically cached and reused. See also the docs which state If you use any of the other constructors, multiple versions of the same assembly are generated and never unloaded, which results in a memory leak and poor performance. ... Otherwise, you must cache the assemblies in a Hashtable, Commented Jul 19, 2017 at 5:29
  • This guy had exactly this issue and ate 32Gb of RAM in his server. The Evil XMLSerializer Commented May 18, 2019 at 4:18
3

In the project that keeps response models go to Properties/AssemblyInfo.cs

Add

using System.Runtime.Serialization;

and at the bottom add

[assembly: ContractNamespace("", ClrNamespace = "Project.YourResponseModels")]

Replace Project.YourResponseModels with the actual namespace where response models are located. You need to add one per namespace

2
  • Hello, looks like workaround and should be considered as a bad practice. For example, in case of project structure refactoring my YourResponseModels can be moved from Project.YourResponseModels namespace to Project.AnotherPlace.YourResponseModels. Everybody should keep it in mind in case of any refactoring. Commented Jan 24, 2018 at 8:59
  • This was old approach to make XML bodies clean for Web API purpose, so it wouldn't matter when you refactor, the code and entities is structured as you wish, just serializer would handle anything. As for restructuring I don't think AssemblyInfo.cs would contain more than 10-20 lines, still easy to maintain. Who would use XML this days anyway? With WebAPI 2.0+ all this is solved, and JSON should be the only format for modern aps. If you interface with old systems, you can keep XML namespaces in place anyway. Commented Jan 25, 2018 at 19:02
0

You could use the next algorithm

  1. Put attribute for your class

    [XmlRoot("xml", Namespace = "")]
    public class MyClass
    {
       [XmlElement(ElementName = "first_node", Namespace = "")]
       public string FirstProperty { get; set; }
    
       [XmlElement(ElementName = "second_node", Namespace = "")]
       public string SecondProperty { get; set; }
    }
    
  2. Write method into your Controller or util's class

    private ContentResult SerializeWithoutNamespaces(MyClass instanseMyClass)
    {
        var sw = new StringWriter();
        var xmlWriter = XmlWriter.Create(sw, new XmlWriterSettings() {OmitXmlDeclaration = true});
    
        var ns = new XmlSerializerNamespaces();
        ns.Add("", "");
        var serializer = new XmlSerializer(instanseMyClass.GetType());
        serializer.Serialize(xmlWriter, instanseMyClass, ns);
    
        return Content(sw.ToString());
    }
    
  3. Use method SerializeWithoutNamespaces into Action

    [Produces("application/xml")]
    [Route("api/My")]
    public class MyController : Controller
    {
      [HttpPost]
      public ContentResult MyAction(string phrase)
      {                           
        var instanseMyClass = new MyClass{FirstProperty ="123", SecondProperty ="789"};
        return SerializeWithoutNamespaces(instanseMyClass); 
      }
    }
    
  4. Don't forget to put some dependencies into StartUp class

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc()
            .AddXmlSerializerFormatters()
            .AddXmlDataContractSerializerFormatters();
    } 
    
2
  • Show us the output? does it contain default xsi namespaces? Commented Mar 7, 2019 at 10:38
  • 1
    @LeszekRepie The output will be <xml><first_node>123</first_node><second_node>789</second_node></xml> Commented Mar 7, 2019 at 11:18
0

The CustomNamespaceXmlFormatter class did the trick for me except it caused a memory leak (when my web service got hit hard, the memory kept increasing higher and higher), so I modified how the instances of XmlSerializer are created:

public class CustomNamespaceXmlFormatter : XmlMediaTypeFormatter
{
    private readonly string defaultRootNamespace;

    public CustomNamespaceXmlFormatter() : this(string.Empty)
    {
    }

    public CustomNamespaceXmlFormatter(string defaultRootNamespace)
    {
        this.defaultRootNamespace = defaultRootNamespace;
    }

    public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
    {
        if (type == typeof(String))
        {
            //If all we want to do is return a string, just send to output as <string>value</string>
            return base.WriteToStreamAsync(type, value, writeStream, content, transportContext);
        }
        else
        {
            XmlRootAttribute xmlRootAttribute = (XmlRootAttribute)type.GetCustomAttributes(typeof(XmlRootAttribute), true)[0];
            if (xmlRootAttribute == null)
                xmlRootAttribute = new XmlRootAttribute(type.Name)
                {
                    Namespace = defaultRootNamespace
                };
            else if (xmlRootAttribute.Namespace == null)
                xmlRootAttribute = new XmlRootAttribute(xmlRootAttribute.ElementName)
                {
                    Namespace = defaultRootNamespace
                };

            var xns = new XmlSerializerNamespaces();
            xns.Add(string.Empty, xmlRootAttribute.Namespace);

            return Task.Factory.StartNew(() =>
            {
                //var serializer = new XmlSerializer(type, xmlRootAttribute); **OLD CODE**
                var serializer = XmlSerializerInstance.GetSerializer(type, xmlRootAttribute);
                serializer.Serialize(writeStream, value, xns);                    
            });
        }
    }
}

public static class XmlSerializerInstance
{
    public static object _lock = new object();
    public static Dictionary<string, XmlSerializer> _serializers = new Dictionary<string, XmlSerializer>();
    public static XmlSerializer GetSerializer(Type type, XmlRootAttribute xra)
    {
        lock (_lock)
        {
            var key = $"{type}|{xra}";
            if (!_serializers.TryGetValue(key, out XmlSerializer serializer))
            {
                if (type != null && xra != null)
                {
                    serializer = new XmlSerializer(type, xra);
                }

                _serializers.Add(key, serializer);
            }

            return serializer;
        }
    }
}
-4

This works perfectly

public ActionResult JsonAction(string xxx)
{ 
    XmlDocument xmlDoc2 = new XmlDocument();
    xmlDoc2.Load(xmlStreamReader);

    XDocument d = XDocument.Parse(optdoc2.InnerXml);
    d.Root.Attributes().Where(x => x.IsNamespaceDeclaration).Remove();

    foreach (var elem in d.Descendants())
    elem.Name = elem.Name.LocalName;

    var xmlDocument = new XmlDocument();
    xmlDocument.Load(d.CreateReader());

    var jsonText = JsonConvert.SerializeXmlNode(xmlDocument);
    return Content(jsonText);
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.