Mormegil
Mormegil

Reputation: 8071

MVC ignores route defaults and renders full “/Home/Index” URLs instead of root

My ASP.NET MVC project seems to ignore route defaults when composing URLs. For instance, Url.Action("Index", "Home") returns /Home/Index/, while it should return just / (which I have seen in all my other MVC websites). It does not have any problem with using URLs, e.g. by visiting http://myserver/, the default controller and action are found correctly.

How can I fix the behavior? (Note that I have found questions asking how to achieve this behavior, but nobody seems to suffer from this opposite problem.)

The project is nothing spectacular, no custom routing handlers etc., and the routing configuration is quite simple:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.IgnoreRoute("LoginHandler");

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

(The * is there because some actions use arbitrary strings as identifiers which might contain embedded slashes, and without the asterisk, the route does not match for such URLs.)

Upvotes: 0

Views: 1194

Answers (1)

Mormegil
Mormegil

Reputation: 8071

The problem lies indeed in the small asterisk. These catch-all parameters apparently do not match against UrlParameter.Optional defaults, and result in the URL always containing the whole path.

If you want to keep the default behavior, while also allowing arbitrary parameters using the catch-all parameter syntax, you need to use the catch-all parameter in a secondary route:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.IgnoreRoute("LoginHandler");

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

With this definition, simple parameters not containing slashes are handled using the first route (as is the URL generation when not specifying the exact route to be used), while complex parameters with slashes fall back to the second route, and are handled correctly there.


(Note that this might be just a bug in the MVC implementation: For catch-all parameters, MVC’s ParsedRoute.IsParameterRequired returns null as the default parameter value, which is then tested against the supplied UrlParameter.Optional from the route’s defaults, and is found to be different. Therefore, you might change the route definition to use id = null default instead of id = UrlParameter.Optional, instead of adding the secondary route, and it also seems to be working, but it might have unwanted side effects, I guess. I am also not sure if this behavior is deliberate, or just a bug.)

Upvotes: 2

Related Questions