39 comments

  1. I think you have a typo in your AreaRegistrationContextExtensions. Last method should call MapRoute and not MapHttpRoute.

    Chris

    • Thank you for your reply Chris. The method call is actually correct. The purpose of the AreaRegistrationContextExtensions is not to wrap MapHttpRoute around MapRoute, but to provide a version of MapHttpRoute that is Area-aware. MapHttpRoute and MapRoute are methods of the HttpRouteCollection. AreaRegistrationContext has a wrapped version of MapRoute, but no MapHttpRoute. My extension methods add MapHttpRoute to the AreaRegistrationContext.

      Martin Devillers

  2. Trying this out in the RTM and it doesn’t seem to work for me 🙁 Looks like route.DataTokens is null. Any ideas?

    Brian

    • Well, I feel silly. Just added:

      route.DataTokens = new RouteValueDictionary();

      Right before and it’s working great now 🙂 Great article!

      Brian

      • Thank you for your reply, Brian. I have not encountered that problem before. Looking at the source code of ASP.NET MVC 4, I see a change that was made a while ago that may be the cause:


        HttpWebRoute route = new HttpWebRoute(routeTemplate, HttpControllerRouteHandler.Instance)
        {
        Defaults = CreateRouteValueDictionary(defaults),
        Constraints = CreateRouteValueDictionary(constraints),
        DataTokens = new RouteValueDictionary()
        };

        ..was changed into..


        HttpRouteValueDictionary defaultsDictionary = new HttpRouteValueDictionary(defaults);
        HttpRouteValueDictionary constraintsDictionary = new HttpRouteValueDictionary(constraints);
        HostedHttpRoute httpRoute = (HostedHttpRoute) GlobalConfiguration.Configuration.Routes.CreateRoute(routeTemplate, defaultsDictionary, constraintsDictionary, dataTokens: null, handler: handler) ;
        Route route = httpRoute.OriginalRoute;

        Note that after the change, dataTokens is initialized to null instead of an empty dictionary. I’m not sure if this is the culprit because I would expect the underlying HttpRoute (or Route base class) to always initialize its dictionaries.

        Martin Devillers

  3. Great article! Thanks for documenting this so well. I also ran into an issue in the GetAreaName method of AreaHttpControllerSelector where the DataTokens collection had not been initialized when there was a controller that was not in an area. I added a simple check to see if it was null before querying it.

    if (data.Route.DataTokens == null)
    {
    return null;
    }

    That the DataTokens collection is not initialized seems like a bug in the framework.

    Matt Jones

  4. GREAT! I have been going around and around trying to get Areas to work! But I’m confused. I have the second part of your solution done… but solution 1: Where do I put AreaRegistrationContextExtensions
    ?
    No matter where I put it I get an error on the context.Routes.MapHttpRoute… something like ‘System.Web.Routing.RouteCollection’ does not contain a definition for MapHttpRoute.

    Thanks!

    Dave

    • Hello, Dave. The AreaRegistrationContextExtensions are extension methods for the AreaRegistrationContext. Extension methods enable you to “add” methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. You can place the class anywhere in your solution, just make sure you add the right using statement to your AreaRegistration file.

      E.g. Say you have placed your AreaRegistrationContextExtensions in the MvcApplication.Utility namespace, and your AreaRegistration resides in the MvcApplication.Areas.Administration namespace. In order to use the extension methods from in your AreaRegistration, add using MvcApplication.Utility to your AreaRegistration file.

      I hope that this information will help you. Good luck!

      Martin Devillers

      • Thanks so much… but I’m missing something.

        Here is my AreaRegistrationContextExtensions.cs:

        using System.Web.Mvc;
        using System.Web.Routing;

        namespace RouteTest.Extensions
        {
        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;
        }
        }
        }

        And here is my JobsAreaRegistration.cs

        using System.Web.Mvc;
        using RouteTest.Extensions;

        namespace RouteTest.Areas.Jobs
        {
        public class JobsAreaRegistration : AreaRegistration
        {
        public override string AreaName
        {
        get
        {
        return “Jobs”;
        }
        }

        public override void RegisterArea(AreaRegistrationContext context)
        {
        context.Routes.MapHttpRoute(
        name: “Jobs_defaultApi”,
        routeTemplate: “api/Jobs/{controller}/{id}”,
        defaults: new { id = RouteParameter.Optional }
        );

        context.MapRoute(
        “Jobs_default”,
        “Jobs/{controller}/{action}/{id}”,
        new { action = “Index”, id = UrlParameter.Optional },
        new[] { “RouteTest.Areas.Jobs.Controllers” }
        );
        }
        }
        }

        The line: “var route = context.Routes.MapHttpRoute(name, routeTemplate, defaults, constraints);” produces an error so the dang thing wont compile.

        II now understand the concept… just cannot get there from here.

        Thanks for your help!!! If you have a sample project, that would send me on my way.

        D

        Dave

        • Specifically I’m getting the error: ‘System.Web.Routing.RouteCollection’ does not contain a definition for MapHttpRoute.

          Dave

          • OOOHW Wait… needed to add using System.Web.Http; Might be working now.

            Dave

        • Hi Dave.

          In the line with context.Routes.MapHttpRoute, you need to remove the Routes part so it becomes context.MapHttpRoute. This will pickup the MapHttpRoute of the AreaRegistrationContextExtensions.

          Success!

          Martin Devillers

  5. OK… works great… except… when I come in the first time and the routes are initialized, the route.DataTokens is set to a new RouteValueDictionary. All seems to be good and the first call works like a charm. In fact all calls to that area work fine. But when I make a call to another area, the DataTokens retains the first area. eg… first call is “api/Jobs/Send” then DataToken[“area”] = “Jobs”… next call goes to “api/Reports/Read” and the DataToken[“area”] = “Jobs” still. I think that something needs to reinitialize DataTokens… Am I missing something? Or did something change since RC?

    Thanks!

    Dave

    • OK… some new information

      The function:
      private static string GetAreaName(HttpRequestMessage request)
      {
      var data = request.GetRouteData();
      object areaName;
      return data.Route.DataTokens.TryGetValue(AreaRouteVariableName, out areaName) ? areaName.ToString() : null;
      }

      Is returning the wrong value. ‘data’ seems to contain the right values, but the data.Route.DataTokens contains the wrong values. I’m probably not setting something correctly so I’m sorry if I’m wasting time.

      Thanks!

      Dave

      • Hi Dave,

        In our project we are still on RC, but we will soon be upgrading to RTM. I’ll investigate the issue once we’ve made the transition. In my personal projects I’ve already noticed some subtle behavioral differences between RC and RTM, so I suspect this may have something to do with it.

        Greetings

        Martin

        Martin Devillers

  6. Hi Dave,

    I’m getting the following exception in GetControllerType:

    Value cannot be null. Parameter name: controllerType

    Any ideas? Thanks!

    Jonathan Bull

    • Hi Jonathan,

      In what class in what method and on what line do you see this error? How are your routes configured? Is this happening in an area request or an area-less request? What is the name of the controller you are trying to reach? In what namespace does it reside?

      Answers to these questions will help you and me to solve this issue 🙂

      Martin

      Martin Devillers

      • Hi,

        My mistake! I incorrectly defined the routes – maybe I should have paid a bit more attention to the tutorial…

        Thanks for your quick response.

        Jonathan Bull

        • Hi Jonathan,

          Glad to hear you solved the problem on your own. Yes, this blog has a lot of information to digest. The routing mechanism of .NET is an intricate system with plenty of ‘gotcha’s’.

          Martin

          Martin Devillers

  7. […] WebApi and Areas to play nicely I’m using Martin Deviller’s tutorial to get MVC4′s Web API working with Areas. I believe I’ve followed the instructions […]

    Getting WebApi and Areas to play nicely | Jisku.com - Developers Network

  8. Trying to call an api controller in ~/Controllers/ that is called the same as a controller in ~/Areas/Administration/Controllers, and it’s always sending me to the ~/Areas/Administration/Controllers class instead of going to ~/Controllers/{controller}. Is there something special I need to do to my root to get this to work? How should the default controller mapping look for WebAPI with this functionality?

    Richard B.

  9. I’m finding that I have to define the route in each [X]AreaRegistration class like so:
    context.MapHttpRoute(
    name: “Whatever_Api_default”,
    routeTemplate: “whatever/api/{controller}/{id}”,
    defaults: new { area = “Whatever”, id = RouteParameter.Optional }
    );

    Is that expected? It would be nice to define the area api route globally instead of each time a new area is added something like this:
    routes.MapHttpRoute(
    name: “DefaultAreaApi”,
    routeTemplate: “{area}/api/{controller}/{id}”,
    defaults: new { id = RouteParameter.Optional }
    );

    Is this not possible?

    Trey

  10. This solution has been great for me so far with one minor issue… optional query string parameters. Typically I was able to use nullable types for parameters that I want to be optional in the query string, but now when one of those parameters is not in the query string, i get the error: “No action was found on the controller ” that matches the request.”

    Is anyone else experiencing this as well and/or have any suggestions?

    Steve

    • Yes, I’m having this issue, too. And I’d call it more than just “minor”. Anybody solve this one yet?

      Jeff

      • I solved my problem, and it was (mostly) unrelated. Some of my WebAPI actions are non-RESTful (e.g., /product/DoSomething) and so my routeTemplate needed to be “api/{contoller}/{action}/{id}”, not just “api/{controller}/{id}”. After making that change, my default parameters worked as expected again.

        Jeff

  11. The solution does not work with the global definition of api route. In that case the datatokens are still null.

    config.Routes.MapHttpRoute(
    name: “AdminApi”,
    routeTemplate: “api/{area}/{controller}/{id}”,
    defaults: new { id = RouteParameter.Optional }

    For this to work we have to define the api route in the area registration only where you init the data tokens to an empty dictionary in case it is null

    its a great little piece of code. thanks for the work.

    codetantrik

  12. […] a bit I found a great article ASP .net MVC 4RC: Getting Web API and Areas to play nicely ( a must read, and step by step explained), the only thing I missed was a sample solution to […]

    ASP .net MVC 4 Web API + Areas sample solution | Lemoncode

  13. Just wanted to say thanks for the excellent blog. I have been struggling with this for over a day, and this did the trick. It is extremely handy to have APIs in their own areas…I started running into situations where I was having name conflicts between MVC controllers and API controllers.

    Jon Rista

  14. Does this work with MVC5?

    Patrick

  15. Hi! Thanks a lot for this nicely solution.
    I have a issue with not found controllers on ‘root’.
    Currently I’ve received a HttpStatusCode 500 but NotFound is 404.
    I’ve tried to add the following code but not worked.

    Do you have a ‘light in the end of the tunnel?’ rsrsrs

    Regards

    private HttpControllerDescriptor GetApiController(HttpRequestMessage request)
    {
    var areaName = GetAreaName(request);
    var controllerName = GetControllerName(request);
    var type = GetControllerType(areaName, controllerName);

    if (type == null) {
    request.CreateResponse(System.Net.HttpStatusCode.NotFound, “Invalid route”);
    return new HttpControllerDescriptor();
    }
    return new HttpControllerDescriptor(_configuration, controllerName, type);
    }

    Lucas Massena

  16. This doesn’t seem to be working for MVC5 with Ninject. I’m wanting to make restful calls to controllers converted from MVC to API.

    See anything wrong?
    public override void RegisterArea(AreaRegistrationContext context)
    {
    //calling extension method for webApi calls in Admin area
    context.MapHttpRoute(
    name: “Admin_DefaultApi”,
    routeTemplate: “Admin/api/{controller}/{id}”,
    defaults: new { area = AreaName, id = RouteParameter.Optional }
    );

    context.MapRoute(
    “Admin_default”,
    “Admin/{controller}/{action}/{id}”,
    new { area = AreaName, action = “Index”, id = UrlParameter.Optional },
    new[] { “VYSA.Areas.Admin.Controllers” }
    );

    }

    Nick Turner

  17. Now, when I navigate to the “Admin” area, it cannot locate the controller. Any ideas? Any help would be much appreciated.

    Nick Turner

    • I got it working. It was a stupid namespace issue in my AreaRegistration.cs file.

      Nick Turner

  18. Hello Martin Devillers, I created a sample application for show the solution this article, forward:

    https://github.com/renattomachado/WebApiAreasRouteFix

    Renatto Machado

  19. I published a NuGet base on your code but improved to use context.Namespaces replacing your ControllerTypeSpecifications.

    Package available here : https://www.nuget.org/packages/UsefulBits.Web.Http.Areas

    I’m going to publish it on GitHub soon.

    Guillaume

Comments are closed.