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?
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));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With