Click here to Skip to main content
15,867,704 members
Articles / Web Development / HTML

Integrating AngularJS with ASP.NET MVC

Rate me:
Please Sign up or sign in to vote.
4.95/5 (99 votes)
2 Oct 2015CPOL34 min read 298.2K   13.6K   139   57
Single Page Applications and the Model View Controller Design Pattern

Download source

Introduction

Whenever I go to my favorite New York deli, I have trouble placing an order. I love deli food so much that everything on the menu looks awesome. I want the pastrami. I want the corned beef. I want the sliced roasted turkey. I want the potato pancakes. I want the potato salad. I want the knishes. I want, I want, I want. I want it all.

When it comes to developing new computer software, I want it all too. I want the latest in JavaScript technologies on the front end. I want the latest in RESTful Web API services technology on the server side. I want the latest database technologies and I want the latest design patterns and techniques.

When selecting the latest software technologies, several factors come into play, including how will these technologies will be integrated. One of my favorite technologies the past two years has been AngularJS for Single Page Applications (SPA). Being a Microsoft stack developer, I'm also a big fan of Microsoft's ASP.NET MVC platform implementing the MVC design pattern and all the plumbing that it comes with - including its bundling and minification functionality and its implementation of Web API controllers for RESTful services.

To have your cake and eat it too, this article covers an integration scenario using AngularJS with ASP.NET MVC to get the best of both worlds.

Overview

Image 1

The sample web application in this article will have three goals:

  • Implement AngularJS on the frontend for the views and the JavaScript AngularJS controllers.
  • Use Microsoft's ASP.NET MVC platform for delivering, bootstrapping and bundling the application.
  • Load AngularJS controllers and services dynamically on-demand by functional module.

This article is a follow up to my previous article AngularJS - Developing a Large Scale Application with a Single Page Application (SPA) using AngularJS.

http://www.codeproject.com/Articles/808213/Developing-a-Large-Scale-Application-with-a-Single

The sample application for this article will contain three main folders, the Home folder for the About, Contact and Index views; a Customers folder that will allow you to create, update and inquire customers; and a Products folder for creating, updating and inquiring on product information.

Besides using AngularJS and ASP.NET MVC, this application will also implement Microsoft's ASP.NET Web API services for creating RESTful services. Microsoft's Entity Framework will be used for generating and updating a SQL-Server Express database.

This application will also use some dependency injection using Ninject. Additionally, A small validation library for .NET called FluentValidation will be incorporated that uses a fluent interface and lambda expressions for building validation business rules that will reside in the application business layer.

AngularJS vs ASP.NET Razor Views

For several years, I have developed web applications using the entire Microsoft ASP.NET MVC platform. The ASP.NET MVC platform using Razor views compared with the traditional ASP.NET Web Forms postback model; came with the proper separation of concerns (SoC) between business logic, data, and the presentation logic. After developing with MVC with its convention over configuration and it's clean design pattern, you will never want to go back to Web Forms development.

However, the ASP.NET MVC platform and its Razor view engine, though cleaner than Web Forms, still encourages and allows you to mix .NET server side code with the presentation. Mixing .NET code with HTML in a Razor view can begin to look like spaghetti code very quickly. Also, in the ASP.NET MVC model, some of the business logic might end up being written in an MVC controller. It is tempting to write code in an MVC controller to control information in the presentation layer.

AngularJS provides the following enhanced functionality over Microsoft ASP.NET MVC Razor views:

  • AngularJS views are pure HTML
  • AngularJS views are cached on the client for faster response and not generated server side on each request
  • AngularJS provides for an entire framework for writing high quality client-side JavaScript code
  • AngularJS provides a complete separation of concerns between JavaScript controllers and HTML views

ASP.NET MVC Bundling and Minification

Bundling and minification are two techniques you can use to improve request load time of your web application. Bundling and minification improves load time by reducing the number of requests to the server and reduces the size of requested assets (such as CSS and JavaScript.) Minification also makes it harder for someone to hack your JavaScript code by obfuscating logic in the code.

When it comes to bundling technology and the AngularJS framework, you often see the bundling and minification process being automated using such frameworks as Grunt or Gulp, etc. Technologies like Grunt and Gulp are popular web libraries with great ecosystems that come with useful plugins that allows you to automate just about every task you might have.

However, if you are a Microsoft developer, you are used to publishing your web application from Visual Studio with a press of a button without having to learn any 3rd party tools or libraries. Fortunately, bundling and minification is a feature in ASP.NET since ASP.NET 4.5 that makes it easy to combine or bundle multiple files into a single file. You can create CSS, JavaScript and other bundles. Fewer files means fewer HTTP requests and that can improve first page load performance.

Dynamic loading of MVC Bundles with RequireJS

One of the things when developing AngularJS single page applications, is that out of the box, AngularJS requires that all the JavaScript files and controllers for the application be referenced and downloaded from within the shell master page upon bootstrapping the application at start-up. For a large-scale application that may contain hundreds of JavaScript files, this may not be ideal. Since I wanted to load all my AngularJS controllers using ASP.NET bundling, a huge challenge came into play. ASP.NET bundles are rendered server-side and once bootstrapped, everything in AngularJS happens client side.

To dynamically load ASP.NET bundles for this sample application, I decided to use the RequireJS JavaScript library. RequireJS is a well-known JavaScript module and file loader, which is supported in the latest versions of popular browsers. At first, this seemed like a very simple thing, but as time went by I ended up writing a lot of code that didn't solve the problem of using a server-side rendered bundle with a client-side technology like AngularJS and loading these bundles on-demand after the application was bootstrapped and running.

Eventually after much research and trial and error and failure, I came up with a solution that ended up being a lot less code and worked well. The rest of this article walks through the process of integrating AngularJS with ASP.NET MVC.

Create MVC Project and install Angular NuGet Packages

To get started with the sample application, I created an ASP.NET MVC 5 web application by selecting the ASP.NET Web Application template in Visual Studio 2013 Professional. After that I selected an MVC project and clicked the Web API checkbox to add folders and references for the MVC Web API that this application will use. The next step was to download and install AngularJS from NuGet by selecting ‘Manage NuGet Packages for Solution' from the Tools menu.

For this sample application, I installed all the following NuGet packages:

  • AngularJS - Installs the entire AngularJS library
  • Angular UI - The companion suite of UI widgets and scripts to the AngularJS framework.
  • Angular UI Bootstrap - contains a set of native AngularJS directives based on Bootstrap's markup and CSS
  • Angular Block UI - AngularJS BlockUI directive that blocks the UI during HTTP requests
  • RequireJS - RequireJS is a JavaScript file and module loader
  • Ninject - Provides dependency injection support for MVC and the MVC Web API
  • Entity Framework - Microsoft’s recommended data access technology for new applications
  • Fluent Validation - A .NET validation library for building validation rules.
  • Font Awesome - Gives you scalable vector icons that can instantly be customized with CSS

NuGet is an excellent package manager. When you use NuGet to install a package, it copies the library files to your solution and automatically updates your project references and config file). If you remove a package, NuGet reverses whatever changes it made so that no clutter is left.

Pretty URLS

For this sample application, I wanted to implement pretty URLs in the browser's address bar. By default, AngularJS will route URLs with a hashtag:

