Showing posts with label C#. Show all posts
Showing posts with label C#. Show all posts

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-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-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-03-15

Using the Stopwatch class in C#

During the development of any kind of C# application you might find you need to measure how long some part of your code take to execute. Perhaps your customer demand an operation to finish in a maximum amount of time or you might be puzzled what part of a function is making it run slower than expected.

2011-03-09

XOR enciphering data for medium security

Sometimes when developing systems that involve sending or storing semi-sensitive data as a string there might not be an easy option or enough time to implement conventional encryption even though there might be some need to hide the contents of the data in some way. Such situations may include where you want to send scrambled messages between servers on the interwebs, store data in places where others may have access to it (on shared environments with nosy admins etc) or where you want to be able to easily unscramble data in something like javascript or some other language that lack libraries for conventional encryption.

2011-02-22

Optimizing the for loop for fun and profit

The three-expression, C style for loop is one of the most useful iteration statements in many programming languages including C#, javascript and perl. However it is commonly used in a sloppy manner and can often be optimized for better performance which is why I have set out to try and explain the inner workings of the for loop in this blog post. This is pretty basic stuff but it seems a fair share of developers has overseen the details of how the for loop really, really works when learning it.