Getting ApiController to work with scopes?

I currently have 2 areas in my ASP.NET MVC 5 project. One of them is called Supporters, and one is called Chatter. In each of these two areas, there is an ApiController named CommunicationController , and this poses a problem due to the nature of how ApiController works with routing.

Problem example

If I had only one ApiController named CommunicationController in the scope, its routing would not include the scope in the URL, and the URL would be something like this:

http://example.com/api/communication/someAction

But in the above URL, where is the area located?

Since two of my controllers are called the same, they have problems with routing.

What have i tried?

I tried following the instructions here: http://blogs.infosupport.com/asp-net-mvc-4-rc-getting-webapi-and-areas-to-play-nicely/

They seem to be applicable to ASP.NET MVC 4 RC, which is no longer relevant since I am using MVC 5, and that is probably why it did not work.

However, to recall this blogpost, here are my routing files.

App_Start \ RouteConfig.cs

 public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute("Default", "{controller}/{action}", new {action = "Index", controller = "Home"}, new[] { "Website.Controllers" } ); } } 

App_Start \ WebApiConfig.cs

 public static class WebApiConfig { public static void Register(HttpConfiguration config) { config.MapHttpAttributeRoutes(); //the two lines below were added. 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" } ); } } 

Neighborhoods \ Chatter \ ChatterAreaRegistration.cs

 public class ChatterAreaRegistration : AreaRegistration { public override string AreaName { get { return "Chatter"; } } public override void RegisterArea(AreaRegistrationContext context) { //the below line was added. context.Routes.MapHttpRoute("Chatter_api", "api/chatter/{controller}/{id}", new { id = RouteParameter.Optional, area = "Chatter" } ); context.MapRoute( "Chatter_default", "Chatter/{controller}/{action}/{id}", new {action = "Index", id = UrlParameter.Optional} ); } } 

Neighborhoods \ Supporting \ SupportersAreaRegistration.cs

 public class SupportersAreaRegistration : AreaRegistration { public override string AreaName { get { return "Supporters"; } } public override void RegisterArea(AreaRegistrationContext context) { //the below line was added. context.Routes.MapHttpRoute("Supporters_api", "api/supporters/{controller}/{id}", new { id = RouteParameter.Optional, area = "Supporters" } ); context.MapRoute( "Supporters_default", "Supporters/{controller}/{action}/{id}", new { action = "Index", controller = "Home", id = UrlParameter.Optional } ); } } 

What am I doing wrong here, and what are my options?

+6
source share
5 answers

Use WebAPI 2 attributes since you are using MVC 5 and you can get rid of a lot of this template code by declaring routes for your API along with its implementation (you can also specify verbs for HTTP actions and even use attributes to automatically convert to XML / JSON / serialization per month).

If you are not using scopes for any other reason, you really don't need them to implement the web API.

In particular, you need the RoutePrefix attribute.

+5
source

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)); 
+4
source

Try the following configuration. The trick here is to register a namespace to search for API controllers when the route matches.

 config.Routes.MapHttpRoute( name: "chatterApi", routeTemplate: "api/chatter/{controller}/{action}", defaults: new { action = "", controller = "", namespaces = new string[] { "WebApplication.chatter.api" } } ); config.Routes.MapHttpRoute( name: "supportersApi", routeTemplate: "api/supporters/{controller}/{action}", defaults: new { action = "", controller = "", namespaces = new string[] { "WebApplication.supporters.api" } } ); 
+3
source

Since you are using MVC5 and comes with WebAPI 2.0, you can use

 [RoutePrefix("api/Supporters/Communication")] 

to indicate the area as @Clever Neologism. But don't forget to call

 config.MapHttpAttributeRoutes(); 

when configuring routing in Global.asax.cs. Also see this answer

+2
source

This is a route error. DataTokens is read-only, cannot set value

0
source

Source: https://habr.com/ru/post/1200444/


All Articles