Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

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

0.00/5 (No votes)
20 Jun 2016 2  
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.
  • Source code: 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>

Explanation 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:

Image 1

Click Student menu:

Image 2

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 returns 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:

Image 3

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 clears 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;
            });
    }
})();

Explanation:

  • We use $http for get and post requests.
  • vm.createStudent will be called when we click Submit button in Create form.
  • $http.post or $http.get is a shortcut method (more details here).
  • After student has been 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:

Image 4

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

Image 5

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 fails 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 rendered here.

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

    Let's see the main parts:

    main.html: As I said before, this page is our Master page, all child pages will be rendered:

    <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 - Created Part 2
  • 2016/06/04 - Updated authentication using CookieAuthenticationOptions

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here