If two or more areas have an apicontroller with the same name, then to call the controller in a specific area, the area name must be included in the URL.
So http://example.com/api/communication/someAction does not work.
In this case, it could be http://example.com/supporters/api/communication/someAction and http://example.com/chatters/api/communication/someAction
The custom httpcontrollerselector specified at http://blogs.infosupport.com/asp-net-mvc-4-rc-getting-webapi-and-areas-to-play-nicely works great with mvc5.
Delete the following lines in webapiconfig
config.Routes.MapHttpRoute("SupportersApi", "api/supporters/{controller}/{id}", new {id = RouteParameter.Optional, area = "Supporters"} ); config.Routes.MapHttpRoute("ChatterApi", "api/chatter/{controller}/{id}", new { id = RouteParameter.Optional, area = "Chatter" } );
Here are the steps that work great
1. Add the following expansion project to the project.
public static class AreaRegistrationContextExtensions { public static Route MapHttpRoute(this AreaRegistrationContext context, string name, string routeTemplate) { return context.MapHttpRoute(name, routeTemplate, null, null); } public static Route MapHttpRoute(this AreaRegistrationContext context, string name, string routeTemplate, object defaults) { return context.MapHttpRoute(name, routeTemplate, defaults, null); } public static Route MapHttpRoute(this AreaRegistrationContext context, string name, string routeTemplate, object defaults, object constraints) { var route = context.Routes.MapHttpRoute(name, routeTemplate, defaults, constraints); if (route.DataTokens == null) { route.DataTokens = new RouteValueDictionary(); } route.DataTokens.Add("area", context.AreaName); return route; }
}
2. In each AreaRegistration file, add a route that includes the name of the area in routeTemplate
To support support, add
context.MapHttpRoute( name: "Supporters_DefaultApi", routeTemplate: "supporters/api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } );
In ChatterAreaRegistration add
context.MapHttpRoute( name: "Chatters_DefaultApi", routeTemplate: "chatters/api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } );
Property context.MapHttpRoute, not context.Routes
3. Add custom HttpControllerSelector
public class AreaHttpControllerSelector : DefaultHttpControllerSelector { private const string AreaRouteVariableName = "area"; private readonly HttpConfiguration _configuration; private readonly Lazy<ConcurrentDictionary<string, Type>> _apiControllerTypes; public AreaHttpControllerSelector(HttpConfiguration configuration) : base(configuration) { _configuration = configuration; _apiControllerTypes = new Lazy<ConcurrentDictionary<string, Type>>(GetControllerTypes); } public override HttpControllerDescriptor SelectController(HttpRequestMessage request) { return this.GetApiController(request); } private static string GetAreaName(HttpRequestMessage request) { var data = request.GetRouteData(); if (data.Route.DataTokens == null) { return null; } else { object areaName; return data.Route.DataTokens.TryGetValue(AreaRouteVariableName, out areaName) ? areaName.ToString() : null; } } private static ConcurrentDictionary<string, Type> GetControllerTypes() { var assemblies = AppDomain.CurrentDomain.GetAssemblies(); var types = assemblies .SelectMany(a => a .GetTypes().Where(t => !t.IsAbstract && t.Name.EndsWith(ControllerSuffix, StringComparison.OrdinalIgnoreCase) && typeof(IHttpController).IsAssignableFrom(t))) .ToDictionary(t => t.FullName, t => t); return new ConcurrentDictionary<string, Type>(types); } private HttpControllerDescriptor GetApiController(HttpRequestMessage request) { var areaName = GetAreaName(request); var controllerName = GetControllerName(request); var type = GetControllerType(areaName, controllerName); return new HttpControllerDescriptor(_configuration, controllerName, type); } private Type GetControllerType(string areaName, string controllerName) { var query = _apiControllerTypes.Value.AsEnumerable(); if (string.IsNullOrEmpty(areaName)) { query = query.WithoutAreaName(); } else { query = query.ByAreaName(areaName); } return query .ByControllerName(controllerName) .Select(x => x.Value) .Single(); } } public static class ControllerTypeSpecifications { public static IEnumerable<KeyValuePair<string, Type>> ByAreaName(this IEnumerable<KeyValuePair<string, Type>> query, string areaName) { var areaNameToFind = string.Format(CultureInfo.InvariantCulture, ".{0}.", areaName); return query.Where(x => x.Key.IndexOf(areaNameToFind, StringComparison.OrdinalIgnoreCase) != -1); } public static IEnumerable<KeyValuePair<string, Type>> WithoutAreaName(this IEnumerable<KeyValuePair<string, Type>> query) { return query.Where(x => x.Key.IndexOf(".areas.", StringComparison.OrdinalIgnoreCase) == -1); } public static IEnumerable<KeyValuePair<string, Type>> ByControllerName(this IEnumerable<KeyValuePair<string, Type>> query, string controllerName) { var controllerNameToFind = string.Format(CultureInfo.InvariantCulture, ".{0}{1}", controllerName, AreaHttpControllerSelector.ControllerSuffix); return query.Where(x => x.Key.EndsWith(controllerNameToFind, StringComparison.OrdinalIgnoreCase)); } }
4. Make changes to the Application_Start method in the Global.Asax file to use AreaHttpControllerSelector instead of DefaultHttpControllerSelector
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), new AreaHttpControllerSelector(GlobalConfiguration.Configuration));