Click here to Skip to main content
14,034,395 members
Click here to Skip to main content
Add your own
alternative version

Stats

22.1K views
15 bookmarked
Posted 29 May 2016
Licenced CPOL

ASP.NET Core RC2 Using WEB API And AngularJS - Part 2

, 20 Jun 2016
Rate this:
Please Sign up or sign in to vote.
In this article you will learn about ASP.NET Core RC2 using WEB API and AngularJS.

Introduction

Prerequisites

Visual Studio 2015: You can download it from here.

.NET Core SDK: download .NET Core SDK from this link.

.NET Framework 4.5.2: download .NET Framework 4.5.2 from this link.

.NodeJS: download NodeJS from this link.

Sourcecode: download source from this link.

In this part we will see in detail how to:

  • Create a student component
  • Communicate with Server via Web API
  • Create Form and pushing data to server
  • Authentication using AspNetCore CookieAuthenticationOptions

Background

Before reading this Part, you should see Part 1.

Using the code

Let's rock!

Add student components:

  • wwwroot/app/components/student/student.html
  • wwwroot/app/components/student/student.controller.html

Content of student.html:

<h2>Student List</h2>
<table class="table table-striped">
    <thead>
        <tr>
            <th>Firstname</th>
            <th>Lastname</th>
            <th>Email</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>John</td>
            <td>Doe</td>
            <td>john@example.com</td>
        </tr>
        <tr>
            <td>Mary</td>
            <td>Moe</td>
            <td>mary@example.com</td>
        </tr>
        <tr>
            <td>July</td>
            <td>Dooley</td>
            <td>july@example.com</td>
        </tr>
    </tbody>
</table>

Content of student.controller.js:

(function () {
    'use strict';

    angular
        .module('app')
        .controller('StudentController', MainController);

    function MainController($scope) {
        var vm = this;

    }
})();

Modify main.html into:

<h2>This application consists of:</h2>
<ul>
    <li>Sample pages using ASP.NET Core with AngularJS, NPM, Gulp, Bower, Bootstrap, Jquery</li>
    <li>More detail <a href="http://www.codeproject.com/script/Articles/ArticleVersion.aspx?waid=209236&aid=1102877">here</a></li>
</ul>

Modify index.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>AspNetCoreSPA</title>

    <script type="text/javascript">
        var site = site || {};
    </script>

    <!-- bower:css -->
    <!-- endbower -->
    <!-- inject:css -->
    <!-- endinject -->
</head>
<body ng-app="app">
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a ui-sref="home" class="navbar-brand">AspNetCoreSPA</a>
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li><a ui-sref="home">Home</a></li>
                    <li><a ui-sref="student">Student</a></li>
                </ul>
            </div>
        </div>
    </div>
    <div class="container body-content">
        <div class="jumbotron">
            <h1>AspNetCoreSPA</h1>
            <p class="lead">Welcome to AspNetCoreSPA</p>
        </div>
        <div class="row">
            <div ui-view></div>
        </div>

        <hr />
        <footer>
            <p>&copy; 2016 - Toan Manh Nguyen</p>
        </footer>
    </div>

    <!-- bower:js -->
    <!-- endbower -->
    <!-- inject:js -->
    <!-- endinject -->
</body>
</html>

Explaination about Routing:

ui-sref="home"

will map with:

$stateProvider
            .state('home', {
                url: '/',
                templateUrl: 'app/main/main.html',
                controller: 'MainController',
                controllerAs: 'mainCtrl'
            })

Full content of index.route.js:

(function () {
    'use strict';

    angular
      .module('app')
      .config(routerConfig);

    routerConfig.$inject = ['$stateProvider', '$urlRouterProvider'];

    function routerConfig($stateProvider, $urlRouterProvider) {
        $urlRouterProvider.otherwise('/');

        $stateProvider
            .state('home', {
                url: '/',
                templateUrl: 'app/main/main.html',
                controller: 'MainController',
                controllerAs: 'mainCtrl'
            })
            .state('student', {
                url: '/student',
                templateUrl: 'app/components/student/student.html',
                controller: 'StudentController',
                controllerAs: 'studentCtrl'
            });
    }
})();

