Orchard, WebApi, Global ActionFilters and Dependency Resolution
Orchard & WebApi
Today I had the privilege of trying out the new WebApi feature in Orchard, which as you may know is new as of Orchard 1.6.
Personally I think this is a very exciting addition, as it allows us to expose Orchard services as well as our own services via a RESTful API to the outside world.
Writing a Secured WebAPI
One of the requirements I was given is that this API should only be accessible by authorized clients. An easy way to do that is by requiring some sort of API credentials as part of the querystring, e.g. "apiKey", and check against a database of keys. There are a couple of ways this could be done, one of which by implementing an AuthorizationFilterAttribute and apply it to the controller. However, since AuthorizationFilterAttributes are attributes, we cannot simply use Dependency Injection to get access to our services.
So what is one supposed to do?
WorkContext and the Service Locator Pattern
As luck (or rather, brilliant minds,) would have it, there's an easy way we can resolve instances of our precious services.
When implementing the OnAuthorization method of the AuthorizationFilterAttribute, we get access to a HttpActionContext object, which in turn provides access to the HttpControllerContext. Through this HttpControllerContext, we can get our hands on the WorkContext, which can be used to resolve instances of types registered with Autofac. Resolving services this way is known as the Service Locator Pattern, and although this is generally not the pattern to prefer over Dependency Injection, it works pretty well for these types of scenarios.
Show me the code!
Allright, here it is. This is how a custom ApiKeyAuthorizationAttribute could look like:
using System.Net; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Filters; using MyModule.Models; using Orchard; using Orchard.ContentManagement; using Orchard.Environment.Extensions; namespace MyModule.Attributes { public class ApiKeyAuthorizationAttribute : AuthorizationFilterAttribute { public override void OnAuthorization(HttpActionContext actionContext) { var query = actionContext.Request.RequestUri.ParseQueryString(); var apiKey = query["apiKey"]; var workContext = actionContext.ControllerContext.GetWorkContext(); var settings = workContext.CurrentSite.As<WebServiceSettingsPart>(); if (apiKey != settings.ServiceApiKey) { actionContext.Response = actionContext.ControllerContext.Request.CreateResponse(HttpStatusCode.Unauthorized); } } } }
What we're doing here is getting a querystring parameter value keyed "apiKey" and checking it against a custom content part that is attached to the Site content item.
This implementation could be easily swapped with a version that checks the database for a key, or anything else that makes sence in your situation. Or even better: the implementation could be changed by applying the Strategy pattern, where the actual key checking is provided by another class.
Another improvement that could be made is having the "apiKey" turned into a configurable property, passed in via the constructor of the attribute. But I'll leave that up to you.
Applying the attribute is simple:
using System.Web.Http; using MyModule.Attributes; namespace MyModule.Api { [ApiKeyAuthorization] public class OrderController : ApiController { public HttpResponseMessage Post(ShoppingCart cart) { // Create an order here var order = repository.Create(cart) var response = Request.CreateResponse<Order>(HttpStatusCode.Created, order); response.Headers.Location = new Uri(URL TO RESOURCE); return response; } } }
The attribute can be applied on the controller or on actions.
UPDATE: General tips when implementing a WebApi
Some general tips & guidelines when implementing a WebApi
- When implementing a controller, simply use action names like Get, Put, Post, Patch and Delete. Naming actions like CreateOrder on an OrderController is redundant. Also, using these standards frees you from having to explicitly apply HTTP verb attributes such as HttpGet, HttpPut, HttpPost, HttpDelete and HttpPatch to your action methods
- When creating a resource (in this example an Order), return a url to that resource (as shown in the 2nd code snippet)
Thanks to Nick Mayne not only for providing me with a review of this article and excellent tips as shared above, but also for making WebApi possible in Orchard.