13

I have an ASP.NET MVC 3 application where users can post suggestions along the lines of "bla bla would be better if yada yada yada". For the suggestion detail page I have defined a nice SEO friendly route as follows:

routes.MapRoute(null, "suggestion/{id}/{it}/would-be-better-if-{if}", 
    new { controller = "suggestion", action = "details" });

As you can see I want the "would be better if" part to be fixed.

This route works perfectly for any old suggestion and generates links like suggestion/5/this-site/would-be-better-if-it-had-a-iphone-application, and clicking on the link actually requests the appropriate detail page.

A friend of mine, who ironically happens to be a tester, has managed to, involuntarily, post a suggestion that actually breaks the route: "This site would be better if 'would be better if' was always aligned in the middle".
The link generated for this suggestion is /suggestion/84/this-site/would-be-better-if-would-be-better-if-was-always-alligned-in-the-middle.

I have tried Phil Haack's Routing Debugger and have confirmed that the route will actually work up until suggestion/84/this-site/would-be-better-if-would-be-better-if-, so the second "would-be-better-if" is actually accepted; adding anything after that will actually cause the url not to match any route (thanks to Omar -see comments- for help).


Please bear in mind that I really don't want to change the route definition since I think it is as good as I can manage for this case, SEO-wise.

So, how come having text equal to the fixed part of the route prevents the link from matching the route? why is the route breaking?

I am actually rahter more interested in the why, as I believe understanding the why will lead to a solution or at least a proper understanding of a rather interesting problem.

Sergi Papaseit
  • 15,999
  • 16
  • 67
  • 101
  • 2
    This is very interesting. Not sure why it's happening, but for if you would like a temporary fix, replace the `-{if}` with `/{if}`. I guess ASP.NET MVC treats `/` different than other characters. – Omar Mar 11 '11 at 23:15
  • 1
    Some testing revealed that the route `suggestion/84/this-site/would-be-better-if-would-be-better-if-` works; it's only after it has anything after the last `-` does it break. – Omar Mar 11 '11 at 23:20
  • @Omar - I *really* don't want to change the route although your proposed workaround is quite acceptable. Regarding your second comment, thanks, I totally missed that one... This makes it even more curious if you ask me. – Sergi Papaseit Mar 11 '11 at 23:26
  • I think this is a bug, I had a similar issue: http://stackoverflow.com/questions/4318373/asp-net-routing-using-delimeters-other-than-slash – Max Toro Mar 12 '11 at 01:12
  • I don't really know double would-be-better-if is about. but honestly you need to use custom route like @Lukáš or here http://stackoverflow.com/questions/1107507/asp-net-mvc-custom-route-handler-constraint – CallMeLaNN Mar 14 '11 at 18:27
  • @CallMeLaNN - I don't think I *need* to use something like what @Lukáš proposed. @Lukáš's answer is very helpful, and that's why I upvoted it, but, as stated above, if I have choose a workaround I'll probably just change the "-" for a "/". [K.I.S.S](http://en.wikipedia.org/wiki/KISS_principle) and all that. :) – Sergi Papaseit Mar 14 '11 at 20:48

2 Answers2

5

I'm not sure why it behaves this way, but you can use something like this:

public interface IRouteRule
{
    object ProcessIncoming(object value);
    object ProcessOutgoing(object value);
}

public class StartsWithRouteRule : IRouteRule
{
    public StartsWithRouteRule(string value)
    {
        Value = value;
    }

    public string Value { get; protected set; }

    public object ProcessIncoming(object value)
    {
        var result = value as string;
        if (result == null)
            return null;

        if (!result.StartsWith(Value))
            return null;

        return result.Substring(Value.Length);
    }

    public object ProcessOutgoing(object value)
    {
        var result = value as string;
        if (result == null)
            return null;

        return Value + result;
    }
}

public class ComplexRoute : Route
{
    public ComplexRoute(string url, object defaults, object rules)
        : this(url, new RouteValueDictionary(defaults), rules)
    { }
    public ComplexRoute(string url, RouteValueDictionary defaults, object rules)
        : base(url, defaults, new MvcRouteHandler())
    {
        Rules = new Dictionary<string, IRouteRule>();
        foreach (var pair in new RouteValueDictionary(rules))
            Rules.Add(pair.Key, (IRouteRule)pair.Value);
    }

    public Dictionary<string, IRouteRule> Rules { get; protected set; }

    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        var result = base.GetRouteData(httpContext);

        if (result == null)
            return null;

        foreach (var pair in Rules)
        {
            var currentValue = result.Values[pair.Key];
            if (currentValue == null)
                return null;

            var value = pair.Value.ProcessIncoming(currentValue);
            if (value == null)
                return null;

            result.Values[pair.Key] = value;
        }

        return result;
    }

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        values = new RouteValueDictionary(values);

        foreach (var pair in Rules)
        {
            var currentValue = values[pair.Key];
            if (currentValue == null)
                return null;

            var value = pair.Value.ProcessOutgoing(currentValue);
            if (value == null)
                return null;

            values[pair.Key] = value;
        }

        return base.GetVirtualPath(requestContext, values);
    }
}

Usage:

routes.Add(new ComplexRoute(
    "suggestion/{id}/{it}/{if}",
    new { controller = "suggestion", action = "details" },
    new { @if = new StartsWithRouteRule("would-be-better-if-") }));
Lukáš Novotný
  • 9,012
  • 3
  • 38
  • 46
2

This looks like a dupe of ASP.NET routing: Literal sub-segment between tokens, and route values with a character from the literal sub-segment which is a much simpler version of the bug. I'd recommend closing this one in favor of that one.

I answered that question.

Community
  • 1
  • 1
Haacked
  • 58,045
  • 14
  • 90
  • 114