ASP.net MVC support for URL's with hyphens
Asked Answered
A

9

35

Is there an easy way to get the MvcRouteHandler to convert all hyphens in the action and controller sections of an incoming URL to underscores as hyphens are not supported in method or class names.

This would be so that I could support such structures as sample.com/test-page/edit-details mapping to Action edit_details and Controller test_pagecontroller while continuing to use MapRoute method.

I understand I can specify an action name attribute and support hyphens in controller names which out manually adding routes to achieve this however I am looking for an automated way so save errors when adding new controllers and actions.

Allegraallegretto answered 15/1, 2010 at 10:45 Comment(5)
Hyphens are seen as more user friendly in URLs. Underscores clash with underlined links.Transpontine
You could take a look at this article showing how to implement a custom MvcRouteHandler.Annulus
possible duplicate of Asp.Net MVC: How do I enable dashes in my urls?Valerie
Anyone coming from a Google search check out @Ghazi answer as it builds upon the previous ones, and IMO is the most robust of any solution I've seen.Amadeus
for ASP.NET Core -> #53023424Heimdall
A
18

I have worked out a solution. The requestContext inside the MvcRouteHandler contains the values for the controller and action on which you can do a simple replace on.

Public Class HyphenatedRouteHandler
    Inherits MvcRouteHandler

    Protected Overrides Function GetHttpHandler(ByVal requestContext As System.Web.Routing.RequestContext) As System.Web.IHttpHandler
        requestContext.RouteData.Values("controller") = requestContext.RouteData.Values("controller").ToString.Replace("-", "_")
        requestContext.RouteData.Values("action") = requestContext.RouteData.Values("action").ToString.Replace("-", "_")
        Return MyBase.GetHttpHandler(requestContext)
    End Function

End Class

Then all you need to replace the routes.MapRoute with an equivalent routes.Add specifying the the new route handler. This is required as the MapRoute does not allow you to specify a custom route handler.

routes.Add(New Route("{controller}/{action}/{id}", New RouteValueDictionary(New With {.controller = "Home", .action = "Index", .id = ""}), New HyphenatedRouteHandler()))
Allegraallegretto answered 15/1, 2010 at 11:30 Comment(3)
How would you use @Html.Actionlink to point to the hyphen url? @Html.ActionLink("Test", "Action", "ControllerName-Hyphen") In the above case actionlink doesn't know how to handle the hyphens would I have to create another extension that would replace the "-" with ""? I would like the generated url to have the hyphen in thereVoyles
Write your action link with the hyphens in, when the MVC looks up the route it will use the HyphenatedRouteHandler to resolve it and generate the full URL.Allegraallegretto
Ok ok looks like it does work well, I was just getting a syntax check problem from resharper because it doesn't know about the mapping thanksVoyles
P
36

C# version of John's Post for anyone who would prefer it: C# and VB version on my blog

public class HyphenatedRouteHandler : MvcRouteHandler{
        protected override IHttpHandler  GetHttpHandler(RequestContext requestContext)
        {
            requestContext.RouteData.Values["controller"] = requestContext.RouteData.Values["controller"].ToString().Replace("-", "_");
            requestContext.RouteData.Values["action"] = requestContext.RouteData.Values["action"].ToString().Replace("-", "_");
            return base.GetHttpHandler(requestContext);
        }
    }

...and the new route:

routes.Add(
            new Route("{controller}/{action}/{id}", 
                new RouteValueDictionary(
                    new { controller = "Default", action = "Index", id = "" }),
                    new HyphenatedRouteHandler())
        );

You can use the following method too but bear in mind you would need to name the view My-Action which can be annoying if you like letting visual studio auto generate your view files.

[ActionName("My-Action")]
public ActionResult MyAction() {
    return View();
}
Penhall answered 6/2, 2010 at 13:57 Comment(2)
This worked great... however watch out for controller = "Default". I got a 404 a couple times before noticing that and changing it to "Home".Suitable
How would you use @Html.Actionlink to point to the hyphen url? @Html.ActionLink("Test", "Action", "ControllerName-Hyphen") In the above case actionlink doesn't know how to handle the hyphens would I have to create another extension that would replace the "-" with ""? I would like the generated url to have the hyphen in thereVoyles
A
18

