Remove namespace in XML from ASP.NET Web API
Asked Answered
P

7

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>
Planospore answered 25/9, 2012 at 20:47 Comment(1)
You could try something like this: #29352515Chattanooga
D
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).

Damiano answered 25/9, 2012 at 22:5 Comment(6)
I did the UseXmlSerializer and now it just uses JSON.Planospore
[DataContract()] attribute requires a reference to the System.Runtime.Serialization libraryUmiak
@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.Deoxygenate
I'm setting UseXmlSerializer = true; but the format is the same as the OP wroteSnavely
Don't know why, but I tried both methods and none work for me. Konamiman's answer did the job.Galcha
Option1 works for me but I'm getting null values inside my child tag, please check [link]#41878863 for more info. Any advice will be much appreciatedChlordane
F
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});
        ...
    }
}   
Face answered 29/5, 2013 at 15:48 Comment(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.Electrolysis
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.Face
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.Futurity
This is the only solution that worked for me, after 5 hours searching. I ended up using Konamiman's version of this.Galcha
Note: config.Formatters.Clear(); eliminates all other formatters, including json.Galcha
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.Brandonbrandt
O
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);
        });
    }
}
Oleg answered 10/11, 2014 at 15:5 Comment(2)
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,Overgrow
This guy had exactly this issue and ate 32Gb of RAM in his server. The Evil XMLSerializerTheophylline
G
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

Gudrunguelderrose answered 21/7, 2016 at 18:40 Comment(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.Dumdum
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.Gudrunguelderrose
U
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();
    } 
    
Undergrown answered 15/1, 2019 at 16:9 Comment(2)
Show us the output? does it contain default xsi namespaces?Hackman
@LeszekRepie The output will be <xml><first_node>123</first_node><second_node>789</second_node></xml>Undergrown
B
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;
        }
    }
}
Brandonbrandt answered 18/4, 2019 at 19:35 Comment(0)
L
-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);
}
Latinist answered 29/11, 2017 at 16:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.