Dependency injection for ASP.NET Web API action method parameters - c#

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));
}

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

Related

Can you override the body deserialization web api

When I attempt to override serialization settings for a controller they are used for creating the response but ignored when parsing the body.
I need to override the json serialization settings for each requests in WebAPI. I am trying to change the serialization settings based on the route in use. Example: if it is a V1 api use SerializationSettingsA, if it is a V2 api use SerializationSettingsB.
I have tried multiple approaches including overriding the IContentNegotiator and IHttpControllerActivator hoping to set the serialization settings for the context but in all case it does not work. The behavior I am seeing is that the override serialization settings are used when crating the response body but not when parsing the request. Is there some other settings that need overriding for changing how the request body is parsed.
class PerControllerConfigActivator : IHttpControllerActivator
{
private static readonly DefaultHttpControllerActivator Default = new DefaultHttpControllerActivator();
public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
{
controllerDescriptor.Configuration = HttpConfigurationFactory.CreateDefaultConfiguration();
controllerDescriptor.Configuration.Formatters.JsonFormatter.SerializerSettings.Converters.Insert(0,
new VersionedPropertyConverter(request.RequestUri.AbsoluteUri));
var result = Default.Create(request, controllerDescriptor, controllerType);
return result;
}
}
public class Startup
{
public void Configuration(IAppBuilder app)
{
var configuration = new HttpConfiguration();
configuration.MapHttpAttributeRoutes();
configuration.Formatters.Clear();
configuration.Services.Replace(typeof(IHttpControllerActivator), new PerControllerConfigActivator());
app.UseWebApi(configuration);
}
}
Borrowing some ideas from here, you could use custom input formatters with a specific content-type header on the request itself.
First you need to create an input formatter for the overrided version
public class MyNonDefaultJsonFormatter : InputFormatter
{
public MyNonDefaultJsonFormatter()
{
this.SupportedMediaTypes.Clear()
this.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/non-default-json");
}
public override bool CanRead(InputFormatterContext context)
{
return base.CanRead(context);
}
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext)
{
//Do some custom deserializing on context.HttpContext.Request.Body
}
}
Then you need to register the input formatter in configure services
services.AddMvc(options =>
{
options.InputFormatters.Add(new MyNonDefaultJsonFormatter());
}
Then specify the Content-Type as "application/non-default-json" when making your request.
As another idea, you may be able to remove the default JSON deserializer all together and instead register two serializers of your own, both accepting standard application/json, but with type checking in the CanReadType override that allows you to differentiate. I haven''t had a chance to test this out myself, but if I do will report back here.

Use FromQuery model binder in output formatter

I have a custom OutputFormatter in my .net core project. In there I want to use some info inside the querystring of the initial request.
Inside the controller this is nicely done with the FromQuery modelbinder, giving me an object to work with. I would like to have this object (model) in my output formatter as well.
Can I somehow call FromQuery as an instance or such, so I can pass in the HttpContext or the querystring even, to get the model?
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
{
// Want a model from my querystring here
}
Use HttpContext.Items. The objects placed in there will be cleaned up at the end of request. And you can pass even more with it, defaulted values or changed values (probably it can be a dangerous point if you are going to change binded object)
// GET api/values
[HttpGet]
public ActionResult Get([FromQuery] QData data)
{
HttpContext.Items["data"] = data;
.......
return Ok(....);
}
Also you can have multiple formatters for different types of requests.
public class Formatter : OutputFormatter
{
public override bool CanWriteResult(OutputFormatterCanWriteContext context)
{
return context.HttpContext.Items["data"] is QData;
}
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
{
var incoming = context.HttpContext.Items["data"] as QData;
.......
}
}
The same way you can put any other object in Items and work with it in formatter. In case of other object it can be more generic and stable solution as it relies on specific structure.

Register subset of Web API controllers with simple injector

I'm manually registrering a subset of my project's Web API controllers:
container.Register(typeof(ILGTWebApiController), controllerType, Lifestyle.Transient);
Works fine. However, when I run:
GlobalConfiguration.Configuration.DependencyResolver =
new SimpleInjectorWebApiDependencyResolver(container);
It seems to affect all Web API controllers in the project. I would like to leave the other ones untouched by simple injector.
If I don't run the code above, simple injector will complain about my controllers not having an empty constructor (which they obviously won't, since I'm using DI).
Instead of replacing the IDependencyResolver, create a custom IHttpControllerActivator that resolves the controller or fallbacks to the original activator otherwise:
public sealed class MyControllerActivator : IHttpControllerActivator
{
private readonly Container container;
private readonly IHttpControllerActivator original;
public MyControllerActivator(Container container, IHttpControllerActivator original)
{
this.container = container;
this.original = original;
}
public IHttpController Create(
HttpRequestMessage req, HttpControllerDescriptor desc, Type type)
{
if (type == typeof(ILGTWebApiController))
return (IHttpController)this.container.GetInstance(type);
return this.original.Create(req, desc, type);
}
}
You can configure your custom MyControllerActivator as follows:
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator),
new MyControllerActivator(
container,
GlobalConfiguration.Configuration.Services.GetHttpControllerActivator()));

Custom MediaTypeFormatter (Serializer) for ApiController specific method

