Using Tokens in RouteDescriptor

Topics: Writing modules
Jun 18, 2013 at 1:00 PM
I'm trying to use a custom Keyword token inside my search page route and ideally would like to write
new RouteDescriptor
{
    Priority = 20,
    Route = new Route(
        "{Keyword.Search}/{action}",
        new RouteValueDictionary
            {
                { "area", "Teamaton.Search" },
                { "controller", "TeamatonSearch" },
                { "action", "index" },
            },
        null,
        new RouteValueDictionary
            {
                { "area", "Teamaton.Search" }
            },
        new MvcRouteHandler())
}
This, of course will not work because the curly braces token syntax conflicts with the MVC route parameters syntax.
My next attempt was to extract the token evaluation into a method like this:
private string GetSearchPageUrl() {
    object token;
    return _tokenManager.Evaluate("{Keyword.Search}", new Dictionary<string, string>(), new Dictionary<string, object>())
            .TryGetValue("{Keyword.Search}", out token)
            ? (string) token + "/"
            : null;
}
but this throws inside the RequestTokens.Evaluate() method because of a NullReferenceException.

Could anybody advise on how to use tokens in routes? In my case, I don't depend on any content item so it shouldn't be a problem that I want to use the token during the application's initialization phase.
Developer
Jun 18, 2013 at 2:08 PM
Edited Jun 18, 2013 at 2:24 PM
Yes, the first approach will never work.

The question is why do you need to use tokens here in first place? It doesn't seem like a legitimate reason for using tokens, but please correct me if I'm wrong.

Routes need to be explicitly defined - there is no way to provide a token-based, 'auto-matching' placeholder directly through path parameter - neither in Orchard, nor in plain ASP.NET MVC. I guess this is what you want to achieve, right?

It would be way easier and cleaner to just have a "keyword" parameter with constraint:
new RouteDescriptor
{
    Priority = 20,
    Route = new Route(
        "{keyword}/{action}",
        new RouteValueDictionary
            {
                { "area", "Teamaton.Search" },
                { "controller", "TeamatonSearch" },
                { "action", "index" },
            },
        new RouteValueDictionary 
            {
                { "keyword", _keywordConstraint }
            },
        new RouteValueDictionary
            {
                { "area", "Teamaton.Search" }
            },
        new MvcRouteHandler())
}
Note the third argument - a parameter constraint. Please take a look at eg. Orchard.Blogs/Routes.cs for a few implementation examples. The constraint class implementation holds the whole matching logic.

The technique above - using a single route with constraints - is the best way to go if you have some route parameter that needs to be matched at runtime.
The other technique would be to pregenerate all possible routes upfront, but it's not the best idea if there is a large number of possible matches.
Jun 18, 2013 at 2:41 PM
Thanks for this great idea, Piotr! That's how I'm doing it now - with a custom IRouteConstraint implementation. Not using any tokens in there, but it really was not all that important, actually.
Developer
Jun 18, 2013 at 2:49 PM
Cool, glad it helped!