ASP.NET MVC JavaScript Routing

Have you ever done this sort of thing in your ASP.NET MVC view?

image

The weird thing about this isn’t the alert function, it’s the code block containing the Url formation using the ASP.NET MVC UrlHelper. The terrible thing about this experience is the obvious lack of IntelliSense and this ugly inline JavaScript code. Inline JavaScript isn’t portable to other pages beyond the current page of execution. It is generally considered bad practice to use inline JavaScript in your public-facing pages. How ludicrous would it be to copy and paste the entire jQuery code base into your pages…? Not something you’d ever consider doing.

The problem is that your URLs have to be generated by ASP.NET at runtime and really can’t be copied to your JavaScript code without some trickery.

How about this?

image

Does the hard-coded URL bother you? It really bothers me. The typical solution to this whole routing in JavaScript issue is to just hard-code your URLs into your JavaScript files and call it done. But what if your URLs change? You have to now go an track down the places in JavaScript and manually replace them. What if you get the pattern wrong? Do you have tests around it? This isn’t something you should have to worry about.

 

The Solution To Our Problems

The solution is to port routing over to JavaScript. Does that sound daunting to you? It’s actually not very hard, but I decided to create my own generator that will do all the work for you.

What I have created is a very basic port of the route formation feature of ASP.NET routing. It will generate the formatted URLs based on your routing patterns. Here’s how you’d do this:

image

Does that feel familiar? It looks a lot like something you’d do inside of your ASP.NET MVC views… but this is inside of a JavaScript file… just a plain ol’ .js file.  Your first question might be why do you have to have that “.toUrl()” thing. The reason is that I wanted to make POST and GET requests dead simple. Here’s how you’d do a POST request (and the same would work with a GET request):

image 

The first parameter is extra data passed to the post request and the second parameter is a function that handles the success of the POST request. If you’re familiar with jQuery’s Ajax goodness, you’ll know how to use it. (if not, check out http://api.jquery.com/jQuery.Post/ and the parameters are essentially the same).

But we still haven’t gotten rid of the magic strings. We still have controller names and action names represented as strings. This is going to blow your mind…

image

If you’ve seen T4MVC, this will look familiar. We’re essentially doing the same sort of thing with my JavaScript router, but we’re porting the concept to JavaScript. The good news is that parameters to the controllers are directly reflected in the action function, just like T4MVC.

And the even better news… IntlliSense is easily transferred to the JavaScript version if you’re using Visual Studio as your JavaScript editor.

image

image

The additional data parameter gives you the ability to pass extra routing data to the URL formatter.

 

About the Magic

You may be wondering how this all work.

It’s actually quite simple. I’ve built a simple jQuery pluggin (called routeManager) that hangs off the main jQuery namespace and routes all the URLs. Every time your solution builds, a routing file will be generated with this pluggin, all your route and controller definitions along with your documentation. Then by the power of Visual Studio, you get some really slick IntelliSense that is hard to live without.

But there are a few steps you have to take before this whole thing is going to work.

First and foremost, you need a reference to the JsRouting.Core.dll to your projects containing controllers or routes.

Second, you have to specify your routes in a bit of a non-standard way. See, we can’t just pull routes out of your App_Start in your Global.asax. We force you to build a route source like this:

image

The way we determine the routes is by pulling in all RouteSources and generating routes based upon the mapped routes.

There are various reasons why we can’t use RouteCollection (different post for another day)… but in this case, you get the same route mapping experience. Converting the RouteSource to a RouteCollection is trivial (there’s an extension method for that).

Next thing you have to do is generate a documentation XML file. This is done by going to the project settings, going to the build tab and clicking the checkbox. (this isn’t required, but nice to have).

The final thing you need to do is hook up the generation mechanism. Pop open your project file and look for the AfterBuild step. Now change the build step task to look like this:

image

The “PathToOutputExe” is the path to the JsRouting.Output.exe file. This will change based on where you put the EXE. The “PathToOutputJs” is a path to the output JavaScript file. The “DicrectoryOfAssemblies” is a path to the directory containing controller and routing DLLs. The JsRouting.Output.exe executable pulls in all these assemblies and scans them for controllers and route sources.

 

Now that wasn’t too bad, was it :)

 

The State of the Project

This is definitely not complete… I have a lot of plans for this little project of mine. For starters, I need to look at the generation mechanism. Either I will be creating a utility that will do the project file manipulation or I will go a different direction. I’d like some feedback on this if you feel partial either way.

Another thing I don’t support currently is areas. While this wouldn’t be too hard to support, I just don’t use areas and I wanted something up quickly (this is, after all, for a current project of mine). I’ll be adding support shortly.

There are a few things that I haven’t covered in this post that I will most certainly be covering in another post, such as routing constraints and how these will be translated to JavaScript.

I decided to open source this whole thing, since it’s a nice little utility I think others should really be using.

Currently we’re using ASP.NET MVC 2, but it should work with MVC 3 as well. I’ll upgrade it as soon as MVC 3 is released. Along those same lines, I’m investigating how this could be put on the NuGet feed.

Show me the Bits!

OK, OK! The code is posted on my GitHub account. Go nuts. Tell me what you think. Tell me what you want. Tell me that you hate it. All feedback is welcome!

https://github.com/zowens/ASP.NET-MVC-JavaScript-Routing


