Introduction
In this article, I explain how to setup your ASP.NET 5 project for a single page application (SPA) in Angular JS. I have used MVC 6 Web API for some static data to display, eventually I will use database to store and display the data using Web API. I will also show how to copy the front-end JavaScript files automatically using gulp, bower and npm. In later modules, I will describe how to combine and minify the front-end JavaScripts files.
In my demo, I have Visual Studio 2015 RC, the latest pre-release version of next generation Visual Studio for cross platform development. I will also use Entity Framework 7 and DNX (.NET Execution Environment) to migrate the database and run the web site in different environments.
Background
Single Page Application (SPA) now-a-days is well-known term due to the advancement of front-end tools and technologies that have been evolved in recent years. Among them, Angular JS is the most popular in building SPAs. Recent release of Microsoft's Visual Studio 2015 RC is a fantastic platform for developing SPAs in which the front-end tools can be easily managed and maintained for the application. Also the Web API 2 in MVC 6 is being used as back end service that are very efficient support for calling it from the front-end.
I will be using Visual Studio 2015 RC, MVC 6 Web API2, gulp, bower and npm throughout the journey to work with front-end and back-end development.
Creating the Project
To start with, I have opened Visual Studio 2015 RC, and from the Start page, I have selected New Project and from the available template, I chose ASP.NET Web Application. I have named the project as BooksAngularApp
.
After clicking Ok, the template select wizard appears where I will select Web Site from the available ASP.NET 5 Preview Templates. The reason I have chosen the template is that the template provides us a framework that is helpful rather than starting the empty template.
To note here, the Web Site has gulp task runner as the default which I will be using to combine and minify the front-end JavaScripts and CSS files.
After clicking OK, I have got the following project created with the project structure and the preview page as below:
Task Runner
If we look at the project structure on the Solution Explorer, we can see the pre-installed packages such as bootstrap, jquery, hammer, etc. in the bower folder. Also, we can see the npm folder where gulp and rimraf have been installed as front-end tools that are being used to manage the front-end scripts, especially JavaScripts and CSS files.
Now let us have a look at the gulpfile.js which will be used to manage our front-end code or scripts.
var gulp = require("gulp"),
rimraf = require("rimraf"),
fs = require("fs");
eval("var project = " + fs.readFileSync("./project.json"));
var paths = {
bower: "./bower_components/",
lib: "./" + project.webroot + "/lib/"
};
gulp.task("clean", function (cb) {
rimraf(paths.lib, cb);
});
gulp.task("copy", ["clean"], function () {
var bower = {
"bootstrap": "bootstrap/dist/**/*.{js,map,css,ttf,svg,woff,eot}",
"bootstrap-touch-carousel": "bootstrap-touch-carousel/dist/**/*.{js,css}",
"hammer.js": "hammer.js/hammer*.{js,map}",
"jquery": "jquery/jquery*.{js,map}",
"jquery-validation": "jquery-validation/jquery.validate.js",
"jquery-validation-unobtrusive":
"jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"
}
for (var destinationDir in bower) {
gulp.src(paths.bower + bower[destinationDir])
.pipe(gulp.dest(paths.lib + destinationDir));
}
});
We can see the scripts runs some tasks to copy and clean the folders for the JavaScripts and CSS files in the wwwroot folders whenever any change is made to the files during development.
Let us see how these happen. I have opened the Task Runner from View->Other Windows->Task Runner.
We can the clean and copy tasks as defined in the gulpfile.js. Before we running any of them, we can see a lib folder has a copy of all the JavaScripts and CSS files by default.
I have run the clean task and it cleans the lib folder in wwwroot directory, that means gulp
is being used to run the clean task to remove the static files.
I have run the copy
task and the lib folder is back with the copied files again.
Installing Angular Packages
For our SPA, I will be using three angular packages: angular
, angular-route
and angular-resource
. Currently, the default bower.json file looks like below:
>
{
"name": "ASP.NET",
"private": true,
"dependencies": {
"bootstrap": "3.0.0",
"jquery": "1.10.2",
"jquery-validation": "1.11.1",
"jquery-validation-unobtrusive": "3.2.2",
"hammer.js": "2.0.4",
"bootstrap-touch-carousel": "0.8.0"
}
}
I have added the three angular
packages as below:
{
"name": "ASP.NET",
"private": true,
"dependencies": {
"bootstrap": "3.0.0",
"jquery": "1.10.2",
"jquery-validation": "1.11.1",
"jquery-validation-unobtrusive": "3.2.2",
"hammer.js": "2.0.4",
"bootstrap-touch-carousel": "0.8.0",
"angular": "*",
"angular-route": "*",
"angular-resource": "*"
}
}
After adding the three lines for angular
, I haven't saved the bower.json file yet, and in the Solution Explorer in Bower folder, there are no angular
modules.
After saving the file, we can see the angular components on the bower folder in the dependencies list after few seconds.
You might see the bower folder as below where the angular
modules are not installed:
If they are not installed, right click on the Bower folder and click on Restore Packages and it will restore all the uninstalled packages that are added to the bower.json file.
Creating gulp Tasks for Angular Scripts
So I have got angular
packages installed now and we need to update the task runner so that the angular files are copied over the lib folder with the other bower components.
I have updated the copy
task as below:
gulp.task("copy", ["clean"], function () {
var bower = {
"bootstrap": "bootstrap/dist/**/*.{js,map,css,ttf,svg,woff,eot}",
"bootstrap-touch-carousel": "bootstrap-touch-carousel/dist/**/*.{js,css}",
"hammer.js": "hammer.js/hammer*.{js,map}",
"jquery": "jquery/jquery*.{js,map}",
"jquery-validation": "jquery-validation/jquery.validate.js",
"jquery-validation-unobtrusive":
"jquery-validation-unobtrusive/jquery.validate.unobtrusive.js",
"angular": "angular/angular*.{js,map}",
"angular-route": "angular-route/angular-route*.{js,map}",
"angular-resource": "angular-resource/angular-resource*.{js,map}"
}
Now I have run the copy
task again and I can see the angular files are copied over to the lib folder of wwwroot.
I have configured the copy
and copyapp
tasks to be run after every build of the application to make sure the changes are copied over.
Now we are ready to start working with the SPA BooksAngularApp
.
Creating Data Model and Web API
For the SPA, I will be using a fictitious book store where we will be able to view the list of books and their details. In this module, I will be using some static data for the book items. For this purpose, I need a data model that will be used for accessing and displaying the books.
I have added a new class called Book
in the Models folder of my project and the model looks like below:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
namespace BooksAngularApp.Models
{
public class Book
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public string Type { get; set; }
public DateTime DatePublished { get; set; }
public decimal Price { get; set; }
}
}
After this, I have created an MVC Controller Class named as BooksController
which will be the API Controller for our data access back and forth.
As default, the Controller
class looks like below:
I have made changes to the class to be as an API Controller by adding the...
[Route("api/[controller]")]
...before the class definition so that by the Route
attribute, it will act as an API Controller.
I have added the HttpGet
method to display the static list of books as below:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;
using BooksAngularApp.Models;
namespace BooksAngularApp.Controllers
{
[Route("api/[controller]")]
public class BooksController : Controller
{
[HttpGet]
public IEnumerable<Book> Get()
{
return new List<Book>
{
new Book {Id=1, Title="Wonders of the Sky",
Author="Martin James", DatePublished=Convert.ToDateTime("1/1/2013"),
Type="Science",Price=23.23m },
new Book {Id=2, Title="Secrets of the Mind ",
Author="Allan Sue ", DatePublished=Convert.ToDateTime("2/1/2011"),
Type="Psychology", Price=12.50m },
new Book {Id=3, Title="We are Alive",
Author="Dick Smith", DatePublished=Convert.ToDateTime("2/11/2010"),
Type="Science Fiction", Price=21.25m } ,
new Book {Id=4, Title="Last day of the world",
Author="Martin James", DatePublished=Convert.ToDateTime("1/1/2013"),
Type="History", Price=10.40m },
};
}
}
}
To see whether the API Controller that I created is working, let us run the API from the browser:
We can see the data is being displayed as JSON from the API Controller, that means, we are ready to use the API to display the data.
Writing Angular Code
I have created an app folder in my application root where I will be placing all angular related modules such as controllers, services, etc.
I have added app.js as the first module as below:
(function () {
'use strict';
angular.module('booksApp', ['booksServices']);
})();
The booksApp
Angular module will depend on the service called booksServices
which I will define now.
I have created a new folder called services within app folder and I have added booksServices.js as below:
(function () {
'use strict';
var booksServices = angular.module('booksServices', ['ngResource']);
booksServices.factory('Books', ['$resource',
function ($resource) {
return $resource('/api/books/', {}, {
query: { method: 'GET', params: {}, isArray: true }
});
}]);
})();
The service will inject ngResource
as dependency which will eventually call the API Controller to get the data from the API.
Now we need another Angular module which we will call booksController
which I have created in controllers folder within app folder named as booksController.js.
(function () {
'use strict';
angular
.module('booksApp')
.controller('booksController',booksController)
booksController.$inject = ['$scope', 'Books'];
function booksController($scope, Books)
{
$scope.books = Books.query();
}
})();
Here we can see, the controller injects the scope
object which returns the query results from the API.
Writing Tasks for App Scripts
We need to write the task to copy the files over to the wwwroot folder as with other JavaSript files we did before.
var paths = {
bower: "./bower_components/",
lib: "./" + project.webroot + "/lib/",
app: "./" + project.webroot + "/app/",
srcapp: "./app/",
};
gulp.task("cleanappp", function (cb) {
rimraf(paths.app, cb);
});
gulp.task("copyapp",["cleanappp"], function () {
var app = {
"controllers": "controllers/booksController.js",
"services": "services/booksServices*.js",
"/": "app.js"
}
for (var destinationDir in app) {
gulp.src(paths.srcapp + app[destinationDir])
.pipe(gulp.dest(paths.app + destinationDir));
}
});
I have added the app and srcapp in the paths variable to define the source and destination for the app scripts. I have added a new task called cleanapp which will be used to clean the app folder in wwwroot. Finally, I have added another task called copyapp which has a inject of cleanapp task that means before copying the app scripts, it will clean up the folder first and then copy all script files.
<!DOCTYPE html>
<html ng-app="booksApp">
<head>
<meta charset="utf-8" />
<title>List of Books</title>
<link href="lib/bootstrap/css/bootstrap.css" rel="stylesheet" />
<link href="lib/bootstrap/css/bootstrap-theme.css" rel="stylesheet" />
<script src="lib/jquery/jquery.min.js"></script>
<script src="lib/angular/angular.js"></script>
<script src="lib/bootstrap/js/bootstrap.js"></script>
<script src="lib/angular-resource/angular-resource.js"></script>
<script src="app/app.js"></script>
<script src="app/controllers/booksController.js"></script>
<script src="app/services/booksServices.js"></script>
</head>
<body ng-cloak>
<div ng-controller="booksController">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>Type</th>
<th>Date Published</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="book in books">
<td>{{book.Title}}</td>
<td>{{book.Author}}</td>
<td>{{book.Type}}</td>
<td>{{book.DatePublished|date:"dd/MM/yyyy"}}</td>
<td>${{book.Price}}</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
Here, the web site is running both IIS Express and WebListener - side-by-side.
History
- 16th May, 2015: Initial submission
- 24th May, 2015: Added ZIP solution to the article
Source Code
The source code for this article is available in the GitHub at: https://github.com/mostafaasad/ASPNET5/tree/master/BooksAngular-Part1/BooksAngularApp
Next
In the next module, I will explore searching and filtering data and migration of the database using Entity Framework 7 and DNX.