Transform your site into a single-page app

angular.module(‘RomanSPA’, [‘ngRoute’])
.config([‘$routeProvider’, function ($routeProvider, $locationProvider) {
$.ajax({
url: ‘/api/RouteApi/AllRoutes’,
success: function (data) {
for (var i = 0; i data.length; i++) {
$routeProvider.when(data[i].RoutePattern, {
controller: data[i].controller,
templateUrl: data[i].templateUrl
});
}
$routeProvider.otherwise({ redirectTo: ‘/’ });
},
fail: function (data) {
}
});
}])
.value(‘breeze’, window.breeze);
 
Core functions
Finally, in our GenericController, we put in two core   functions – one to manually apply our specified template, and one to retrieve the model using the factory on the action attribute. angular.module(‘RomanSPA’)
.controller(‘GenericController’, [‘$scope’, ‘$route’, function ($scope, $route){
$scope.pageUrl = ‘/’;
if ($route.current !== undefined) { $scope.pageUrl = $route.current.templateUrl; }
// Retrieve our view – be it from server side, or custom template location
$.get({
url: $scope.pageUrl.toString(),
beforeSend: function(xhr) { xhr.setRequestHeader(‘X-RomanViewRequest’, ‘true’); },
success: applyView
});
// Retrieve our model using the modelfactory for our current URL path
$.get({
url: $scope.pageUrl.toString(),
beforeSend: function (xhr) { xhr.setRequestHeader(‘X-RomanModelRequest’, ‘true’); },
success: applyModel
});
function applyView(data) {
$scope.$apply(function () { angular.element(‘body’).html($compile(data)($scope)); });
}
function applyModel(data) { $scope.model = data;   }
}
}]);
 
