13

I'm working on a MVC WebAPI, that uses EF with POCO classes for storage. What I want to do is get rid of the namespace from the XML, so that the endpoints would return and accept xml objects without it. (json works just fine)

<ACCOUNT xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Platform.Services.AccountService.Data">
<id>22</id>
<City i:nil="true"/>
<Country i:nil="true"/>
<Email>testas@email.com</Email>
<Phone i:nil="true"/> ...

I would like this to work

 <ACCOUNT>
    <id>22</id>
    <City i:nil="true"/>
    <Country i:nil="true"/>
    <Email>testas@email.com</Email>
    <Phone i:nil="true"/> ...

Hopefully without having to decorate the POCO's with a bunch of attributes.

I've set up a test solution for this, and indeed, these methods are beeing hit (must be some other problem in my system). Anyways - the result that I get using this solutions is this:

<ArrayOfAccount>
<Account>
<id>22</id>
<name>TestAcc</name>
<parentid xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance" d3p1:nil="true"/>
<status_id xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance" d3p1:nil="true"/>
<Email>Test@Test.com</Email>
</Account>
</ArrayOfAccount>

Got rid of the schema on top, but the properties are now messed up :( Here's a link to a sample project

Josh Noe
  • 2,664
  • 2
  • 35
  • 37
Marty
  • 3,485
  • 8
  • 38
  • 69
  • What's wrong with namespaces? Removing namespaces changes semantics of your document. You will never get rid of http://www.w3.org/2001/XMLSchema-instance namespace if you don't get rid of nil attribute that is in this namespace. If you get rid of nil attribute (or change their namespace to the empty namespace) you will change the meaning of the document. Suddenly parent_id and status_id may be treated as empty strings and not as null values. – Pawel Oct 05 '12 at 15:48
  • Well, Having only the " w3.org/2001/XMLSchema-instance " would be acceptable. I want to get rid of that "../Platform.Services.AccountService.Data" part mostly Interesting thing though. It the Data Class lives in the same project, under Model, there's no issue. But my model lives in a separate project and different namespace. – Marty Oct 10 '12 at 12:07

4 Answers4

13

This answer here is spot on the mark Remove namespace in XML from ASP.NET Web API.\

If you don't want to decorate your POCO's at all use the 1st option:

config.Formatters.XmlFormatter.UseXmlSerializer = true;

If you use option 2, you may need to add a reference to System.Runtime.Serialization

Assuming a post like this with Accept set correct:

GET http:// ANY OLD SERVER/api/foos/5 Accept: application/xml

Controller

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Runtime.Serialization;
using System.Web.Http;

namespace CutomXmlFormater.Controllers
{
//[DataContract(Namespace = "")]
public class Foo
{
    //[DataMember]
    public string Bar { get; set; }
}

public class FoosController : ApiController
{
    // GET api/foos/5
    public Foo Get(int id)
    {
        return new Foo() { Bar = "Test" };
    }
}

}

Config (App_Start/WebApiConfig)

//(Use this is you don't go the data contact and model annotation route)
config.Formatters.XmlFormatter.UseXmlSerializer = true;

Result

Either (With annotation and data contact):

<Foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xmlns:xsd="http://www.w3.org/2001/XMLSchema"><Bar>Test</Bar></Foo>

Or (with XML serialiser route):

<Foo xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Bar>Test</Bar></Foo>
Community
  • 1
  • 1
Mark Jones
  • 12,156
  • 2
  • 50
  • 62
  • 1
    I don't know if this is MCV 4 of smth else, but setting the UseXmlSerializer = True, makes it return JSON :( – Marty Oct 10 '12 at 12:05
  • Seems, that there's something else in my solution, messing things up. Thanks. – Marty Oct 10 '12 at 12:20
  • @Marty: try adding the ACCEPT header to your request and set it to 'application/xml' and see if that results in XML being returned in the response body e.g. Accept: application/xml – JTech Feb 14 '13 at 16:49
6

Maybe you could try with this:

Replace default XmlFormatter with your own:

GlobalConfiguration.Configuration.Formatters.Add(new CustomXmlFormatter());
GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);

And impement it using XmlSerializer, with specifying empty namespace during serialization, like this:

