Route constraint checked by the wrong route

Topics: General, Writing modules
May 13, 2014 at 11:29 AM
Edited May 13, 2014 at 4:55 PM
I'm using a route constraint in Orchard (1.7.2) to enable or disable a route URL, based on a database value. I also want to keep a way to call the associated action through another test URL, even if the setting is not set. So here's what I did.

Routes:
public class Routes : IRouteProvider
{
    private readonly IMyCustomRouteConstraint routeConstraint;

    public Routes(IMyCustomRouteConstraint routeConstraint)
    {
        this.routeConstraint = routeConstraint;
    }

    public void GetRoutes(IEnumerable<RouteDescriptor> routes)
    {
        foreach (var route in GetRoutes())
            routes.Add(route);
    }

    public IEnumerable<RouteDescriptor> GetRoutes()
    {
        return new[] {

            // Default action route
            new RouteDescriptor {
                Priority = 1000, // Default action
                Route = new Route(
                    "myaction/", // url
                    new RouteValueDictionary {
                        {"area", "MyArea"},
                        {"controller", "MyController"},
                        {"action", "MyDefaultAction"}
                    }, // defaults
                    new RouteValueDictionary(), // constraints
                    new RouteValueDictionary {{"area", "MyArea"}}, // dataTokens
                    new MvcRouteHandler() // routeHandler
                )
            },

            // Action route, enabled only if parameter is activated in settings
            new RouteDescriptor {
                Priority = 2000, // Overrides default action
                Route = new Route(
                    "myaction/", // url
                    new RouteValueDictionary {
                        {"area", "MyArea"},
                        {"controller", "MyController"},
                        {"action", "MyAction"}
                    }, // defaults
                    new RouteValueDictionary {{ "controller", this.routeConstraint }}, // constraints
                    new RouteValueDictionary {{"area", "MyArea"}}, // dataTokens
                    new MvcRouteHandler() // routeHandler
                )
            },

            // Test action route (used to ensure settings are correct)
            new RouteDescriptor {
                Priority = 5,
                Route = new Route(
                    "test/testmyaction/", // url
                    new RouteValueDictionary {
                        {"area", "MyArea"},
                        {"controller", "MyController"},
                        {"action", "MyAction"}
                    }, // defaults
                    new RouteValueDictionary(), // constraints
                    new RouteValueDictionary {{"area", "MyArea"}}, // dataTokens
                    new MvcRouteHandler() // routeHandler
                )
            }
        }
    }
}
Constraint:
public interface IMyCustomRouteConstraint : IRouteConstraint, IDependency
{
}

public class MyCustomRouteConstraint : IMyCustomRouteConstraint
{
    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        bool setting = false;
        // Code to get my database setting
        return setting;
    }
}
Controller:
public class MyCustomController : Controller
{
    public ActionResult MyDefaultAction()
    {
        // Default action code
        return this.View();
    }

    public ActionResult MyAction()
    {
        // Action code
        return this.View();
    }
}
My views are stored like this:
  • Views/MyCustom/MyAction.cshtml
  • Views/MyCustom/MyDefaultAction.cshtml
When I access "http://example.com/myaction", MyController.MyAction() is not called because the constraint is not met. Instead the action MyController.MyDefaultAction() is called. This is the behavior I expected.

But when I access "http://example.com/test/testmyaction", things happen in this order:
  • MyController.MyAction() method gets called, and returns this.View()
Instead of stopping here and rendering the view, the process continues as follow (without any new request made by the browser):
  • MyCustomRouteConstraint.Match() gets called (even though it's not in the route definition...) with route.Url equal to "myaction/" (???). The constraint is not matched.
  • MyController.MyDefaultAction() method gets called, and returns this.View()
  • The browser displays the view associated to MyDefaultAction, under the http://example.com/test/testmyaction URL...
Bonus: after this, when I refresh the page (F5, not ctrl-F5), MyAction never gets called. MyDefaultAction does... as if Orchard had cached the final action associated with this URL.

What am I missing?
May 13, 2014 at 4:56 PM
What I was missing: a typo in the name of my views sub-directory (which should be named exactly like the controller's prefix). I didn't expect such a behavior afterwards but the mistake was mine.
Marked as answer by madmox on 5/13/2014 at 8:56 AM