IT:AD:ASP.NET:WebAPI:HowTo:Configure WebAPI Routing and MVC Areas
Summary
Small apps and POCs don't have enough WebAPI controllers to need compartmentalization of code into discreet zones.
Larger apps, and apps concerned with versioning, do.
Separating controllers using MVC Area is a possibility.
Unfortunately, WebAPIs do not work work with MVC Area out of the box. * Ref:SO
Process
Define a custom AreaHttpControllerSelector and register it (wiki munged):
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;
namespace XAct.Spikes.WebAPI.I03
{
/// <summary>
/// An specialization of the
/// <see cref="DefaultHttpControllerSelector"/>
/// so that in route to webAPI controllers within
/// <c>Areas.</c>
/// </summary>
/// <remarks>
/// <para>
/// Basically, the issue is that Areas allow the organisation of code
/// so that not all controllers are in the root (large apps generally
/// have too many to manage easily).
/// </para>
/// <para>
/// Registration is done as follows:
/// <para>
/// \<sxh>
/// <![CDATA[
/// protected void Application_Start()
/// {
/// ...
/// GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), new AreaHttpControllerSelector(GlobalConfiguration.Configuration));
/// }
/// ]]>
/// <\/code>
/// then add a route:
/// \<sxh>
/// <![CDATA[
/// routes.MapHttpRoute(
/// name: "DefaultApi",
/// routeTemplate: "api/{area}/{controller}/{id}",
/// defaults: new { id = RouteParameter.Optional }
/// );
/// ]]>
/// <\/code>
/// </para>
/// </para>
/// </remarks>
public class AreaHttpControllerSelector : DefaultHttpControllerSelector
{
private const string ControllerSuffix = "Controller";
private const string AreaRouteVariableName = "area";
private readonly HttpConfiguration _configuration;
public AreaHttpControllerSelector(HttpConfiguration configuration)
: base(configuration)
{
//keep a ref to config that we passed to underlying
//defaultHttpControllerSelector
_configuration = configuration;
}
#region Private Methods
private Dictionary<string, Type> _apiControllerTypes;
private Dictionary<string, Type> ApiControllerTypes
{
get {
var result =
_apiControllerTypes ?? (_apiControllerTypes = GetControllerTypes());
return result;
}
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
var result = GetApiController(request) ?? base.SelectController(request);
return result;
}
/// <summary>
/// Method GetControllerTypes takes all the API controllers types from all of your assemblies, and store it inside the dictionary, where the key is FullName of the type and value is the type itself.
/// </summary>
/// <returns></returns>
private static Dictionary<string, Type> GetControllerTypes()
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var types = assemblies.SelectMany(
a =>
a.GetTypes().Where(
t =>
!t.IsAbstract && t.Name.EndsWith(ControllerSuffix) && typeof (IHttpController).IsAssignableFrom(t)))
.ToDictionary(t => t.FullName, t => t);
return types;
}
private static string GetAreaName(HttpRequestMessage request)
{
var data = request.GetRouteData();
var result =
!data.Values.ContainsKey(AreaRouteVariableName) ? null : data.Values[AreaRouteVariableName].ToString().ToLower();
return result;
}
private Type GetControllerTypeByArea(string areaName, string controllerName)
{
var areaNameToFind = string.Format(".{0}.", areaName.ToLower());
var controllerNameToFind = string.Format(".{0}{1}", controllerName, ControllerSuffix);
Type type= ApiControllerTypes.Where(t => t.Key.ToLower().Contains(areaNameToFind) && t.Key.EndsWith(controllerNameToFind, StringComparison.OrdinalIgnoreCase))
.Select(t => t.Value).FirstOrDefault();
return type;
}
private HttpControllerDescriptor GetApiController(HttpRequestMessage request)
{
var controllerName = base.GetControllerName(request);
var areaName = GetAreaName(request);
if (string.IsNullOrEmpty(areaName))
{
return null;
}
var type = GetControllerTypeByArea(areaName, controllerName);
if (type == null)
{
return null;
}
return new HttpControllerDescriptor(_configuration, controllerName, type);
}
#endregion
}
}
Don't forget to register it:
protected void Application_Start()
{
...
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), new AreaHttpControllerSelector(GlobalConfiguration.Configuration));
}
}
Usage
Make sure to look at HowTo Configure WebAPI Routing to see how to use it.