9

I am working on an ASP.NET Web API project in C# for a JSON interface to a mobile application. My idea was to create interfaces for all requests and then only use these interfaces in the Web API code.

I ended up with something like this:

public interface IApiObject {}
public interface IApiResponse<T> : IApiObject where T : IApiObject {}
public interface IApiRegistrationRequest : IApiObject {}

My controller looks like this:

public class MyApiController : ApiController
{

    public IApiResponse<IApiObject> Register(IApiRegistrationRequest request) {
        // do some stuff
    }
}

My Web API project also contains implementations of these interfaces.

I assumed Web API projects use model binding like MVC projects do, so I created an inheritance aware ModelBinderProvider for providing a binder for all IApiObjects and a custom model binder using a Unity container to resolve the interfaces to their implementations.

However, after some more investigation, I came across How Web API does parameter binding and found out that Web API uses formatters instead of model binders for complex types. The linked blog post recommends using a ModelBinderAttribute on my action parameters, but that attribute only accepts a type as a parameter. My custom model binder does, however, not contain an empty constructor (it needs a unity container), so I would need to pass an instance of it.

The other way I can think of is using dependency injection for the formatters. Unfortunately, I am unfamiliar with them as I have never used them before.

Which is the right way to go? And how do I do it?

2 Answers 2

5

This is what I came up with now and it works.

I decided to create a custom formatter which does the unity calls and forwards all further operations to another formatter using the resolved type. It looks like a lot of code, but that is only because all the methods need to be overwritten so the type can always be resolved.

public class UnityFormatter : MediaTypeFormatter
{
    private MediaTypeFormatter formatter;

    private IUnityContainer container;

    public UnityFormatter(MediaTypeFormatter formatter, IUnityContainer container)
    {
        this.formatter = formatter;
        this.container = container;

        foreach (var supportedMediaType in this.formatter.SupportedMediaTypes)
        {
            this.SupportedMediaTypes.Add(supportedMediaType);
        }

        foreach (var supportedEncoding in this.formatter.SupportedEncodings)
        {
            this.SupportedEncodings.Add(supportedEncoding);
        }

        foreach (var mediaTypeMapping in this.MediaTypeMappings)
        {
            this.MediaTypeMappings.Add(mediaTypeMapping);
        }

        this.RequiredMemberSelector = this.formatter.RequiredMemberSelector;
    }

    private Type ResolveType(Type type)
    {
        return this.container.Registrations.Where(n => n.RegisteredType == type).Select(n => n.MappedToType).FirstOrDefault() ?? type;
    }

    public override bool CanReadType(Type type)
    {
        return this.formatter.CanReadType(this.ResolveType(type));
    }

    public override bool CanWriteType(Type type)
    {
        return this.formatter.CanWriteType(this.ResolveType(type));
    }

    public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type, HttpRequestMessage request, MediaTypeHeaderValue mediaType)
    {
        return this.formatter.GetPerRequestFormatterInstance(this.ResolveType(type), request, mediaType);
    }

    public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
    {
        return this.formatter.ReadFromStreamAsync(this.ResolveType(type), readStream, content, formatterLogger);
    }

    public override void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType)
    {
        this.formatter.SetDefaultContentHeaders(this.ResolveType(type), headers, mediaType);
    }

    public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
    {
        return this.formatter.WriteToStreamAsync(this.ResolveType(type), value, writeStream, content, transportContext);
    }
}

Finally, register our custom formatter in the application config (Global.asax Application_Start). I chose to replace all current formatters with an instance of my custom one, so I get reflection for all data types.

// set up unity container, register all types
UnityContainer container = new UnityContainer();
container.RegisterType<IApiRegistrationRequest, ApiRegistrationRequest>();

// save existing formatters and remove them from the config
List<MediaTypeFormatter> formatters = new List<MediaTypeFormatter>(GlobalConfiguration.Configuration.Formatters);
GlobalConfiguration.Configuration.Formatters.Clear();

// create an instance of our custom formatter for each existing formatter
foreach (MediaTypeFormatter formatter in formatters)
{
    GlobalConfiguration.Configuration.Formatters.Add(new UnityFormatter(formatter, container));
}
Sign up to request clarification or add additional context in comments.

Comments

2

I Suggest you Have a look at Service Stack http://www.servicestack.net/

Its got the same all around design as asp.net-WebApi but it has goodies like IOC baked into it.

There is a amazing series on service stack at http://pluralsight.com/training/Courses/TableOfContents/service-stack

1 Comment

Thanks, that looks interesting, but I cannot easily change the technology right now.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.