2015-11-11

Routing to different actions based on posted json values in Web API 2

Background and context

I was working on a RESTful web service whose purpose, somewhat simplified, is to let clients create, handle, browse and search for Subject objects (which are pretty much strings bundled with some metadata). These are some examples from the service:

GET /api/subjects (List all Subjects)
GET /api/subjects/<id> (Get a specific Subject)
GET /api/subjects/<id>/translations (List a specific subject and all its translations)
...
POST /api/subjects (Create a Subject from posted json)

I ended up in a situation where I wanted implementing clients to be able to POST several types of json objects to a single route. Creating a Subject was already implemented according to the above list of routes but now I also wanted the following:

POST /api/subjects (Get a list of subjects according to a Query object from posted json)

Problem?

Web API figures out what to do with a request using a IHttpActionSelector. The default implementation is ApiControllerActionSelector and it basically picks the action with the most matching route and query parameters. Running it can result in three outcomes:
  • No suitable action found
  • Finds the most suitable action
  • Finds more than one most suitable actions, ambiguity

The problem is that ApiControllerActionSelector does not care about the body of a post (which is where json resides in the request object when it is posted). Therefore, adding two HttpPost actions to the same route will result in ambiguity even though they have different parameter types.

A custom IHttpActionSelector

I wanted to extend ApiControllerActionSelector (which implements IHttpActionSelector) so that the json body of post requests were inspected for action selection. I also wanted it to fall back on the default action selector if my own selection is unable to select a suitable action. The first step was to create the skeleton for the class:

public class CustomActionSelector : ApiControllerActionSelector
{
  public override HttpActionDescriptor SelectAction(HttpControllerContext context)
  {
    return base.SelectAction(context);
  }
}

..and to configure the project to call it; in the Register method in WebApiConfig:

config.Services.Replace(typeof(IHttpActionSelector), new CustomActionSelector());

The second was to add some constraints. It is only meaningful to execute the action selection logic if the request is a Post and the request body has some content of json mime type.

public override HttpActionDescriptor SelectAction(HttpControllerContext context)
{
  var request = new HttpMessageContent(context.Request).HttpRequestMessage;

  if (request.Method == HttpMethod.Post &&
      request.Content.Headers.ContentType.MediaType.Equals("application/json"))
  {
    var json = request.Content.ReadAsStringAsync().Result;

    if (!string.IsNullOrWhiteSpace(json))
    {
    }
  }

  return base.SelectAction(context);
}

For the logic I wanted to consider all public actions that are decorated with the [HttpPost] attribute and that has at least one parameter.

private static IEnumerable<MethodInfo> GetActionMethods(HttpControllerContext context)
{
  return context.ControllerDescriptor.ControllerType
           .GetMethods(BindingFlags.Instance | BindingFlags.Public)
           .Where(m => m.GetCustomAttributes(typeof(HttpPostAttribute)).Any())
           .Where(m => m.GetParameters().Any());
}

After some experimentation with different approaches to selecting the best action I had to accept the fact that this whole thing got so much easier and faster if I introduced a couple of restraints:

  1. The parameter that is to be mapped to the json data has to be the first parameter.
  2. Required properties on the models corresponding to the incoming json objects has to be decorated with the [Required] attribute.
Obviously, there are ways of coding around these restraints and making the algorithm much more flexible but for now this was a good enough solution to solve my problem.
private static IEnumerable<string> GetRequiredParameterNames(MethodInfo methodInfo)
{
  return methodInfo.GetParameters()[0].ParameterType
           .GetProperties()
           .Where(p => p.CanRead && p.GetCustomAttributes(typeof(RequiredAttribute)).Any())
           .Select(rm => rm.Name);
}
With that in place I could simply loop through the required properties on the first parameter type and ensure that the json data has corresponding keys with some value. A black-hole-try-catch ensures any exceptions from weird and unexpected input data are swallowed. No exception handling seems necessary since the only objective is to find out wether the posted data can be mapped to the action parameter or not.

Summary

This is what the result looks like. It sure is going to be interesting seeing some performance testing results on this :)

namespace Subject.Api.ActionSelectors
{
  using System.Collections.Generic;
  using System.ComponentModel.DataAnnotations;
  using System.Linq;
  using System.Net.Http;
  using System.Reflection;
  using System.Web.Http;
  using System.Web.Http.Controllers;

  using Newtonsoft.Json.Linq;

  public class CustomActionSelector : ApiControllerActionSelector
  {
    public override HttpActionDescriptor SelectAction(HttpControllerContext context)
    {
      var request = new HttpMessageContent(context.Request).HttpRequestMessage;

      if (request.Method == HttpMethod.Post &&
          request.Content.Headers.ContentType.MediaType.Equals("application/json"))
      {
        var json = request.Content.ReadAsStringAsync().Result;

        if (!string.IsNullOrWhiteSpace(json))
        {
          MethodInfo result = null;

          foreach (var actionMethod in GetActionMethods(context))
          {
            try
            {
              foreach (var methodName in GetRequiredParameterNames(actionMethod))
              {
                if (JObject.Parse(json)?.GetValue(methodName)?.Value<object>() == null)
                {
                  break;
                }

                result = actionMethod;
              }
            }
            catch
            {
              // Swallow these exceptions
            }
          }

          if (result != null)
          {
            return new ReflectedHttpActionDescriptor(context.ControllerDescriptor, result);
          }
        }
      }

      return base.SelectAction(context);
    }

