Single Page Application Using ASP.NET Core - Angular

In this post, we are going to create a Single Page Application (SPA) with AngularJS and ASP.Net Core. We will use Angular-UI-Router for our application routing instead of MVC routing.

In this post, we are going to create a Single Page Application (SPA) with AngularJS and ASP.Net Core. We will use Angular-UI-Router for our application routing instead of MVC routing.

If you are new to ASP.Net Core, get a complete startup overview here.

Prerequisites

Before getting started make sure development environment is prepared properly. These are prerequisites to develop the sample application.

  1. Visual Studio 2017
  2. .NET Core 2.0
  3. NodeJS
  4. MSSQL 2016
  5. IIS

Key Points

The following points will be discussed to develop the sample application:

  1. Manage Packages
  2. Transfer Libraries Using Gulp
  3. Working with AngularJs App
  4. Adding Middleware
  5. Scaffolding MSSQL Database
  6. Web-API
  7. Enable CORS
  8. Build AngularJS App Using Gulp
  9. Test Application (CRUD)
  10. Publish in IIS 

Getting started

Let’s get started. Open Visual Studio to create a new project, Goto > File > New > Project then choose a project for creating ASP.Net Core application with ASP.Net Core 2.0 MVC Web Application like below screenshot.

ASP.NET Core

Initially, application folder structure might look like this, we are going to modify those following our requirements.

ASP.NET Core

We are using AngularJS routing to create the Single Page Application by ASP.NET static file serving.

We need to point a static page to get all the requests, then Angular will handle the request and decide which page to serve. Let’s create HTML page in a wwwroot folder. This will be our main page to load on client request.  

Serve Default Page

Now, we need to modify Startup.cs file to add middleware for serving the static HTML page. Here is the code snippet which is going to serve the page. In this app, I have created it as “index.html”. 

  1. DefaultFilesOptions options = new DefaultFilesOptions();  
  2. options.DefaultFileNames.Clear();  
  3. options.DefaultFileNames.Add("/index.html");  
  4. app.UseDefaultFiles(options);  

Middleware to Handle Client Side Routes Fallback

To avoid 404 error while reloading the page in AngularJS SPA app we need to add middleware to handle client side route fallback. The below code snippet will take care of that.

  1. app.Use(async (context, next) =>  
  2. {  
  3.     await next();  
  4.     if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value))  
  5.     {  
  6.         context.Request.Path = "/index.html";  
  7.         context.Response.StatusCode = 200;  
  8.         await next();  
  9.     }  
  10. });   

Get more details on middleware here: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?tabs=aspnetcore2x

If we run our application to test, the static page will serve by default. Now let’s get started with client-end package managing.

Client-Side - Package Manage

We need to add some frontend package like AngularJS, as we can see our initial template has nothing like that.

ASP.NET Core

To add new package right click on a project then click on “Manage Bower Packages

ASP.NET Core

ASP.NET Core

Install the required package by searching one by one. Finally, the installed packages will be listed like below screenshot. 

ASP.NET Core

Let’s add some Node Packages to our application, first we need to add npm config file to our project. Right-click the project Goto > Add > New Item.

From the new item add window choose npm Configuration File like the below screenshot.

ASP.NET Core

Add all those packages to devDependencies, it’ll automatically install to our project.

  1. "gulp""^3.9.1",  
  2. "gulp-concat""^2.6.1",  
  3. "gulp-rename""^1.2.2",  
  4. "gulp-cssmin""^0.2.0",  
  5. "gulp-uglify""^3.0.0",  
  6. "gulp-htmlclean""^2.7.20",  
  7. "rimraf""^2.6.2"  

Finally our package dependencies are listed.

ASP.NET Core

Let’s transfer the required libraries from bower_componen folder to “wwwroot” for calling it to the main HTML page.

