Abhiroop
Abhiroop

Reputation: 3

Url routing in Post Actions not working properly

In my application, I have a Area(XYZ) having a controller(XYZController) and bunch of other Controllers inside the area. The XYZController has general actions like Index,View,Create,Edit etc. The More specific actions related to some particular functionality are arranged accordingly in the corresponding controller.

To avoid URL having structure like: app/XYZ(area)/XYZ(Controller)/Create, I added a routing as follows to the Area route register file.

context.MapRoute(
                  "XYZ_AreaDefaultControllerActions",
                  "XYZ/{action}/{id}",
                  new { controller = "XYZ", id = UrlParameter.Optional },
                  new { controller = "XYZ" },
                  new string[] { "App.Web.Areas.XYZ.Controllers.*" } 
              );

context.MapRoute(
                "XYZ_default",
                "XYZ/{controller}/{action}/{id}",
                new { controller = "XYZ", action = "Index", id = UrlParameter.Optional}
        );

This mapping routes the url: app/XYZ/Create to area = XYZ, Controller = XYZ and Action = Create which is correct and is what I want to do but messes up routing of some other Post actions.

Consider this, I have another controller called Notes controller which has some Post actions.

A action with url as /app/XYZ/Notes/List/id is routed correctly when the request is a HTTP Get and the output from route debugger shows that the 2nd route definition matches and the first one doesn't.

When i do a post to the same controller with an action AddNote with url /App/XYZ/Notes/AddNote the 1st route definition matches according to the route debugger and as a result the action is not found as it takes Controller = XYZ, Action = Notes, Id = AddNote. Here is the output from the route debugger:

Matches | Url | Defaults | Constraints

  1. True | XYZ/{action}/{id} | controller = XYZ, id = | controller = XYZ

  2. True | XYZ/{controller}/{action}/{id} | controller = XYZ, action = Index, id = |(null)

The problem is that seems like the contraints of the first route are not restricting it enough in the case of Post while in Get it applied the constraint.

Any suggestions what is going wrong here?

Upvotes: 0

Views: 481

Answers (1)

danludwig
danludwig

Reputation: 47375

If your XYZController does not have any POST actions, you could add a constraint like this to prevent POST requests from matching your first route:

context.MapRoute("XYZ_AreaDefaultControllerActions",
    "XYZ/{action}/{id}",
    new { controller = "XYZ", id = UrlParameter.Optional },
    new { controller = "XYZ", httpMethod = new HttpMethodConstraint("GET") },
    new string[] { "App.Web.Areas.XYZ.Controllers.*" }
);

Update

Since you have post actions in the XYZController, like you mentioned, the above route constraint won't work for you.

The problem here is that you have a controller with the same name as your area. This is why MVC is matching the first route to your notes controller POST action, because it is just trying to match the tokens up based on the incoming URL:

  • /XYZ - this matches the first segment of your first route literally
  • /Notes - this gets filled into your {action} segment
  • /AddNote - this gets filled into your {id} segment

Given your first route, a { controller = "XYZ" } constraint does not make any difference because MVC is trying to route an inbound URL to a controller + action + args. MVC does not have any idea what controller it is coming from, it is trying to match what controller and action will handle the URL request.

One solution is to add action constraints to your first route, but this will only work if your action names in XYZController don't match any of the POST action names in your notes controller:

new { controller = "XYZ", action = "XYZAction1|XYZAction2|XYZActionN" },

For incoming routes, this will only match the first route when the action is one of the pipe-separated names in the constraint.

Another option would be to constrain your {id} route segment using a regex. If your id's are strings, this might not be an option. However if your ids are always numbers, you can constrain the first route to only match route segments to the {id} token when they are numeric. This way, "AddNote" would not match the {id} segment of your first route because it is not numeric:

new { controller = "XYZ", id = @"\d+" },

To answer your second comment, the reason this works with GET is because your second route has an additional route segment. GETS with id's don't match the first route because they have 4 tokens, not 3.

Upvotes: 0

Related Questions