Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XML Namespaces in ASP.NET Web API

I am currently working on a project that requires me to output XML from its endpoints along with JSON. I have the following model:

[DataContract(Namespace="http://www.yale.edu/tp/cas")]
[XmlType("serviceResponse")]
[XmlRoot(Namespace="http://www.yale.edu/tp/cas")]
public class ServiceResponse
{
    [XmlElement("authenticationSuccess")]
    public AuthenticationSuccess Success { get; set; }

    [XmlElement("authenticationFailure")]
    public AuthenticationFailure Failure { get; set; }
}

The output is as follows when success is not null:

<serviceResponse>
<authenticationSuccess />
</serviceResponse>

Now, I can see that obviously, I don't have a prefix assigned to the namespace I told the elements to be a part of. My issue is that I cannot find a place to add the namespace prefixes in MVC4 using the media formatter. I have the following in my global.asax:

GlobalConfiguration.Configuration.Formatters.XmlFormatter.UseXmlSerializer = true;
GlobalConfiguration.Configuration.Formatters.XmlFormatter.RemoveSerializer(typeof(Models.ServiceResponse));
GlobalConfiguration.Configuration.Formatters.XmlFormatter.SetSerializer(typeof(Models.ServiceResponse), new Infrastructure.NamespaceXmlSerializer(typeof(Models.ServiceResponse)));

I made a custom serializer based on XmlSerializer in an attempt to intercept the writing request and tack the namespace list on there. The issue with this method is that right now I have breakpoints inside every overrideable method and none of them are tripped when serializing which leads me to believe that my serializer isn't being used.

Is there some built in way to accomplish what I want to do or am I stuck re-implementing the XmlMediaTypeFormatter to pass in namespaces when it serializes objects?

like image 444
Los Frijoles Avatar asked Oct 04 '22 17:10

Los Frijoles


1 Answers

As a followup answer: The easiest solution for me was to write my own XmlMediaTypeFormatter. As it turns out, its not nearly as intimidating as I thought.

public class NamespacedXmlMediaTypeFormatter : XmlMediaTypeFormatter 
{
    const string xmlType = "application/xml";
    const string xmlType2 = "text/xml";

    public XmlSerializerNamespaces Namespaces { get; private set; }

    Dictionary<Type, XmlSerializer> Serializers { get; set; }

    public NamespacedXmlMediaTypeFormatter()
        : base()
    {
        this.Namespaces = new XmlSerializerNamespaces();
        this.Serializers = new Dictionary<Type, XmlSerializer>();
    }

    public override Task WriteToStreamAsync(Type type, object value, System.IO.Stream writeStream, System.Net.Http.HttpContent content, System.Net.TransportContext transportContext)
    {
        lock (this.Serializers)
        {
            if (!Serializers.ContainsKey(type))
            {
                var serializer = new XmlSerializer(type);
                //we add a new serializer for this type
                this.Serializers.Add(type, serializer);
            }
        }

        return Task.Factory.StartNew(() =>
        {
            XmlSerializer serializer;
            lock (this.Serializers)
            {
                serializer = Serializers[type];
            }

            var xmlWriter = new XmlTextWriter(writeStream, Encoding.UTF8);
            xmlWriter.Namespaces = true;
            serializer.Serialize(xmlWriter, value, this.Namespaces);
        });
    }
}

Here is the formatter as a gist: https://gist.github.com/kcuzner/eef239003d4f99dfacea

The formatter works by exposing the XmlSerializerNamespaces that the XmlSerializer is going to use. This way I can add arbitrary namespaces as needed.

My top model looks as follows:

[XmlRoot("serviceResponse", Namespace="http://www.yale.edu/tp/cas")]
public class ServiceResponse
{
    [XmlElement("authenticationSuccess")]
    public CASAuthenticationSuccess Success { get; set; }

    [XmlElement("authenticationFailure")]
    public CASAuthenticationFailure Failure { get; set; }
}

In my Global.asax I added the following to place my formatter on the top of the list:

var xmlFormatter = new Infrastructure.NamespacedXmlMediaTypeFormatter();
xmlFormatter.Namespaces.Add("cas", "http://www.yale.edu/tp/cas");
GlobalConfiguration.Configuration.Formatters.Insert(0, xmlFormatter);

After adding the formatter and making sure my attributes were set up properly, my XML was properly namespaced.

In my case, I needed to add the cas namespace linking to http://www.yale.edu/tp/cas. For others using this, just change/replicate the Add call to your heart's content adding namespaces.

like image 107
Los Frijoles Avatar answered Oct 09 '22 23:10

Los Frijoles