Now hit F5 and see the new result:

Click Student menu:

The same result of Part 1 but it have some navigation, now we will get student list from server via http and load the result in student.html

Firstly, create StudentController

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;

namespace AspNetCoreSPA.Web.Controllers
{
    [Route("api/student")]
    public class StudentController : Controller
    {
        private List<Student> _students = new List<Student>
        {
            new Student { FirstName = "John", LastName = "Doe", Email = "john@example.com"},
            new Student { FirstName = "Mary", LastName = "Moe", Email = "mary@example.com"},
            new Student { FirstName = "July", LastName = "Dooley", Email = "july@example.com"}
        };

        [Route("getAll")]
        public IEnumerable<Student> GetAll()
        {
            return _students;
        }
    }

    public class Student
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
    }
}

We have GetAll method, this method return list of student (JSON formatted).

Modify student.html:

<h2>Student List</h2>
<table class="table table-striped">
    <thead>
        <tr>
            <th>Firstname</th>
            <th>Lastname</th>
            <th>Email</th>
        </tr>
    </thead>
    <tbody>
        <tr ng-repeat="student in studentCtrl.students">
            <td>{{student.FirstName}}</td>
            <td>{{student.LastName}}</td>
            <td>{{student.Email}}</td>
        </tr>
    </tbody>
</table>

Did you see "studentCtrl.students", if we studied angular before, it must have ng-controller="StudentController as studentCtrl" right? But take a look at index.route.js:

.state('student', {
    url: '/student',
    templateUrl: 'app/components/student/student.html',
    controller: 'StudentController',
    controllerAs: 'studentCtrl'
);

Key point is here: controller and controllerAs, it's the same as ng-controller.

Next, we modify student.controller.js:

(function () {
    'use strict';

    angular
        .module('app')
        .controller('StudentController', StudentController);

    StudentController.$inject = ['$http', '$http'];

    function StudentController($scope, $http) {
        var vm = this;

        vm.students = [];

        $http.get("api/student/getAll").then(function(response) { vm.students = response.data; });
    }
})();

Hit F5, click Student menu and see the result:

Exactly the same before!

Next, we will create a Create button and create a new student.

Before doing that, add those codes in index.module.js: This automatically clear the cache whenever the ng-view content changes.

angular.module('app').run(function ($rootScope, $templateCache) {
    $rootScope.$on('$viewContentLoaded', function () {
        $templateCache.removeAll();
    });
});

Modify student.html:

<h2>Student List</h2>
<table class="table table-striped">
    <thead>
    <tr>
        <th>Firstname</th>
        <th>Lastname</th>
        <th>Email</th>
    </tr>
    </thead>
    <tbody>
    <tr ng-repeat="student in studentCtrl.students">
        <td>{{student.FirstName}}</td>
        <td>{{student.LastName}}</td>
        <td>{{student.Email}}</td>
    </tr>
    </tbody>
</table>

<input type="button" class="btn btn-default" value="Create" data-toggle="modal" data-target="#formCreateStudent"/>

<form id="formCreateStudent" class="modal fade" role="dialog">
    <div class="modal-dialog">
        <!-- Modal content-->
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal">&times;</button>
                <h4 class="modal-title">Create new student</h4>
            </div>
            <div class="modal-body">
                <div class="form-group">
                    <label for="firstName">First Name</label>
                    <input type="text" class="form-control" id="firstName" placeholder="First Name" ng-model="studentCtrl.createStudentInput.FirstName">
                </div>
                <div class="form-group">
                    <label for="lastName">Last Name</label>
                    <input type="text" class="form-control" id="lastName" placeholder="Last Name" ng-model="studentCtrl.createStudentInput.LastName">
                </div>
                <div class="form-group">
                    <label for="emailAddress">Email address</label>
                    <input type="email" class="form-control" id="emailAddress" placeholder="Email" ng-model="studentCtrl.createStudentInput.Email">
                </div>
            </div>
            <div class="modal-footer">
                <button type="submit" class="btn btn-success" ng-click="studentCtrl.createStudent()">Submit</button>
                <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
            </div>
        </div>
    </div>
</form>

Note that we use bootstrap modal to visible dialog.

Modify student.controller.js:

(function () {
    'use strict';

    angular
        .module('app')
        .controller('StudentController', StudentController);

    StudentController.$inject = ['$scope', '$http'];

    function StudentController($scope, $http) {
        var vm = this;

        vm.students = [];

        vm.createStudentInput = {};

        vm.createStudent = function () {
            $http.post("api/student/createStudent", JSON.stringify(vm.createStudentInput))
                .then(function (response) {
                    // Re-load data
                    vm.students = response.data;

                    // Close dialog
                    $('#formCreateStudent').modal('toggle');
                });
        }

        $http.get("api/student/getAll")
            .then(function(response) {
                vm.students = response.data;
            });
    }
})();

Explaination:

  • We use $http for get and post request.
  • vm.createStudent will be called when we click Submit button in Create form.
  • $http.post or $http.get is shortcut method (more detail here)
  • After student created, we also need to reload data and close modal
  • vm.students = response.data;
    
    $('#formCreateStudent').modal('toggle');

Let's see StudentController now

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;

namespace AspNetCoreSPA.Web.Controllers
{
    [Route("api/student")]
    public class StudentController : Controller
    {
        private static List<Student> _students = new List<Student>
        {
            new Student { FirstName = "John", LastName = "Doe", Email = "john@example.com"},
            new Student { FirstName = "Mary", LastName = "Moe", Email = "mary@example.com"},
            new Student { FirstName = "July", LastName = "Dooley", Email = "july@example.com"}
        };

        [Route("getAll")]
        [HttpGet]
        public IActionResult GetAll()
        {
            return Json(_students);
        }

        [Route("createStudent")]
        [HttpPost]
        public IActionResult CreateStudent([FromBody] Student student)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest();
            }

            _students.Add(student);

            return Json(_students);
        }
    }

    public class Student
    {
        [Required]
        public string FirstName { get; set; }

        [Required]
        public string LastName { get; set; }

        [Required]
        public string Email { get; set; }
    }
}