Add a gulp file then copy the below code snippet and paste it to the newly added file.

  1. /// <binding AfterBuild='build-all' />  
  2.   
  3. var gulp = require("gulp"),  
  4.     rimraf = require("rimraf"),  
  5.     concat = require("gulp-concat"),  
  6.     cssmin = require("gulp-cssmin"),  
  7.     uglify = require("gulp-uglify"),  
  8.     rename = require("gulp-rename");  
  9.   
  10. var root_path = {  
  11.     webroot: "./wwwroot/"  
  12. };  
  13.   
  14. //library source  
  15. root_path.nmSrc = "./bower_components/";  
  16.   
  17. //library destination  
  18. root_path.package_lib = root_path.webroot + "lib/";  
  19.   
  20.   
  21. gulp.task('copy-lib-css'function () {  
  22.     gulp.src('./bower_components/bootstrap/dist/css/bootstrap.min.css')  
  23.         .pipe(gulp.dest(root_path.webroot + '/css/'));  
  24.     gulp.src('./bower_components/toastr/toastr.min.css')  
  25.         .pipe(gulp.dest(root_path.webroot + '/css/'));  
  26. });  
  27.   
  28. gulp.task('copy-lib-js'function () {  
  29.     gulp.src('./bower_components/jquery/dist/jquery.min.js')  
  30.         .pipe(gulp.dest(root_path.package_lib + '/jquery/'));  
  31.   
  32.     gulp.src('./bower_components/bootstrap/dist/js/bootstrap.min.js')  
  33.         .pipe(gulp.dest(root_path.package_lib + '/bootstrap/'));  
  34.   
  35.     gulp.src('./bower_components/toastr/toastr.min.js')  
  36.         .pipe(gulp.dest(root_path.package_lib + '/toastr/'));  
  37.   
  38.     gulp.src('./bower_components/angular/angular.min.js')  
  39.         .pipe(gulp.dest(root_path.package_lib + 'angular/'));  
  40.     gulp.src('./bower_components/angular-ui-router/release/angular-ui-router.min.js')  
  41.         .pipe(gulp.dest(root_path.package_lib + 'angular/'));  
  42. });  
  43.   
  44. gulp.task("copy-all", ["copy-lib-css""copy-lib-js"]);  
  45. //Copy End  

Now from the top menu in Visual Studio Goto > View > Other Window > Task Runner Explorer. Let’s get the task list by refreshing then run the task like the below screenshot.

ASP.NET Core

As we can see the libraries have transferred to the root folder. We have used an admin template so the others libraries are listed in “js” folder.

ASP.NET Core

We are now going to add the AngularJs library reference in the main layout page. Like below.

  1. <!DOCTYPE html>  
  2. <html lang="en" ng-app="templating_app">  
  3.    <head></head>  
  4.    <body>  
  5.     <!-- Core JS Files   -->  
  6. <script src="/lib/jquery/jquery-1.10.2.js" type="text/javascript"></script>  
  7. <script src="/lib/bootstrap/tether.min.js"></script>  
  8. <script src="/lib/bootstrap/bootstrap.min.js" type="text/javascript"></script>  
  9.   
  10. <!-- App JS Files   -->  
  11. <script src="/lib/angular/angular.min.js"></script>  
  12. <script src="/lib/angular/angular-ui-router.min.js"></script>  
  13.    </body>  
  14. </html>  

AngularJS Application

Let’s get started with AngularJS application, create a new folder name it “app”. The “app” folder will hold our all of frontend development files.

Let’s create all required file and folders. In shared folder, we have added partial views like sidebar/topbar menu that is going to call by an Angular directive.

ASP.NET Core

Module

This will define our application. 

  1. var templatingApp;  
  2. (  
  3.     function () {  
  4.         'use strict';  
  5.         templatingApp = angular.module('templating_app', ['ui.router']);  
  6.     }  
  7. )();  

Route