    private static IEnumerable<MethodInfo> GetActionMethods(HttpControllerContext context)
    {
      return context.ControllerDescriptor.ControllerType
               .GetMethods(BindingFlags.Instance | BindingFlags.Public)
               .Where(m => m.GetCustomAttributes(typeof(HttpPostAttribute)).Any())
               .Where(m => m.GetParameters().Any());
    }

    private static IEnumerable<MethodInfo> GetActionMethods(HttpControllerContext context)
    {
      return context.ControllerDescriptor.ControllerType
               .GetMethods(BindingFlags.Instance | BindingFlags.Public)
               .Where(m => m.GetCustomAttributes(typeof(HttpPostAttribute)).Any())
               .Where(m => m.GetParameters().Any());
    }
  }
}

2012-05-02

PropertyData object with name "FooBar" already exists


I ran into this during the weekend while I did some work from home. The site I was working on is running EPi6r2 with PageTypeBuilder and I was just adding a couple of new properties for a page type when I suddenly and unexpectedly got the yellow screen of death exception message thrown a me. I couldn't access any page, nor the edit or admin modes.

Googling a little, there was some talk on forums and blogs about the problem being related to language handling and such but none of the things I found applied to my situation.

...so I started tracing the origin of the exception back through Reflector and ended up in EpiServer.Core.PropertyDataCollection:

public void Add(string name, PropertyData value)
{
    this.InternalApproveObject(name, value);
    if (base.BaseGet(name) != null)
    {
        throw new ApplicationException("PropertyData object with name \"" + name + "\" already exists");
    }
    value.Parent = this;
    base.BaseAdd(name, value);
}

EPi, rightfully I might add, detected that I had added more than one property with the same name. (On a side note, I can think of a bunch of more pleasant behaviors to handle property doublets than to just throw an Application Exception; use the first (lowest id) and automatically remove the clones, rename the clones using a prefix or suffix, etc.). How could I add several properties with the same name on the same page type? To be honest I'm not exactly sure but I think this is what might have happened:

First off, at the time I was on a fairly slow Wifi connection, running the site locally but connecting it to a remote database over VPN. The fact that, after compiling. I hit the reload button in Chrome for both the site, edit mode and admin mode in three different tabs pretty much at the same time probably also contributed since, iirc, they each run as a separate application. Delays in contact with SQL probably enabled PTB to insert multiple copies of the same property from the different applications in question.

Once I figured this out it was easy enough to manually edit out doublets from tblPageDefinition in the database and the site would work as expected again.

2012-04-28

Note to self: DropCreateDatabaseIfModelChanges during development

While developing using Entity Framework, code first, I should add the following to Global.asax:

protected void Application_Start()
{
    #if DEBUG
    Database.SetInitializer(new DropCreateDatabaseIfModelChanges<DbCtx>());
    #endif
}
That way, given that that the models have changed, when I am running the application in debug mode, the database will be dropped and recreated.

2012-03-07

Reflections from taking the EPiServer (CMS 6) Certified Developer exam

I passed the exam earlier today and thought I should share some reflections. The test wasn't really that difficult but it was very broad. On top of actual questions regarding programming it ranged from the product as such (almost bordering on sales talk at times) to configuring and licensing.

2012-02-21

Programmatically adding options to EPi CMS 6 R2 drop-down list property when using PageTypeBuilder 2.0

Since EPi CMS had property types with custom settings added I have found the drop-down list (EPiServer.SpecializedProperties.PropertyDropDownList) especially useful for allowing an editor to choose from a few different teaser content block appearances when creating teaser page types. The out-of-the-box approach is for an CmsAdmin or developer to manually add key-value pair type options in the CMS admin mode for the property but when using PageTypeBuilder there is a nice way of adding settings programmatically:

2011-09-05

Extensions...

Here's a little extension on Object in C#

public static bool EqualsAny<T>(this T obj, params T[] compareTo)
{
  for (int i = compareTo.Length - 1; i >= 0; --i)
  {
    if (obj.Equals(compareTo[i]))
    {
      return true;
    }
  }

  return false;
}
If the original code would have looked something like this before:
int nowHour = DateTime.Now.Hour;

if (nowHour.Equals(10) ||
    nowHour.Equals(12) ||
    nowHour.Equals(15) ||
    nowHour.Equals(17) ||
    nowHour.Equals(19) ||
    nowHour.Equals(23))
{
  //Happy hour!
}
the after code would look like this:
if (DateTime.Now.Hour.EqualsAny(10, 12, 15, 17, 19, 23))
{
  //Happy hour!
}

2011-09-04

Lazy mans guide to code syntax highlighting on Blogger

Having just moved some old blog entries to a brand new blogger.com blog from Wordpress I was looking for an easy way to publish highlighted code in my posts.

There are several different tools freely available such as SyntaxHighlighter, highlight.js and Prettify which I decided on using.