In AspNetCore, ApiController and Controller were merged into one called "Controller", with request mapping model we should include [FromBody] for mapping data to our model.

After all, hit F5 and see the result!

Step 1: Press Create button and fill in data:

Step 2: Press submit button and see the result, new record added

Authentication using CookieAuthenticationOptions

Context:

  • Any user must be authenticated before joining our system
    • User is authenticated by Username + Password
  • After login successful, user can call our APIs
  • Screen transition: Login -> Main screen -> Child screen. If login fail or cookie timeout, transfer back to Login screen.

Implementation:

1. Client side

Index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>AspNetCoreSPA</title>

    <script type="text/javascript">
        var site = site || {};
    </script>

    <!-- bower:css -->
    <!-- endbower -->
    <!-- inject:css -->
    <!-- endinject -->
</head>
<body ng-app="app">
    <div ui-view="login"></div>
    <div ui-view="main"></div>

    <!-- bower:js -->
    <!-- endbower -->
    <!-- inject:js -->
    <!-- endinject -->
</body>
</html>

<div ui-view="login"></div>: Login.html will be renderred here

<div ui-view="main"></div>: Main.html will be renderred here (Main.html like _Layout.cshtml)

Let's see main parts:

main.html: As i said before, this page is our Master page, all child pages will be renderred 
<div ui-view="content"></div>

<div class="navbar navbar-inverse navbar-fixed-top">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a ui-sref="home" class="navbar-brand">AspNetCoreSPA</a>
        </div>
        <div class="navbar-collapse collapse">
            <ul class="nav navbar-nav">
                <li><a ui-sref="main">Home</a></li>
                <li><a ui-sref="student">Student</a></li>
            </ul>
        </div>
    </div>