kick it on DotNetKicks.com

13 Comments

  • Nice post & work Zak, I have been toying with a few ideas on the same subject. I love the way people battle hard to avoid hard coded strings server side however any old rubbish goes into the client side of things!

    I currently have a json settings view that outputs any info I need on the client, this includes all the urls I am interested in

    e.g

    myapp.settings.urls = { route1 : '/bla'....};

    I then use these in the following manner

    var urls = myapp.settings.urls;
    $.post(urls.route1, data, fn);

    However this solution requires manual cruft to setup the script settings view. I was thinking t4 could generate this but your method sounds nifty.

    Could you also generate the models that the routes accept as json structures ready for use in js? It would make creating them client side much easier and safer. Actually you could take it a step further and create a partial client mvc framework with namespaced controller classes, models etc (sorry getting ahead of myself!)

    The intellisense power does not really interest me. I would love to see a move away from including doc ready blocks in views & partials. I always keep them a js free zone, as the app grows it becomes a real pain to maintain.

  • @asyahub

    Thanks for the feedback.

    I think the reason people focus so hard on removing magic strings is that things can change easily and often do. URLs especially change as there is a renewed focus on SEO.

    I've also experimented with the technique of just pushing URLs down to the client. However, I find this to be a huge pain when you have dynamic content, like /post/123.

    I've been thinking about T4, but the way I go about doing the generation might not lend itself to T4. The way this whole thing works is to pull in DLLs using StructureMap. T4MVC REQUIRES the use of Visual Studio to generate the goodness. I really don't like that... I want to have the ability to do this outside of Visual Studio.

    Generating model types could be done manually... or it could even be an addition (there's an IJavaScriptAddition that will output any JavaScript you give it). I guess you could use this ability to generate models, but I'm personally not going to use it.

    IntelliSense isn't required... its nice to have sometimes... plus it's taken out whenever you put the JavaScript through Google Closure Compiler or some JavaScript minifier. (I didn't want to put in minification... people should do this on their own terms).

    What do you mean by it being a pain to maintain??? All this stuff is generated...

  • I meant that js shoved inside a view is not reusable and becomes a headache to maintain when you get into a project with lots of views and partials. I guess to harness the power of the dynamic urls you need to use in page js. I like the separation of having js in modular namespaced .js files.

    I use the jquery ajaxStart global event to add common post data to a request. For example an I'd of the record your viewing. In most cases (well all so far!) this negates the need to have the url's with the correct params generated by the mvc routes.

    Regarding the generation of models - I take it you normally just serialize the form? I have found it handy to have the inputModels that mvc expects on the client side, especially when dealing with grids when you can be without a html form for each row.

  • Very cool. Is this on GitHub, Codeplex etc?

  • @asyahub

    Of course it isn't. For dynamic URLs, you still have to store some sort of ID, as you eluded to. But this is usually in the HTML or in the URL (possibly the query string, hash, etc). But you can easily get to it through a .js file.

    This is true... you could go about it that way. But if you have requests that don't require the ID, then this isn't really the case. Or perhaps you have a different ID (maybe of a child entity), you'd have to override this. For my personal use, this wouldn't work out so well.

    Yea, I do serialize forms when I have forms but this isn't globally the case.

    @Paul

    Check out https://github.com/zowens/ASP.NET-MVC-JavaScript-Routing

  • Cool article! One question: which VS theme are You using ?

  • @Geoff

    I'm using a theme I created a few years ago. I can give it to you if you shoot me an email.

  • Is there a way to write this so it doesn't use the .net 4.0? At first glance, I see you're using Tuple but is that really necessary?

  • @steve

    Certainly. I'm just not going to maintain it. The goal is to have this integrated with MVC3 when it is released.

  • Cool! We definitely can use this one, we have a span to hold the routes/action methods then retrieve this via jQuery, it's particularly helpful since we don't have to worry about how the routes are configured(eg add an extension for IIS6).

    Could you include license for this though?

    Thanks

  • Your special method of getting the Routes with Route Name isn't really absolutely necessary. If you use reflection to get the "RouteCollection._namedMap" private variable, then you have direct access to the Routes with Names directly from "RouteTable.Routes".
    Here's the code to get a reference to "_namedMap":
    var namedMapField = typeof(RouteCollection).GetField("_namedMap", BindingFlags.Instance | BindingFlags.NonPublic);
    var namedMap = namedMapField.GetValue(RouteTable.Routes) as Dictionary;
    foreach (var d in namedMap)
    {
    var routeName = d.Key;
    var route = d.Value;
    }

  • @bonskijr

    I understand... that's what I want to prevent. The way I see it, routing should be accessible where it is needed.

    @Chris Pietschmann

    I understand... it's possible to do that. But I have a real issue with getting to information that should be public from the beginning. Getting to the named routes via reflection doesn't really send the right message. The ASP.NET people should have exposed this when they created the class. Plus, I'm considering my own abstraction over routing anyways that is much cleaner than a list with an extension method. I guess we could offer this option, but having a custom way of routing isn't too much of a stretch in my opinion.

  • Zack,

    I have been using this for a while on MVC2. When i went to do an upgrade to MVC3, the routing.js file it generates has NONE of my routes in it. Just the default logic.

    Any Ideas?

Comments have been disabled for this content.