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>
tugberk
  • 57,477
  • 67
  • 243
  • 335
Mike Flynn
  • 22,342
  • 54
  • 182
  • 341
  • You could try something like this: http://stackoverflow.com/questions/29352015/how-can-i-create-custom-xml-namespace-attributes-when-consuming-a-legacy-soap-se – John Meyer Mar 30 '15 at 17:06

7 Answers7

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

JackPoint
  • 4,031
  • 1
  • 30
  • 42
Filip W
  • 27,097
  • 6
  • 95
  • 82
  • 2
    I did the UseXmlSerializer and now it just uses JSON. – Mike Flynn Sep 25 '12 at 22:59
  • 6
    `[DataContract()]` attribute requires a reference to the [`System.Runtime.Serialization`](http://msdn.microsoft.com/en-us/library/kd1dc9w5.aspx) library – Andrew Nov 26 '12 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. – cdeutsch Feb 14 '13 at 22:49
  • 7
    I'm setting `UseXmlSerializer = true;` but the format is the same as the OP wrote – Abir Sep 12 '13 at 12:02
  • Don't know why, but I tried both methods and none work for me. Konamiman's answer did the job. – Guilherme May 19 '16 at 06:35
  • Option1 works for me but I'm getting null values inside my child tag, please check [link]http://stackoverflow.com/questions/41878363/asp-net-webapi-xml-deserialize for more info. Any advice will be much appreciated – Binny Jan 27 '17 at 09:59
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});
        ...
    }
}   
pobed2
  • 359
  • 2
  • 9
  • 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. – Andras Toth Nov 05 '13 at 09: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. – pobed2 Nov 07 '13 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. – now he who must not be named. Jun 04 '14 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. – Guilherme May 19 '16 at 06:32
  • Note: config.Formatters.Clear(); eliminates all other formatters, including json. – Guilherme May 19 '16 at 06:57
  • I used the CustomNamespaceXmlFormatter class above and it worked great, however, it seems like it's causing a memory leak or other memory issue where the web service's memory just grows and grows to several gigabytes after thousands of requests and then the web service stops working. Anyone else experience this? I see the comment by dbc below about being careful how you declare the class, but I wasn't seeing a bunch of dynamic assemblies created and I did change how I instantiated it, didn't make a difference. – Kelly Apr 18 '19 at 15:02
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);
        });
    }
}
Konamiman
  • 49,681
  • 17
  • 108
  • 138
  • 1
    As explained in [this answer](https://stackoverflow.com/a/23897411/3744182), to avoid a severe memory leak, an `XmlSerializer` constructed with a non-default constructor must be statically cached and reused. See also [the docs](https://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer.aspx#Remarks) 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,* – dbc Jul 19 '17 at 05:29
  • This guy had exactly this issue and ate 32Gb of RAM in his server. [The Evil XMLSerializer](https://kalapos.net/Blog/ShowPost/how-the-evil-system-xml-serialization-xmlserializer-class-can-bring-a-server-with-32gb-ram-down) – strattonn May 18 '19 at 04: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

Pawel Cioch
  • 2,895
  • 1
  • 30
  • 29
  • 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. – Sergey Popov Jan 24 '18 at 08: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. – Pawel Cioch Jan 25 '18 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();
    } 
    
Larissa Savchekoo
  • 6,412
  • 1
  • 13
  • 7
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;
        }
    }
}
Kelly
  • 945
  • 2
  • 18
  • 31
-3

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);
}