</div>
<div class="container body-content">
    <div class="jumbotron">
        <h1>AspNetCoreSPA</h1>
        <p class="lead">Welcome to AspNetCoreSPA</p>
    </div>

    <div ui-view="content"></div>

    <hr />
    <footer>
        <p>&copy; 2016 - Toan Manh Nguyen</p>
    </footer>
</div>

main.controller.js

(function () {
    'use strict';

    angular
        .module('app')
        .controller('MainController', MainController);

    MainController.$inject = ['$scope'];

    function MainController($scope) {
        var vm = this;
    }
})();

main.route.js: Refer here for Nested States and Nested Views for the convention "content@main".

(function () {
    'use strict';

    angular
      .module('app')
      .config(routerConfig);

    routerConfig.$inject = ['$stateProvider'];

    function routerConfig($stateProvider) {
        $stateProvider
            .state('main',
            {
                url: '/',
                views: {
                    'main': {
                        templateUrl: 'app/main/main.html',
                        controller: 'MainController',
                        controllerAs: 'vm'
                    },
                    'content@main': {
                        templateUrl: 'app/components/home/home.html',
                        controller: 'HomeController',
                        controllerAs: 'vm'
                    }
                }
            });
    }
})();

Back to old Asp.net mvc, the concept is: _Layout.cshtml + Home.cshtml (We use layout for master page and the first page is home). Now in angular js, the concept is similar: We always render home.html in main.html at the first time.

The login parts:

login.html

<div class="container">
    <div class="row">
        <div class="col-sm-6 col-md-4 col-md-offset-4">
            <h1 class="text-center login-title">Login to our System</h1>
            <div class="account-wall">
                <form class="form-signin">
                    <input type="text" ng-model="vm.loginInfo.UserName" class="form-control" placeholder="Username" required autofocus>
                    <input type="password" ng-model="vm.loginInfo.Password" class="form-control" placeholder="Password" required>
                    <input class="btn btn-lg btn-primary btn-block" type="button" ng-click="vm.login()" value="Sign in" />
                </form>
            </div>
        </div>
    </div>
</div>

login.controller.js

(function () {
    'use strict';

    angular
        .module('app')
        .controller('LoginController', LoginController);

    LoginController.$inject = ['$scope', '$http', '$state', 'auth0Service'];

    function LoginController($scope, $http, $state, auth0Service) {
        var vm = this;

        vm.loginInfo = {
            UserName: "test01",
            Password: "Qwer!@#12345"
        };

        vm.login = function () {
            auth0Service.login(vm.loginInfo, function (response) {
                if (response == "OK") {
                    auth0Service.authenticate();
                    $state.go('main');
                }
            });
        }
    }
})();

Notice that we will transfer to "main" page when the login status is "OK": $state.go('main');

login.route.js

(function () {
    'use strict';

    angular
      .module('app')
      .config(routerConfig);

    routerConfig.$inject = ['$stateProvider'];

    function routerConfig($stateProvider) {
        $stateProvider
            .state('login', {
                url: '/login',
                views: {
                    'login@': {
                        templateUrl:'app/login/login.html',
                        controller: 'LoginController',
                        controllerAs: 'vm'
                    }
                }
            });
    }
})();

But if status is 401??? What will happen?

Let's see site.ng.js

'responseError': function (ngError) {
    var state = $injector.get('$state');
    var auth0 = $injector.get('auth0Service');
    var error = {
        message: ngError.data || site.ng.http.defaultError.message,
        details: ngError.statusText || site.ng.http.defaultError.details,
        responseError: true
    }

    if (ngError.status === 401) {
        auth0.clear();
        state.go("login");
    } else {
        site.ng.http.showError(error);
    }

    return $q.reject(ngError);
}

We check if the status is 401 -> transfer back to login page.

2. Server side

Create "MyIdentity" class:

