71

Question:

I am creating a wiki software, basically a clone of wikipedia/mediawiki, but in ASP.NET MVC (the MVC is the point, so don't recommend me ScrewTurn).

Now I have a question:

I use this route mapping, to route a URL like:
http://en.wikipedia.org/wiki/ASP.NET

        routes.MapRoute(
            "Wiki", // Routenname
            //"{controller}/{action}/{id}", // URL mit Parametern
            "wiki/{id}", // URL mit Parametern
            new { controller = "Wiki", action = "dbLookup", id = UrlParameter.Optional } // Parameterstandardwerte
        );

Now it just occured to me, that there might be titles like 'AS/400':
http://en.wikipedia.org/wiki/AS/400

Incidentially, there is also this one (title 'Slash'):
http://en.wikipedia.org/wiki//

And this one:
http://en.wikipedia.org/wiki//dev/null

Overall, Wikipedia seems to have a list of interesting titles like this: http://en.wikipedia.org/wiki/Wikipedia:Articles_with_slashes_in_title

How do I make routes like this route correctly ?

Edit:
Something like:
If the URL starts with /Wiki/, and if it doesn't start with /wiki/Edit/ (but not /Wiki/Edit) then pass all the rest of the URL as Id.

Edit:
Hmm, just another problem: How can I route this one:
http://en.wikipedia.org/wiki/C&A

Wikipedia can...

Edit:
According to wikipedia, due to clashes with wikitext syntax, only the following characters can never be used in page titles (nor are they supported by DISPLAYTITLE):

# < > [ ] | { }

http://en.wikipedia.org/wiki/Wikipedia:Naming_conventions_(technical_restrictions)#Forbidden_characters

Edit:
To allow * and &, put

<httpRuntime requestPathInvalidCharacters="" />

into section <system.web> in file web.config

(Found here: http://www.christophercrooker.com/use-any-characters-you-want-in-your-urls-with-aspnet-4-and-iis)

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
  • Can you change your routing parameter character to something "more usual", like a question mark, or a comma... something is NOT valid in a title? – corlettk Jun 13 '11 at 09:39
  • 1
    ASP.NET MVC routing isn't your only problem. Try topics like "LPT", "SQL*plus", "US$", "C#" etc. A lot of them will be caught by IIS. You better think about escaping some of them. – Codo Jun 13 '11 at 10:40

4 Answers4

107

You could use a catchall route to capture everything that follows the wiki part of the url into the id token:

routes.MapRoute(
    "Wiki",
    "wiki/{*id}",
     new { controller = "Wiki", action = "DbLookup", id = UrlParameter.Optional }
);

Now if you have the following request: /wiki/AS/400 it will map to the following action on the Wiki controller:

public ActionResult DbLookup(string id)
{
    // id will equal AS/400 here
    ...
}

As far as /wiki// is concerned I believe you will get a 400 Bad Request error from the web server before this request ever reaches the ASP.NET pipeline. You may checkout the following blog post.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • Nice, that works, but what about when I want to excempt /wiki/Edit/ArticleTitle from this rule ? (but that shouldn't excemp /wiki/Edit) – Stefan Steiger Jun 13 '11 at 09:42
  • 3
    @Quandary, I don't think you could exempt with a catchall rule. You could try defining another route **before** the one I showed which looks like this `wiki/Edit/{*id}`. – Darin Dimitrov Jun 13 '11 at 09:43
  • @Darin: OK, works perfectly. In Edit, that requires to catch id isnullorempty and redirect to action action dbLookup with id as Edit. – Stefan Steiger Jun 13 '11 at 09:53
  • @Darin: if in the Edit action, I do a if (string.IsNullOrEmpty(id)) return RedirectToAction("dbLookup", "Wiki", new { id = "Edit" }); then I enter a infinite loop... why ? – Stefan Steiger Jun 13 '11 at 13:54
  • @Quandary, I guess that `RedirectToAction("dbLookup", "Wiki", new { id = "Edit" })` resolves to the Edit action once again. – Darin Dimitrov Jun 13 '11 at 15:30
  • @Darin: Well, that much is obvious, the question is: Why ? controller + action + id are given, it's like it's passing all these to routing again... – Stefan Steiger Jun 13 '11 at 17:38
  • @DarinDimitrov: This is a similar case, could your help here? http://stackoverflow.com/questions/24932519/mvc-how-to-manage-slahses-in-route-parameters/ – Daniel Peñalba Jul 24 '14 at 13:23
  • Frustratingly, it works for me, except when there is a space before the slash. Space after the slash or anywhere else is fine. Unfortunately, I need it to work even with a space before the slash. – Dave Nov 27 '18 at 22:41
  • @Darin is there any built in support to have routes defined where in more than one route parameter might have "/" in its value. I understand {*ParameterName} can only work for last parameter, can not be used for any in between parameter in route. If there is no built in support what is most clean approach to still support such urls having multiple params with "/"? Thanks in advance. – LearningNeverEnds Jul 04 '19 at 09:08
31

in Attribute Routing in mvc i had the same problem having / in string abc/cde in HttpGet

        [Route("verifytoken/{*token}")]
        [AllowAnonymous]
        [HttpGet]
        public ActionResult VerifyToken(string token)
        {
          //logic here
        }

so you have to place * because after this it will be considered as parameter

Usman
  • 4,615
  • 2
  • 17
  • 33
  • 3
    awesome. this is the best and easiest solution :) – Emil Oct 10 '18 at 11:56
  • 2
    This seems to work for one slash, but not two in a row. any ideas there? – nixkuroi Nov 13 '19 at 23:01
  • 3
    @Usman What about if you have another route that is [Route("verifytoken/{token}/something")]? Wouldn't the wildcard potentially have conflicts with this route if there are forward slashes? Is there another way to do this for this use case? – Braden Brown Jun 08 '20 at 16:36
  • You are a hero! I was trying to create a custom url with slashes without any luck, no matter what i tried i was getting /Admin/Customers/Edit?id=1 instead of teh desired output /Admin/Customers/Edit/1. That asterisk did the trick! Thanks once again – Dimitris Thomas Mar 19 '21 at 17:59
  • Great answer. it works in HttpMethodAttribute as well. (.net core 3.1) [HttpGet("verifytoken/{*token}")] It works for more than one slash as well. e.g: https://localhost:44352/api/verifytoken/1234/12/23/rebase.pdf token = "1234/12/23/rebase.pdf" – pedram Aug 31 '21 at 01:41
5

@Darin: Well, that much is obvious, the question is: Why ? controller + action + id are given, it's like it's passing all these to routing again... – Quandary Jun 13 '11 at 17:38

Quandry - maybe you have already figured this out since your question is over a year old, but when you call RedirectToAction, you are actually sending an HTTP 302 response to the browser, which causes the browser to make a GET request to the specified action. Hence, the infinite loop you are seeing.

See: Controller.RedirectToAction Method

Nikhil
  • 16,194
  • 20
  • 64
  • 81
Adam
  • 86
  • 2
  • 2
-3

Still as an option write in the file Global.asax:

 var uri = Context.Request.Url.ToString();
        if (UriHasRedundantSlashes(uri))
        {
            var correctUri = RemoveRedundantSlashes(uri);
            Response.RedirectPermanent(correctUri);
        }
    }

    private string RemoveRedundantSlashes(string uri)
    {
        const string http = "http://";
        const string https = "https://";
        string prefix = string.Empty;

        if (uri.Contains(http))
        {
            uri = uri.Replace(http, string.Empty);
            prefix = http;
        }
        else if (uri.Contains(https))
        {
            uri = uri.Replace(https, string.Empty);
            prefix = https;
        }

        while (uri.Contains("//"))
        {
            uri = uri.Replace("//", "/");
        }

        if (!string.IsNullOrEmpty(prefix))
        {
            return prefix + uri;
        }
        return uri;
    }

    private bool UriHasRedundantSlashes(string uri)
    {
        const string http = "http://";
        const string https = "https://";

        if (uri.Contains(http))
        {
            uri = uri.Replace(http, string.Empty);
        }
        else if (uri.Contains(https))
        {
            uri = uri.Replace(https, string.Empty);
        }
        return uri.Contains("//");
    }
Arseni Mourzenko
  • 50,338
  • 35
  • 112
  • 199