For example:

  • http://localhost:16390/
  • http://localhost:16390/#/contact
  • http://localhost:16390/#/about
  • http://localhost:16390/#/customers/CustomerInquiry
  • http://localhost:16390/#/products/ProductInquiry

It is very easy to get clean URLs and remove the hashtag from the URL by turning on html5Mode and setting the base URL. In HTML5 mode, the AngularJS $location service interacts with the browser URL address through the HTML5 History API. The HTML5 History API is a standardized way to manipulate the browser history via script. At it's core, this is the central plumbing for single page applications.

To turn on html5Mode, you set the $locationProvider html5Mode to true during Angular's configuration phase as follows:

JavaScript
// CodeProjectRouting-production.js

angular.module("codeProject").config('$locationProvider', function ($locationProvider) {
    $locationProvider.html5Mode(true);
}]);

When you configure the $locationProvider to use html5Mode, you need to specify the base URL for the application with a base href tag. The base URL is used to resolve all relative URLs throughout your application. You set the base URL in the header section of the master page of your application as follows:

HTML
<!-- _Layout.cshtml -->

<html>
<head>
   <base href="http://localhost:16390/" />
</head>

For the sample application, I am storing the Base URL in the web.config file as an application setting. It’s a best practice to make the base URL a configuration setting so you can set the base URL to different values based on which environment and configration or site you are deploying your application to. Also, when setting the base URL, make sure the base URL ends with a "/" because the base URL prefixes all your routes.

XML
<!-- web.config.cs -->

<appsettings>
    <add key="BaseUrl" value="http://localhost:16390/" />
</appsettings>

After turning on html5Mode and setting the base URL, you end up with the following pretty URL routes:

  • http://localhost:16390/
  • http://localhost:16390/contact
  • http://localhost:16390/about
  • http://localhost:16390/customers/CustomerInquiry
  • http://localhost:16390/products/ProductInquiry

Directory Structure and Configuration

By convention, an MVC project template requires that all your Razor views reside in the Views folder; all your JavaScript files reside in the Scripts folder; and all your content files resides in the Content folder. For this sample application, I wanted to keep all my Angular views together with their associated Angular JavaScript controllers in the same directory. Web based applications can grow very large and I didn't want related functionality to spread across different folders of the application directory structure.

Image 2

 

In the sample application, there will be only be two Razor views used, Index.cshtml, and the _Layout.cshtml master page layout. These two Razor views will be used to bootstrap and configure the application. The rest of the application will consist of AngularJS views and controllers.

For the sample application, I created two additional folders under the Views folder, one sub-folder for Customers, and one sub-folder for Products. All the Angular views and controllers for customers will reside in the Customers sub-folder, and all the Angular views and controllers for products will reside in the Products sub-folder.

Since Angular views are HTML files, and Angular controllers are JavaScript files, ASP.NET MVC must be configured to allow HTML files and JavaScript files to be accessed and delivered to the browser from the Views folder. This is a ASP.NET MVC default convention. Fortunately, you can change this convention by editing the web.config file under the Views folder and add a handler for both HTML files and JavaScript files which will allow these file types to be served up to the browser.

XML
<!-- web.config under the Views folder -->

<system.webserver>
<handlers>
<add name="JavaScriptHandler" path="*.js" verb="*" precondition="integratedMode"
     type="System.Web.StaticFileHandler" />

<add name="HtmlScriptHandler" path="*.html" verb="*" precondition="integratedMode"
     type="System.Web.StaticFileHandler" />
</handlers>
</system.webserver>

Application Version Incrementing and Project Builds

For this sample application, I wanted to keep track of versions and build numbers each time I compiled, tested and published the application using the information in the AssemblyInfo.cs file under the Properties folder. Each time the application runs, I wanted to get the latest version of the application and use the version number to help with such things as appending a version number to the end of HTML files and JavaScript files that would tell the browser to get new versions of these files instead of the browser running an older version of these files from its browser cache when changes to these files are made.

For this application, I was using Visual Studio Professional 2013, and to make things easy, I downloaded an Automatic Version plugin for Visual Studio Professional 2013 from

https://visualstudiogallery.msdn.microsoft.com/dd8c5682-58a4-4c13-a0b4-9eadaba919fe

that will automatically increment assembly version(s) for C# and VB.NET projects. The download installs the plugin into the Tools menu called Automatic Version Settings. The plugin comes with a configuration tool that allows you to configure your major and minor build numbers that will update your AssemblyInfo.cs file automatically on each compile. Currently this plugin is only supported in the Visual Studio Professional 2013 version. Optionally, you can manually update the version number or use something like Microsoft's TFS to manage your build numbers in a fully integrated continuous build and configuration management environment.

Image 3

Below is a sample AssemblyInfo.cs with an updated AssemblyVersion and AssemlyFileVersion number that was automatically updated by the plugin after a build was completed.

C#
// AssemblyInfo.cs

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

[assembly: AssemblyTitle("CodeProject.Portal")]
[assembly: AssemblyProduct("CodeProject.Portal")]
[assembly: AssemblyCopyright("Copyright © 2015")]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("1d9cf973-f876-4adb-82cc-ac4bdf5fc3bd")]
// Version information for an assembly consists of the following four values:
//

// Major Version
// Minor Version
// Build Number
// Revision
//

// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:

[assembly: AssemblyVersion("2015.9.12.403")]
[assembly: AssemblyFileVersion("2015.9.12.403")]

Replace About, Contact Us, Razor Views With Angular Views and Controllers

One of the first things you will want to do with your MVC project is to replace the Contact Us and About Razor views with AngularJS views and controllers. This is a good starting place to test out your configuration to make sure AngularJS is set-up and working properly. Later you might decide to delete the About and Contact Us views and controllers if you don't need these pages.

The classic way of creating a controller in AngularJS is by injecting $scope into it. The views and controllers in the sample application use the "controller as" syntax. This syntax replaces the use of $scope in controllers, simplifying the syntax of your controllers. When you declare a controller with the "controller as" syntax, you get a single instance of that controller.

With the "controller as" syntax, all your properties attached to the controller scope (view model) must be prefixed with an alias in your view. In the view snippet below, the property title is prefixed with the "vm" alias.

HTML
<!-- aboutController.js -->

<div ng-controller="aboutController as vm" ng-init="vm.initializeController()">
   <h4 class="page-header">{{vm.title}}</h4>
</div>

Using the "controller as" syntax, when the controller constructor function is called, an object called "this" is created which is the controller instance. Instead of using the $scope variable provided by Angular, you simply declare a vm variable (which stands for view model) and assign "this" (the instance of the controller function) to it. All variables then can be assigned to the vm object instead of $scope. With our variables assigned to the instance of the controller function we are then able to access the variables within the view using the alias.

Additionally, all the controllers in the sample application run in strict mode using the "use strict" JavaScript command. Strict mode makes it easier to write "secure" JavaScript. Strict mode changes previously accepted "bad syntax" into real errors. As an example, in normal JavaScript, mistyping a variable name creates a new global variable. In strict mode, this will throw an error, making it impossible to accidentally create a global variable.

JavaScript
// aboutController.js