using System;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace AspNetCoreSPA.Web.Configurations
{
    public static class MyIdentity
    {
        public static void UseMyIdentity(this IApplicationBuilder app)
        {
            var applicationCookie = new CookieAuthenticationOptions
            {
                AutomaticAuthenticate = true,
                AutomaticChallenge = true,
                CookieName = "AspNetCoreSPA",
                Events = new CookieAuthenticationEvents
                {
                    OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync,
                    OnRedirectToLogin = ctx =>
                    {
                        // If request comming from web api
                        // always return Unauthorized (401)
                        if (ctx.Request.Path.StartsWithSegments("/api") &&
                            ctx.Response.StatusCode == (int)HttpStatusCode.OK)
                        {
                            ctx.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                        }
                        else
                        {
                            ctx.Response.StatusCode = (int)HttpStatusCode.NotFound;
                        }

                        return Task.FromResult(0);
                    }
                },

                // Set to 1 hour
                ExpireTimeSpan = TimeSpan.FromHours(1),

                // Notice that if value = false, 
                // you can use angular-cookies ($cookies.get) to get value of Cookie
                CookieHttpOnly = true
            };

            IdentityOptions identityOptions = app.ApplicationServices.GetRequiredService<IOptions<IdentityOptions>>().Value;
            identityOptions.Cookies.ApplicationCookie = applicationCookie;

            app.UseCookieAuthentication(identityOptions.Cookies.ExternalCookie);
            app.UseCookieAuthentication(identityOptions.Cookies.ApplicationCookie);
        }
    }
}

In Startup.cs, add 

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            app.UseStaticFiles();

            app.UseMyIdentity();

            app.UseMvcWithDefaultRoute();
        }

From now, if you want to secure any controller or action, just use [Authorize] attribute

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace AspNetCoreSPA.Web.Controllers
{
    [Produces("application/json")]
    [Route("api/student")]
    [Authorize]
    public class StudentController : Controller
    {
        private static List<Student> _students = new List<Student>
        {
            new Student { FirstName = "John", LastName = "Doe", Email = "john@example.com"},
            new Student { FirstName = "Mary", LastName = "Moe", Email = "mary@example.com"},
            new Student { FirstName = "July", LastName = "Dooley", Email = "july@example.com"}
        };

        [Route("getAll"), HttpGet]
        public IActionResult GetAll()
        {
            return Json(_students);
        }

        [Route("createStudent"), HttpPost]
        public IActionResult CreateStudent([FromBody] Student student)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest();
            }

            _students.Add(student);

            return Json(_students);
        }
        
        [Route("searchStudent"), HttpGet]
        public IActionResult Search([FromQuery] string firstName)
        {
            return Json(_students.Where(student => student.FirstName.Equals(firstName)));
        }
    }

    public class Student
    {
        [Required]
        public string FirstName { get; set; }

        [Required]
        public string LastName { get; set; }

        [Required]
        public string Email { get; set; }
    }
}

 

 

History

2016/05/29 - Create Part 2

2016/06/04 - Update authentication using CookieAuthenticationOptions

License

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

Share

About the Author

Toan Manh Nguyen
Architect FPT Software Hochiminh
Vietnam Vietnam
No Biography provided

You may also be interested in...

Comments and Discussions

 
QuestionWaiting for part 3 Pin
dangquang10207-Aug-16 23:15
memberdangquang10207-Aug-16 23:15 
AnswerRe: Waiting for part 3 Pin
Toan Manh Nguyen27-Sep-16 21:08
memberToan Manh Nguyen27-Sep-16 21:08 
QuestionAuthorization? Pin
Luis Zeta29-May-16 18:39
memberLuis Zeta29-May-16 18:39 
AnswerRe: Authorization? Pin
Toan Manh Nguyen29-May-16 20:58
memberToan Manh Nguyen29-May-16 20:58 
GeneralRe: Authorization? Pin
Highflyer20-Jun-16 23:33
memberHighflyer20-Jun-16 23:33 
AnswerRe: Authorization? Pin
rhubka27-Jun-16 9:51
professionalrhubka27-Jun-16 9:51 

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.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web05 | 2.8.190423.1 | Last Updated 21 Jun 2016
Article Copyright 2016 by Toan Manh Nguyen
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid