Click here to Skip to main content
15,881,812 members
Articles / Web Development / ASP.NET / ASP.NET Core

Single Page Application Using ASP.Net Core-AngularJs

, ,
Rate me:
Please Sign up or sign in to vote.
4.83/5 (10 votes)
29 Mar 2018CPOL6 min read 49.7K   2.5K   25   4
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: http://shashangka.com/2016/11/26/net-core-startup/

Prerequisites: Before getting started make sure development environment is prepared properly. These are listed 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 point will be cared to develop the sample application, which is the key mechanism listed below.

  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 project for creating ASP.Net Core application with ASP.Net Core 2.0 MVC Web Application like below screenshot.

Image 1

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

Image 2

As our planning, 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 request then Angular will handle the request and decide which page to serve. Let’s create html page in 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 reload the page in AngularJS SPA app we need to add middleware to handle client side route fallback.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.

Image 3

To add new package right click on project the click on “Manage Bower PackagesImage 4 Image 5 Install the required package by searching one by one. Finally the installed packages will listed like below screenshot.

Image 6

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 below screenshot. Image 7 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.

Image 8

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 below code snippet and paste it to the newly added file

/// <binding AfterBuild='build-all' />

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/"
};

//library source
root_path.nmSrc = "./bower_components/";

//library destination
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"]);
//Copy End

Now from 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 below screeshot. Image 9

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

Image 10

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 angular directive. Image 11

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) {

        //console.log('Appt.Main is now running')
        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 URL’s. In AngularJs we need to enable HTML5 Mode to remove /#!/ symbols in 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 call 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();

    //******=========Get All User=========******
    function getallData() {
        $http({
            method: 'GET',
            url: '/api/Values/GetUser/'
        }).then(function (response) {
            $scope.ListUser = response.data;
        }, function (error) {
            console.log(error);
        });
    };

    //******=========Get Single User=========******
    $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);
        });
    };

    //******=========Save User=========******
    $scope.saveUser = function () {
        $http({
            method: 'POST',
            url: '/api/Values/PostUser/',
            data: $scope.userModel
        }).then(function (response) {
            $scope.reset();
            getallData();
        }, function (error) {
            console.log(error);
        });
    };

    //******=========Update User=========******
    $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);
        });
    };

    //******=========Delete User=========******
    $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);
            });
        }
    };

    //******=========Clear Form=========******
    $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 use Entity Framework to our application we need to install packages. Let’s right click on 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

Image 12

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.

//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 13

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 operation using Entity Framework DbContext.

[Route("api/Values"), Produces("application/json"), EnableCors("AppPolicy")]
public class ValuesController : Controller
{
    private dbCoreContext _ctx = null;
    public ValuesController(dbCoreContext context)
    {
        _ctx = context;
    }


    // GET: api/Values/GetUser
    [HttpGet, Route("GetUser")]
    public async Task<object> GetUser()
    {
        List<User> users = null;
        object result = null;
        try
        {
            using (_ctx)
            {
                users = await _ctx.User.ToListAsync();
                result = new
                {
                    User
                };
            }
        }
        catch (Exception ex)
        {
            ex.ToString();
        }
        return users;
    }

    // GET api/Values/GetUserByID/5
    [HttpGet, Route("GetUserByID/{id}")]
    public async Task<User> GetUserByID(int id)
    {
        User user = null;
        try
        {
            using (_ctx)
            {
                user = await _ctx.User.FirstOrDefaultAsync(x => x.Id == id);
            }
        }
        catch (Exception ex)
        {
            ex.ToString();
        }
        return user;
    }


    // POST api/Values/PostUser
    [HttpPost, Route("PostUser")]
    public async Task<object> PostUser([FromBody]User model)
    {
        object result = null; string message = "";
        if (model == null)
        {
            return BadRequest();
        }
        using (_ctx)
        {
            using (var _ctxTransaction = _ctx.Database.BeginTransaction())
            {
                try
                {
                    _ctx.User.Add(model);
                    await _ctx.SaveChangesAsync();
                    _ctxTransaction.Commit();
                    message = "Saved Successfully";
                }
                catch (Exception e)
                {
                    _ctxTransaction.Rollback();
                    e.ToString();
                    message = "Saved Error";
                }

                result = new
                {
                    message
                };
            }
        }
        return result;
    }

