ASP.Net MVC: Single Page Application (SPA) from Scratch; A How To Step by Step
During my presentation to the Denver Visual Studio Users Group on Asp.net SPA on March 25, 2013, I created an example SPA application from scratch using Visual Studio 2012 with Asp.Net and MVC. The intent was to provide an understanding of the tools and technologies needed to build an Asp.net spa. The following are the steps I used to create the basic SPA entitled UserGroupSPA in VS2012.
The following technologies and templates were used or provided inspiration in creating the example:
- Visual Studio 2012
- Nuget
- Breeze.WebApi
- Durandal
- Durandal.Router
- Durandal.Transitions
- jQuery
- knockoutjs
- Microsoft.AspNet.Mvc
- toastr
- Twitter.Bootstrap
- WebActivator
- HotTowel SPA template – Uses design components from this template which gives it a similar look and feel.
- Single Page Application template/info
- Responsive Design bookmarklet for Chrome
Pre Setup
- Using Microsoft Web Platform Installer 4.5, install Asp.Net and Web Tools 2012.2 Update.
- Note that as of this writing that update will be included in the future Visual Studio 2012 Update #2.
- In the Microsoft Web Platform installer it is listed as Asp.net and Web Tools 2012.2
Visual Studio Project
- Create new project
- ASP.Net MVC 4 Web Application named UserGroupSPA
- Choose Basic Asp.net MVC4 Project Template with Razor view engine
- ASP.Net MVC 4 Web Application named UserGroupSPA
- Package Manager Console (NUGET)
- Get-Package –Update
- Update-Package jQuery
- … (update all packages which can be updated as reported)
- Update-Package knockoutjs
- Install-Package Breeze.Webapi
- Get-Package –Update
Create Viewable Web Page in MVC at Root
- In the Views Folder
- Delete Shared folder and all of its contents
- Delete the view _View Start.cshtml
- Create DVSUG directory in the Views folde
- Add the view index.cshtml into the DVSUG directory
-
This is the code for index.cshtml @using System.Web @using System.Web.Optimization <!DOCTYPE html> <html> <head> <title>Denver Visual Studio Users Group</title> @Styles.Render("~/Content/css") </head> <body> <h1>Denver Visual Studio Users Group Main</h1> </body> </html>
- Global.asax
- Comment out the line RegisterBundles such as:
WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); //BundleConfig.RegisterBundles(BundleTable.Bundles);
- Comment out the line RegisterBundles such as:
- App_Start folder
- Add C# class DVSUGConfig
using System.Web.Optimization; [assembly: WebActivator.PostApplicationStartMethod( typeof(UserGroupSPA.App_Start.DVSUGConfig), "PreStart")] namespace UserGroupSPA.App_Start { public class DVSUGConfig { public static void PreStart() { BundleConfig.RegisterBundles(BundleTable.Bundles); } } }
- Add C# class DVSUGRouteConfig
using System.Web.Mvc; [assembly: WebActivator.PreApplicationStartMethod( typeof(UserGroupSPA.App_Start.DVSUGRouteConfig), "RegisterDVSUGPreStart", Order = 2)] namespace UserGroupSPA.App_Start { ///<summary> /// Inserts the DVSUGRouteConfig SPA sample view controller to the front of all MVC routes /// so that the DVSUGRouteConfig SPA sample becomes the default page. ///</summary> ///<remarks> /// This class is discovered and run during startup /// http://blogs.msdn.com/b/davidebb/archive/2010/10/11/light-up-your-nupacks-with-startup-code-and-webactivator.aspx ///</remarks> public static class DVSUGRouteConfig { public static void RegisterDVSUGPreStart() { // Preempt standard default MVC page routing to go to DVSUG System.Web.Routing.RouteTable.Routes.MapRoute( name: "DVSUGMvc", url: "{controller}/{action}/{id}", defaults: new { controller = "DVSUG", action = "Index", id = UrlParameter.Optional } ); } } }
- Add C# class DVSUGConfig
- Controllers directory
- Add controller DVSUGController (Empty MVC Controller)
- Run the application in IE (not chrome) in debug (F5)
Adding Toastr For Notifications
- Package Manager Console
- install-package toastr
- App_Start
- BundleConfig.CS change to:
using System.Web; using System.Web.Optimization; namespace UserGroupSPA { public class BundleConfig { // For more information on Bundling, visit http://go.microsoft.com/fwlink/?LinkId=254725 public static void RegisterBundles(BundleCollection bundles) { bundles.IgnoreList.Ignore("*.intellisense.js"); bundles.IgnoreList.Ignore("*-vsdoc.js"); bundles.Add( new ScriptBundle("~/scripts/UGScripts") .Include("~/Scripts/jquery-{version}.js") .Include("~/scripts/knockout-{version}.debug.js") .Include("~/scripts/toastr.js") .Include("~/scripts/Q.js") .Include("~/scripts/breeze.debug.js") ); bundles.Add( new StyleBundle("~/Content/css") .Include("~/Content/bootstrap.css") .Include("~/Content/toastr.css") ); } } }
- BundleConfig.CS change to:
- Views\DVSUG\index.cshtml
@using System.Web @using System.Web.Optimization <!DOCTYPE html> <html> <head> <title>Denver Visual Studio Users Group</title> @Styles.Render("~/Content/css") </head> <body onload="toastr.info('Loading Main Body')"> @Scripts.Render("~/scripts/UGScripts") <h1>Denver Visual Studio Users Group Main</h1> </body> </html>
- Run in IE again and verify the toast event “Loading Main Body” comes up. such as:
Install Durandal
- Package Manager Console
- install-package Durandal
- install-package Durandal.Router
- install-package Durandal.Transitions
- install-package Twitter.Bootstrap
- App_Start\BundleConfig.CS
using System.Web; using System.Web.Optimization; namespace UserGroupSPA { public class BundleConfig { // For more information on Bundling, visit http://go.microsoft.com/fwlink/?LinkId=254725 public static void RegisterBundles(BundleCollection bundles) { bundles.IgnoreList.Ignore("*.intellisense.js"); bundles.IgnoreList.Ignore("*-vsdoc.js"); bundles.Add( new ScriptBundle("~/scripts/UGScripts") .Include("~/Scripts/jquery-{version}.js") .Include("~/scripts/knockout-{version}.debug.js") .Include("~/scripts/sammy-{version}.js") .Include("~/scripts/toastr.js") .Include("~/scripts/Q.js") .Include("~/scripts/breeze.debug.js") .Include("~/scripts/bootstrap.js") .Include("~/scripts/moment.js") ); bundles.Add( new StyleBundle("~/Content/css") .Include("~/Content/ie10mobile.css") .Include("~/Content/bootstrap.css") .Include("~/Content/bootstrap-responsive.css") .Include("~/Content/durandal.css") .Include("~/Content/toastr.css") .Include("~/Content/app.css") ); } } }
Work With Views as a SPA using Durandal
- UserGroupSPA\App directory (created by Durandal)
- Add the folders
- viewmodels
- views
- Add the javascript file main.js
require.config({ paths: { "text": "durandal/amd/text" } }); define(['durandal/app', 'durandal/viewLocator', 'durandal/system', 'durandal/plugins/router'], function (app, viewLocator, system, router) { system.debug(true); // Outputs to the console the process, turn off for production app.start().then(function () { toastr.options.positionClass = 'toast-bottom-right'; toastr.options.backgroundpositionClass = 'toast-bottom-right'; router.handleInvalidRoute = function (route, params) { toastr.info('No Route Found: ' + route); }; router.useConvention(); viewLocator.useConvention(); // Our three views router.mapNav('home'); router.mapNav('speakers'); router.mapNav('events'); app.adaptToDevice(); // Touch devices to adapt to app.setRoot('viewmodels/shell', 'entrance'); //Show the SPA at its root }); });
- Add the folders
- Views\DVSUG\index.cshtml changed to
@using System.Web @using System.Web.Optimization <!DOCTYPE html> <html> <head> <title>Denver Visual Studio Users Group</title> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" /> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-status-bar-style" content="black" /> <meta name="format-detection" content="telephone=no"/> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> @Styles.Render("~/Content/css") </head> <body onload="toastr.info('Loading Main Body')"> <div id="applicationHost"> </div> @Scripts.Render("~/scripts/UGScripts") @if(HttpContext.Current.IsDebuggingEnabled) { <script type="text/javascript" src="~/App/durandal/amd/require.js" data-main="@Url.Content("~/App/main")"></script> } else { <script type="text/javascript" src="~/App/main-built.js"></script> } </body> </html>
- App\viewmodels
- add event.js
define(function () { var vm = { activate: activate, title: 'Events View' }; return vm; function activate() { toastr.info('Events View Activated'); return true; } });
- add home.js
define(function () { var vm = { activate: activate, title: 'Home View' }; return vm; function activate() { toastr.info('Home View Activated'); return true; } });
- add shell.js
define(['durandal/system', 'durandal/plugins/router'], function (system, router) { var shell = { activate: activate, router: router }; return shell; function activate() { return boot(); } function boot() { toastr.info('DVSUG Boot Loaded!'); return router.activate('home'); } });
- add speakers.js
define(function () { var vm = { activate: activate, title: 'Speakers View' }; return vm; function activate() { toastr.info('Speakers View Activated'); return true; } });
- add event.js
- App\views
- add events.html
<section> <h2 class="page-title" data-bind="text: title"></h2> </section>
- add footer.html
<nav class="navbar navbar-fixed-bottom"> <div class="navbar-inner navbar-content-center"> <span class="pull-left"><a href="http://omegacoder.com" target="_blank">OmegaCoder.com </a></span> <span class="pull-right"><a href="http://www.denvervisualstudio.net/" target="_blank">Denver Visual Studio User Group</a></span> </div> </nav>
- add home.html
<section> <table> <tr><td><h2 class="page-title" data-bind="text: title"></h2></td></tr> <tr><td><h3>Next: Asp.Net SPA</h3></td></tr> <tr><td><p>Come and find out all about it in this introduction to building Single Page Applications (SPA) which allow for rich client side interactions using JavaScript, HTML 5, and CSS in the ASP.NET world.</p> <p>This introduction will familiarize you, the developer, to the templates available as well as an overview of the components which make up SPAs and how to put them together. This talk is a general how-to on being able to come up to speed on the technology and will focus on actually creating a page and cover the interactions of ASP.Net MVC with basic data transport.</p> <p>You will see how ASP.NET Single-Page Applications (SPA) enable cutting-edge web developers to deliver rich, engaging web applications that surface information and interactivity in fresh and exciting new ways.</p> </td></tr> </table> </section>
- add nav.html
<nav class="navbar navbar-fixed-top"> <div class="navbar-inner"> <a class="brand" href="/"> <span class="title">Denver Visual Studio Users Group</span> </a> <div class="btn-group" data-bind="foreach: router.visibleRoutes"> <a data-bind="css: { active: isActive }, attr: { href: hash }, text: name" class="btn btn-info" href="#"></a> </div> </div> </nav>
- add shell.html
<div> <header> <!--ko compose: {view: 'nav'} --><!--/ko--> </header> <section id="content" class="main container-fluid"> <!--ko compose: {model: router.activeItem, afterCompose: router.afterCompose, transition: 'entrance'} --> <!--/ko--> </section> <footer> <!--ko compose: {view: 'footer'} --><!--/ko--> </footer> </div>
- add speakers.html
<section> <table> <tr><td><h2 class="page-title" data-bind="text: title"></h2></td></tr> <tr><td><h3>William Wegerson</h3></td></tr> <tr><td> <p>William Wegerson is an Architect Developer who has been working here in Denver for over 20 years.</p> <p>He has presented to the group before on such topics as Silverlight and Visual Studio releases (VS 2010 and 2012) as well as teaching labs to the members.</p> <p>He has been awarded Microsoft MVP status since 2009 as a result of his community work such as this presentation as well as his blog and as an active resource on the MSDN forums and StackOverflow, answering questions of all types.</p> </td></tr> </table> </section>
- add events.html