web api 2 area routes
Solution 1
You need to get the HttpConfiguration
instance from the GlobalConfiguration
object and call the MapHttpAttributeRoutes()
method from inside the RegisterArea()
method of the AreaRegistration.cs
.
public override void RegisterArea(AreaRegistrationContext context)
{
GlobalConfiguration.Configuration.MapHttpAttributeRoutes();
//... omitted code
}
Finally you must in the WebApiConfig
remove config.MapHttpAttributes()
method or you will get duplicate exception.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
config.EnableCors();
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
//config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Solution 2
First, you should register the route with the Area it belongs to, that only makes sense, so inside your AreaNameAreaRegistration
class be sure to add using System.Web.Http
so you get the extension method MapHttpRoute
, it's not part of System.Web.Mvc
.
The order above is a bit off, which is causing the 404. Add the following Route:
context.Routes.MapHttpRoute(
name: "AreaNameWebApi",
routeTemplate: "api/AreaName/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
If you want to get fancy and append the Area Name from the property AreaName
(of course you care about good coding practices ;)):
context.Routes.MapHttpRoute(
name: "AreaNameWebApi",
routeTemplate: string.Concat("api/", AreaName, "/{controller}/{id}"),
defaults: new { id = RouteParameter.Optional }
);
This will correct the 404 issue. In the Web API module by default it first scans for "api" to determine where to look for the controller (otherwise it'll be confused, it's not magic) so api needs to be first when dynamically appending the Area Name and Controller. Of course you CAN change this order by hard-coding your routes, but I don't recommend that because you'll need to provide every route and every controller in the route config or using the RouteAttribute
.
Plus, with "api" first it will make a nice standard looking URL for you and your users instead of having API all over the place. Here are some samples:
http://site/api/members/myAccount/update
http://site/api/members/myAccount/get/12345
http://site/api/members/contacts/getByOwnerId/12345
Hope this helps!
Zev
Solution 3
The issue with that specific API call is that you have not specified a controller to use in your route.
context.Routes.MapHttpRoute(
name: "AuditModel_Api",
routeTemplate: "WorkOrder/api/AuditModelApi/{id}",
defaults: new { id = RouteParameter.Optional }
);
Should be
context.Routes.MapHttpRoute(
name: "AuditModel_Api",
routeTemplate: "WorkOrder/api/AuditModelApi/{id}",
defaults: new { controller = "AuditModelApi", id = RouteParameter.Optional }
);
Constant segments in the URL are to specify what URL to match, but you still must tell it what the route values are in order for it to get to the controller and action. It has no way of knowing which segment to use as the controller name unless you make it a {controller}
segment.
user2040850
Updated on June 04, 2022Comments
-
user2040850 almost 2 years
I am using asp.net mvc 5 and web api 2. For the asp.net mvc 5 project I have everything working... but new I am trying to add web api 2 routes... when I am using areas.
I have the web api 2 controller working at the project root:
//this is working namespace EtracsWeb.Controllers { public class TestController : ApiController { //localhost:port/api/test ...this is working [HttpGet] public HttpResponseMessage Get() { return new HttpResponseMessage() { Content = new StringContent("GET: Test message") }; } } }
So I am assuming my
Global.asax
, myrouteconfig.cs
and mywebapiconfig.cs
are correct ... (not shown)...But now I am trying to get the web api 2 in my AREAS working...
I have read everything I could find on the web and this seems like it should work:
namespace EtracsWeb.Areas.WorkOrder { public class WorkOrderAreaRegistration : AreaRegistration { public override string AreaName { get { return "WorkOrder"; } } public override void RegisterArea(AreaRegistrationContext context) { context.Routes.MapHttpRoute( name: "AuditModel_Api", routeTemplate: "WorkOrder/api/AuditModelApi/{id}", defaults: new { id = RouteParameter.Optional } ); //default context.Routes.MapHttpRoute( name: "WorkOrder_Api", routeTemplate: "WorkOrder/api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); context.MapRoute( "WorkOrder_default", "WorkOrder/{controller}/{action}/{id}", new { action = "Index", id = UrlParameter.Optional } ); } } }
My controller code is:
namespace EtracsWeb.Areas.WorkOrder.ApiControllers { public class AuditModelApiController : ApiController { IManufacturerStopOrderModelService auditModelService = new WorkOrder.Services.VWAuditModelService(UserSession.EtracsUserID, UserSession.ProgramID, UserSession.EtracsSessionID, UserSession.ConnectionString); [HttpGet] [Route("AuditModelApi")] public HttpResponseMessage Get() { return new HttpResponseMessage() { Content = new StringContent("GET: Test message") }; } [Route("AuditModelApi/AuditModels")] public IEnumerable<VwAuditModel1> GetAuditModels() { return auditModelService.GetModels(); } public IHttpActionResult UpdateAuditMode() { //example of what can be returned ... NotFound, Ok ... look into uses... VwAuditModel1 result = new VwAuditModel1(); return Ok(result); return NotFound(); } } }
I have tried the controller with and without the attribute naming
[Route]
... and I can't get either get to work...Both the simple case
public HttpResponseMessage Get()
and the "real" case
public IEnumerable<VwAuditModel1> GetAuditModels()
return the same result. From the browser, using
http://localhost:64167/WorkOrder/api/AuditModelApi
and
http://localhost:64167/WorkOrder/api/AuditModelApi/AuditModels
I get the following:
<Error> <Message> No HTTP resource was found that matches the request URI 'http://localhost:64167/WorkOrder/api/AuditModelApi/AuditModels'. </Message> <MessageDetail> No route providing a controller name was found to match request URI 'http://localhost:64167/WorkOrder/api/AuditModelApi/AuditModels' </MessageDetail> </Error>
-
user2040850 about 9 yearsThat does work...Thanks ... but this is tying the area to a single controller .. what do we do if there is more than one controller?
-
NightOwl888 about 9 yearsIn that case, you would specify the routeTemplate argument with a controller placeholder:
WorkOrder/api/{controller}/{id)
. -
user2040850 about 9 yearsI the original code for the registration I had already tried a variation of this... NOT specifying the controller and using the route you gave .. and it gave an error that it could not find the resource (again see the original message)... I thought this was correct... do I have something wrong the mapphttproute statement?
-
NightOwl888 about 9 yearsNot that I can tell. But note that routing is order specific. If you have another route that matches that URL before the route you intend to match it will attempt to route to the wrong controller. Post your entire routing configuration including what you have in Application_Start and maybe the problem can be spotted.
-
jpaugh almost 7 yearsIt looks like that should instead be
GlobalConfiguration.Configure(x => x.MapHttpAttributeRoutes())
as of Web Api v2. -
MikeTeeVee over 4 yearsFinally! Someone on the internet answered this question. I would have never figured this out. Now I understand. When deriving your Web.Api controller from
ApiController
, you needMapHttpRoute()
in yourAreaRegistration
(for a regular MVCController
you would useMapRoute()
). Addingusing System.Web.Http
was the key. Then I commented out the auto-generatedMapHttpRoute()
code in theApp_Start/WebApiConfig.cs
file to avoid any confusion down the road. Also, I like the idea of standardizing on the "api/" prefix to appear first in the Url - even within Areas. Thanks a Bunch!