This file will handle our application route coming from URL’s.

  1. templatingApp.config(['$locationProvider''$stateProvider''$urlRouterProvider''$urlMatcherFactoryProvider''$compileProvider',  
  2.     function ($locationProvider, $stateProvider, $urlRouterProvider, $urlMatcherFactoryProvider, $compileProvider) {  
  3.   
  4.         //console.log('Appt.Main is now running')  
  5.         if (window.history && window.history.pushState) {  
  6.             $locationProvider.html5Mode({  
  7.                 enabled: true,  
  8.                 requireBase: true  
  9.             }).hashPrefix('!');  
  10.         };  
  11.         $urlMatcherFactoryProvider.strictMode(false);  
  12.         $compileProvider.debugInfoEnabled(false);  
  13.   
  14.         $stateProvider  
  15.             .state('home', {  
  16.                 url: '/',  
  17.                 templateUrl: './views/home/home.html',  
  18.                 controller: 'HomeController'  
  19.             })  
  20.             .state('dashboard', {  
  21.                 url: '/dashboard',  
  22.                 templateUrl: './views/home/home.html',  
  23.                 controller: 'HomeController'  
  24.             })  
  25.             .state('user', {  
  26.                 url: '/user',  
  27.                 templateUrl: './views/user/user.html',  
  28.                 controller: 'UserController'  
  29.             })  
  30.             .state('about', {  
  31.                 url: '/about',  
  32.                 templateUrl: './views/about/about.html',  
  33.                 controller: 'AboutController'  
  34.             });  
  35.   
  36.         $urlRouterProvider.otherwise('/home');  
  37.     }]);   

Route Problem

This file will handle our application route coming from URLs. In AngularJs we need to enable HTML5 Mode to remove /#!/ symbols in the URL’’s below code snippet for enabling the mode.

  1. $locationProvider.html5Mode({  
  2.     enabled: true,  
  3.     requireBase: true  
  4. }).hashPrefix('!');  

Also, we need to specify the base in main page.

<base href="/">

Directives

For Top Navigation Bar

  1. templatingApp.directive("navbarMenu"function () {  
  2.     return {  
  3.         restrict: 'E',  
  4.         templateUrl: 'views/shared/navbar/nav.html'  
  5.     };  
  6. });  

For Top Side Bar

  1. templatingApp.directive("sidebarMenu"function () {  
  2.     return {  
  3.         restrict: 'E',  
  4.         templateUrl: 'views/shared/sidebar/menu.html'  
  5.     };  
  6. });  

Angular Controller

This is the Angular Controller which will manage the views and perform all HTTP calls from client end to server.

  1. templatingApp.controller('UserController', ['$scope''$http'function ($scope, $http) {  
  2.     $scope.title = "All User";  
  3.     $scope.ListUser = null;  
  4.     $scope.userModel = {};  
  5.     $scope.userModel.id = 0;  
  6.     getallData();  
  7.   
  8.     //******=========Get All User=========******  
  9.     function getallData() {  
  10.         $http({  
  11.             method: 'GET',  
  12.             url: '/api/Values/GetUser/'  
  13.         }).then(function (response) {  
  14.             $scope.ListUser = response.data;  
  15.         }, function (error) {  
  16.             console.log(error);  
  17.         });  
  18.     };  
  19.   
  20.     //******=========Get Single User=========******  
  21.     $scope.getUser = function (user) {  
  22.         $http({  
  23.             method: 'GET',  
  24.             url: '/api/Values/GetUserByID/' + parseInt(user.id)  
  25.         }).then(function (response) {  
  26.             $scope.userModel = response.data;  
  27.         }, function (error) {  
  28.             console.log(error);  
  29.         });  
  30.     };  
  31.   
  32.     //******=========Save User=========******  
  33.     $scope.saveUser = function () {  
  34.         $http({  
  35.             method: 'POST',  
  36.             url: '/api/Values/PostUser/',  
  37.             data: $scope.userModel  
  38.         }).then(function (response) {  
  39.             $scope.reset();  
  40.             getallData();  
  41.         }, function (error) {  
  42.             console.log(error);  
  43.         });  
  44.     };  
  45.   
  46.     //******=========Update User=========******  
  47.     $scope.updateUser = function () {  
  48.         $http({  
  49.             method: 'PUT',  
  50.             url: '/api/Values/PutUser/' + parseInt($scope.userModel.id),  
  51.             data: $scope.userModel  
  52.         }).then(function (response) {  
  53.             $scope.reset();  
  54.             getallData();  
  55.         }, function (error) {  
  56.             console.log(error);  
  57.         });  
  58.     };  
  59.   
  60.     //******=========Delete User=========******  
  61.     $scope.deleteUser = function (user) {  
  62.         var IsConf = confirm('You are about to delete ' + user.Name + '. Are you sure?');  
  63.         if (IsConf) {  
  64.             $http({  
  65.                 method: 'DELETE',  
  66.                 url: '/api/Values/DeleteUserByID/' + parseInt(user.id)  
  67.             }).then(function (response) {  
  68.                 $scope.reset();  
  69.                 getallData();  
  70.             }, function (error) {  
  71.                 console.log(error);  
  72.             });  
  73.         }  
  74.     };  
  75.   
  76.     //******=========Clear Form=========******  
  77.     $scope.reset = function () {  
  78.         var msg = "Form Cleared";  
  79.         $scope.userModel = {};  
  80.         $scope.userModel.id = 0;  
  81.     };  
  82. }]); 