angular.module("codeProject").register.controller('aboutController', 
['$routeParams', '$location', function ($routeParams, $location) {
{
    "use strict";
    var vm = this;

    this.initializeController = function () {
        vm.title = "About Us";
    }

}]);

As stated before, one of the advantages you get with AngularJS views and controllers over MVC Razor views, is that Angular provides for a great mechanism for writing high-quality JavaScript modules and code with a complete separation between the pure HTML view and the JavaScript controller. You no longer need to parse the browser's document object model (DOM) with AngularJS two-way data-binding technology and thus allowing you to write unit-testable JavaScript code.

As a footnote, you will see in the aboutController a method called register.controller. Later in this article you will see where the register method is coming from and what its used for.

Home Index Razor View and MVC Routing

One of the interesting things to understand when integrating AngularJS with ASP.NET MVC, is how the application actually starts up and gets routed. When you start your application, ASP.NET MVC kicks in and looks at its routing table that is set-up by default as follows:

C#
// RouteConfig.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace CodeProject.Portal
{

    public class RouteConfig
    {

        public static void RegisterRoutes(RouteCollection routes)
        {

            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
               name: "Default",
               url: "{controller}/{action}/{id}",
               defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );

        }

    }

}

The above MVC routing table configured out-of-the-box will route the application upon start-up of the application to the MVC Home controller and execute the Index method within the Home controller which in turn will render the Index.cshtml MVC Razor view to the user and the output will render the standard home page content that comes with the MVC default project template.

The goal of this application is to replace all the MVC views with Angular views. The thing is, the Index Razor view for the Home page gets executed and injected into the master page _Layout.cshtml before AngularJS has even started up.

Since I decided that I wanted the Home page to come from an AngularJS view, I simply deleted all the content from the Index Razor view with the following div tag containing the AngularJS ng-view tag.

HTML
<!-- Index.cshtml -->

<div ng-view></div>

The AngularJS ngView tag is a directive that complements the $route service by including the rendered template of the current route into the main layout. I had two options, either imbed the ng-view tag directly into the master page _Layout.cshtml or have it injected into the master page using the Index Razor view. I decided to simply inject the tag from the Index Razor view. Essentially, the Index Razor view will simply be used during the bootstrapping of the application and will not be referenced after the application has started.

Once the application has been bootstrapped and started-up, AngularJS kicks in and executes its own routing system and executes the default route as configured in its own routing table. At this point I created a separate AngularJS Index.html and an IndexController.js file for the Home Page that AngularJS renders upon start up.

HTML
<!-- Index.html -->

<div ng-controller="indexController as vm" ng-init="vm.initializeController()">
    <h4 class="page-header">{{vm.title}}</h4>
</div>

The Index Angular view executes the indexController intializeController function through the ng-init directive when the view loads.

JavaScript
// indexController.js

angular.module("codeProject").register.controller('indexController',
['$routeParams', '$location', function ($routeParams, $location) {

    "use strict";

    var vm = this;

    this.initializeController = function () {
        vm.title = "Home Page";
    }

}]);

RouteConfig.cs

One of the first things that will happen to you when you are developing an AngularJS application, is that you might be developing a page such as CustomerInquiry that resides in the route

/Views/Customers/ CustomerInquiry

and when you are in the HTML page for this view and you hit the run button in Visual Studio to execute this page directly, MVC will execute and try to find an MVC controller and view for the Customers route. What happens is that you will get an MVC routing error saying it can't find the view or the controller for the route.

Of course you get this error because the /View/Customers/CustomerInquiry route is an Angular route and not a MVC route. MVC knows nothing about this route. But you still want to run this page directly. To mitigate this issue, additional routes need to be added to the MVC routing table to tell MVC to route all valid requests to the MVC Home controller and render the Razor Index view and bootstrap the application from that route.

Since I have three view folders, Home, Customers and Products, I added the following to the MVC RouteConfig class to route all requests to the Home/Index route. Hitting F5 while the application is running will also go through this MVC routing table. Basically you are rebooting the AngularJS application when you hit F5 in the browser in terms of how Angular and single page applications operate.

With these additional routes in place, you can now execute AngularJS routes directly. Optionally you could probably have a single wild carded route in the MVC route table to handle your routes, but I prefer to be explicit with the routing table and have MVC reject any invalid routes.

The basic thing to remember is that MVC routing occurs first before AngularJS starts up, and once bootstrapped, AngularJS takes over all of the routing requests after that.

C#
// RouteConfig.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace CodeProject.Portal 
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {

            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name: "HomeCatchAllRoute",
                url: "Home/{*.}",
                defaults: new { controller = "Home", action = "Index", 
                          id = UrlParameter.Optional }
            );

            routes.MapRoute(
                name: "CustomersCatchAllRoute",
                url: "Customers/{*.}",
                defaults: new { controller = "Home", action = "Index", 
                          id = UrlParameter.Optional }
           );

           routes.MapRoute(
                name: "ProductsCatchAllRoute",
                url: "Products/{*.}",
                defaults: new { controller = "Home", action = "Index", 
                          id = UrlParameter.Optional }
           );

           routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", 
                          id = UrlParameter.Optional }
          );

       }
   }
}

$controllerProvider and Dynamically Loading Controllers

When the sample application starts up, the application preloads the core controllers and services for the application. This includes all the controllers in the Home directory and any shared services for the application.

Shared services for this application are services that will be executed across all modules - including an Ajax Service and an Alerts Service. As stated previously, this application has three functional modules; a module for the basic About, Contact Us and Home pages; a customers module and a products module.

Since this application could grow over time, I didn't want to preload all my functional modules up front during the configuration and bootstrap phase of the application. After the application has started, I only want to load the controllers for the customers and products modules when these modules are request by the user.

By default, AngularJS was designed to have all your controllers preloaded. A typical controller might look as such:

JavaScript
// aboutController.js

angular.module("codeProject").controller('aboutController',
['$routeParams', '$location', function ($routeParams, $location) {

    "use strict";

    var vm = this;

    this.initializeController = function () {
        vm.title = "About";
    }

}]);

If you attempt to dynamically load the above controller after the configuration phase, you will receive an Angular error. What you need to do to is dynamically load a controller after the configuration phase with the $controllerProvider service. The $controllerProvider service is used by Angular to create new controllers. This provider allows controller registration via the register method.

JavaScript
// aboutController.js

angular.module("codeProject").register.controller('aboutController',
['$routeParams', '$location', function ($routeParams, $location) {

    "use strict";

     var vm = this;

     this.initializeController = function () {
         vm.title = "About";
     }

}]);

The about controller above was modified to execute the register method of the $controllerProvider. To enable the register method, the register method must be configured during the configuration phase. The below code snippet uses the $controllerProvider to make the register method available after the start-up of the application. In the example below, the register method is provided for registering and dynamically loading both controllers and services. If you want, you could include a register function for Angular factories and directives too.

JavaScript
// CodeProjectBootStrap.js

(function () {

    var app = angular.module('codeProject', ['ngRoute', 'ui.bootstrap', 'ngSanitize', 'blockUI']);
    
    app.config(['$controllerProvider', '$provide', function ($controllerProvider, $provide) {

        app.register =
        {
            controller: $controllerProvider.register,
            service: $provide.service
        };

    }]);

})();

ASP.NET Bundling And Minification

Bundling and minification of CSS and JavaScript is one of the most popular and powerful features of ASP.NET MVC. Bundling and minification reduces the number of HTTP requests and payload size resulting in faster and better performing ASP.NET MVC websites. There are a number of ways you can reduce and combine the size of CSS and JavaScript.

