Proper Dependency Injection for Web API using StructureMap

Topics: Web Api
Jan 22, 2012 at 3:59 AM

Hey guys,

I've been following a couple threads with interest

http://wcf.codeplex.com/discussions/242055

http://wcf.codeplex.com/discussions/286557

but was not satisfied with the inheritance-based approach that SiggiG proposed.  Though this is a viable solution (and seemingly similar to what the team will expose in a future drop based on Danroth's comment in the 2nd thread above), I wanted to achieve a solution of constructor injection to avoid relying on inheritance.  I happen to be fairly opinionated on this topic and figured it could be done.  Alas, it is doable with StructureMap (and likely other good IoC containers).

The sample code I've pasted below contains many custom classes and extension methods that I've created so it won't compile if you try to copy/paste it but it should give you a good idea of how I solved the problem.

The magic (via StructureMap) is in configuring your Web API.  StructureMap has a neat method on its container called Inject that allows you to put objects into the container at run-time. Precisely what we need!

	private static HttpConfiguration GetHttpConfiguration(IContainer container)
	{
		return new HttpConfiguration
			{
				CreateInstance = (type, context, request) =>
					{
						container.Inject<IApiContext>(new ApiContext(request));
						return container.GetInstance(type);
					}
			}
	}

ApiContext is an adapter that takes HttpRequestMessage in its constructor and exposes custom headers and properties (injected into HttpRequestMessages.Properties property via OperationHandlers). It looks like this:

	public ApiContext(HttpRequestMessage request)
	{
		object principalProperty;
		if (request.Properties.TryGetValue("Principal", out principalProperty))
		{
			principal = principalProperty as CustomPrincipal
		}

		customHeaderValue = request.Headers.GetValues("CustomHeader").FirstOrDefault();

		object remoteEndpointMessageProperty;
		if (request.Properties.TryGetValue("System.ServiceModel.Channels.RemoteEndpointMessageProperty", out remoteEndpointMessageProperty))
		{
			var remoteEndpointMessage = remoteEndpointMessageProperty as RemoteEndpointMessageProperty;
			ipAddress = remoteEndpointMessage.IfNotNull(p => p.Address);
		}

		requestUri = request.RequestUri.AbsoluteUri;
	}

	public CustomPrincipal Principal
	{
		get
		{
			return principal;
		}
	}

	public string CustomHeaderValue
	{
		get
		{
			return customHeaderValue;
		}
	}

	public string IpAddress
	{
		get
		{
			return ipAddress;
		}
	}

	public string RequestUri
	{
		get
		{
			return requestUri;
		}
	}

Naturally, if you simply want to inject HttpRequestMessage directly, you can do that, too. Personally, I didn't want to add CustomPrincipal as an argument to my API methods nor did I want to expose the entire HttpRequestMessage property. The choice is yours.

And here is the API implementation that leverages this little bit of magic:

	public BackupsApi(IBackupService backupService, IApiContext context)
	{
		this.backupService = backupService;
		this.context = context;
	}

	[WebInvoke(UriTemplate = "/Automatic", Method = "POST")]
	[ApiAuthorization(MembershipRole.EnhancedBackup)]
	public BackupResponse CreateAutomaticBackup(AutomaticBackup backup)
	{
		IBackupView backupView = backupService.SaveAutomaticBackup(
			context.Principal.Identity.Name,
			context.DeviceUuid,
			backup.Database.DatabaseFile,
			backup.Database.Name,
			backup.Database.Locale,
			backup.Database.Is24Hour);
		return new BackupResponse(backupView, HttpStatusCode.Created);
	}

Hope this helps others.  I was pretty excited when I figured it out.  :D