5
Vote

More virtual extensibility to XmlMediaTypeFormatter and make it instance-type-aware

description

I'm going to try and keep this brief - but it might be difficult.

In the project I'm writing at the moment, I've customised the XML formatter to correctly serialize an instance based on it's actual type rather than the method's return type - paralleling what the JSON.Net serialiser does; which fixes a bug with XML content for the following service:

interface IFoo{
string Bar { get; set; }
}

class Foo : IFoo {
public string Bar { get; set; }
}

public class ExampleController : ApiController
{
public IFoo GetExample(){
return new Foo() { Bar = "Hello World!" };
}
}

When executed as JSON content, you get a JSON representation of the Foo.

When executed as XML content, the operation fails because the DataContractSerializer expects Foo to be added as a known type.

This can be fixed by changing the return type of the method - however, in my current project this just isn't desirable; as it's a multi-tenant application where controllers can be overriden based on request hostname; and the actual return types of the underlying service operations only have to implement the interface - but could have many more members specific to their remote operation.

To fix this - I created an 'Ex' version of the formatter - that picks up some data added by an ActionFilter which is used by the programmer to state declaratively that XML serialization should consider the whole-object, not just the static return type. I attach that code.

You'll notice that I've had to use the GetPerRequestFormatterInstance for this purpose - which is not ideal. More on that in a moment.

This solution does not, nor can it, solve cases where an IEnumerable<IFoo> or IDictionary<string, IFoo> will be returned. Again, the JSON.Net serializer is quite happy with this scenario, whereas the XML one is not.

In this case, it would be necessary to customise the actual serializer instance that is constructed based on the instance of the enumerable being serialized - a simple dynamic implementation being to forward-scan an IEnumerable or IDictionary (although IDictionary can be treated as IEnumerable<KeyValuePair<TKey, TValue>>) looking for all instances whose actual types are different to the static type of the generic, and then adding those to a dynamic list of known types.

This cannot be done in the current codebase - the creation of the serializer is private. However, making it virtual won't help on it's own either, because the value to be serialized must also be passed.

Ideally we need to be able to override the serializer creation code - and have that receive not only the type to be constructed, but also the value AND a state value that can be built from the request's properties (perhaps the properties themselves, or even the request).

I know that the GetPerRequestFormatterInstance method goes some way to solving this - and that it is possible to override serializers through the SetSerializer method - but the former is a very inefficient solution where lots of types are involved - and the second is clumsy. They also do not allow us to handle the case of the enumerables, as I say.

I will look forward to any furhter thoughts on this. If I've missed something blindingly obvious, then do let me know!

file attachments

comments

psewar wrote Jun 6, 2012 at 9:56 PM

Hey there,

we fixed a similar issues with inheritance with an own implementation of the XmlFormatter which derives from the MediaTypeFormatter.
During the Read and Write events we access an own instance of the DataContractSerializer where we passed in a DataContractResolver.

An Example of such a DataContractResolver can be found here:
http://blogs.msdn.com/b/youssefm/archive/2009/06/05/introducing-a-new-datacontractserializer-feature-the-datacontractresolver.aspx
public class DeserializeAsBaseResolver : DataContractResolver
{
    public override bool TryResolveType(Type type, Type declaredType, DataContractResolver knownTypeResolver, out XmlDictionaryString typeName, out XmlDictionaryString typeNamespace)
    {
        bool result = knownTypeResolver.TryResolveType(type, declaredType, null, out typeName, out typeNamespace);
        if (result != true)
        {
            // Hack for the BusinessObjects so that they can get serialized anyway
            if (typeof(IBusinessObject).IsAssignableFrom(declaredType))
            {
                result = true;
            }
        }
        return result;
    }

    public override Type ResolveName(string typeName, string typeNamespace, Type declaredType, DataContractResolver knownTypeResolver)
    {
        return knownTypeResolver.ResolveName(typeName, typeNamespace, declaredType, null) ?? declaredType;
    }
}

I have not tested it with interfaces until now, so I hope this helps you at least to get a bit further.

Cheers,
Kay

LordZoltan wrote Jun 7, 2012 at 9:52 AM

Hi Kay,

Yes I started looking at a DataContractResolver, and can see that it might work well for a 'known list' of types, however the scenario I discuss is at a much lower and more generic level where a common base or interface wouldn't be known.

I don't want to have to tell the serializer, or a resolver (despite the documentation's assertions, a DataContract resolver is not really a way to employ dynamic Known Type functionality - it's just an extension) which types I'm expecting - I want it to just work in the same way that the JSON.Net serializer does. To be honest, until it does, Web API is not offering a level playing field for both content types.

I'm currently looking at a custom XmlObjectSerializer that proxies DataContractSerializer, which seems like it might work - although performance won't be ideal.

Another problem I've noticed is how ugly the XML is (lots of repeated namespaces) and that it's impossible to change the writer settings (NamespaceHandling member) of the XML writer. So I'm going to have create my own writer to write to memory then WriteRaw to the output stream if I want my XML to look professional.

I have created a fork and started work on a pull request to put the extensibility into XmlMediaTypeFormatter to be able to customise both the XmlWriter that's constructed and the settings that are used; I was then going to put the serializer construction code into a protected virtual method as well (all of which would take the type, object and http content available in the async Write method).

However because of the strong-name difference between the nuget packages and the local build I can't easily get these customised DLLs into my test project without pulling apart my own nuget package chain (of internal extensions to MVC et al). Doing all that for a change that might not even get accepted is too risky for me (or, rather, my employer!)

LordZoltan wrote Jun 7, 2012 at 1:06 PM

Have submitted a pull request here: http://aspnetwebstack.codeplex.com/SourceControl/network/forks/LordZoltan/XmlMediaTypeFormatter/contribution/2289

With a couple of minor changes to the XmlMediaTypeFormatter that will enable a cleaner implementation of this; but which will also mean a bit more control can be had over the output xml and how it is generated on a per-instance basis without having to use the SetSerializer method.

HongmeiG wrote Sep 26, 2012 at 5:53 AM

We should improve our serializer extensibility to enable this customization.