Finally, we’re at a point where:
We have gathered up all the routes on the server that we want AngularJS to take advantage of. I’m glad you asked that, because that’s what we’re going to build in this tutorial. NodeJS doesn’t yet have a JavaScript-based CMS. If you don’t like that, the design patterns will easily translate into an MVC framework of your choice – Zend, CodeIgniter, Ruby, Django and so on. For more information on advanced MVC routing, route constraints and filtering, I highly recommend checking out Scott Allen’s courses on Pluralsight on the ASP.NET MVC   stack. This   means we can take advantage of HTML5 offline caching to create a great user experience on   the app   side. This gives our routing table the option to specify any explicit overrides we want, or to go with a generic, default behaviour (i.e. Child actions that we may want AngularJS to have access to, but shouldn’t be in the routing table (such as navigation, footer and other partial views), can be marked out with [RomanPartial]. The ASP.NET MVC RomanSPA framework! The key tenet in this architecture is working out how to share the model and the view between the two patterns. NuGet (the package manager for Visual Studio) makes this simple to add into our project. This is to be shared with the JS side of the site, with metadata, using a .NET feature called Reflection to gather these controllers up. We’ll leave the Index and About views blank. You can use this foundation to build out many more complex parts to your site. A request to /RomanDemo/Index will give us a full page, but when AngularJS requests it, it’ll either provide   a partial view or a JSON object, depending on the metadata we have supplied. An MVC app that sits on top, powered by AngularJS, with a matching route table. Architecture is everything, I will cover a simple blog site, which you can then go on to adjust according to your project. On these controllers, we’ll be marking out our actions with metadata to identify the corresponding JS view, model and controller, as shown in the diagram on the right. Firstly, we   update our controllers to inherit from our generic RomanController. Warning: here be dragons! JavaScript solves the majority of the cross-platform issues, but then you’re not building a website any more. BreezeJS depends on Q, a tool for making and   managing asynchronous promises in JavaScript. (vomitorium, sickle-scrapers and sulphur-tasting water all optional). These   will be exported to Angular’s routing library when our app boots up. What about JavaScript? Sliced bread The Kingsmill Bread desktop and mobile websites share content using the Umbraco CMS, with user-agent specific templatesThis model works great if you have specialists in iOS, Java and Silverlight (yes, Silverlight – the joys), but falls flat when it comes to scalability and manageability. custom JSON model, custom template URL or custom AngularJS controller – are marked with [RomanAction]. The traditional pattern for developing a digital strategy is becoming a lot more streamlined

What’s that I hear? We’ll then use this as a way to enable users to navigate a blog, whilst most of the data is held in HTML5 offline storage for us. Actions we want to specify metadata for (or export to Angular routing) – i.e. We’ll be marking our controllers on the server. Wrapping up
We now have:
A basic MVC site, with routes for basic pages and   a   blog. You can also extend the view engine to automatically store templates and views offline. It contains everything on the left-hand side of the diagram below – a basic model, a controller and a couple of views, just to get everything started. By doing this, you’re allowing your website visitor to treat your website more like an app – which they can use even while they’re without WiFi or phone signal. (I had someone suggest this solution to me at the Scotch on the Rocks conference this year, and my reply was pretty much exactly that.)
So how do we design a website that’s accessible, and then ‘upgrade’ it to look, feel and behave like an SPA? AngularJS making AJAX requests using jQuery, which server-side MVC then interprets to return JSON model or HTML view appropriately. We’ll be overriding some parts of this later, but for now it’s a generic marker for us   to identify the routes and URLs we want Angular to have access to. The result of this mashup? public class RomanDemoController : RomanController {
private RomanSPA.Demo.Models.RomanSPAStarterKitEntities _context;
public RomanDemoController() : base() {
// Yes, I’m not using dependency injection for my DB context, cause this is a demo 😉
if (_context == null) _context = new Models.RomanSPAStarterKitEntities();
}
[RomanAction]
public ActionResult Index() { return View(new IndexModel()); }
[RomanAction(Factory=typeof(BlogListFactory), ControllerName=”BlogController”, ViewPath=”/assets/blog-list.html”)]
public ActionResult Blog() { return View(_context.BlogPosts); }
[RomanAction(ControllerName=”BlogPostController”)]
public ActionResult BlogPost(string slug) {
if (_context.BlogPosts.Any(p = MakeTitleUrlFriendly(p.Title) == slug)) {
return View(_context.BlogPosts.First(p = MakeTitleUrlFriendly(p.Title) == slug));
} else {
return HttpNotFound();
}
}
[RomanAction]
public ActionResult About() { return View(); }
private string MakeTitleUrlFriendly(string title) {
return title.ToLower().Replace(“ “, “-”);
}
}

 
This attribute is an action filter, and has three parameters, all optional: model factory, controller name and view name. This approach is not foolproof. As a freelancer, I’ve experienced clients asking for a website, then a mobile website, then an app (I built Kingsmill Bread’s mobile and desktop/tablet websites on the MIT-licensed, .NET-based Umbraco CMS). However, gaps still persist between languages and frameworks – especially between building ‘apps’ for mobile and tablet platforms, and building traditional, accessible and progressive websites. Both sides Architecture of MVC and MVVM design patterns, and how we’re going to try and bring them together across the client and the serverIt’s a breeze
We’re now going to install AngularJS and BreezeJS into the project. This article first appeared in net magazine issue 257. Next, we need to mark the actions we want to be   available in AngularJS, with a custom attribute: RomanActionAttribute. Platforms like PhoneGap enable us to create apps in HTML5 and JavaScript, which can then be adapted and cross-compiled for various mobile platforms (iOS, Android, Blackberry and Windows Phone). Whereas a controller would have   an action to handle a particular event (submission of data), a viewmodel would have a specific event defined for handling UI updates and any model binding modifications (people familiar with C# will   know this as the INotifyPropertyChanged pattern). Factor in the average cost of bespoke app development (approximately £22,000) and average return (£400 a year, if you’re lucky), and suddenly you’re pouring money into an awfully large technological black hole. blog list and blog post, as these contain more than just vanilla data). If you have filters or constraints on the routes or controller actions, whatever is provided by the JS routeProvider as Angular routes may not actually end up being rendered by the server. It’s got a couple of controllers in it, a model created with Entity Framework to show an example of server-side model population, and some views with a responsive CSS3 template built with Bootstrap. The fundamental part of this exercise is that we should be able to share views and expose server data for complex transformations in our app. Piece by Piece
Now we’ve provided Angular with our routing table, we need to start gluing it together. The only difference is when and how they are populated with model data – be it by controller on the server, or   controller on the client. We’re almost set. To do this, we need to identify which controllers (and corresponding routes) are to be fed from the server into Angular. We have specified a generic controller, from which our specific controllers can inherit. To create a very simple SPA experience from our existing site foundation, we need to share the routing table in MVC on the server with AngularJS. The ‘traditional’ route of building this would be to do the site first, with a responsive (or specially-designed) CSS layout, then build a separate app that plugs into an underlying content store. We’ll start off with creating a simple website (which can be downloaded at netm.ag/demo-257) using ASP.NET MVC 4. When designing your site in this fashion, 99.9 per cent of the time your views will be identical both on the server and client sides. Not so fast.

Updated: 06.12.2014 — 23:43