Dependency injection directly into actions in ASP.NET Web API

Β· 1250 words Β· 6 minutes to read

There is a ton of great material on the Internet about dependency injection in ASP.NET Web API. One thing that I have not seen anywhere though, is any information about how to inject dependencies into the action, instead of a controller (constructor injection).

Injecting your dependencies directly into an action, rather than in the controller is a very reasonable approach, as it helps you falling into an over-injecting trap, and perhaps resolving too much things, for no real reason.

With Web API, it’s actually extremely easy to do, so let’s go ahead and implement it.

More after the jump.

Laying out the ground πŸ”—

Suppose we have the following controller and a service that we’d like to inject. The example is trivial, but the solution is going to be the same regardless of the complexity.

public interface IHelloService  
{  
string SayHello();  
}

public class HelloService : IHelloService  
{  
public string SayHello()  
{  
return "Hello";  
}  
}

public class RequestDto  
{  
public string Text {get; set;}  
}

public class ValuesController : ApiController  
{  
public string Post(RequestDto input, IHelloService helloService)  
{  
return helloService.SayHello() + " " + input.Text;  
}  
}  

Of course if we just try to use this controller now, we’ll inevitably suffer from a null reference, as there is nothing yet that instructs Web API what to do with the enigmatic IHelloService action parameter.

Building a custom HttpParameterBinding πŸ”—

Web API uses various implementations of HttpParameterBinding abstarct class to hydrate the action parameters with values and objects extracted from the incoming HTTP request. Out of the box, Web API uses a couple of implementations of the parameter binding, with the two main ones being ModelBindingParameterBinder or FormatterParameterBinder.

For simple and primitive types, or for those parameters that are decorated with ModelBinderAttribute, ModelBindingParameterBinder is used. In other cases (complex types), FormatterParameterBinder is used - to deserialize the body of the request into a relevant object that can be passed into the action.

The real problem of performing injection into actions in ASP.NET Web API is that something, somehow, would need to be plugged into the Web API pipeline, so that such an injected action argument:

  • is not treated as all of the other ones (so prevent FormatterParameterBinder from kicking in for it)
  • is correctly resolved from whatever IoC container you are using

Finally, our injected parameter has to be resolved from somewhere - in our case that would be the IoC container registered in your Web API application.

You can easily introudce your own HttpParameterBinding to fulfill both of these requirements. The code for our InjectParameterBinding is shown below.

public class InjectParameterBinding : HttpParameterBinding  
{  
public InjectParameterBinding(HttpParameterDescriptor descriptor) : base(descriptor)  
{  
}

public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)  
{  
if (actionContext.ControllerContext.Configuration.DependencyResolver != null)  
{  
var resolved = actionContext.Request.GetDependencyScope().GetService(Descriptor.ParameterType);  
actionContext.ActionArguments[Descriptor.ParameterName] = resolved;  
}  
return Task.FromResult(0);  
}  
}  

HttpParameterBinding has an abstract method ExecuteBindingAsync which you will need to ipmlement. It gives you access to contextual information such as HttpActionContext which in turns allows you to find out what action is executing, which controller is in use, or reach into the HttpConfiguration.

The semantic information about the parameter in play is represented by HttpParameterDescriptor. This instance is always passed through the constructor and you can use it to make decisions whether your custom binding should kick in for this particular parameter or not. In our case we simply rely on the parameter name and paramater type.

So, assuming our paramater type will be an interface that needs resolving from IoC, we can reach into the DependencyResolver (representing an adapter to our IoC container) and do just that. Then you will have to set the resolved instance on the ActionArguments dictionary of the HttpActionContext. This is the moment where we inject our service, and instead of a null, it will then be available from within then action as concrete instance resolved from IoC.

Plugging in an HttpParameterBinding πŸ”—

Once we have the InjectParameterBinding in place, the next step is to plug it in, and instruct Web API pipeline to use it when handling the incoming HTTP requests. Typically you plug in Web API services by registering them against HttpConfiguration.