public CustomXmlFormatter()
{
    SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xml"));
    SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/xml"));
    Encoding = new UTF8Encoding(false, true);
}

protected override bool CanReadType(Type type)
{
    if (type == (Type)null)
        throw new ArgumentNullException("type");

    if (type == typeof(IKeyValueModel))
        return false;

    return true;
}

protected override bool CanWriteType(Type type)
{
    return true;
}

protected override Task OnReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext)
{
    return Task.Factory.StartNew(() =>
            {
                using (var streamReader = new StreamReader(stream, Encoding))
                {
                    var serializer = new XmlSerializer(type);
                    return serializer.Deserialize(streamReader);
                }
            });
}

protected override Task OnWriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext, System.Net.TransportContext transportContext)
{
    var serializer = new XmlSerializer(type);
    return Task.Factory.StartNew(() =>
            {
                using (var streamWriter = new StreamWriter(stream, Encoding))
                {
                    XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
                    ns.Add("", "");
                    serializer.Serialize(streamWriter, value, ns);
                }
            });
    }
}

Custom XML serializer was stolen from here, and as such is untested.

This should serialize objects w/o writing the namespace. I'm not sure if it will work OOTB for deserialization, you'd may have to experiment with XmlSerializer.Deserialize() overload that provides events and handle UnknownElement or UnknownNode event.

Boris B.
  • 4,933
  • 1
  • 28
  • 59
1

It's been awhile since I messed with MVC 4, but we ended up replacing the default formatter with the XmlSerializer like so:

protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);

        GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings = GetSerializeSettings();
        GlobalConfiguration.Configuration.Formatters.XmlFormatter.UseXmlSerializer = true;
    }

    internal JsonSerializerSettings GetSerializeSettings()
    {
        return new JsonSerializerSettings
                           {
                               Formatting = Formatting.Indented,
                               ContractResolver = new CamelCasePropertyNamesContractResolver(),
                               Converters = new List<JsonConverter> { new IsoDateTimeConverter() }
                           };
    }

This might help... I know we also customized the property names using attributes on the POCOs which you said you don't want to do, but that's because we wanted them to be camel-cased.

BlakeH
  • 3,354
  • 2
  • 21
  • 31
0

I have customized Boris's answer to MVC Webapi 5. Use either of the following http headers render the result using the CustomFormatter:

accept: application/xml

accept: text/xml

WebApiConfig.cs :

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        GlobalConfiguration.Configuration.Formatters.Add(new CustomXmlFormatter());
        GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
    }
}

CustomXmlFormatter.cs :

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Xml.Serialization;

namespace Custom.Formatter
{
    public class CustomXmlFormatter: MediaTypeFormatter
    {
        private  UTF8Encoding encoder;

        public CustomXmlFormatter()
        {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xml"));
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/xml"));
            encoder = new UTF8Encoding(false, true);
        }

        public override bool CanReadType(Type type)
        {
            if (type == (Type)null)
                throw new ArgumentNullException("type");

            //Type filtering
            if (type == typeof(SendEmailMessageResponse) || type == typeof(SendSmsMessageResponse))
                return true;
            else
                return false;
        }

        public override bool CanWriteType(Type type)
        {
            return true;
        }

        public override Task<object> ReadFromStreamAsync(Type type, Stream stream, HttpContent content, IFormatterLogger formatterLogger)
        {
            return Task.Factory.StartNew(() =>
                    {
                        using (var streamReader = new StreamReader(stream, encoder))
                        {
                            var serializer = new XmlSerializer(type);
                            return serializer.Deserialize(streamReader);
                        }
                    });
        }

        public override Task WriteToStreamAsync(Type type, object value, Stream stream,    HttpContent content, TransportContext transportContext)
        {
            var serializer = new XmlSerializer(type);
            return Task.Factory.StartNew(() =>
                    {
                        using (var streamWriter = new StreamWriter(stream, encoder))
                        {
                            XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
                            ns.Add("", "");
                            serializer.Serialize(streamWriter, value, ns);
                        }
                    });
        }
    }
}
Timores
  • 14,439
  • 3
  • 46
  • 46
Y P
  • 594
  • 8
  • 10