Bundling makes it easy to combine or bundle multiple files into a single file. You can create CSS, JavaScript and other bundles. Minification performs a variety of different code optimizations to scripts or css, such as removing unnecessary white space and comments and shortening variable names to one character. Since bundling and minification reduces the size of your JavaScript and CSS files, the bytes sent over the HTTP wire is significantly reduced.

When configuring your bundles, you need to think of a bundling strategy and how to organize your bundles. The below BundleConfig class is the configuration file for the built-in ASP.NET Bundling feature. In the BundleConfig class, I decided to organize my files by functional module. I set-up a separate bundle for each folder in the project, including separate bundles for Scripts, Content, Angular core files, shared JavaScript files, and separate bundles for the Home directory, the Customers directory and the Products directory.

I created separate bundles for the Customers and Products directory, with the idea that the application will load these bundles dynamically when the user requests resources from these areas of the application. Since AngularJS is a pure client side framework, dynamically loading ASP.NET bundles, a server side technology, and combining these two technologies together became the biggest challenge developing the sample application which also required supporting both release and debug modes.

C#
// BundleConfig.cs

using System.Web;
using System.Web.Optimization;

public class BundleConfig
{

    // For more information on bundling, visit http://go.microsft.com/fwlink/?LinkId=301862
    public static void RegisterBundles(BundleCollection bundles)
    {

        bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
            "~/Scripts/jquery-{version}.js"));

        bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
            "~/Scripts/bootstrap.js",
            "~/Scripts/respond.js"
        ));

        bundles.Add(new StyleBundle("~/Content/css").Include(
           "~/Content/bootstrap.css",
           "~/Content/site.css",
           "~/Content/SortableGrid.css",
           "~/Content/angular-block-ui.min.css",
           "~/Content/font-awesome.min.css"
        ));

        bundles.Add(new ScriptBundle("~/bundles/angular").Include(
           "~/Scripts/angular.min.js",
           "~/Scripts/angular-route.min.js",
           "~/Scripts/angular-sanitize.min.js",
           "~/Scripts/angular-ui.min.js",
           "~/Scripts/angular-ui/ui-bootstrap.min.js",
           "~/Scripts/angular-ui/ui-bootstrap-tpls.min.js",
           "~/Scripts/angular-ui.min.js",
           "~/Scripts/angular-block-ui.js"
        ));

        bundles.Add(new ScriptBundle("~/bundles/shared").Include(
           "~/Views/Shared/CodeProjectBootstrap.js",
           "~/Views/Shared/AjaxService.js",
           "~/Views/Shared/AlertService.js",
           "~/Views/Shared/DataGridService.js",
           "~/Views/Shared/MasterController.js"
        ));

        bundles.Add(new ScriptBundle("~/bundles/routing-debug").Include(
           "~/Views/Shared/CodeProjectRouting-debug.js"
        ));

        bundles.Add(new ScriptBundle("~/bundles/routing-production").Include(
           "~/Views/Shared/CodeProjectRouting-production.js"
        ));

        bundles.Add(new ScriptBundle("~/bundles/home").Include(
           "~/Views/Home/IndexController.js",
           "~/Views/Home/AboutController.js",
           "~/Views/Home/ContactController.js",
           "~/Views/Home/InitializeDataController.js"
        ));

 
        bundles.Add(new ScriptBundle("~/bundles/customers").Include(
           "~/Views/Customers/CustomerMaintenanceController.js",
           "~/Views/Customers/CustomerInquiryController.js"
        ));

 
        bundles.Add(new ScriptBundle("~/bundles/products").Include(
           "~/Views/Products/ProductMaintenanceController.js",
           "~/Views/Products/ProductInquiryController.js"
        ));

    }

}

Cache Busting with ASP.NET Bundling

One of the advantages of using ASP.NET Bundling is its 'cache busting' helper methods that enable easy caching and cache busting of bundled files with automatic detection if you change the cached CSS or JavaScript. The below sample snippet of code is executed in a MVC Razor view (usually in the _Layout.cshtml master page). The Scripts.Render method renders bundles to the client, and when executed in non-debug mode, it generates the virtual path to the bundle and appends a version number to the end of the bundle. When you change the contents of your bundle and re-publish your application, a new version number is appended to the bundle, which helps bust the browser cache on the client and forces a new download of your bundle.

C#
// _Layout.cshtml

@Scripts.Render("~/bundles/customers")
@Scripts.Render("~/bundles/products")

The Scripts.Render function is a nice feature, but in this sample application, I wanted to load the customers and products bundles client side dynamically with AngularJS, so I couldn't use the Render function to render some of my bundles. This is where the challenge began. The question began, How do I render server-side ASP.NET bundles from client side JavaScript using AngularJS?

_Layout.cshtml - Server side start up Code

One of the advantages of bootstrapping your AngularJS application using ASP.NET MVC is that you can execute some server side code in the _Layout.cshtml master page prior to loading and executing AngularJS code. This was the first step to help solve my dilemma of rendering server side bundles through client side code. Sure you can simply imbed script tags in your client side code, but I needed a way to render a bundle and reference and maintain the automatic version number that gets appended to the bundle for cache busting purposes.

To start off, I created some server-side code at the top of the _Layout.cshtml master page. The first two things I did was to get the version number of the application from the AssemblyInfo class and retrieve the base URL from the application settings. Both of these values will be parsed by the Razor View Engine later in the HTML.

The meat and potatoes of this code snippet below generates a list of bundles that I wanted to load dynamically on-demand later on. I did not want to load all the bundles up-front when the application started-up. The important piece of information I needed was the virtual path and long version number of each bundle. Fortunately, the bundling functionality comes with classes and methods for accessing bundle information.

The key line of code below references the BundleTable. This line of code executes the ResolveBundleUrl method that returns the virtual path and version number of each referenced bundle. Basically the code generates a list of bundles and converts the list into a JSON collection. Later the JSON collection is added to AngularJS. Having bundle information in a JSON collection was the initial bridge that allowed the application to load server-side bundles from client-side AngularJS.

C#
// _Layout.cshtml

@using CodeProject.Portal.Models
@{
    string version = typeof(CodeProject.Portal.MvcApplication).Assembly.GetName().Version.ToString();
    string baseUrl = System.Configuration.ConfigurationManager.AppSettings["BaseUrl"].ToString();

    List<CustomBundle> bundles = new List<CustomBundle>();
    CodeProject.Portal.Models.CustomBundle customBundle;

    List<string> codeProjectBundles = new List<string>();
    codeProjectBundles.Add("home");
    codeProjectBundles.Add("customers");
    codeProjectBundles.Add("products");

    foreach (string controller in codeProjectBundles)
    {
        customBundle = new CodeProject.Portal.Models.CustomBundle();
        customBundle.BundleName = controller;
        customBundle.Path = BundleTable.Bundles.ResolveBundleUrl("~/bundles/" + controller);
        customBundle.IsLoaded = false;
        bundles.Add(customBundle);
    }

    BundleInformation bundleInformation = new BundleInformation();
    bundleInformation.Bundles = bundles;
    string bundleInformationJSON = Newtonsoft.Json.JsonConvert.SerializeObject(
    bundleInformation, Newtonsoft.Json.Formatting.None);

}

