Saturday 31 October 2015

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 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 namedcompanionship 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:
Example Output: The Dwarves in 'The Hobbit'

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:
More AngularJS Material:

20 Comments

question
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.
Ehsan Shirvan
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
Marius Schulz
Ehsan: You can simply make an AJAX request to fetch the data from the action method of your ASP.NET MVC controller.
Tony
Perfect example! Thanks for sharing.
Nainil
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?
Marius Schulz
@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!
Dhruv
Great Man.. Keep it up.. best Luck.. :-)
Ling
Great article. This is exactly what I am looking for. I am so glad I found it. It solved my issue. Thanks a lot.
Ling
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?
Matt
Thanks for the article sir! Exactly what I was looking for!
Andrew
Thanks for this post Marius. It is a great solution. But when I tried to implement it in my project it results in pending request to my controller action which returns partial view. Can you help with this please?
Cesar Vega
Hi, what an awesome way to do it, I have a question; can you implement jquery 1.11.2 and angualarJS 1.2 ; I'm trying to do it with the razor engine
Phil
Hi, rather than calling @Html.Action to call InitialData I tried putting the script directly on the page that uses the data. It works if I set a hard-coded string, but doesn't work if I try to set a model property like @Model.Name. Do you know why it has to be done separately?
mohannad
that's wonderfull thanks!!!!!
Michael
What's the advantage of this over ng-init? This is also good, just wondering.
Marius Schulz
@Michael: The advantage of the outlined approach is that the initial data is accessible through Angular's regular dependency injection system. You can declare companionship as a dependency in one of your components and have Angular inject it correctly.
Akshay Gaikwad
will it work if browser disabled javascript ?
fyi
in your "Update" you link to "Asynchronously Bootstrapping AngularJS Applications with Server-Side Data" but the link is actually to google.com
mvc user
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?
Marius Schulz
@mvc user: I've prepared a demo application on GitHub that shows how the various parts fit together. Make sure to check it out!

1 comment:

  1. Great Content. It will useful for knowledge seekers. Keep sharing your knowledge through this kind of article.
    MVC Training in Chennai
    MVC Classes in Chennai

    ReplyDelete

Angular Tutorial (Update to Angular 7)

As Angular 7 has just been released a few days ago. This tutorial is updated to show you how to create an Angular 7 project and the new fe...