I have ApiController in place and one of methods needs have custom serialization.
public async Task<Session> PostSession([FromBody] Session session)
There is massive Session object that holds everything and consumer does not want do get more than half of it, normally I could just decorate properties with [XmlIgnore()] and be done, however this object is being passed internally across many APIs and removing these would break these mechanisms.
Another solution would be to just replace that object with different one like CastratedSession and return that. I can't do this since same endpoint is being called by existing 3rd party APIs that expect to get Session or they would have to rewrite their stuff (not going to happen).
I then proposed to create a different endpoint that this 3rd party could call - but architect objected and said to do the custom MediaTypeFormatter based on specific content-type value they will set in header, problem is we already are using in this same controller custom MediaTypeFormatter, and the way I understand out of the box one can only set them in configuration per controller
public static void ConfigureApis(HttpConfiguration config)
{
config.Formatters.Add(new CustomJsonFormatter());
config.Formatters.Add(new CustomXmlFormatter());
}
Which sorta paints me into the corner.
How (Can) I setup a custom MediaTypeFormatter per method on ApiController?
you could write a custom action filter, override the OnActionExecuting method to inspect the target action, then add the proper formatter to the configuration.
internal class DecisionMakingFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var actionName= actionContext.ActionDescriptor.ActionName;
if(actionName == "Some Foo")
{
actionContext.RequestContext.Configuration.Formatters.Add(new CustomMediaFormatter());
}
base.OnActionExecuting(actionContext);
actionContext.RequestContext.Configuration.Formatters.Remove(new CustomMediaFormatter());
}
}
figured out a workaround myself
I inherited and replaced CustomXmlFormatter() with EvenMoreCustomXmlFormatter()
then in WriteToStreamAsync did this:
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
{
if (content.Headers.ContentType.MediaType == "text/sessionXml") // this was agreed with 3rd party
{
//do my serialization stuff
}
else
{
return base.WriteToStreamAsync(type, value, writeStream, content, transportContext);
}
}
hope this saves you some time.

How to register Drum.UriMaker<> using Simple Injector?

I'm using Drum which provides a generic class `UriMaker:
public class UriMaker<TController>
{
// I need use this one
public UriMaker(UriMakerContext context, HttpRequestMessage request) { }
public UriMaker(Func<MethodInfo, RouteEntry> mapper, UrlHelper urlHelper) { }
}
Used like this:
public class UserController : ApiController
{
public UserController(UriMaker<UserController> urlMaker) {}
}
I've used to register it with Unity:
container.RegisterType(typeof(UriMaker<>),
new InjectionConstructor(typeof(UriMakerContext), typeof(HttpRequestMessage)));
but now migrating to Simple Injector. I already have this:
UriMakerContext uriMaker = config.MapHttpAttributeRoutesAndUseUriMaker();
container.RegisterSingle(uriMakerContext);
So how now register UriMaker<> itself?
Although it is possible to configure Simple Injector to allow injecting an UriMaker<TController> directly into your controllers, I strongly advice against this for multiple reasons.
First of all, you should strive to minimize the dependencies your application takes on external libraries. This can easily be done by defining an application specific abstraction (conforming the ISP).
Second, injecting the UriMaker directly makes your extremely hard to test, since the UriMaker is pulled into your test code, while it assumes an active HTTP request and assumes the Web API route system to be configured correctly. These are all things you don't want your test code to be dependent upon.
Last, it makes verifying the object graph harder, since the UriMaker depends on an HttpRequestMessage, which is a runtime value. In general, runtime values should not be injected into the constructors of your services. You should build up your object graph with components (the stuff that contains the application's behavior) and you send runtime data through the object graph after construction.
So instead, I suggest the following abstraction:
public interface IUrlProvider
{
Uri UriFor<TController>(Expression<Action<TController>> action);
}
Now your controllers can depend on this IUrlProvider instead of depending on an external library:
public class UserController : ApiController
{
private readonly IUrlProvider urlProvider;
public UserController(IUrlProvider urlProvider)
{
this.urlProvider = urlProvider;
}
public string Get()
{
this.urlProvider.UriFor<HomeController>(c => c.SomeFancyAction());
}
}
Under the covers you of course still need to call Drum, and for this you need to define a proxy implementation for IUrlProvider:
public class DrumUrlProvider : IUrlProvider
{
private readonly UriMakerContext context;
private readonly Func<HttpRequestMessage> messageProvider;
public DrumUrlProvider(UriMakerContext context,
Func<HttpRequestMessage> messageProvider)
{
this.context = context;
this.messageProvider= messageProvider;
}
public Uri UriFor<TController>(Expression<Action<TController>> action)
{
HttpRequestMessage message = this.messageProvider.Invoke();
var maker = new UriMaker<TController>(this.context, message);
return maker.UriFor(action);
}
}
This implementation can be registered as singleton in the following way:
container.EnableHttpRequestMessageTracking(config);
UriMakerContext uriMakerContext =
config.MapHttpAttributeRoutesAndUseUriMaker();
IUrlProvider drumProvider = new DrumUrlProvider(uriMakerContext,
() => container.GetCurrentHttpRequestMessage());
container.RegisterSingle<IUrlProvider>(drumProvider);
This example uses the Simple Injector Web API integration package to allow retrieving the current request's HttpRequestMessage using the EnableHttpRequestMessageTracking and GetCurrentHttpRequestMessage extension methods as explained here.

Categories

Resources