<img height="1" width="1" style="display:none;" alt="" src="https://ct.pinterest.com/v3/?tid=2618634321756&amp;noscript=1">

Turn your ASP.NET MVC app into a Single Page Application with one classy Sammy.js route

Jul 1, 2013 Code Jon Fazzaro

I'd like to be serious for a moment and talk about pizzazz.

The traditional line of business web applications that you and I have been building with ASP.NET just don't have it. And if we want to do what we do for much longer while also not making everybody sad, we're going to have to venture outside our comfort zone of submitting the HTML form, churning server-side for a spell, and loading the next pedestrian document.

One avenue to baking more pep into a web app is to build it as a single-page application. In a single-page application (or SPA, if you're into the whole brevity thing), we only ever load one full HTML document into the browser. That document, in turn, pulls in its content and otherwise does its business exclusively using client-script-based out-of-band calls back to the server. Of course, building your app as a SPA is no guarantee of tyrannosaurus rex playing a drum solo in front of a nuclear explosion. But it will open doors in terms of sharing objects across pages, slick transitions from one activity to the next, and a richer, more excellent sort of user experience in general.

You may have even noticed that ASP.NET devs get some single-page love in Visual Studio 2012.2, with a Knockout-based SPA template (and plenty of variations thereupon available for download). But templates are only good for new projects, and not much comfort to those of us tilling browner fields.

Well, chins up, nerds-with-established-ASP.NET-MVC-applications-who-are-looking-to-take-them-single-page-but-don't-really-know-where-the-front-door-would-be-on-such-a-thing. I have here a way for you to convert your existing app over to a SPA. Like, in an afternoon.

Ladies.

Step One: The Layout Page

For those of us who are accustomed to working with ASP.NET Master/Layout Pages, the shift to single-page-appitude will require just a skosh of standing on your head. Metaphorically, for the most part.

Used on-label, a Layout page is a template that ASP.NET uses to render a single HTML document by merging it with the markup in a View. In a SPA, our Layout becomes a shell page into which we load the view's markup via AJAX calls. The Layout page's lifespan is now the user's session, rather than just the lifespan of the page, so be careful what you scope at this level; the safety net of a page refresh that cleans up after us is gone, and browser memory leaks are distinctly counter-excellent in nature.

So here's how to set up our Layout for single-page goodness. First, we'll use NuGet to add Sammy.js to our project. Sammy is a jQuery-dependent JavaScript framework which (among other things) lets us add URL routing to the client side of our app faster than you can say Bojangles. This is one of those standing-on-your-head bits I mentioned earlier–routing happens on the client side, too, now that we've gone SPA.

Once we've brought in Sammy, we'll add it to a bundle (along with jQuery) in BundleConfig.RegisterBundles:

bundles.Add(new ScriptBundle("~/js/mvc-spa").Include(
    "~/Scripts/jquery-{version}.js",
    "~/Scripts/sammy-{version}.js"));

Then we'll render the bundle at the bottom of our Layout page:

            @Scripts.Render("~/js/mvc-spa")
    </body>
</html>

We'll also need to designate the area of the Layout where the view content will be loaded by finding the element we are currently calling RenderBody and giving it a unique id. For this example, let's go with "page".

<!-- ... -->
</header>
<section id="page">
	@RenderBody()
</section>
<footer>
<!-- ... -->    

Step Two: Go Partial

Next, we need to go through our views, and, with the exception of the Home/Index view, convert each to a Partial View.

Unlike vanilla MVC Views, Partial Views are not merged with the Layout page to render as complete HTML documents. When you make a controller request that returns a Partial View, the response is merely an HTML fragment–precisely what we're looking for in a SPA page.

To convert a View to a Partial, simply change the View call to a call to PartialView in its corresponding Controller Action:

public class SpaFtwController : Controller {
    public ActionResult Index() {
        return PartialView();
    }
}

As I may have mentioned above, do not convert your default view to a partial. This view is the "single page" in "single-page application", and it still needs to render as a complete HTML document, just the once. However, we should move this Index view's content into a separate Partial View, and replace it with some sort of splash content that will only display while our app is still loading. More on that in the next step.

Step Three: One Route to rule them all

Sammy Davis, Jr.

Speaking of pizzazz, it's time for Mr. Show Business himself.

The typical usage of Sammy is to describe multiple hash-based routes, along with the js to be executed when the user navigates to them. What we're going to do in this case, though, is create a solitary Sammy route that acts as a pass-through to the existing MVC routing already in place on the server-side.

For example, a request to /#/spaftw would load the content from /SpaFTW into our app. Using fragment identifiers for routing is a classic SPA technique; it changes the browser's URL without causing a whole page request, while also allowing the back button to still work as expected, and enables deep-linking into the app.

Here is a module that contains the code to do just this. We'll add it to our project in a file named Routing.js.

var Routing = function (appRoot, contentSelector, defaultRoute) {
function getUrlFromHash(hash) { var url = hash.replace('#/', ''); if (url === appRoot) url = defaultRoute; return url; }
return { init: function () { Sammy(contentSelector, function () { this.get(/\#\/(.*)/, function (context) { var url = getUrlFromHash(context.path); context.load(url).swap(); }); }).run('#/'); } }; }

Let's unpack that. See the this.get call about two-thirds of the way down? That's Sammy's bread and butter. We're using a regular expression to describe a route URL that captures any href beginning with '/#'. When a request is made for a URL matching this expression, Sammy intercepts it and runs the function that we've passed as get's second parameter. This function grabs this path, strips out the hash, makes an AJAX request for the Partial View for that URL, and swaps it into the element designated by contentSelector. Which, of course, will be our content area in the Layout page.

Finally, we need to wire this into our Layout page. Add Routing.js to our bundle, after Sammy.js:

bundles.Add(new ScriptBundle("~/js/mvc-spa").Include(
        "~/Scripts/jquery-{version}.js",
        "~/Scripts/sammy-{version}.js",
        "~/Scripts/Routing.js"));

Then, add the following script block to the Layout page, after rendering the bundle:

@Scripts.Render("~/js/mvc-spa")
<script>
$(function () {
    var routing = new Routing('@Url.Content("~/")', '#page', 'welcome');
    routing.init();
});
</script>

This instantiates a Routing instance with the root URL for the application (this becomes important if our app is deployed anywhere other than '/'), the selector of our content area ('#page'), and the route to default to when the user navigates to the root of the app. This should be the Partial View that inherited the original content from our Home/Index view, in step two.

Step Four: Stay Classy

That's it, baby! We get to keep our existing MVC views and routes, and bask in the warm familiar glow of ASP.NET development, while our users enjoy the dinosaur percussion, atomic ordnance, and yes, pizzazz that they expect from a single-page app.

Posted in:

Topics: Code SPA ASP.NET

Comments