Server-Side

Database

Let’s Create a Database in MSSQL Server. Here is the table where we are storing data. 

  1. CREATE TABLE [dbo].[User](  
  2.     [Id] [int] IDENTITY(1,1) NOT NULL,  
  3.     [Name] [nvarchar](250) NULL,  
  4.     [Email] [nvarchar](250) NULL,  
  5.     [Phone] [nvarchar](50) NULL  
  6. ON [PRIMARY]  
  7. GO  

In server-end we are going to generate EF models from existing database using reverse engineering.

Entity Framework

Before we add Entity Framework to our application we need to install packages.

Let’s right-click on the project then GoTo > Tools > NuGet Package Manager > Package Manager Console install below packages one by one.

  • Install-Package Microsoft.EntityFrameworkCore.SqlServer
  • Install-Package Microsoft.EntityFrameworkCore.SqlServer.Design
  • Install-Package Microsoft.EntityFrameworkCore.Tools.DotNet

After installation the .csproj file will look like this.

NUGET Packages

  1. <ItemGroup>  
  2.   <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />  
  3.   <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.0.2" />  
  4.   <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.5" />  
  5.   <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.2" />  
  6. </ItemGroup>  
  7.   
  8. <ItemGroup>  
  9.   <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.3" />  
  10.   <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />  
  11. </ItemGroup>  

EF Model

Let’s run the following command using Package Manager Console.

dotnet ef dbcontext scaffold "Server=DESKTOP-80DEJMQ;Database=dbCore;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer –-output-dir Models

ASP.NET Core

Now, open the DbContext file then add a constructor to pass configuration like connectionstring into the DbContext. 

  1. public dbCoreContext(DbContextOptions<dbCoreContext> options) :  
  2.              base(options){  
  3.         }  

Register DbContext

In Startup.cs let’s add our DbContext as service to enable database connection. 

  1. //Database Connection  
  2. var connection = @"Server=DESKTOP-80DEJMQ;Database=dbCore;Trusted_Connection=True;";  
  3. services.AddDbContext<dbCoreContext>(options => options.UseSqlServer(connection));  

Enable CORS

To access the API’s from other domain we have enabled CORS. We have added the service in Startup.cs in ConfigureServices method. 

  1. services.AddCors(o => o.AddPolicy("AppPolicy", builder =>  
  2.             {  
  3.                 builder.AllowAnyOrigin()  
  4.                        .AllowAnyMethod()  
  5.                        .AllowAnyHeader();  
  6.             }));  

CORS Mechanism

ASP.NET Core
i
mage Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

API’s

Here’s our MVC API Controller using specific RoutePrefix attribute globally. With this api controller class we are performing database operations using Entity Framework DbContext. 

  1. [Route("api/Values"), Produces("application/json"), EnableCors("AppPolicy")]  
  2. public class ValuesController : Controller  
  3. {  
  4.     private dbCoreContext _ctx = null;  
  5.     public ValuesController(dbCoreContext context)  
  6.     {  
  7.         _ctx = context;  
  8.     }  
  9.   
  10.   
  11.     // GET: api/Values/GetUser  
  12.     [HttpGet, Route("GetUser")]  
  13.     public async Task<object> GetUser()  
  14.     {  
  15.         List<User> users = null;  
  16.         object result = null;  
  17.         try  
  18.         {  
  19.             using (_ctx)  
  20.             {  
  21.                 users = await _ctx.User.ToListAsync();  
  22.                 result = new  
  23.                 {  
  24.                     User  
  25.                 };  
  26.             }  
  27.         }  
  28.         catch (Exception ex)  
  29.         {  
  30.             ex.ToString();  
  31.         }  
  32.         return users;  
  33.     }  
  34.   
  35.     // GET api/Values/GetUserByID/5  
  36.     [HttpGet, Route("GetUserByID/{id}")]  
  37.     public async Task<User> GetUserByID(int id)  
  38.     {  
  39.         User user = null;  
  40.         try  
  41.         {  
  42.             using (_ctx)  
  43.             {  
  44.                 user = await _ctx.User.FirstOrDefaultAsync(x => x.Id == id);  
  45.             }  
  46.         }  
  47.         catch (Exception ex)  
  48.         {  
  49.             ex.ToString();  
  50.         }  
  51.         return user;  
  52.     }  
  53.   
  54.   
  55.     // POST api/Values/PostUser  
  56.     [HttpPost, Route("PostUser")]  
  57.     public async Task<object> PostUser([FromBody]User model)  
  58.     {  
  59.         object result = null; string message = "";  
  60.         if (model == null)  
  61.         {  
  62.             return BadRequest();  
  63.         }  
  64.         using (_ctx)  
  65.         {  
  66.             using (var _ctxTransaction = _ctx.Database.BeginTransaction())  
  67.             {  
  68.                 try  
  69.                 {  
  70.                     _ctx.User.Add(model);  
  71.                     await _ctx.SaveChangesAsync();  
  72.                     _ctxTransaction.Commit();  
  73.                     message = "Saved Successfully";  
  74.                 }  
  75.                 catch (Exception e)  
  76.                 {  
  77.                     _ctxTransaction.Rollback();  
  78.                     e.ToString();  
  79.                     message = "Saved Error";  
  80.                 }  
  81.   
  82.                 result = new  
  83.                 {  
  84.                     message  
  85.                 };  
  86.             }  
  87.         }  
  88.         return result;  
  89.     }  
  90.   
  91.     // PUT api/Values/PutUser/5  
  92.     [HttpPut, Route("PutUser/{id}")]  
  93.     public async Task<object> PutUser(int id, [FromBody]User model)  
  94.     {  
  95.         object result = null; string message = "";  
  96.         if (model == null)  
  97.         {  
  98.             return BadRequest();  
  99.         }  
  100.         using (_ctx)  
  101.         {  
  102.             using (var _ctxTransaction = _ctx.Database.BeginTransaction())  
  103.             {  
  104.                 try  
  105.                 {  
  106.                     var entityUpdate = _ctx.User.FirstOrDefault(x => x.Id == id);  
  107.                     if (entityUpdate != null)  
  108.                     {  
  109.                         entityUpdate.Name = model.Name;  
  110.                         entityUpdate.Phone = model.Phone;  
  111.                         entityUpdate.Email = model.Email;  
  112.   
  113.                         await _ctx.SaveChangesAsync();  
  114.                     }  
  115.                     _ctxTransaction.Commit();  
  116.                     message = "Entry Updated";  
  117.                 }  
  118.                 catch (Exception e)  
  119.                 {  
  120.                     _ctxTransaction.Rollback(); e.ToString();  
  121.                     message = "Entry Update Failed!!";  
  122.                 }  
  123.   
  124.                 result = new  
  125.                 {  
  126.                     message  
  127.                 };  
  128.             }  
  129.         }  
  130.         return result;  
  131.     }  
  132.   
  133.     // DELETE api/Values/DeleteUserByID/5  
  134.     [HttpDelete, Route("DeleteUserByID/{id}")]  
  135.     public async Task<object> DeleteUserByID(int id)  
  136.     {  
  137.         object result = null; string message = "";  
  138.         using (_ctx)  
  139.         {  
  140.             using (var _ctxTransaction = _ctx.Database.BeginTransaction())  
  141.             {  
  142.                 try  
  143.                 {  
  144.                     var idToRemove = _ctx.User.SingleOrDefault(x => x.Id == id);  
  145.                     if (idToRemove != null)  
  146.                     {  
  147.                         _ctx.User.Remove(idToRemove);  
  148.                         await _ctx.SaveChangesAsync();  
  149.                     }  
  150.                     _ctxTransaction.Commit();  
  151.                     message = "Deleted Successfully";  
  152.                 }  
  153.                 catch (Exception e)  
  154.                 {  
  155.                     _ctxTransaction.Rollback(); e.ToString();  
  156.                     message = "Error on Deleting!!";  
  157.                 }  
  158.   
  159.                 result = new  
  160.                 {  
  161.                     message  
  162.                 };  
  163.             }  
  164.         }  
  165.         return result;  
  166.     }  
  167. }  

We are almost done with application steps, now it’s time to minify our application in a single js file then reference it to the main HTML page.

Modify Gulp

Add the below code snippet to existing gulp file, Goto > TaskRunnerExplorer then refresh the window, it will work while we build the application due to its binding.

  1. gulp.task('min-js'function () {  
  2.     gulp.src(['./app/**/*.js'])  
  3.         .pipe(concat('app.js'))  
  4.         .pipe(uglify())  
  5.         .pipe(gulp.dest(root_path.webroot))  
  6. });  
  7.   
  8. gulp.task('copy-folder-html'function () {  
  9.     gulp.src('app/**/*.html')  
  10.         .pipe(gulp.dest(root_path.webroot + 'views'));  
  11. });  
  12.   
  13. gulp.task("build-all", ["min-js""copy-folder-html"]);  
  14. //Build End  

As we can see from the below screenshot the build is done successfully.

ASP.NET Core

After building the application the  “wwwroot” folder will look like this.

ASP.NET Core

Finally Main Html

Let’s add the app.js file to the main html page. 

  1. <!DOCTYPE html>  
  2. <html lang="en" ng-app="templating_app">  
  3.    <head>  
  4.    </head>  
  5.    <body>  
  6.     <!-- Core JS Files   -->  
  7. <script src="/lib/jquery/jquery-1.10.2.js" type="text/javascript"></script>  
  8. <script src="/lib/bootstrap/tether.min.js"></script>  
  9. <script src="/lib/bootstrap/bootstrap.min.js" type="text/javascript"></script>  
  10.   
  11. <!-- App JS Files   -->  
  12. <script src="/lib/angular/angular.min.js"></script>  
  13. <script src="/lib/angular/angular-ui-router.min.js"></script>  
  14. <script src="/app.js"></script>     
  15.    </body>  
  16. </html>  

OutPut

Here, we can see user data is listed in the grid, this data can also be accessed from other domains or devices.

ASP.NET Core

Publish To IIS

Right Click on project file Goto > Publish window, choose a type to publish. I have chosen folder publishing. 

ASP.NET Core

Now open IIS to add a new website

ASP.NET Core

Goto Application Pool makes sure about .Net CLR Version of 2.0 like the below image.

ASP.NET Core

Solve Underlying provider Issue in IIS

Goto URL - http://shashangka.com/2015/12/15/the-underlying-provider-failed-on-open-only-under-iis

Let’s browse the website from IIS.

ASP.NET Core

Source Code

I’ve uploaded the full source code to download/clone @github, Hope this will help 🙂

Reference

  • http://shashangka.com/2016/08/12/crud-using-net-core-angularjs2-webapi
  • http://shashangka.com/2016/11/26/net-core-startup
  • https://benjii.me/2016/01/angular2-routing-with-asp-net-core-1
  • https://docs.microsoft.com/en-us/aspnet/core/security/cors
  • https://docs.microsoft.com/en-us/ef/core/get-started/aspnetcore/existing-db
  • https://www.c-sharpcorner.com/article/enable-cross-origin-resource-sharing-cors-in-asp-net-core