Orchard, WebApi, Global ActionFilters and Dependency Resolution

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.

General tips when implementing a WebApi

Some general tips & guidelines when implementing a WebApi

  1. 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
  2. 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 some kuddos for making WebApi possible in Orchard in the first place.

6 comments

  • ... Posted by Ramón Esteban Posted 01/29/2013 10:35 AM [http://twitter.com/ramonesteban78]

    Very useful, thanks so much.

    Just the way to access services throught the WorkContext is like:

    <p>workContext.Resolve&lt; IService &gt;().ServiceMethod(serviceParameters);<br>Cheers</p>
  • ... Posted by JeffOlmstead Posted 01/30/2013 02:46 PM

    Excellent, I fought with the exact same problem two weeks ago - trying to use an attribute with dependency injection. Finally just set up a method that needed called every time, your attribute technique will slim down my code tremendously.

    <p>I am using the WebAPI.Hmac model with the goal of having a private key for each user. So I require the username be passed in with the request then go to the user part to get their private key. I see you used a site key though I am guessing that is a matter of need for you as oppossed to a rule. <br>Thanks again for explaining how this can be done.</p>
  • ... Posted by Sipke Schoorstra Posted 11/07/2014 07:52 AM [http://www.ideliverable.com]

    Glad to hear! Yes, I used a site key here just as an example.

  • ... Posted by Jyoti Gupta Posted 11/07/2014 07:52 AM

    Valuable Information!! Thanks so much.

  • ... Posted by Guest Posted 11/07/2014 07:53 AM

    Thanks for article.

    To get WorkContext in different situation can be used:

    var httpContextAccessor = System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IHttpContextAccessor)) as IHttpContextAccessor;

    var workContext = WorkContextExtensions.GetWorkContext(httpContextAccessor.Current().Request.RequestContext);

  • ... Posted by neTp9c Posted 11/07/2014 07:53 AM

    Thanks for article.

    To get WorkContext in different situation can be used:

    var httpContextAccessor = System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IHttpContextAccessor)) as IHttpContextAccessor;

    var workContext = WorkContextExtensions.GetWorkContext(httpContextAccessor.Current().Request.RequestContext);

Leave a comment