ASP.NET MVC regex route constraint

2019-04-23 19:15发布

问题:

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?

Thanks in advance, Fabian

回答1:

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.



回答2:

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.