The ASP.NET Bundle classes has a lot of functionality. For example, if you wanted to iterate through all the files inside a bundle, you could execute the EnumerateFiles method and return the virtual path of each file inside a particular bundle.

C#
foreach (var file in bundle.EnumerateFiles(new BundleContext(
         new HttpContextWrapper(HttpContext.Current), BundleTable.Bundles, "~/bundles/shared")))
{
    string filePath = file.IncludedVirtualPath.ToString();
}

_Layout.cshtml - Header

In the Header section of the HTML document, there is a reference to RequireJS. This application will use RequireJS to load the bundles dynamically through client-side AngularJS code. RequireJS is an Asynchronous Module Definition (AMD) API for loading JavaScript modules. RequireJS has many features, but for the purpose of this application, only the require function from RequireJS will be needed and used later in this application.

Additionally, the Scripts.Render and Styles.Render methods are being executed in the header section. The Render method generates multiple script tags for each item in the bundle when running your application in debug mode or when EnableOptimizations is set to false. When in release mode and when optimizations are enabled, the Render method generates a single script tag to a version-stamped URL which represents the entire bundle.

This led to another challenge where the application needed to support both the ability to generate bundle script tags in release mode and script tags for individual files from the bundle in debug mode. This is important because you want to be able to set break-points in your JavaScript code during debug mode, which is not possible in release mode with optimized bundled versions of your JavaScript code.

Finally in the header section, the base URL is set to the server side baseUrl variable created earlier using Razor syntax.

HTML
<!-- _Layout.cshtml -->

!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />

<title>AngularJS MVC Code Project</titlev>

<script src="~/Scripts/require.js"></script>

@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@Scripts.Render("~/bundles/modernizr")
@Scripts.Render("~/bundles/angular")

@Styles.Render("~/Content/css")

<base href="#baseUrl" />

</head>

Debug Mode vs Release Mode

The @Scripts.Render method generates multiple script tags for each item in the bundle when EnableOptimizations is set to false or when running in debug mode. This is necessary if you wish to set break-points and debug your JavaScript files. Another option you have is to render custom script tags using the RenderFormat method while in debug mode.

In the code snippet below that is contained in the _layout.cshtml master page, RenderFormat is being used when the application is running in debug mode that will append the application version number to the script tag for all the JavaScript files in the bundle. This a small enhancement over the standard rendered script tag format that does not include an appended version number.

Sometimes when launching your application from Visual Studio, you might encounter browser caching issues while also spending time trying to guess if you are running the latest version of your JavaScript files. Hitting F5 in the browser may resolve this. To avoid this problem all together, the application version number is appended to the script tags. The version number is automatically incremented on each build with the automatic version plugin. With this technique, I saved alot of time while also knowing that I was using the latest version of my JavaScript files on each compile and run.

C#
// _Layout.cshtml

