MVC API Routes by Type in C#
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/
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!