8
votes

I'd like to maintain ASP.NET MVC 4's existing controller/action/id routing with default controller = Home and default action = Index, but also enable controller/id to route to the controller's index method as long as the second item is not a known action.

For example, given a controller Home with actions Index and Send:

/Home/Send -> controller's Send method
/Home -> controller's Index method
/Home/Send/xyz -> controller's Send method with id = xyz
/Home/abc -> controller's Index method with id = abc

However, if I define either route first, it hides the other one. How would I do this?

6
how did you define your routes on RouteTable? - Felipe Oriani
@FelipeOriani I didn't -- if I create either one, it will hide the other one as far as I can tell. - David Pfeffer

6 Answers

5
votes

Do the specific one first before the default generic one. The order matters.

routes.MapRoute(name: "single", url: "{controller}/{id}",
    defaults: new { controller = "Home", action = "Index" }, 
    constraints: new { id = @"^[0-9]+$" });

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home",
    action = "Index",
    id = UrlParameter.Optional }
);
2
votes

In case, that the list of your actions (e.g. Send) is well known, and their (action) names cannot be the same as some ID value, we can use our custom ConstraintImplementation:

public class MyRouteConstraint : IRouteConstraint
{
  public readonly IList<string> KnownActions = new List<string> 
       { "Send", "Find", ... }; // explicit action names

  public bool Match(System.Web.HttpContextBase httpContext, Route route
                  , string parameterName, RouteValueDictionary values
                  , RouteDirection routeDirection)
  {
    // for now skip the Url generation
    if (routeDirection.Equals(RouteDirection.UrlGeneration))
    {
      return false; // leave it on default
    }

    // try to find out our parameters
    string action = values["action"].ToString();
    string id = values["id"].ToString();

    // id and action were provided?
    var bothProvided = !(string.IsNullOrEmpty(action) || string.IsNullOrEmpty(id));
    if (bothProvided)
    {
      return false; // leave it on default
    }

    var isKnownAction = KnownActions.Contains(action
                           , StringComparer.InvariantCultureIgnoreCase);

    // action is known
    if (isKnownAction)
    {
      return false; // leave it on default
    }

    // action is not known, id was found
    values["action"] = "Index"; // change action
    values["id"] = action; // use the id

    return true;
  }

And the route map (before the default one - both must be provided), should look like this:

routes.MapRoute(
  name: "DefaultMap",
  url: "{controller}/{action}/{id}",
  defaults: new { controller = string.Empty, action = "Index", id = string.Empty },
  constraints: new { lang = new MyRouteConstraint() }
);

Summary: In this case, we are evaluating the value of the "action" parameter.

  • if both 1) action and 2) id are provided, we won't handle it here.
  • nor if this is known action (in the list, or reflected...).
  • only if the action name is unknown, let's change the route values: set action to "Index" and action value to ID.

NOTE: action names and id values need to be unique... then this will work

1
votes

The easiest way is to simply create two Action methods in your Controller. One for Index and one for Send and place your string id parameter on both. Since you cannot have duplicate or overloaded action methods, that solves that problem. Your Index method will now handle both index or blank paths where the id is present or not (null) and process your views that way. Your Send method will do the exact same as Index. You can then route, process, or redirect how you like, depending on if id is null. This should work with no changes to RouteConfig.cs:

public ActionResult Index(string id) {if (id == null) Do A else Do B}

public ActionResult Send(string id) {if (id == null) Do A else Do B}

I had this struggle for a long time, and this was the simplest solution.

0
votes

I don't think there is a solution for your requirements, as you have two competing routes. Maybe you can define something specific (in case you don't have any other controllers).

routes.MapRoute(
        name: "Send",
        url: "{controller}/Send/{id}",
        defaults: new { controller = "Home", id = UrlParameter.Optional }
    );
routes.MapRoute(
        name: "Home",
        url: "Home",
        defaults: new { controller = "Home", action = "Index" }

routes.MapRoute(
        name: "Index",
        url: "{controller}/{id}",
        defaults: new { controller = "Home", action= "Index", 
                           id = UrlParameter.Optional }
    );

routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index",
                                               id = UrlParameter.Optional }
    );
0
votes

This what worked for me:

1) In RouteConfig, I put this to be the first line:

routes.MapRoute(name: "single", url: "{controller}/{lastName}",
            defaults: new { controller = "LeaveRequests", action = "Index" });

2) In my controller:

public ViewResult Index(string lastName)
{
    if (lastName == null)
    {
        return //return somthing
    }
    else
    {
        return //return something
    }
 }

3) when you call the controller

http://localhost:34333/leaveRequests/Simpsons

it will give you all requests for Simpsons.

if you call http://localhost:34333/leaveRequests/ it will give all requests

0
votes

this is working for me

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(
            name: "Default1",
            url: "{id}",
            defaults: new { controller ="Home", action ="Index" }
        );
routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller ="Home", action ="Index", id = UrlParameter.Optional }
        );