@if (HttpContext.Current.IsDebuggingEnabled)
{

    @Scripts.RenderFormat("<script type=\"text/javascript\" src=\"{0}?ver =" + @version + " \">
                           </script>", "~/bundles/shared")

    @Scripts.RenderFormat("<script type=\"text/javascript\" src=\"{0}?ver =" + @version + " \">
                           </script>","~/bundles/routing-debug")

}

else
{
    @Scripts.Render("~/bundles/shared")
    @Scripts.Render("~/bundles/routing-production")
}

Bridging data between server side Razor data and AngularJS

Now that I have created a server-side collection of bundle data, the next challenge was injecting and creating a bridge between the server-side data and client side AngularJS code. In the _Layout.cshtml master page, I created an anonymous JavaScript function that creates an AngularJS provider. Initially I had planned on creating a normal AngularJS service or factory in the _Layout.cshtml file that could contain server-side data that would be injected with data with Razor syntax.

Unfortunately, AngularJS services and factories are not available until after the AngularJS configuration phase has been completed, thus I could not create a service in the master page without getting an AngularJS error. To overcome this limitation, an AngularJS provider needed to be created. Provider functions are constructor functions whose instances are responsible for "providing" a factory for a service. Providers allow you to create and configure a service during Angular's configuration phase.

Service provider names start with the name of the service they provide followed by the word Provider. In the code snippet below,  the code is creating a  'applicationConfiguration'  provider which is being referenced with the name applicationConfigurationProvider. The provider is configured in a constructor function that sets the assembly version number and the list of bundles used for this application that will need to be dynamically loaded upon request. MVC Razor code injects server-side data in the constructor function.

JavaScript
// _Layout.cshtml

(function () {

        var codeProjectApplication = angular.module('codeProject');

        codeProjectApplication.provider('applicationConfiguration', function () {

            var _version;
            var _bundles;

            return {
                setVersion: function (version) {
                _version = version;
            },

            setBundles: function (bundles) {
                _bundles = bundles;
            },

            getVersion: function () {
                return _version;
            },

            getBundles: function () {
                return _bundles;
            },

            $get: function () {
                return {
                    version: _version,
                    bundles: _bundles
                }
            }

       }

    });

    codeProjectApplication.config(function (applicationConfigurationProvider) {
        applicationConfigurationProvider.setVersion('@version');
        applicationConfigurationProvider.setBundles('@Html.Raw(bundleInformationJSON)');
    });

})();

Production Routing and Dynamically Loaded MVC Bundles

By now you probably have seen a lot of AngularJS examples that implements a hardcoded route for each content page. The routing for the sample application uses a convention-based approach that allows the routing table to be un-encumbered with hard coded routes. Using a convention-based approach; all the content pages and associated JavaScript files follow a naming convention that allow the application to parse the route and dynamically determine which JavaScript file is needed with each content page.

The routing table below for the sample application only needs to parse out three routes:

  • one for the root path '/'
  • one for a standard route path such as '/:section/:tree'
  • one route containing a route parameter '/:section/:tree/:id' 

Because I decided to load JavaScript files from ASP.NET bundles, the route configuration code below also needed to include some code that references the applicationConfigurationProvider created previously that holds the bundle information. The bundle information gets parsed into a JSON collection. The JSON collection of bundles will be used to return the virtual path of the bundle. Additionally, the JSON collection will be used to keep track of which bundles have been loaded. Once a bundle has been loaded there is no need to determine if the bundle needs to be downloaded a second time.

There are a few things going on in the routing code. First, whenever the user selects a page to load within a certain functional module, the bundle for all the JavaScript files for that module's bundle is also downloaded. For example, when the user selects one of the content pages in the customers module, the code below checks to see if the bundle for that module has been loaded by checking the isLoaded property of the JSON _bundles collection, and if isLoaded is false, the bundle is loaded and the isLoaded property for the bundle is set to true.

When it is determined that a bundle for a module needs to be downloaded, two things are used to load the bundle; a deferred promise and RequireJS. A deferred promise helps you run functions asynchronously, and the promise returns when it is done processing. In this case, the promise is returned once the bundle has been completely loaded.

Now, the final piece to the puzzle of this article was to determine a way to load a bundle from client-side code. Having used RequireJS in my previous CodeProject.com article (mentioned previously) to load JavaScript files dynamically, I gave RequireJS a try with bundle loading. Using the RequireJS "require" function, I passed the virtual path of the bundle into the require function. As it turns out, the require function will load any path that you give it and it loaded my bundles perfectly. This was a means to an end.

When I first went down the path of using RequireJS to load bundles, I had fully implemented RequireJS and all of its configuration and setup plumbing. As it turned out, I was able to strip out all of that and simply just load the RequireJS library and use it's require function. In fact, I didn't even have to prefix any of my dynamically loaded controllers with a RequireJS define statement. After much trial and error, I had reached the Holy Grail of this article. I could now load server-side bundles through client-side code.

JavaScript
// CodeProjectRouting-production.js

​angular.module("codeProject").config(
['$routeProvider', '$locationProvider', 'applicationConfigurationProvider',

    function ($routeProvider, $locationProvider, applicationConfigurationProvider) {

        var baseSiteUrlPath = $("base").first().attr("href");

        var _bundles = JSON.parse(applicationConfigurationProvider.getBundles());

        this.getApplicationVersion = function () {
            var applicationVersion = applicationConfigurationProvider.getVersion();
            return applicationVersion;
        }

        this.getBundle = function (bundleName) {

            for (var i = 0; i < _bundles.Bundles.length; i++) {
                if (bundleName.toLowerCase() == _bundles.Bundles[i].BundleName) {
                    return _bundles.Bundles[i].Path;
                }
            }
        }

        this.isLoaded = function (bundleName) {
            for (var i = 0; i < _bundles.Bundles.length; i++) {
                if (bundleName.toLowerCase() == _bundles.Bundles[i].BundleName) {
                    return _bundles.Bundles[i].IsLoaded;
                }
            }
        }

        this.setIsLoaded = function (bundleName) {
            for (var i = 0; i < _bundles.length; i++) {
                if (bundleName.toLowerCase() == _bundles.Bundles[i].BundleName) {
                    _bundles.Bundles[i].IsLoaded = true;
                    break;
                }
            }
        }

        $routeProvider.when('/:section/:tree',
        {
            templateUrl: function (rp) { return baseSiteUrlPath + 'views/' + 
                         rp.section + '/' + rp.tree + '.html?v=' + this.getApplicationVersion(); },

            resolve: {

                load: ['$q', '$rootScope', '$location', function ($q, $rootScope, $location) {

                    var path = $location.path().split("/");
                    var parentPath = path[1];

                    var bundle = this.getBundle(parentPath);
                    var isBundleLoaded = this.isLoaded(parentPath);
                    if (isBundleLoaded == false) {

                        this.setIsLoaded(parentPath);

                        var deferred = $q.defer();

                        require([bundle], function () {
                            $rootScope.$apply(function () {
                                deferred.resolve();
                            });
                        });

                        return deferred.promise;

                    }

                }]
            }

        });

        $routeProvider.when('/:section/:tree/:id',
        {
            templateUrl: function (rp) { return baseSiteUrlPath + 'views/' + 
                         rp.section + '/' + rp.tree + '.html?v=' + this.getApplicationVersion(); },

            resolve: {

                load: ['$q', '$rootScope', '$location', function ($q, $rootScope, $location) {

                    var path = $location.path().split("/");
                    var parentPath = path[1];

                    var bundle = this.getBundle(parentPath);
                    var isBundleLoaded = this.isLoaded(parentPath);
                    if (isBundleLoaded == false) {

                        this.setIsLoaded(parentPath);

                        var deferred = $q.defer();

                        require([bundle], function () {
                            $rootScope.$apply(function () {
                                deferred.resolve();
                            });
                        });

                        return deferred.promise;

                    }

                }]
            }

        });

        $routeProvider.when('/',
        {

            templateUrl: function (rp) { return baseSiteUrlPath + 
                                         'views/Home/Index.html?v=' + this.getApplicationVersion(); },

            resolve: {

                load: ['$q', '$rootScope', '$location', function ($q, $rootScope, $location) {

                    var bundle = this.getBundle("home");
                    var isBundleLoaded = this.isLoaded("home");

                    if (isBundleLoaded == false) {

                        this.setIsLoaded("home");

                        var deferred = $q.defer();

                        require([bundle], function () {
                            $rootScope.$apply(function () {
                                deferred.resolve();
                            });
                        });

                        return deferred.promise;
                    }

                }]
            }

        });

        $locationProvider.html5Mode(true);

     }

]);

Debug Routing Table - HTML Cache Busting

Just when I thought I was done with the sample application, I realized that I had to provide two versions of the routing table, one for running the application in debug mode and one for running the application in release mode. In debug mode, JavaScript files are downloaded individually without minification. This is required if you want to debug and set breakpoints in your JavaScript controllers. In fact, the production version of the routing table presented some challenges because I couldn't debug the code because the production routing code uses JavaScript bundles and bundles can't be stepped-into and debugged in Visual Studio as normal. I had to put some console.log commands along with some JavaScript alert statements to develop and test the production routing table.

One thing both versions of the routing code includes; is some support for busting the cache for HTML files. Like bundles and JavaScript, you also need to provide a version number that is appended to the HTML AngularJS views. In both the debug and production routing code, the assembly version number is pulled from the applicationConfigurationProvder and appended to the HTML path for cache busting purposes.

// CodeProjectRouting-debug.js

angular.module("codeProject").config(
['$routeProvider', '$locationProvider', 'applicationConfigurationProvider',

    function ($routeProvider, $locationProvider, applicationConfigurationProvider) {

    this.getApplicationVersion = function () {
        var applicationVersion = applicationConfigurationProvider.getVersion();
        return applicationVersion;
    }

    var baseSiteUrlPath = $("base").first().attr("href");
   
    $routeProvider.when('/:section/:tree',
    {
        templateUrl: function (rp) { return baseSiteUrlPath + 'views/' + 
                     rp.section + '/' + rp.tree + '.html?v=' + this.getApplicationVersion(); },

        resolve: {

            load: ['$q', '$rootScope', '$location', function ($q, $rootScope, $location) {

                var path = $location.path().split("/");
                var directory = path[1]'
                var controllerName = path[2];
               
                var controllerToLoad = "Views/" + directory + "/" + 
                    controllerName + "Controller.js?v=" + this.getApplicationVersion();

                var deferred = $q.defer();

                require([controllerToLoad], function () {
                    $rootScope.$apply(function () {
                        deferred.resolve();
                    });
                });

                return deferred.promise;
              
            }]
        }

    });

    $routeProvider.when('/:section/:tree/:id',
    {
        templateUrl: function (rp) { return baseSiteUrlPath + 'views/' + 
                     rp.section + '/' + rp.tree + '.html?v=' + this.getApplicationVersion(); },

        resolve: {

            load: ['$q', '$rootScope', '$location', function ($q, $rootScope, $location) {

                var path = $location.path().split("/");
                var directory = path[1];
                var controllerName = path[2];

                var controllerToLoad = "Views/" + directory + "/" + controllerName + 
                                       "Controller.js?v=" + this.getApplicationVersion();

                var deferred = $q.defer();

                require([controllerToLoad], function () {
                    $rootScope.$apply(function () {
                        deferred.resolve();
                    });
                });

                return deferred.promise;

            }]
        }

    });

    $routeProvider.when('/',
    {

        templateUrl: function (rp) { return baseSiteUrlPath + 'views/Home/Index.html?v=' + 
                                     this.getApplicationVersion(); },

        resolve: {

            load: ['$q', '$rootScope', '$location', function ($q, $rootScope, $location) {

                var controllerToLoad = "Views/Home/IndexController.js?v=" + 
                                        this.getApplicationVersion();

                var deferred = $q.defer();

                require([controllerToLoad], function () {
                    $rootScope.$apply(function () {
                        deferred.resolve();
                    });
                });

                return deferred.promise;

            }]
        }

    });

    $locationProvider.html5Mode(true);  

}]);

Testing the Browser Cache

One of the things you will want to do when developing a web application is to test all of the browser caching and cache busting functionality.  You will want to make sure that your application content is being properly downloaded and cached and that the content is coming from the cache after subsequent page requests.

You will also will want to make some changes to your content, rebuild your application and make sure that the changes bust the cache and the new versions of your content gets downloaded again.

To test all this, I ran the application in release mode by setting debug mode to false in the web.config and ran the application in Chrome and hit F12 to bring up the Network tab in Chrome. This is where you can see how long it takes to download your content and see if the content is coming from the server or from the browser cache. You can even see your bundles being downloaded with it's associated version number rendered by the ASP.NET bundling feature.

Eventually as you hit all the pages in your application, you will notice that all your content is coming from the browser cache. This is the beauty of the Single Page Application architecture.  All your content ends up getting cached for great response time and the only need to hit the web server is to return JSON formatted data from a RESTful Web API call to be presented in the views.

Image 4

Other Points of Interest

Other points of interest in the sample application include a couple other .NET libraries that execute on the server side. For validating data entry input, this application uses the FluentValidation library in the business layer. 
FluentValidation is a small validation library for .NET that uses a fluent interface and  lambda expressions for building validation rules.

Image 5

When trying to create a customer in the sample application, the Customer Code and Company Name are required fields. The sample application manages validations in the business layer using the FluentValidation library. By passing a populated customer object into the CreateCustomer method, the properties on the object can be validated against business rules that are set up with FluentValidation expressions.  If the business object fails validation,  the business layer can return a collection of errors from the validation library and send the error collection back to the client that can be used to render error messages in the browser.

C#
/// <summary>
/// Create Customer
/// </summary>
/// <param name="customer"></param>
/// <param name="transaction"></param>
/// <returns></returns>
public Customer CreateCustomer(Customer customer, out TransactionalInformation transaction)
{
     transaction = new TransactionalInformation();

     try
     {
         CustomerBusinessRules customerBusinessRules = new CustomerBusinessRules();
         ValidationResult results = customerBusinessRules.Validate(customer);

         bool validationSucceeded = results.IsValid;
         IList<ValidationFailure> failures = results.Errors;

         if (validationSucceeded == false)
         {
             transaction = ValidationErrors.PopulateValidationErrors(failures);
             return customer;
         }

         _customerDataService.CreateSession();
         _customerDataService.BeginTransaction();
         _customerDataService.CreateCustomer(customer);
         _customerDataService.CommitTransaction(true);

         transaction.ReturnStatus = true;
         transaction.ReturnMessage.Add("Customer successfully created.");

    }
    catch (Exception ex)
    {
         string errorMessage = ex.Message;
         transaction.ReturnMessage.Add(errorMessage);
         transaction.ReturnStatus = false;
    }
    finally
    {
        _customerDataService.CloseSession();
    }

    return customer;
}

Below is a customer business rules class that defines the business rules for the customer object. Using the FluentValidation library, you define a set of lambda expressions and create the business rules and the error messages associated with each validation.  The FluentValidation library comes complete with a full set of different types of lambda expressions for validating properties on a business object or entity.

C#
// CustomerBusinessRules.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FluentValidation;
using CodeProject.Business.Entities;
using System.Configuration;
using CodeProject.Interfaces;

namespace CodeProject.Business
{
    public class CustomerBusinessRules : AbstractValidator<Customer>
    {

        public CustomerBusinessRules()
        {       
            RuleFor(c => c.CompanyName).NotEmpty().WithMessage("Company Name is required.");
            RuleFor(c => c.CustomerCode).NotEmpty().WithMessage("Customer Code is required.");   
        }

    }

}

One more point of interest in the sample application includes the implementation of Dependency Injection with the Ninject library. When Ninject is installed from NuGet, a configuration file NinjectWebCommon.cs is created for you . This is where you can tell the Ninject library what objects you want to have created when certain areas of the application are executed, such as a within a Web API service. In the RegisterServices below, I am telling Ninject to associate a Customer Data Service and a Product Data Service to their respective interfaces that they implement. This tells Ninject where to load the dll references which could be any dll that implements the matching interface.

C#
// NinjectWebCommon.cs

namespace CodeProject.Portal.App_Start
{
    using System;
    using System.Web;

    using Microsoft.Web.Infrastructure.DynamicModuleHelper;

    using Ninject;
    using Ninject.Web.Common;

    public static class NinjectWebCommon 
    {
        private static readonly Bootstrapper bootstrapper = new Bootstrapper();

        /// <summary>
        /// Starts the application
        /// </summary>
        public static void Start() 
        {
            DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
            DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
            bootstrapper.Initialize(CreateKernel);
        }
        
        /// <summary>
        /// Stops the application.
        /// </summary>
        public static void Stop()
        {
            bootstrapper.ShutDown();
        }
        
        /// <summary>
        /// Creates the kernel that will manage your application.
        /// </summary>
        /// <returns>The created kernel.</returns>
        private static IKernel CreateKernel()
        {
            var kernel = new StandardKernel();
            try
            {
                kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
                kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();

                RegisterServices(kernel);
                System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver = 
                                new Ninject.Web.WebApi.NinjectDependencyResolver(kernel);
                return kernel;
            }
            catch
            {
                kernel.Dispose();
                throw;
            }
        }

        /// <summary>
        /// Load your modules or register your services here!
        /// </summary>
        /// <param name="kernel">The kernel.</param>
        private static void RegisterServices(IKernel kernel)
        {
            kernel.Bind<CodeProject.Interfaces.ICustomerDataService>().
                                    To<CodeProject.Data.EntityFramework.CustomerDataService>();
            kernel.Bind<CodeProject.Interfaces.IProductDataService>().
                                    To<CodeProject.Data.EntityFramework.ProductDataService>();

        }        
    }
}

Using the Ninject data annotation [Inject], you can tell the Ninject library when and where to instantiate your objects. In the Web API service below, the Customer Data Service gets created by Ninject. Since the Customer Business Service is dependent on the Customer Data Service to access data, the Customer Data Service is injected into the constructor of the Customer Business Service. All this is done by creating an Interface for the Customer Data Service and then simply implementing the Interface in the Customer Data Service. Dependency Injection is great for creating loosly coupled application layers which allows you to mock and test application code in isolation from each other.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using CodeProject.Portal.Models;
using CodeProject.Business.Entities;
using CodeProject.Business;
using CodeProject.Interfaces;
using Ninject;

namespace CodeProject.Portal.WebApiControllers
{
    [RoutePrefix("api/CustomerService")]
    public class CustomerServiceController : ApiController
    {

        [Inject]
        public ICustomerDataService _customerDataService { get; set; }

        /// <summary>
        /// Create Customer
        /// </summary>
        /// <param name="request"></param>
        /// <param name="customerViewModel"></param>
        /// <returns></returns>
        [Route("CreateCustomer")]     
        [HttpPost]
        public HttpResponseMessage CreateCustomer(HttpRequestMessage request, 
                                   [FromBody] CustomerViewModel customerViewModel)
        {
            TransactionalInformation transaction;

            Customer customer = new Customer();
            customer.CompanyName = customerViewModel.CompanyName;
            customer.ContactName = customerViewModel.ContactName;
            customer.ContactTitle = customerViewModel.ContactTitle;
            customer.CustomerCode = customerViewModel.CustomerCode;
            customer.Address = customerViewModel.Address;
            customer.City = customerViewModel.City;
            customer.Region = customerViewModel.Region;
            customer.PostalCode = customerViewModel.PostalCode;
            customer.Country = customerViewModel.Country;
            customer.PhoneNumber = customerViewModel.PhoneNumber;
            customer.MobileNumber = customerViewModel.MobileNumber;

            CustomerBusinessService customerBusinessService = 
                                    new CustomerBusinessService(_customerDataService);

            customerBusinessService.CreateCustomer(customer, out transaction);
            if (transaction.ReturnStatus == false)
            {                
                customerViewModel.ReturnStatus = false;
                customerViewModel.ReturnMessage = transaction.ReturnMessage;
                customerViewModel.ValidationErrors = transaction.ValidationErrors;

                var responseError = Request.CreateResponse<CustomerViewModel>
                                    (HttpStatusCode.BadRequest, customerViewModel);
                return responseError;
              
            }

            customerViewModel.CustomerID = customer.CustomerID;
            customerViewModel.ReturnStatus = true;
            customerViewModel.ReturnMessage = transaction.ReturnMessage;

            var response = Request.CreateResponse<CustomerViewModel>
                           (HttpStatusCode.OK, customerViewModel);
            return response;

        }

Conclusion

Integrating AngularJS with ASP.NET MVC and ASP.NET Bundling seemed like a simple endeavor at first that turned into a challenge. With each iteration of trial and failure, this challenge turned into a obsession and then later into a vendetta. I simply wanted to make all this work together and I wasn't going to stop trying.

You can debate the merits of bundling and minifying your JavaScript and CSS with ASP.NET Bundling and integrating this with AngularJS compared to using one of the popular minification tools in the Grunt and Gulp space,  but if you are Microsoft developer who just likes to press a button and publish your application from Visual Studio without learning any additional technologies or tools, you'll probably will want to use the ASP.NET Bundling functionality. I found this functionality to be exactly what I wanted in the end; it just took a long time to figure out how to integrate it with AngularJS.

There is a lot of technology to write about these days. Some of my future articles may include AngularJS 2 and the rest of the MEAN stack, including Node.JS, Express and MongoDB.

There is also Apache Cordova for mobile application development which was included in the latest release of Visual Studio 2015. The Ionic framework, an Advanced HTML5 Hybrid Mobile Application Framework that works with Apache Cordova looks promising too. Ionic is said to make it easy to build great and interactive mobile applications using HTML5 and AngularJS. Stay tuned!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Joey Software Solutions
United States United States
Mark Caplin has specialized in Information Technology solutions for the past 30 years. Specializing in full life-cycle development projects for both enterprise-wide systems and Internet/Intranet based solutions.

For the past fifteen years, Mark has specialized in the Microsoft .NET framework using C# as his tool of choice. For the past four years Mark has been implementing Single Page Applications using the Angular platform.

When not coding, Mark enjoys playing tennis, listening to U2 music, watching Miami Dolphins football and watching movies in Blu-Ray technology.

In between all this, his wife of over 25 years, feeds him well with some great home cooked meals.

You can contact Mark at mark.caplin@gmail.com

...

Comments and Discussions

 
QuestionGreat article! Pin
rahbert22-Oct-18 4:33
rahbert22-Oct-18 4:33 
QuestionAngular Indexing issues Pin
Member 1401527610-Oct-18 14:11
Member 1401527610-Oct-18 14:11 
QuestionAngularJS SPA and SEO Crawling and Indexing Pin
salim_648-Feb-18 1:20
salim_648-Feb-18 1:20 
QuestionHow to use Angular-Ui modal dialog in this architecture Pin
Umesh AP4-Dec-17 2:56
Umesh AP4-Dec-17 2:56 
QuestionCustomers are not loading in CUstomerInquiry page. Am I missing something? Pin
pv7514-Jun-17 3:47
pv7514-Jun-17 3:47 
QuestionExcellent Article Pin
pv7513-Jun-17 6:05
pv7513-Jun-17 6:05 
QuestionProblem loading with Visual Studio 2015 Pin
mago30-May-17 6:04
mago30-May-17 6:04 
Questioncan you provide Database script for MS SQL Server 2012 ? Pin
Alok Kumar Sharma30-May-17 2:33
Alok Kumar Sharma30-May-17 2:33 
AnswerRe: can you provide Database script for MS SQL Server 2012 ? Pin
Henrique Fernandes Lopes31-Aug-17 14:13
Henrique Fernandes Lopes31-Aug-17 14:13 
GeneralRe: can you provide Database script for MS SQL Server 2012 ? Pin
Alok Kumar Sharma31-Aug-17 22:34
Alok Kumar Sharma31-Aug-17 22:34 
PraiseExcellent Article Pin
Member 110472708-May-17 23:34
Member 110472708-May-17 23:34 
QuestionFluentValidation not working Pin
Umesh AP5-Apr-17 23:53
Umesh AP5-Apr-17 23:53 
QuestionAppriciation Pin
NikhilParate30-Jan-17 1:05
NikhilParate30-Jan-17 1:05 
QuestionPaging is not working. Pin
Ricky Yadav6-Jan-17 4:18
Ricky Yadav6-Jan-17 4:18 
GeneralVery helpfull Pin
Surendrasinh Rathod30-Dec-16 7:31
Surendrasinh Rathod30-Dec-16 7:31 
Questionpaging Pin
Abhinandan Nimsarkar29-Dec-16 2:19
Abhinandan Nimsarkar29-Dec-16 2:19 
QuestionUsing Kendo UI component Pin
mymmb21-Nov-16 20:17
mymmb21-Nov-16 20:17 
QuestionPagination Not working Pin
jadhav rahul3-Nov-16 1:30
jadhav rahul3-Nov-16 1:30 
GeneralGood Article Pin
Alireza_13622-Nov-16 7:39
Alireza_13622-Nov-16 7:39 
QuestionFIle not found error Pin
jankhana_200815-Sep-16 21:18
jankhana_200815-Sep-16 21:18 
AnswerRe: FIle not found error Pin
The Jonas Persson15-Feb-17 18:59
The Jonas Persson15-Feb-17 18:59 
Questionversion of visual studio. Pin
Shaikh Ansar9-Sep-16 6:08
Shaikh Ansar9-Sep-16 6:08 
AnswerRe: version of visual studio. Pin
Umesh AP25-Oct-16 19:52
Umesh AP25-Oct-16 19:52 
QuestionUnit testing controller Pin
ChungNguyen8-Sep-16 21:25
professionalChungNguyen8-Sep-16 21:25 
How would you suggest writing unit tests in this case? Since the controller are being registered on demand.
QuestionLoading bundles in debug mode. Pin
SergTheSpy6-Aug-16 14:17
SergTheSpy6-Aug-16 14:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.