Bootstrapping AngularJS Applications with Server-Side Data from ASP.NET MVC & Razor
With server-side technologies like ASP.NET Web API and client-side frameworks like AngularJS, single page applications on the .NET web stack have become more enjoyable to write than ever. Because a lot of the application logic has been moved from the back-end to the browser, thus resulting in rich client interfaces, single page apps require a different application structure than traditional websites.
Usually, the initial HTTP request to a single page app returns the site's HTML which references the required static assets (like CSS files, images, and JavaScript libraries), but doesn't contain the application data itself. That data is later retrieved asynchronously by making AJAX calls to some back-end API.
In some cases, though, you might not want to wait for the AJAX request to complete. After all, awaiting an additional HTTP request to display its result in the UI can lead to a noticeable visual delay, especially when latency is high or the server is busy. It would be nice to have the data available as soon as the initial HTML response is returned. In the following, I want to highlight how to create an Angular service that bootstraps the application with data defined in an ASP.NET MVC back-end.
A word of caution: The method I'm about to use is probably not a good fit for large amounts of data. Since the JavaScript data is inlined into the HTML response, it's sent over the wire every single time you request that page. Also, if the data is specific to the authenticated user, the response can't be cached and delivered to different users anymore. Please keep that in mind when considering to bootstrap your Angular Apps with .NET data this way.
[Update] This post is about embedding the server-side data into the HTML response. If you'd rather load the JSON data asynchronously from a dedicated endpoint, make sure to check out Asynchronously Bootstrapping AngularJS Applications with Server-Side Data.
#Serializing the Server-Side C# Data
Let's assume we have some data defined in our ASP.NET MVC back-end. Since I'm a huge fan of Tolkien's writing and in dire need of some exemplary data, I'll borrow from The Hobbit for demo purposes here:
object companionship = new
{
Dwarves = new[]
{
"Fili", "Kili",
"Dori", "Nori", "Ori", "Oin", "Gloin",
"Balin", "Dwalin",
"Bifur", "Bofur", "Bombur", "Thorin"
},
Hobbits = new[] { "Bilbo" },
Wizards = new[] { "Gandalf" }
};
In a real-world application, this data would probably be retrieved from a database or fetched from some remote service, but I'll keep things simple here for the sake of brevity.
First, let's serialize the companionship
object using the excellent Json.NET library so that we can pass it to the client later. The easiest way to achieve this would be to simply call the JsonConvert.SerializeObject
method:
string serializedCompanions = JsonConvert.SerializeObject(companionship);
// {"Dwarves":["Fili","Kili","Dori","Nori","Ori","Oin","Gloin","Balin","Dwalin","Bifur","Bofur","Bombur","Thorin"],"Hobbits":["Bilbo"],"Wizards":["Gandalf"]}
Note that the property names are wrapped in quotes; this is a requirement for valid JSON, but not for JavaScript literals which we want to emit. Also, the property names begin with an uppercase letter, which doesn't align with JavaScript's naming conventions.
Now, we could work with the above output, but it would be nicer if our data were serialized cleanly. A custom serialization method helps us fix the two flaws:
public static IHtmlString SerializeObject(object value)
{
using (var stringWriter = new StringWriter())
using (var jsonWriter = new JsonTextWriter(stringWriter))
{
var serializer = new JsonSerializer
{
// Let's use camelCasing as is common practice in JavaScript
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
// We don't want quotes around object names
jsonWriter.QuoteName = false;
serializer.Serialize(jsonWriter, value);
return new HtmlString(stringWriter.ToString());
}
}
(I've blogged about how to pass .NET server-side data to JavaScript before. Among other ways to hand data from an ASP.NET back-end to JavaScript clients, I've written about the above SerializeObject
method.)
Calling SerializeObject
yields the desired serialization format:
var serializedCompanions = JavaScriptConvert.SerializeObject(companionship);
// {dwarves:["Fili","Kili","Dori","Nori","Ori","Oin","Gloin","Balin","Dwalin","Bifur","Bofur","Bombur","Thorin"],hobbits:["Bilbo"],wizards:["Gandalf"]}
No more quotes around property names, no more Pascal Casing. Yay!
Let's now create a new controller and within that an action method that contains the data to be serialized. We'll later invoke that action method as a child action:
public class AngularController : Controller
{
[ChildActionOnly]
public ActionResult InitialData()
{
object companionship = new
{
Dwarves = new[]
{
"Fili", "Kili",
"Dori", "Nori", "Ori", "Oin", "Gloin",
"Balin", "Dwalin",
"Bifur", "Bofur", "Bombur", "Thorin"
},
Hobbits = new[] { "Bilbo" },
Wizards = new[] { "Gandalf" }
};
var serializedCompanions = SerializeObject(companionship);
return PartialView(serializedCompanions);
}
}
Make sure to also add the corresponding Razor view named InitialData.cshtml
.
This is where it gets interesting: Let's see how we make that data available to (and accessible through) the Angular infrastructure.
#Accessing the Data Through Angular's Infrastructure
The method of choice to hold our bootstrapped application data is an Angular service or, to be more exact, an Angular provider. Let's register an Angular provider named companionship
like this within the InitialData
Razor view:
<script>
angular
.module("hobbitModule")
.value("companionship", @Html.Raw(Model));
</script>
The view's Model
property contains the serialized object data. To prevent the Razor view engine from HTML-encoding the quotes around the string value, the model is emitted using the Html.Raw
method. By utilizing Angular's value
method, we tell its dependency resolution component to always return the specified object (which contains our serialized data) when being asked to resolve the companionship
service. That enables us to access the bootstrapped data in a clean manner through Angular's dependency injector.
Here's what that could look like:
angular
.module("hobbitModule")
.controller("CompanionshipController", function ($scope, companionship) {
$scope.companions = companionship;
});
#Plugging the Pieces Together
Finally, we need to invoke the InitialData
action method as a child action to have the content of its view rendered into our response:
@Html.Action("InitialData", "Angular")
Of course, we need to include Angular first; otherwise, we couldn't use the angular
global variable. Also notice that we've been referencing the hobbitModule
before, which, well, has to be defined before we can reference it:
angular.module("hobbitModule", []);
If we did everything correctly, we should now be able to render an HTML list of all dwarves using our bootstrapped data:
<div ng-app="hobbitModule" ng-controller="CompanionshipController">
<h1>The Dwarves in <strong>The Hobbit</strong></h1>
<ul>
<li ng-repeat="dwarf in companions.dwarves"></li>
</ul>
</div>
And here we go:
#Wrapping it Up in a Demo
Admittedly, this post contained a lot of disjointed code snippets. To give you a better overview of how the different pieces work together, I've created small MVC application which you can find here on GitHub.
Happy coding, everyone!
Related Posts:
- Asynchronously Bootstrapping AngularJS Applications with Server-Side Data
- Generating External JavaScript Files Using Partial Razor Views
- Passing .NET Server-Side Data to JavaScript
More AngularJS Material:
- [Pro AngularJS][proangularjs_amazoncom]: a comprehensive introduction
- [ng-book][ngbook_amazoncom]: another complete book
- egghead.io: bite-sized video tutorials
- AngularJS: Get Started: an introductory video course
- AngularJS Patterns: Clean Code: patterns and best practices