I have worked out a solution. The requestContext inside the MvcRouteHandler contains the values for the controller and action on which you can do a simple replace on.

Public Class HyphenatedRouteHandler
    Inherits MvcRouteHandler

    Protected Overrides Function GetHttpHandler(ByVal requestContext As System.Web.Routing.RequestContext) As System.Web.IHttpHandler
        requestContext.RouteData.Values("controller") = requestContext.RouteData.Values("controller").ToString.Replace("-", "_")
        requestContext.RouteData.Values("action") = requestContext.RouteData.Values("action").ToString.Replace("-", "_")
        Return MyBase.GetHttpHandler(requestContext)
    End Function

End Class

Then all you need to replace the routes.MapRoute with an equivalent routes.Add specifying the the new route handler. This is required as the MapRoute does not allow you to specify a custom route handler.

routes.Add(New Route("{controller}/{action}/{id}", New RouteValueDictionary(New With {.controller = "Home", .action = "Index", .id = ""}), New HyphenatedRouteHandler()))
Allegraallegretto answered 15/1, 2010 at 11:30 Comment(3)
How would you use @Html.Actionlink to point to the hyphen url? @Html.ActionLink("Test", "Action", "ControllerName-Hyphen") In the above case actionlink doesn't know how to handle the hyphens would I have to create another extension that would replace the "-" with ""? I would like the generated url to have the hyphen in thereVoyles
Write your action link with the hyphens in, when the MVC looks up the route it will use the HyphenatedRouteHandler to resolve it and generate the full URL.Allegraallegretto
Ok ok looks like it does work well, I was just getting a syntax check problem from resharper because it doesn't know about the mapping thanksVoyles
M
14

All you really need to do in this case is name your views with the hyphens as you want it to appear in the URL, remove the hyphens in your controller and then add an ActionName attribute that has the hyphens back in it. There's no need to have underscores at all.

Have a view called edit-details.aspx

And have a controller like this:

[ActionName("edit-details")]
public ActionResult EditDetails(int id)
{
    // your code
}
Minister answered 11/2, 2011 at 13:12 Comment(1)
This works for actions but not for controllers, at least not in MVC 1Allegraallegretto
G
9

I realize this is quite an old question, but to me this is only half the story of accepting url's with hyphens in them, the other half is generating these urls while still being able to use Html.ActionLink and other helpers in the MVC framework, I solved this by creating a custom route class similar, here is the code in case it helps anyone coming here from a google search. It also includes the lower casing of the url too.

public class SeoFriendlyRoute : Route
{
     // constructor overrides from Route go here, there is 4 of them

     public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
     {
          var path = base.GetVirtualPath(requestContext, values);

          if (path != null)
          {
              var indexes = new List<int>();
              var charArray = path.VirtualPath.Split('?')[0].ToCharArray();
              for (int index = 0; index < charArray.Length; index++)
              {
                  var c = charArray[index];
                  if (index > 0 && char.IsUpper(c) && charArray[index - 1] != '/')
                      indexes.Add(index);
              }

              indexes.Reverse();
              indexes.Remove(0);
              foreach (var index in indexes)
                  path.VirtualPath = path.VirtualPath.Insert(index, "-");

              path.VirtualPath = path.VirtualPath.ToLowerInvariant();
          }

          return path;
     }
}

then when adding routes, you can either create a RouteCollection extensions or just use the following in your global routing declarations

routes.Add(
        new SeoFriendlyRoute("{controller}/{action}/{id}", 
            new RouteValueDictionary(
                new { controller = "Default", action = "Index", id = "" }),
                new HyphenatedRouteHandler())
    );
Ghazi answered 27/6, 2012 at 3:10 Comment(1)
Above answer was just the ticket. Big thanks. I would like to draw attention to the important addition by Sylvia J (currently below)Bare
P
2

