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 theJsonConvert.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 how that could look like:var module = angular.module("hobbitModule");
module.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: a comprehensive introduction
- ng-book: another complete book
- egghead.io: bite-sized video tutorials
- AngularJS: Get Started: an introductory video course
- AngularJS Patterns: Clean Code: patterns and best practices
20 Comments
At first, I must say this is a fantastic post which gives a solution to an existing problem.
However, tough it is only a proof of concept as you say - just to make sure I didn't miss anyting I must ask you why you split the real controller ("home") from the initial Controller ("initialData"). I understand it from the view point of view, in which you wanted to defer between the real view ("Home/Index") and the angular wrapping stuff ("Angular/InitialData"), but as for the controller point of view the initialDataController functionallity seems like it should be be part of the HomeController.
first of all thank you so much cause of your post. but now i have a question ,that would be pleasant if you help me on this road: what if we want to create angular controller in a separate file and then get data from Server (Asp.Net Action method) and show them in Client side. again thank you so much and forgive me for my poor English writing
I have started to learn AngularJS. From whatever I have learned so far, I found out that AngularJS is a pure MVC approach for client side development My question from a 30,000 feet above is that is it possible to use both AngularJS and ASP.NET MVC for an enterprise application? Is this a recommended approach, or when ASP.NET MVC is involved, we should avoid using AngularJS?
@Nainil: I use Angular and ASP.NET MVC together all the time. From my experience, your application will lean a little bit more to either side (server vs. client), but that doesn't mean it's not perfectly plausible to combine these two technologies.
Keep in mind that you'll still benefit from a lot of Angular goodies (e.g. directives) without building a full-fledged single page application (SPA). However, you can also decide to have various small SPAs hosted within an ASP.NET MVC website project. As always, it depends!
How can I pass the data from MVC controller to Angular View without exposing the data in the HTML? For example, I have the following in the index.cshtml
@model IHtmlString
<script>
angular.module("MVCApp", []).value("data", @Html.Raw(Model));
</script>
My home controller is like this:
public ActionResult Index(string name, string password)
{
if (name == null)
{
name = "test";
password = "pass";
}
UserInfoModel infoModel = new UserInfoModel(name, password);
var model = SerializeObject(infoModel);
return View(model);
}
The final generated html will be like
angular.module("MVCApp", []).value("data", {userName:"test",password:"pass"});
But I don't want the userName and password show up in the final html. How can I pass it without it showing up in the html? or can I pass it encrypted?
Hi Marius, This is nice article but still I am not able to visualize a clear picture in mind that how the ASP.Net MVC + Angular Js works. I mean, how can we connect all components like ASP.Net MVC controllers, ASP.Net views generated by Razer view engine, angular js factory services and angular js controllers? Can I combine Razor view engine and Angular Js view?
@mvc user: I've prepared a demo application on GitHub that shows how the various parts fit together. Make sure to check it out!
Great Content. It will useful for knowledge seekers. Keep sharing your knowledge through this kind of article.
ReplyDeleteMVC Training in Chennai
MVC Classes in Chennai