Search code examples
c#asp.netasp.net-mvcasp.net-mvc-routing

Routing to index with id in ASP.NET MVC 4


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?


Solution

  • 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