Thanks dsteuernol for this answer - exactly what I was looking for. However I found that I needed to enhance the HyphenatedRouteHandler to cover the scenario where the Controller or Area was implied from the current page. For example using @Html.ActionLink("My Link", "Index")

I changed the GetHttpHandler method to the following:

public class HyphenatedRouteHandler : MvcRouteHandler
    {
        /// <summary>
        /// Returns the HTTP handler by using the specified HTTP context.
        /// </summary>
        /// <param name="requestContext">The request context.</param>
        /// <returns>
        /// The HTTP handler.
        /// </returns>
        protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
        {

            requestContext.RouteData.Values["controller"] = ReFormatString(requestContext.RouteData.Values["controller"].ToString());
            requestContext.RouteData.Values["action"] = ReFormatString(requestContext.RouteData.Values["action"].ToString());

            // is there an area
            if (requestContext.RouteData.DataTokens.ContainsKey("area"))
            {
                requestContext.RouteData.DataTokens["area"] = ReFormatString(requestContext.RouteData.DataTokens["area"].ToString());
            }

            return base.GetHttpHandler(requestContext);
        }


        private string ReFormatString(string hyphenedString)
        {
            // lets put capitals back in

            // change dashes to spaces
            hyphenedString = hyphenedString.Replace("-", " ");

            // change to title case
            hyphenedString = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(hyphenedString);

            // remove spaces
            hyphenedString = hyphenedString.Replace(" ", "");

            return hyphenedString;
        }
    }

Putting the capitals back in meant that the implied controller or area was then hyphenated correctly.

Perish answered 28/3, 2013 at 11:18 Comment(0)
N
1

I've developed an open source NuGet library for this problem which implicitly converts EveryMvc/Url to every-mvc/url.

Dashed urls are much more SEO friendly and easier to read. (More on my blog post)

NuGet Package: https://www.nuget.org/packages/LowercaseDashedRoute/

To install it, simply open the NuGet window in the Visual Studio by right clicking the Project and selecting NuGet Package Manager, and on the "Online" tab type "Lowercase Dashed Route", and it should pop up.

Alternatively, you can run this code in the Package Manager Console:

Install-Package LowercaseDashedRoute

After that you should open App_Start/RouteConfig.cs and comment out existing route.MapRoute(...) call and add this instead:

routes.Add(new LowercaseDashedRoute("{controller}/{action}/{id}",
  new RouteValueDictionary(
    new { controller = "Home", action = "Index", id = UrlParameter.Optional }),
    new DashedRouteHandler()
  )
);

That's it. All the urls are lowercase, dashed, and converted implicitly without you doing anything more.

Open Source Project Url: https://github.com/AtaS/lowercase-dashed-route

Nerveracking answered 3/8, 2013 at 11:50 Comment(0)
E
0

Don't know of a way without writing a map for each url:

routes.MapRoute("EditDetails", "test-page/edit-details/{id}", new { controller = "test_page", action = "edit_details" });
Erectile answered 15/1, 2010 at 11:9 Comment(1)
This is what I meant by "manually adding routes" and what I am trying to avoid.Allegraallegretto
K
0

If you upgrade your project to MVC5, you can make use of attribute routing.

[Route("controller/my-action")]
public ActionResult MyAction() {
    return View();
}

I much prefer this approach to the accepted solution, which leaves you with underscores in your controller action names and view filenames, and hyphens in your view's Url.Action helpers. I prefer consistency, and not having to remember how the names are converted.

Klein answered 3/9, 2017 at 11:32 Comment(0)
C
0

In MVC 5.2.7 you can simply specifiy using the attribute

ActionName

[ActionName("Import-Export")]
public ActionResult ImportExport()
{
    return View();
}

Then name the view

Import-Export.cshtml

The link would then be:

@Html.ActionLink("Import and Export", "Import-Export", "Services")

Which is of the form:

@Html.ActionLink("LinkName", "ActionName", "ControllerName")
Closer answered 22/8, 2019 at 11:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.