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.
- Visual Studio 2017
- .NET Core 2.0
- NodeJS
- MSSQL 2016
- IIS
Key Points
The following points will be discussed to develop the sample application:
- Manage Packages
- Transfer Libraries Using Gulp
- Working with AngularJs App
- Adding Middleware
- Scaffolding MSSQL Database
- Web-API
- Enable CORS
- Build AngularJS App Using Gulp
- Test Application (CRUD)
- 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.
Initially, application folder structure might look like this, we are going to modify those following our requirements.
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”.
- DefaultFilesOptions options = new DefaultFilesOptions();
- options.DefaultFileNames.Clear();
- options.DefaultFileNames.Add("/index.html");
- 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.
- app.Use(async (context, next) =>
- {
- await next();
- if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value))
- {
- context.Request.Path = "/index.html";
- context.Response.StatusCode = 200;
- await next();
- }
- });
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.
To add new package right click on a project then click on “Manage Bower Packages”
Install the required package by searching one by one. Finally, the installed packages will be listed like below screenshot.
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.
Add all those packages to devDependencies, it’ll automatically install to our project.
- "gulp": "^3.9.1",
- "gulp-concat": "^2.6.1",
- "gulp-rename": "^1.2.2",
- "gulp-cssmin": "^0.2.0",
- "gulp-uglify": "^3.0.0",
- "gulp-htmlclean": "^2.7.20",
- "rimraf": "^2.6.2"
Finally our package dependencies are listed.
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.
-
-
- var gulp = require("gulp"),
- rimraf = require("rimraf"),
- concat = require("gulp-concat"),
- cssmin = require("gulp-cssmin"),
- uglify = require("gulp-uglify"),
- rename = require("gulp-rename");
-
- var root_path = {
- webroot: "./wwwroot/"
- };
-
-
- root_path.nmSrc = "./bower_components/";
-
-
- root_path.package_lib = root_path.webroot + "lib/";
-
-
- gulp.task('copy-lib-css', function () {
- gulp.src('./bower_components/bootstrap/dist/css/bootstrap.min.css')
- .pipe(gulp.dest(root_path.webroot + '/css/'));
- gulp.src('./bower_components/toastr/toastr.min.css')
- .pipe(gulp.dest(root_path.webroot + '/css/'));
- });
-
- gulp.task('copy-lib-js', function () {
- gulp.src('./bower_components/jquery/dist/jquery.min.js')
- .pipe(gulp.dest(root_path.package_lib + '/jquery/'));
-
- gulp.src('./bower_components/bootstrap/dist/js/bootstrap.min.js')
- .pipe(gulp.dest(root_path.package_lib + '/bootstrap/'));
-
- gulp.src('./bower_components/toastr/toastr.min.js')
- .pipe(gulp.dest(root_path.package_lib + '/toastr/'));
-
- gulp.src('./bower_components/angular/angular.min.js')
- .pipe(gulp.dest(root_path.package_lib + 'angular/'));
- gulp.src('./bower_components/angular-ui-router/release/angular-ui-router.min.js')
- .pipe(gulp.dest(root_path.package_lib + 'angular/'));
- });
-
- gulp.task("copy-all", ["copy-lib-css", "copy-lib-js"]);
-
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.
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.
We are now going to add the AngularJs library reference in the main layout page. Like below.
- <!DOCTYPE html>
- <html lang="en" ng-app="templating_app">
- <head></head>
- <body>
- <!-- Core JS Files -->
- <script src="/lib/jquery/jquery-1.10.2.js" type="text/javascript"></script>
- <script src="/lib/bootstrap/tether.min.js"></script>
- <script src="/lib/bootstrap/bootstrap.min.js" type="text/javascript"></script>
-
- <!-- App JS Files -->
- <script src="/lib/angular/angular.min.js"></script>
- <script src="/lib/angular/angular-ui-router.min.js"></script>
- </body>
- </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.
Module
This will define our application.
- var templatingApp;
- (
- function () {
- 'use strict';
- templatingApp = angular.module('templating_app', ['ui.router']);
- }
- )();
Route
This file will handle our application route coming from URL’s.
- templatingApp.config(['$locationProvider', '$stateProvider', '$urlRouterProvider', '$urlMatcherFactoryProvider', '$compileProvider',
- function ($locationProvider, $stateProvider, $urlRouterProvider, $urlMatcherFactoryProvider, $compileProvider) {
-
-
- if (window.history && window.history.pushState) {
- $locationProvider.html5Mode({
- enabled: true,
- requireBase: true
- }).hashPrefix('!');
- };
- $urlMatcherFactoryProvider.strictMode(false);
- $compileProvider.debugInfoEnabled(false);
-
- $stateProvider
- .state('home', {
- url: '/',
- templateUrl: './views/home/home.html',
- controller: 'HomeController'
- })
- .state('dashboard', {
- url: '/dashboard',
- templateUrl: './views/home/home.html',
- controller: 'HomeController'
- })
- .state('user', {
- url: '/user',
- templateUrl: './views/user/user.html',
- controller: 'UserController'
- })
- .state('about', {
- url: '/about',
- templateUrl: './views/about/about.html',
- controller: 'AboutController'
- });
-
- $urlRouterProvider.otherwise('/home');
- }]);
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.
- $locationProvider.html5Mode({
- enabled: true,
- requireBase: true
- }).hashPrefix('!');
Also, we need to specify the base in main page.
<base href="/">
Directives
For Top Navigation Bar
- templatingApp.directive("navbarMenu", function () {
- return {
- restrict: 'E',
- templateUrl: 'views/shared/navbar/nav.html'
- };
- });
For Top Side Bar
- templatingApp.directive("sidebarMenu", function () {
- return {
- restrict: 'E',
- templateUrl: 'views/shared/sidebar/menu.html'
- };
- });
Angular Controller
This is the Angular Controller which will manage the views and perform all HTTP calls from client end to server.
- templatingApp.controller('UserController', ['$scope', '$http', function ($scope, $http) {
- $scope.title = "All User";
- $scope.ListUser = null;
- $scope.userModel = {};
- $scope.userModel.id = 0;
- getallData();
-
-
- function getallData() {
- $http({
- method: 'GET',
- url: '/api/Values/GetUser/'
- }).then(function (response) {
- $scope.ListUser = response.data;
- }, function (error) {
- console.log(error);
- });
- };
-
-
- $scope.getUser = function (user) {
- $http({
- method: 'GET',
- url: '/api/Values/GetUserByID/' + parseInt(user.id)
- }).then(function (response) {
- $scope.userModel = response.data;
- }, function (error) {
- console.log(error);
- });
- };
-
-
- $scope.saveUser = function () {
- $http({
- method: 'POST',
- url: '/api/Values/PostUser/',
- data: $scope.userModel
- }).then(function (response) {
- $scope.reset();
- getallData();
- }, function (error) {
- console.log(error);
- });
- };
-
-
- $scope.updateUser = function () {
- $http({
- method: 'PUT',
- url: '/api/Values/PutUser/' + parseInt($scope.userModel.id),
- data: $scope.userModel
- }).then(function (response) {
- $scope.reset();
- getallData();
- }, function (error) {
- console.log(error);
- });
- };
-
-
- $scope.deleteUser = function (user) {
- var IsConf = confirm('You are about to delete ' + user.Name + '. Are you sure?');
- if (IsConf) {
- $http({
- method: 'DELETE',
- url: '/api/Values/DeleteUserByID/' + parseInt(user.id)
- }).then(function (response) {
- $scope.reset();
- getallData();
- }, function (error) {
- console.log(error);
- });
- }
- };
-
-
- $scope.reset = function () {
- var msg = "Form Cleared";
- $scope.userModel = {};
- $scope.userModel.id = 0;
- };
- }]);
Server-Side
Database
Let’s Create a Database in MSSQL Server. Here is the table where we are storing data.
- CREATE TABLE [dbo].[User](
- [Id] [int] IDENTITY(1,1) NOT NULL,
- [Name] [nvarchar](250) NULL,
- [Email] [nvarchar](250) NULL,
- [Phone] [nvarchar](50) NULL
- ) ON [PRIMARY]
- 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
- <ItemGroup>
- <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.0.2" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.5" />
- <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.2" />
- </ItemGroup>
-
- <ItemGroup>
- <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.3" />
- <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
- </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
Now, open the DbContext file then add a constructor to pass configuration like connectionstring into the DbContext.
- public dbCoreContext(DbContextOptions<dbCoreContext> options) :
- base(options){
- }
Register DbContext
In Startup.cs let’s add our DbContext as service to enable database connection.
-
- var connection = @"Server=DESKTOP-80DEJMQ;Database=dbCore;Trusted_Connection=True;";
- 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.
- services.AddCors(o => o.AddPolicy("AppPolicy", builder =>
- {
- builder.AllowAnyOrigin()
- .AllowAnyMethod()
- .AllowAnyHeader();
- }));
CORS Mechanism
image 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.
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.
- gulp.task('min-js', function () {
- gulp.src(['./app/**/*.js'])
- .pipe(concat('app.js'))
- .pipe(uglify())
- .pipe(gulp.dest(root_path.webroot))
- });
-
- gulp.task('copy-folder-html', function () {
- gulp.src('app/**/*.html')
- .pipe(gulp.dest(root_path.webroot + 'views'));
- });
-
- gulp.task("build-all", ["min-js", "copy-folder-html"]);
-
As we can see from the below screenshot the build is done successfully.
After building the application the “wwwroot” folder will look like this.
Finally Main Html
Let’s add the app.js file to the main html page.
- <!DOCTYPE html>
- <html lang="en" ng-app="templating_app">
- <head>
- </head>
- <body>
- <!-- Core JS Files -->
- <script src="/lib/jquery/jquery-1.10.2.js" type="text/javascript"></script>
- <script src="/lib/bootstrap/tether.min.js"></script>
- <script src="/lib/bootstrap/bootstrap.min.js" type="text/javascript"></script>
-
- <!-- App JS Files -->
- <script src="/lib/angular/angular.min.js"></script>
- <script src="/lib/angular/angular-ui-router.min.js"></script>
- <script src="/app.js"></script>
- </body>
- </html>
OutPut
Here, we can see user data is listed in the grid, this data can also be accessed from other domains or devices.
Publish To IIS
Right Click on project file Goto > Publish window, choose a type to publish. I have chosen folder publishing.
Now open IIS to add a new website
Goto Application Pool makes sure about .Net CLR Version of 2.0 like the below image.
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.
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