    // PUT api/Values/PutUser/5
    [HttpPut, Route("PutUser/{id}")]
    public async Task<object> PutUser(int id, [FromBody]User model)
    {
        object result = null; string message = "";
        if (model == null)
        {
            return BadRequest();
        }
        using (_ctx)
        {
            using (var _ctxTransaction = _ctx.Database.BeginTransaction())
            {
                try
                {
                    var entityUpdate = _ctx.User.FirstOrDefault(x => x.Id == id);
                    if (entityUpdate != null)
                    {
                        entityUpdate.Name = model.Name;
                        entityUpdate.Phone = model.Phone;
                        entityUpdate.Email = model.Email;

                        await _ctx.SaveChangesAsync();
                    }
                    _ctxTransaction.Commit();
                    message = "Entry Updated";
                }
                catch (Exception e)
                {
                    _ctxTransaction.Rollback(); e.ToString();
                    message = "Entry Update Failed!!";
                }

                result = new
                {
                    message
                };
            }
        }
        return result;
    }

    // DELETE api/Values/DeleteUserByID/5
    [HttpDelete, Route("DeleteUserByID/{id}")]
    public async Task<object> DeleteUserByID(int id)
    {
        object result = null; string message = "";
        using (_ctx)
        {
            using (var _ctxTransaction = _ctx.Database.BeginTransaction())
            {
                try
                {
                    var idToRemove = _ctx.User.SingleOrDefault(x => x.Id == id);
                    if (idToRemove != null)
                    {
                        _ctx.User.Remove(idToRemove);
                        await _ctx.SaveChangesAsync();
                    }
                    _ctxTransaction.Commit();
                    message = "Deleted Successfully";
                }
                catch (Exception e)
                {
                    _ctxTransaction.Rollback(); e.ToString();
                    message = "Error on Deleting!!";
                }

                result = new
                {
                    message
                };
            }
        }
        return result;
    }
}

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 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"]);
//Build End

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

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

Image 15

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 grid, this data can also be accessible from other domain or devices. 

Image 16

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

Image 17

Now open IIS to add a new website Image 18

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

Image 19

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.

Image 20

Hope this will help 🙂

Reference:

License

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


Written By
Software Developer (Senior) s3 Innovate Pte Ltd
Bangladesh Bangladesh
Hi, I am Shashangka Shekhar,

Working with Microsoft Technologies. Since March 2011, it was my first step to working with Microsoft Technologies, achieved bachelor’s degree on Computer Science from State University of Bangladesh(Dhaka). Have 12+ years of professional experience, currently working as Technical Lead at Surbana Jurong Private Limited.

I believe in desire of learning & also love to be a part of .Net Community by sharing knowledge’s.

Written By
Software Developer (Senior) Ha-meem Group
Bangladesh Bangladesh
Hi, I am Abhijit Bhowmick

Completed Graduation from Daffodil International University. Currently working as Sr.Software engineer at Ha-Meem Group..

Success Always Starts With Failure.

Thank you.


Written By
Software Developer OnAir International Ltd.
Bangladesh Bangladesh
Hi it's me, Zahid
Started my career very recently as a Jr. Software Engineer and I'm From Patuakhali Science and Technology University, BD

My belief is Technology has to be reached to the common people's door. I am glad to be a part of .Net Community where I can inter-change and share my knowledge's.

Thanks.

Comments and Discussions

 
QuestionWhy using old tech to start a new project ? Pin
AdamPL3-Apr-18 4:15
AdamPL3-Apr-18 4:15 
AnswerRe: Why using old tech to start a new project ? Pin
Shashangka Shekhar3-Apr-18 5:26
professionalShashangka Shekhar3-Apr-18 5:26 
QuestionWhat is the benefit of .Net core being open source if, almost, in all projects that use it Visual Studio is a prerequisite? Pin
ejespinom30-Mar-18 7:03
ejespinom30-Mar-18 7:03 
AnswerRe: What is the benefit of .Net core being open source if, almost, in all projects that use it Visual Studio is a prerequisite? Pin
Shashangka Shekhar30-Mar-18 16:04
professionalShashangka Shekhar30-Mar-18 16:04 

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.