4

I'm looking specifically for a way to automatically hyphenate CamelCase actions and views. That is, I'm hoping I don't have to actually rename my views or add decorators to every ActionResult in the site.

So far, I've been using routes.MapRouteLowercase, as shown here. That works pretty well for the lowercase aspect of URL structure, but not hyphens. So I recently started playing with Canonicalize (install via NuGet), but it also doesn't have anything for hyphens yet.

I was trying...

routes.Canonicalize().NoWww().Pattern("([a-z0-9])([A-Z])", "$1-$2").Lowercase().NoTrailingSlash();

My regular expression definitely works the way I want it to as far as restructuring the URL properly, but those URLs aren't identified, of course. The file is still ChangePassword.cshtml, for example, so /account/change-password isn't going to point to that.

BTW, I'm still a bit rusty with .NET MVC. I haven't used it for a couple years and not since v2.0.

jedmao
  • 10,224
  • 11
  • 59
  • 65
  • Looks like I missed the bounty period by 1 minute. Please consider my answer before awarding it... The route handler although clever, misses the point of the issue, and adds unnecessary complexity. Thanks for your consideration – one.beat.consumer Dec 20 '11 at 17:59

4 Answers4

3

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

Uppercase urls are problematic because cookie paths are case-sensitive, most of the internet is actually case-sensitive while Microsoft technologies treats urls as case-insensitive. (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

Ata S.
  • 890
  • 7
  • 12
3

This might be a tad bit messy, but if you created a custom HttpHandler and RouteHandler then that should prevent you from having to rename all of your views and actions. Your handler could strip the hyphen from the requested action, which would change "change-password" to changepassword, rendering the ChangePassword action.

The code is shortened for brevity, but the important bits are there.

public void ProcessRequest(HttpContext context)
{
    string controllerId = this.requestContext.RouteData.GetRequiredString("controller");
    string view = this.requestContext.RouteData.GetRequiredString("action");

    view = view.Replace("-", "");
    this.requestContext.RouteData.Values["action"] = view;

    IController controller = null;
    IControllerFactory factory = null;

    try
    {
        factory = ControllerBuilder.Current.GetControllerFactory();
        controller = factory.CreateController(this.requestContext, controllerId);

        if (controller != null)
        {
            controller.Execute(this.requestContext);
        }
    }
    finally
    {
        factory.ReleaseController(controller);
    }
}

I don't know if I implemented it the best way or not, that's just more or less taken from the first sample I came across. I tested the code myself so this does render the correct action/view and should do the trick.

hawkke
  • 4,242
  • 1
  • 27
  • 23
0

I tried the solution in the accepted answer above: Using the Canonicalize Pattern url strategy, and then also adding a custom IRouteHandler which then returns a custom IHttpHandler. It mostly worked. Here's one caveat I found:

With the typical {controller}/{action}/{id} default route, a controller named CatalogController, and an action method inside it as follows:

ActionResult QuickSelect(string id){ /*do some things, access the 'id' parameter*/ }

I noticed that requests to "/catalog/quick-select/1234" worked perfectly, but requests to /catalog/quick-select?id=1234 were 500'ing because once the action method was called as a result of controller.Execute(), the id parameter was null inside of the action method.

I do not know exactly why this is, but the behavior was as if MVC was not looking at the query string for values during model binding. So something about the ProcessRequest implementation in the accepted answer was screwing up the normal model binding process, or at least the query string value provider.

This is a deal breaker, so I took a look at default MVC IHttpHandler (yay open source!): http://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Mvc/MvcHandler.cs

I will not pretend that I grok'ed it in its entirety, but clearly, it's doing ALOT more in its implementation of ProcessRequest than what is going on in the accepted answer.

So, if all we really need to do is strip dashes from our incoming route data so that MVC can find our controllers/actions, why do we need to implement a whole stinking IHttpHandler? We don't! Simply rip out the dashes in the GetHttpHandler method of DashedRouteHandler and pass the requestContext along to the out of the box MvcHandler so it can do its 252 lines of magic, and your route handler doesn't have to return a second rate IHttpHandler.

tl:dr; - Here's what I did:

public class DashedRouteHandler : IRouteHandler
    {
        public IHttpHandler GetHttpHandler(RequestContext requestContext)
        {
            requestContext.RouteData.Values["action"] = requestContext.RouteData.GetRequiredString("action").Replace("-", "");
            requestContext.RouteData.Values["controller"] = requestContext.RouteData.GetRequiredString("controller").Replace("-", "");

            return new MvcHandler(requestContext);
        }
    }
BenWillkommen
  • 1,472
  • 13
  • 21
0

Have you tried working with the URL Rewrite package? I think it pretty much what you are looking for.

http://www.iis.net/download/urlrewrite

Hanselman has a great example herE:

http://www.hanselman.com/blog/ASPNETMVCAndTheNewIIS7RewriteModule.aspx

Also, why don't you download something like ReSharper or CodeRush, and use it to refactor the Action and Route names? It's REALLY easy, and very safe.

It would time well spent, and much less time overall to fix your routing/action naming conventions with an hour of refactoring than all the hours you've already spent trying to alter the routing conventions to your needs.

Just a thought.

one.beat.consumer
  • 9,414
  • 11
  • 55
  • 98
  • I do use ReSharper, but I don't have any code to refactor. I'm doing proof of concept on a base MVC 4.0 project. I also played with the rewrite module and the problem with that is the hover link was still capitalized. This is a problem because then search engines will see 2 different links that refer to the same page. It needs to be lowercase before it even gets to the rewrite. – jedmao Dec 21 '11 at 01:55
  • Refactor in this context meaning, just renaming your Actions/Methods... in R# it is simply `Ctr+R, R` and a little guy will pop up for you to type your new name, and with a teeny bit of attention you can clean it up throughout your application without breaking a thing. I would use the URL Rewrite only after. Best bet is to follow MVC conventions since it is a convention based framework – one.beat.consumer Dec 21 '11 at 02:06
  • @one.beat.consumer MVC is convention based, yes, but convention only takes you so far in the real world. The best thing about MVC is its flexibility. It's filled with extendability points to hook into in order to compensate for any shortcomings in the framework. If it doesn't work the way you want it to by default - then extend it. I provided a working, reasonable solution to his question (not the 'change the way you want to do it' solution that you offered). Not worth the downvote in my opinion. – hawkke Dec 21 '11 at 18:19
  • @hawkke Of course. I'm not trying to debate here, just pointing out that sometimes it is not worth the effort of fighting the conventions. Unless there is an absolute need to have the Action names in underscore-inclusive CamelCase naming, it seems silly to alter the RouteHandler and other objects when he already has a licensed tool that will help him avoid the issue entirely. "Is the juice worth the squeeze" is what one of my sys admin guys always says... what does he gain from editing the route handler other than a lot more code to manage? Thanks for the comment, your answer is still good. – one.beat.consumer Dec 21 '11 at 19:22