MVC API Routes by Type in C#

Jan 14, 2014 csharp mvc

In MVC 3 onwards you can constrain routes based on your own rules.

MVC's default API route is /api/{action}/{id} and uses the controller and action based on the controller name and HTTP Verb.

I wanted a way to route anything with /api/version/ to a specific collection of controllers, based on a naming convention of my choice. I decided to use a constraint based on interfaces, but you could implement the match using Regex or any number ways. As long as you implement a IRouteConstraint you can decide your own route logic.

Lets create an empty interface which we'll use a flag. We only want the route to apply for an API controller that implements this interface.

public interface IVersionApi
{ }

Here's an example of one of our API controllers which implements our IVersionAPI. Note this class implements the IVersionAPI interface.
This will be hit when I called /api/versions/FlightTracker and it will still support default HTTP verbs (so Get() will be hit for a HTTP GET request).

public class FlightTrackerController : ApiController, IVersionApi
{
  private readonly IFlightTrackerVersionRepository _flightTrackerVersionRepository;
  private readonly IConfiguration _configuration;
  public FlightTrackerController()
  {
    _configuration = new Configuration();
    _flightTrackerVersionRepository = new FlightTrackerVersionSqlRepository(_configuration);
  }
  public IEnumerable Get()
  {
    var result = _flightTrackerVersionRepository.Get();
    return result;
  }
}

Here's the additional WebAPI route which sets up our route. Note the new constraint object where everything gets tied together.

config.Routes.MapHttpRoute(
  name: "Versions",
  routeTemplate: "api/versions/{controller}/{id}/{format}",
  defaults: new { id = RouteParameter.Optional, format = RouteParameter.Optional },
  constraints: new { abc = new TypeConstraint(typeof(IVersionApi)) }
);

The magic happens our TypeConstraint class. It gets a list of all the types which implement IVersionApi, then compares the requested controller (e.g., a request for "/api/versions/FlightTracker" will match "FlightTrackerController") with each of the types then returns a bool. We implement the MVC IRouteConstraint interface - see Creating a Custom Route Constraint.

public class TypeConstraint : IRouteConstraint
{
  private readonly Type _type;

  public TypeConstraint(Type type)
  {
    _type = type;
  }

  public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
  {
    var controller = values["controller"];

    if (controller == null || string.IsNullOrWhiteSpace(controller as string))
  	  return false;

    var type = _type;
    var types = AppDomain.CurrentDomain.GetAssemblies()
	  .SelectMany(s => s.GetTypes())
	  .Where(type.IsAssignableFrom);

    var matchWith = controller + "Controller";

    if (types.Any(o => o.Name == matchWith))
	  return true;

    return false;
  }
}

Hope you find it useful!