BLOG Web API secret plumbing 14 February 2016 Previous post button Up to TOC button Next post button

A few years ago I became fed-up with how complicated ASP.NET WebForms programming was. The pipeline of events is really long and delicate. If you do something in the wrong order you may get events that don't fire, controls that don't update, missing postback data, and so on. I've spent many tearful hours of my life trying to find out why a row click in a grid doesn't work to eventually discover I was doing something in the event handler that should have waited until PreRender, or vice versa. Another problem is the lack of control over formatting, exemplified by a tiny single-page app I wrote few months ago containing just a grid and a few buttons which renders an incomprehensible mess of html.

A few years ago, ASP.NET MVC was getting a lot of good comments and publicity so I ran the tutorials and bought some books in the hope that I would find the whole web programming model had been simplified with a tiny pipeline and you had more control over the rendered output. I found this to be generally true, but there was still "secret plumbing" all over the place. Magic values and naming conventions had simply replaced the difficulty of WebForms programming with a new type of mysterious difficulty. Everything I wanted to do in ASP.NET MVC required searching for a magical piece of plumbing that had to be named or registered.

After a few months I gave up in disgust at the prospect of using ASP.NET MVC. I just can't imagine what twisted minds can take something as simple as the http request-response model and wrap it in so many layers of complexity. I have been tempted at times to return to the early 1990s and write a web app with a single ashx handler file that takes the raw request and converts it into a raw response, thereby skipping all plumbing and frameworks.

Although I refuse to write web applications using ASP.NET MVC, I do write web services of the ASP.NET Web API variety which share a lot of coding style with MVC using routes and controllers. Even through the Web API is a stripped down MVC, it still unfortunately shares a fair amount of "secret plumbing" that you have to discover and use correctly. I have stumbled upon some important "secrets" which I'm putting in this post as a reminder to myself and anyone else who cares.

ActionFilterAttribute

Write a class that derives from ActionFilterAttribute and override OnActionExecuting and OnActionExecuted so you can automatically intercept the request and response of every API call. You can, for example, validate requests with code in a single place instead of spreading it all over the controller methods. You can extract request information like an auth token and place it in the request's property collection so it's handy at all times. Anything you want to intercept or do automatically on all API calls can be placed in your action filter class. Register the filter on controller methods like this example:

[HttpDelete]
[Route("")]
[MyActionFilter(...)]
public IHttpActionResult Delete()
{
  ...
}

You can pass parameters to the filter's constructor if it's useful.

ExceptionFilterAttribute

Write a class that derives from ExceptionFilterAttribute and override OnException to automatically intercept all errors without the need to spread try-catch code all over the controllers. You can log errors or transform them into different types of responses that are perhaps more friendly for callers of your API. You register the exception filter the same way as the action filter.

HttpParameterBinding

I uncovered this devious and tricky class while trying to automatically convert different types of query values into a controller method's bool parameter. It's quite sensible to allow API callers to specify a true/false value as Y, y, N, n, false, TRUE, 0, 1, and so on. I guessed there was a way of automatically coercing query values like &blah=1 or &blah=y into a bool value, but I had to find the secret plumbing that did this. An obscure hint in a web search pointed me to the HttpParameterBinding class, but further searches revealed ambiguous and confusing use of the class and the context parameters available to it. By trapping a call in the debugger I eventually managed to figure out just which context values were required to perform the transform I required. Here is the stripped down code that does what I want.

public override Task ExecuteBindingAsync(...)
{
  var dict = HttpUtility.ParseQueryString(actionContext.Request.RequestUri.Query);
  string raw = dict[Descriptor.ParameterName];
  bool? value = null;
  if (Regex.IsMatch(raw ?? "", $"^(?:y|true|1)$", RegexOptions.IgnoreCase))
  {
    value = true;
  }
  else if (Regex.IsMatch(raw ?? "", $"^(?:n|false|0)$", RegexOptions.IgnoreCase))
  {
    value = false;
  }
  actionContext.ActionArguments.Add(Descriptor.ParameterName, value ?? Descriptor.DefaultValue);
  return Task.FromResult(0);
}

The raw string value named in Descriptor.ParameterName is parsed out of the request query string, inspected and transformed into the required bool value and placed in the actionContext.actionArguments collection for passing into the controller method. You can use this sort of parameter binding code to transform any incoming value into another type. You will need to create a small custom Attribute to apply your binding to a parameter, then use it like this:

internal class BoolBindingAttribute : ParameterBindingAttribute
{
  public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
  {
    return new BoolParameterBinding(parameter);
  }
}

public IHttpActionResult Foo([MyBoolBinding] bool blah)
{
  ...
}

So if a request contains &blah=1 it will be silently transformed into true.

AddQueryStringMapping

It is a web service convention that the response body format obey the Accept request header MIME type value. So for example sending the header Accept: application/xml will result in an XML response body. For the convenience of callers, and for easier testing, it's possible to map a query parameter to a MIME type. In the static WebApiConfig Register method, add lines like these popular examples:

config.Formatters.JsonFormatter.AddQueryStringMapping("format", "json", "application/json");
config.Formatters.XmlFormatter.AddQueryStringMapping("format", "xml", "application/xml");

You can then simply add ?format=xml to a request query to get the same effect as adding the corresponding request header.