ASP.NET MVC regex route constraint
Asked Answered
R

3

11

I'm having trouble with a specific constraint I'm trying to set up on a route. My URL must look like this one: http://hostname/id-my-title-can-be-that-long where id is composed with digit only and the title is lowercase character with dashes separator. The id and the title are also separated with a dash. For example: http://hostname/123-my-title.

Here's my route definition:

routes.MapRoute(
    "Test",
    "{id}-{title}",
    new { controller = "Article", action = "Index" },
    new { id = @"(\d)+", title = @"([a-z]+-?)+" }
);

The URL is correctly generated with the the html helper:

<%: Html.ActionLink("My link", "Index", "Article", new { id = Model.IdArticle, title = Model.UrlTitle }, null) %>

where, of course, Model.IdArticle is an Int32 and Model.UrlTitle a preformed string of my title that match my requirements (lower case only, space replaced by dashes).

The problem is, when I follow the link, the right controller & method is not called, it falls to the next route which is wrong.

For the records, I'm on ASP.NET MVC 2.

Anyone has an idea?

Rufe answered 30/12, 2010 at 8:57 Comment(1)
Finally, we'll separate the ID from the title with an undescore. I'm still wondering why this is not working but it's not anymore blocking. Thanks for your help!Rufe
J
0

You could try passing the whole route string "{id}-{title}" and parse the string manually yourself before it gets into your action by doing something similar to Phil Haack's slug to id actionfilter attribute - link

Hope this helps.

Jonijonie answered 30/12, 2010 at 9:9 Comment(2)
I read that blog post before but I find it overkill when I could split the route info. As my URL can be created from the route but not resolved as a route, I'm thinking about some kind of bug in MVC.Rufe
Ah fair enough. Will have another think :-)Jonijonie
D
0

Several characters in the route are "special" and will split up the parameters such as - and /. It might be that the extra -s in the route are causing it to fail. Try "{id}-{*title}" as this makes title include everything that follows.

Update

The answer above is what happens when you go on StackOverflow before you've had enough coffee.

We came across the same problem dealing with file names for files uploaded by users, the route included '-' as a delimiter but could also be used in the value in a later parameter, it could generate the correct URL but wouldn't match it. In the end I wrote a SpecialFileRoute class to handle this issue and registered this route. It's a bit ugly though but does the job.

Note that I kept in the old style MVC route for generating the URL, I was playing around with getting this to do it properly but it is something to come back to later.

    /// <summary>
/// Special route to handle hyphens in the filename, a catchall parameter in the commented route caused exceptions
/// </summary>
public class SpecialFileRoute : RouteBase, IRouteWithArea
{
    public string Controller { get; set; }
    public string Action { get; set; }
    public IRouteHandler RouteHandler = new MvcRouteHandler();
    public string Area { get; private set; }

    //Doc/{doccode} - {CatNumber}.{version} - {*filename},

    public SpecialFileRoute(string area)
    {
        Area = area;
    }

    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        string url = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2);
        var urlmatch = Regex.Match(url, @"doc/(\w*) - (\d*).(\d*) - (.*)", RegexOptions.IgnoreCase);
        if (urlmatch.Success)
        {
            var routeData = new RouteData(this, this.RouteHandler);

            routeData.Values.Add("doccode", urlmatch.Groups[1].Value);
            routeData.Values.Add("CatNumber", urlmatch.Groups[2].Value);
            routeData.Values.Add("version", urlmatch.Groups[3].Value);
            routeData.Values.Add("filename", urlmatch.Groups[4].Value);
            routeData.Values.Add("controller", this.Controller);
            routeData.Values.Add("action", this.Action);
            return routeData;
        }
        else
            return null;
    }

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        if (values.ContainsKey("controller") && (!string.Equals(Controller, values["controller"] as string, StringComparison.InvariantCultureIgnoreCase)))
            return null;
        if (values.ContainsKey("action") && (!string.Equals(Action, values["action"] as string, StringComparison.InvariantCultureIgnoreCase)))
            return null;
        if ((!values.ContainsKey("contentUrl")) || (!values.ContainsKey("format")))
            return null;
        return new VirtualPathData(this, string.Format("{0}.{1}", values["contentUrl"], values["format"]));
    }
}

The route is added as follows:

context.Routes.Add(new SpecialFileRoute(AreaName) { Controller = "Doc", Action = "Download" });

As stated above this is a bit ugly and when I have time there's a lot of work I'd like to do to improve this but it solved the problem of splitting the URL into the needed parameters. It's quite strongly tied in to the specific requirements of this one route with the url pattern, Regex and Values hard coded though it should give you a start.

Diskin answered 30/12, 2010 at 10:20 Comment(2)
Thanks for the information but we can't use a catchall (*) parameter in a segment that contains more than one section.Rufe
D'oh, of course. I came up against the same problem a while agoDiskin
A
0

you're not going to have a good time delimiting ID from title with a character that's also part of title's pattern. i'd recommend just using {id}/{title} if you can.

Assumption answered 1/5, 2022 at 4:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.