This is possible here too, and we’ll return to that in a second, but first let’s look at an alternative method, which is creating a custom ParameterBindingAttribute. If you used Web API for at least a little, you are surely already familiar with FromUriAttribute and FromBodyAttribute that you can use to annotate parameters. Well, they are actually ParameterBindingAttribute. The class is shown below.

public abstract class ParameterBindingAttribute : Attribute  
{  
public abstract HttpParameterBinding GetBinding(HttpParameterDescriptor parameter);  
}  

It’s a way to tell Web API - for this parameter, use this specific binding. This is how it will look in our case:

[AttributeUsage(AttributeTargets.Parameter)]  
public class InjectAttribute : ParameterBindingAttribute  
{  
public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)  
{  
return new InjectParameterBinding(parameter);  
}  
}  

So if we now go back to our original example, it would need to look like this:

public class ValuesController : ApiController  
{  
public string Post(RequestDto input, [Inject]IHelloService helloService)  
{  
return helloService.SayHello() + " " + input.Text;  
}  
}  

This ensures that Web API will not try to bind IHelloService from body of the request or do any other crazy stuff, but will defer to our InjectParameterBinding, which will try to resolve this interface from the dependency resolver. Of course, you can inject more services (as additional parameters) if you wish.

Some might argue this is not very elegant - an alternative way would be to register the binding globally, against HttpConfiguration.ParameterBindingRules collection. It’s a list of precendce that Web API goes through when trying to select a parameter binding relevant to handle a given parameter - as soon as a parameter binding suitable for handling the parameter is found, it will be used and no further lookup is performed.

You can register a Func<HttpParameterDescriptor,HttpParameterBinding> there, at position 0 so that it’s used first. There, you can inspect the HttpParameterDescriptor similarly to how we did in our InjectAttribute. If we decide that a given parameter should be using InjectParameterBinding we return that, otherwise, we return null which forces Web API to keep checking other parameter binding rules (the out of the box ones).

{  
if (param.ParameterType.IsInterface)  
{  
return new InjectParameterBinding(param);  
}

return null;  
});  

In this case we simply check if the parameter is an interface, we will try to resolve it from the IoC, using InjectParameterBinding. The controller code can now be modified - we can ditch the [Inject] annotation and things still work.

public class ValuesController : ApiController  
{  
public string Post(RequestDto input, IHelloService helloService)  
{  
return helloService.SayHello() + " " + input.Text;  
}  
}  

You can even package the registration into an extension method to make it a tad cleaner:

public static class HttpConfigurationExtensions  
{  
public static void InjectInterfacesIntoActions(this HttpConfiguration config)  
{  
config.ParameterBindingRules.Insert(0, param =>  
{  
if (param.ParameterType.IsInterface)  
{  
return new InjectParameterBinding(param);  
}

return null;  
});  
}  
}  

So now all you need to call is:

config.InjectInterfacesIntoActions();  

Registering Dependency Resolver πŸ”—

The last thing to do is to register any dependency resolver - my favorite IoC container is Autofac, so I’m going to use that, from their Autofac.WebApi2 Nuget package.

Below is the whole setup of a Web API Startup class, with Autofac, our sample HelloService and the parameter binding allowing us to do action injection.

public class Startup  
{  
public void Configuration(IAppBuilder appBuilder)  
{  
var config = new HttpConfiguration();  
config.MapHttpAttributeRoutes();

var containerBuilder = new ContainerBuilder();  
containerBuilder.RegisterType<HelloService>().As<IHelloService>();  
var container = containerBuilder.Build();

config.DependencyResolver = new AutofacWebApiDependencyResolver(container);

config.InjectInterfacesIntoActions();  
appBuilder.UseWebApi(config);  
}  
}  

And that’s it!

About


Hi! I'm Filip W., a cloud architect from ZΓΌrich πŸ‡¨πŸ‡­. I like Toronto Maple Leafs πŸ‡¨πŸ‡¦, Rancid and quantum computing. Oh, and I love the Lowlands 🏴󠁧󠁒󠁳󠁣󠁴󠁿.

You can find me on Github, on Mastodon and on Bluesky.

My Introduction to Quantum Computing with Q# and QDK book
Microsoft MVP