Building Web App using ASP.NET Web API Angular 7 and SQL Server

Building Web App using ASP.NET Web API Angular 7 and SQL Server

The comprehensive step by step tutorial on building Web Application using ASP.NET Web API, Angular 7 and Microsoft SQL Server

The comprehensive step by step tutorial on building Web Application using ASP.NET Web API, Angular 7 and Microsoft SQL Server. In this tutorial, we will create a RESTful API web service using ASP.NET Core Web API then create a front-end application with Angular 7. For the backend, we use Microsoft SQL Server 2017 Express Edition with the additional database Northwind. This is our first ASP.NET tutorial, so there will be many shortcomings and a lot of discussions.

Table of Contents:

The following tools, frameworks, package, and modules are required for this tutorial:

We assume that you already install all above required tools. Now, we have to check that DotNet SDK already installed and added to the path. Open command prompt then type this command to make sure DotNet installed and runnable via command prompt.

dotnet --version

You will get this result.

2.1.403

We are using DotNet Framework SDK version 2.1.403. That’s mean you’re ready to move to the main steps.

1. Download and Install Northwind Sample Database

To download Northwind sample database for SQL Server 2017, open your browser then go to this link https://northwinddatabase.codeplex.com/. After download, extract to your root folder (for example C:). Next, open Microsoft SQL Server Management Studio. Login to your local Server then right-click Database in the Object Explorer Window then choose Restore Database.

Choose Device then Click ... button. The select device dialog will open then click Add button, browse to the .bak file that previously extracted then click OK button then click again OK button.

Click OK button to Restore the Northwind Database. Now, you should see the Northwind Database in the Database.

2. Create and Configure a new ASP.NET Web API Application

A slightly different of ASP.NET Core application creation, we will use the command prompt. Of course, you can use the more easiest way when using Microsoft Visual Studio. In the command prompt go to your projects folder then type this command.

dotnet  new webapi –o AspNetAngular –n AspNetAngular

You will the output like below in the Command Prompt.

The template "ASP.NET Core Web API" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on AspNetAngular\AspNetAngular.csproj...
  Restoring packages for C:\Users\DIDIN\Projects\AspNetAngular\AspNetAngular.csproj...
  Generating MSBuild file C:\Users\DIDIN\Projects\AspNetAngular\obj\AspNetAngular.csproj.nuget.g.props.
  Generating MSBuild file C:\Users\DIDIN\Projects\AspNetAngular\obj\AspNetAngular.csproj.nuget.g.targets.
  Restore completed in 5.82 sec for C:\Users\DIDIN\Projects\AspNetAngular\AspNetAngular.csproj.

Restore succeeded.

That’s mean, you can open directly as a folder from Visual Studio Code as well as CSharp Project/Solution from Microsoft Visual Studio. Or you can call this new ASP.NET Core Web API Project by type this command.

cd AspNetAngular
code .

The project will be opened by Visual Studio Code. If you get a notification like below just click Yes button.

Next, open terminal from the menu or press Ctrl+Shift+`. Run these commands to installs all required packages

dotnet add package Microsoft.EntityFrameworkCore.Tools -v 2.1.2
dotnet add package Microsoft.EntityFrameworkCore.SqlServer -v 2.1.2
dotnet add package Microsoft.EntityFrameworkCore.SqlServer.Design -v 1.1.6
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design -v 2.1.5
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Tools -v 2.0.4
dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection -v 5.0.1

If you see a notification like below, just click Restore button.

Now, you will have this packages with the right version installed shown in AspNetAngular.csproj file.

<ItemGroup>
&nbsp; <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="6.0.0" />
&nbsp; <PackageReference Include="Microsoft.AspNetCore.App" />
&nbsp; <PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.1.2" PrivateAssets="All" />
&nbsp; <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.1.2" />
&nbsp; <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.6" />
&nbsp; <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.1.2">
&nbsp; &nbsp; <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
&nbsp; &nbsp; <PrivateAssets>all</PrivateAssets>
&nbsp; </PackageReference>
&nbsp; <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.1.5" />
&nbsp; <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.4" />
</ItemGroup>

Next, build the DotNet application to make sure there’s no error in package dependencies.

dotnet build

You must see this output when everything on the right path.

Build succeeded.
&nbsp; &nbsp; 0 Warning(s)
&nbsp; &nbsp; 0 Error(s)

Next, we have to configure connections to Microsoft SQL Server Database. First, open and edit appsettings.json then add this lines before logging JSON object.

"ConnectionStrings": {
&nbsp; "SQLConnection": "Server=.;Database=NORTHWND;Trusted_Connection=True;User Id=sa;Password=q;Integrated Security=false;MultipleActiveResultSets=true"
},

Open and edit Startup.cs then add this line inside ConfigureServices bracket.

services.AddDbContext<NORTHWNDContext>(options => options.UseSqlServer(Configuration.GetConnectionString("SQLConnection")));

Next, add this line to enable CORS after above line.

services.AddCors();

Also, add this line inside Configure bracket.

app.UseCors(x => x.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());

Don’t forget to import the library or module that added in the Configuration.

3. Generate Models from Microsoft SQL Server Database

We will use the Code Generation Tools package to generate all models that represent all tables in the Northwind Database. Run this command to generate the models and their context.

dotnet ef dbcontext scaffold "Server=.;Database=NORTHWND;Trusted_Connection=True;User Id=sa;Password=q;Integrated Security=false;" Microsoft.EntityFrameworkCore.SqlServer -o Models

You will see the generated Models and Context inside the Models folder.

If there’s a lot of warning or error that comes from IDE linter, just re-open the Visual Studio Code. As you see in the table of contents, we will use only Supplier model for this tutorial. The generated suppliers look like this.

using System;
using System.Collections.Generic;

namespace AspNetAngular.Models
{
&nbsp; &nbsp; public partial class Suppliers
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; public Suppliers()
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Products = new HashSet<Products>();
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; public int SupplierId { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string CompanyName { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string ContactName { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string ContactTitle { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Address { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string City { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Region { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string PostalCode { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Country { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Phone { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Fax { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string HomePage { get; set; }

&nbsp; &nbsp; &nbsp; &nbsp; public ICollection<Products> Products { get; set; }
&nbsp; &nbsp; }
}

That model declared in the NORTHWNDContext.cs the file inside Models folder.

public virtual DbSet<Suppliers> Suppliers { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
&nbsp; modelBuilder.Entity<Suppliers>(entity =>
&nbsp; {
&nbsp; &nbsp; &nbsp; entity.HasKey(e => e.SupplierId);

&nbsp; &nbsp; &nbsp; entity.HasIndex(e => e.CompanyName)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .HasName("CompanyName");

&nbsp; &nbsp; &nbsp; entity.HasIndex(e => e.PostalCode)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .HasName("PostalCode");

&nbsp; &nbsp; &nbsp; entity.Property(e => e.SupplierId).HasColumnName("SupplierID");

&nbsp; &nbsp; &nbsp; entity.Property(e => e.Address).HasMaxLength(60);

&nbsp; &nbsp; &nbsp; entity.Property(e => e.City).HasMaxLength(15);

&nbsp; &nbsp; &nbsp; entity.Property(e => e.CompanyName)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .IsRequired()
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .HasMaxLength(40);

&nbsp; &nbsp; &nbsp; entity.Property(e => e.ContactName).HasMaxLength(30);

&nbsp; &nbsp; &nbsp; entity.Property(e => e.ContactTitle).HasMaxLength(30);

&nbsp; &nbsp; &nbsp; entity.Property(e => e.Country).HasMaxLength(15);

&nbsp; &nbsp; &nbsp; entity.Property(e => e.Fax).HasMaxLength(24);

&nbsp; &nbsp; &nbsp; entity.Property(e => e.HomePage).HasColumnType("ntext");

&nbsp; &nbsp; &nbsp; entity.Property(e => e.Phone).HasMaxLength(24);

&nbsp; &nbsp; &nbsp; entity.Property(e => e.PostalCode).HasMaxLength(10);

&nbsp; &nbsp; &nbsp; entity.Property(e => e.Region).HasMaxLength(15);
&nbsp; });
}

4. Create DTO (Data Transfer Object) for Request Body and Response

To specify request body and response fields, we will use a Data Transfer Object (DTO). For that, create a new DTOs folder and AddSupplierDto.cs, EditSupplierDto.cs, SupplierResponse.cs files inside that folder. Next, open and edit AddSupplierDto.cs then Replace all codes with this.

using System;
using System.ComponentModel.DataAnnotations;

namespace AspNetAngular.Dtos
{
&nbsp; &nbsp; public class AddSupplierDto
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; [Required]
&nbsp; &nbsp; &nbsp; &nbsp; public string CompanyName { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string ContactName { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string ContactTitle { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Address { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string City { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Region { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string PostalCode { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Country { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Phone { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Fax { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string HomePage { get; set; }
&nbsp; &nbsp; }
}

Next, open and edit EditSupplierDto.cs then Replace all codes with this.

using System;
using System.ComponentModel.DataAnnotations;

namespace AspNetAngular.Dtos
{
&nbsp; &nbsp; public class EditSupplierDto
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; [Required]
&nbsp; &nbsp; &nbsp; &nbsp; public int SupplierId { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; [Required]
&nbsp; &nbsp; &nbsp; &nbsp; public string CompanyName { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string ContactName { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string ContactTitle { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Address { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string City { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Region { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string PostalCode { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Country { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Phone { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Fax { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string HomePage { get; set; }
&nbsp; &nbsp; }
}

Next, open and edit SupplierResponseDto.cs then Replace all codes with this.

namespace AspNetAngular.Dtos
{
&nbsp; &nbsp; public class SupplierResponseDto
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; public int SupplierId { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string CompanyName { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string ContactName { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string ContactTitle { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Address { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string City { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string PostalCode { get; set; }

&nbsp; &nbsp; }
}

5. Create Helpers Class for Mapping DTO with Model Classes

To create a helper for mapping DTO to Model classes, create the folder Helpers in the root of project folder then create a file AutoMapperProfile.cs inside that new folder. Open and edit this new file then replace all codes with this.

using AspNetAngular.Dtos;
using AspNetAngular.Models;
using AutoMapper;

namespace AspNetAngular.Helpers
{
&nbsp; &nbsp; public class AutoMapperProfiles: Profile
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; public AutoMapperProfiles()
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; CreateMap<AddSupplierDto, Suppliers>();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; CreateMap<EditSupplierDto, Suppliers>();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; CreateMap<Suppliers, SupplierResponseDto>();
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
}

Next, open and edit again Startup.cs then add this line inside ConfigureServices.

services.AddAutoMapper();

Don’t forget to auto import the library in ConfigureServices.

6. Create Repositories for CRUD (Create, Read, Update Delete) Operations

To create a repository class and interface, we have to create a folder Repositories in the root of the project folder. Next, create an interface file inside that folder with the name IDataRepository.cs then replace all codes with this.

using System.Threading.Tasks;

namespace AspNetAngular.Repositories
{
&nbsp; &nbsp; public interface IDataRepository<T> where T : class
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; void Add(T entity);
&nbsp; &nbsp; &nbsp; &nbsp; void Update(T entity);
&nbsp; &nbsp; &nbsp; &nbsp; void Delete(T entity);
&nbsp; &nbsp; &nbsp; &nbsp; Task<T> SaveAsync(T entity);
&nbsp; &nbsp; }
}

Next, create a file inside that folder with the name DataRepository.cs then replace all codes with this.

using System.Threading.Tasks;
using AspNetAngular.Models;

namespace AspNetAngular.Repositories
{
&nbsp; &nbsp; public class DataRepository<T> : IDataRepository<T> where T : class
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; private readonly NORTHWNDContext _context;

&nbsp; &nbsp; &nbsp; &nbsp; public DataRepository(NORTHWNDContext context)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _context = context;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; public void Add(T entity)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _context.Set<T>().Add(entity);
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; public void Update(T entity)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _context.Set<T>().Update(entity);
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; public void Delete(T entity)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _context.Set<T>().Remove(entity);
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; public async Task<T> SaveAsync(T entity)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; await _context.SaveChangesAsync();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return entity;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
}

Next, open and edit again Startup.cs then add this line inside ConfigureServices bracket.

services.AddScoped(typeof(IDataRepository < > ), typeof(DataRepository < > ));

Also, import the required classes and libraries.

7. Create Controller for CRUD Operations

Now, we will show you how the API available via Controller. We will generate a controller for Supplier model. Before generate, we have to install the tools for it. Run this command in the terminal of Visual Studio Code.

dotnet tool install --global dotnet-aspnet-codegenerator --version 2.2.1

Just run this command to generate it.

dotnet aspnet-codegenerator controller -name SupplierController -api -async -m AspNetAngular.Models.Suppliers -dc NORTHWNDContext -namespace AspNetAngular.Controllers -outDir Controllers

Now, we have a Supplier’s controller that looks like this.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using AspNetAngular.Models;

namespace AspNetAngular.Controllers
{
&nbsp; &nbsp; [Route("api/[controller]")]
&nbsp; &nbsp; [ApiController]
&nbsp; &nbsp; public class SupplierController : ControllerBase
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; private readonly NORTHWNDContext _context;

&nbsp; &nbsp; &nbsp; &nbsp; public SupplierController(NORTHWNDContext context)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _context = context;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; // GET: api/Supplier
&nbsp; &nbsp; &nbsp; &nbsp; [HttpGet]
&nbsp; &nbsp; &nbsp; &nbsp; public IEnumerable<Suppliers> GetSuppliers()
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return _context.Suppliers;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; // GET: api/Supplier/5
&nbsp; &nbsp; &nbsp; &nbsp; [HttpGet("{id}")]
&nbsp; &nbsp; &nbsp; &nbsp; public async Task<IActionResult> GetSuppliers([FromRoute] int id)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!ModelState.IsValid)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return BadRequest(ModelState);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var suppliers = await _context.Suppliers.FindAsync(id);

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (suppliers == null)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return NotFound();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return Ok(suppliers);
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; // PUT: api/Supplier/5
&nbsp; &nbsp; &nbsp; &nbsp; [HttpPut("{id}")]
&nbsp; &nbsp; &nbsp; &nbsp; public async Task<IActionResult> PutSuppliers([FromRoute] int id, [FromBody] Suppliers suppliers)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!ModelState.IsValid)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return BadRequest(ModelState);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (id != suppliers.SupplierId)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return BadRequest();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _context.Entry(suppliers).State = EntityState.Modified;

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; try
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; await _context.SaveChangesAsync();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; catch (DbUpdateConcurrencyException)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!SuppliersExists(id))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return NotFound();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; else
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return NoContent();
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; // POST: api/Supplier
&nbsp; &nbsp; &nbsp; &nbsp; [HttpPost]
&nbsp; &nbsp; &nbsp; &nbsp; public async Task<IActionResult> PostSuppliers([FromBody] Suppliers suppliers)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!ModelState.IsValid)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return BadRequest(ModelState);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _context.Suppliers.Add(suppliers);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; await _context.SaveChangesAsync();

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return CreatedAtAction("GetSuppliers", new { id = suppliers.SupplierId }, suppliers);
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; // DELETE: api/Supplier/5
&nbsp; &nbsp; &nbsp; &nbsp; [HttpDelete("{id}")]
&nbsp; &nbsp; &nbsp; &nbsp; public async Task<IActionResult> DeleteSuppliers([FromRoute] int id)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!ModelState.IsValid)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return BadRequest(ModelState);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var suppliers = await _context.Suppliers.FindAsync(id);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (suppliers == null)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return NotFound();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _context.Suppliers.Remove(suppliers);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; await _context.SaveChangesAsync();

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return Ok(suppliers);
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; private bool SuppliersExists(int id)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return _context.Suppliers.Any(e => e.SupplierId == id);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
}

As you see that generated Controllers using original fields from the Suppliers Model. Next, we will use our previous created DTO for custom Object save, edit and their response. Open and edit Controllers/SupplierController.cs then add this variable after context variable.

private readonly IMapper _mapper;
private readonly IDataRepository<Suppliers> _repo;

Also, inject to the SupplierController constructor.

public SupplierController(NORTHWNDContext context, IMapper mapper, IDataRepository<Suppliers> repo)
{
&nbsp; &nbsp; _context = context;
&nbsp; &nbsp; _mapper = mapper;
&nbsp; &nbsp; _repo = repo;
}

Don’t forget to import all required libraries or classes. Next, replace the HTTPPost method with this codes.

// POST: api/Supplier
[HttpPost]
public async Task<IActionResult> PostSuppliers([FromBody] AddSupplierDto addSupplierDto)
{
&nbsp; &nbsp; if (!ModelState.IsValid)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; return BadRequest(ModelState);
&nbsp; &nbsp; }

&nbsp; &nbsp; var preSupplier = _mapper.Map<Suppliers>(addSupplierDto);
&nbsp; &nbsp; _repo.Add(preSupplier);
&nbsp; &nbsp; var saveSupplier = await _repo.SaveAsync(preSupplier);
&nbsp; &nbsp; var supplierResponse = _mapper.Map<SupplierResponseDto>(saveSupplier);

&nbsp; &nbsp; return StatusCode(201, new { supplierResponse });
}

Next, replace the HTTPPut method with this codes.

// PUT: api/Supplier/5
[HttpPut("{id}")]
public async Task<IActionResult> PutSuppliers([FromRoute] int id, [FromBody] EditSupplierDto editSupplierDto)
{
&nbsp; &nbsp; if (!ModelState.IsValid)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; return BadRequest(ModelState);
&nbsp; &nbsp; }

&nbsp; &nbsp; if (id != editSupplierDto.SupplierId)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; return BadRequest();
&nbsp; &nbsp; }

&nbsp; &nbsp; var preSupplier = _mapper.Map<Suppliers>(editSupplierDto);
&nbsp; &nbsp; _repo.Update(preSupplier);
&nbsp; &nbsp; await _repo.SaveAsync(preSupplier);

&nbsp; &nbsp; return NoContent();
}

Finally, replace the HTTPDelete method with this because we will delete manually busing SQL Query the related table.

// DELETE: api/Supplier/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteSuppliers([FromRoute] int id)
{
&nbsp; &nbsp; if (!ModelState.IsValid)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; return BadRequest(ModelState);
&nbsp; &nbsp; }

&nbsp; &nbsp; var suppliers = await _context.Suppliers.FindAsync(id);

&nbsp; &nbsp; if (suppliers == null)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; return NotFound();
&nbsp; &nbsp; }

&nbsp; &nbsp; _context.Database.ExecuteSqlCommand("DELETE FROM [Order Details] WHERE ProductID IN (SELECT ProductID FROM Products WHERE SupplierID = @supplierId)",
&nbsp; &nbsp; &nbsp; &nbsp; new SqlParameter("@supplierId", suppliers.SupplierId));

&nbsp; &nbsp; _context.Database.ExecuteSqlCommand("DELETE FROM Products WHERE SupplierID = @supplierId",
&nbsp; &nbsp; &nbsp; &nbsp; new SqlParameter("@supplierId", suppliers.SupplierId));

&nbsp; &nbsp; _context.Suppliers.Remove(suppliers);
&nbsp; &nbsp; await _context.SaveChangesAsync();

&nbsp; &nbsp; return Ok(suppliers);
}

8. Test API using Postman

Now, we have to run the ASP.NET Core Web API from the Terminal by typing this command.

dotnet watch run

Watch keyword is the additional command for monitoring any change in the codes then reloading the ASP.NET Core Web API application. Next, open or run the Postman application. Use the GET method and fill the right column after the GET method with localhost:5000/api/Supplier, Headers key with Content-Type, Headers value with application/json.

Now, click the Send button and you should see this result for successful GET Supplier.

[
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; "supplierId": 1,
&nbsp; &nbsp; &nbsp; &nbsp; "companyName": "Exotic Liquids",
&nbsp; &nbsp; &nbsp; &nbsp; "contactName": "Charlotte Cooper",
&nbsp; &nbsp; &nbsp; &nbsp; "contactTitle": "Purchasing Manager",
&nbsp; &nbsp; &nbsp; &nbsp; "address": "49 Gilbert St.",
&nbsp; &nbsp; &nbsp; &nbsp; "city": "London",
&nbsp; &nbsp; &nbsp; &nbsp; "region": null,
&nbsp; &nbsp; &nbsp; &nbsp; "postalCode": "EC1 4SD",
&nbsp; &nbsp; &nbsp; &nbsp; "country": "UK",
&nbsp; &nbsp; &nbsp; &nbsp; "phone": "(171) 555-2222",
&nbsp; &nbsp; &nbsp; &nbsp; "fax": null,
&nbsp; &nbsp; &nbsp; &nbsp; "homePage": null,
&nbsp; &nbsp; &nbsp; &nbsp; "products": []
&nbsp; &nbsp; },
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; "supplierId": 2,
&nbsp; &nbsp; &nbsp; &nbsp; "companyName": "New Orleans Cajun Delights",
&nbsp; &nbsp; &nbsp; &nbsp; "contactName": "Shelley Burke",
&nbsp; &nbsp; &nbsp; &nbsp; "contactTitle": "Order Administrator",
&nbsp; &nbsp; &nbsp; &nbsp; "address": "P.O. Box 78934",
&nbsp; &nbsp; &nbsp; &nbsp; "city": "New Orleans",
&nbsp; &nbsp; &nbsp; &nbsp; "region": "LA",
&nbsp; &nbsp; &nbsp; &nbsp; "postalCode": "70117",
&nbsp; &nbsp; &nbsp; &nbsp; "country": "USA",
&nbsp; &nbsp; &nbsp; &nbsp; "phone": "(100) 555-4822",
&nbsp; &nbsp; &nbsp; &nbsp; "fax": null,
&nbsp; &nbsp; &nbsp; &nbsp; "homePage": "#CAJUN.HTM#",
&nbsp; &nbsp; &nbsp; &nbsp; "products": []
&nbsp; &nbsp; },
&nbsp; &nbsp; ...
]

To get single supplier by supplier ID, just change the URL to this localhost:5000/api/Supplier/2. If found, you will see the result of a single supplier. Next, for POST a Supplier changes the method to POST, leave URL same as the GET supplier list then fill the body with a raw JSON object.

{
&nbsp; "CompanyName": "Djamware Inc.",
&nbsp; "ContactName": "Didin J.",
&nbsp; "ContactTitle": "CEO",
&nbsp; "Address": "Whereever Road 123",
&nbsp; "City": "Bandung",
&nbsp; "Region": "JBR",
&nbsp; "PostalCode": "12345",
&nbsp; "Country": "Indonesia",
&nbsp; "Phone": "(022) 123-4567890",
&nbsp; "Fax": "(022) 123-4567890",
&nbsp; "HomePage": "https://www.djamware.com"
}

You will see this result with status 201.

{
&nbsp; &nbsp; "supplierResponse": {
&nbsp; &nbsp; &nbsp; &nbsp; "companyName": "Djamware Inc.",
&nbsp; &nbsp; &nbsp; &nbsp; "contactName": "Didin J.",
&nbsp; &nbsp; &nbsp; &nbsp; "contactTitle": "CEO",
&nbsp; &nbsp; &nbsp; &nbsp; "address": "Whereever Road 123",
&nbsp; &nbsp; &nbsp; &nbsp; "city": "Bandung",
&nbsp; &nbsp; &nbsp; &nbsp; "region": "JBR",
&nbsp; &nbsp; &nbsp; &nbsp; "postalCode": "12345",
&nbsp; &nbsp; &nbsp; &nbsp; "country": "Indonesia",
&nbsp; &nbsp; &nbsp; &nbsp; "phone": "(022) 123-4567890",
&nbsp; &nbsp; &nbsp; &nbsp; "fax": "(022) 123-4567890",
&nbsp; &nbsp; &nbsp; &nbsp; "homePage": "https://www.djamware.com"
&nbsp; &nbsp; }
}

To update the Supplier data, change the method to PUT and URL to localhost:5000/api/Supplier/30 then add a supplier id field to the raw body.

{
&nbsp; "SupplierId": 30,
&nbsp; "CompanyName": "Djamware.com",
&nbsp; "ContactName": "Didin J.",
&nbsp; "ContactTitle": "Engineer",
&nbsp; "Address": "Whereever Road 123",
&nbsp; "City": "Garut",
&nbsp; "Region": "JBR",
&nbsp; "PostalCode": "12345",
&nbsp; "Country": "Indonesia",
&nbsp; "Phone": "(022) 123-4567890",
&nbsp; "Fax": "(022) 123-4567890",
&nbsp; "HomePage": "https://www.djamware.com"
}

The response should be 204 (No Content) for a successful request. Next, for delete a supplier, simply change the method to DELETE, keep the URL as the previous method and change the body to none. You should see the 200 response and data that deleted in the response body.

9. Install or Update Angular 7 CLI and Create Application

Before installing the Angular 7 CLI, make sure you have installed Node.js https://nodejs.org and can open Node.js command prompt. Next, open the Node.js command prompt then type this command to install Angular 7 CLI.

npm install -g @angular/cli

Next, create an Angular 7 application by typing this command in the root of ASP.NET Core application/project directory.

ng new client

Where client is the name of the Angular 7 application. You can specify your own name, we like to name it client because it’s put inside ASP.NET Core Project directory. If there’s a question, we fill them with Y and SASS. Next, go to the newly created Angular 7 application.

cd client

Run the Angular 7 application for the first time.

ng serve

Now, go to localhost:4200 and you should see this page.

To stop the Angular 7 application, just press CTRL+C keys.

10. Add Routes for Navigation between Angular 7 Pages/Component

On the previous steps, we have to added Angular 7 Routes when answering the questions. Now, we just added the required pages for CRUD (Create, Read, Update, Delete) Supplier data. Type this commands to add the Angular 7 components or pages.

ng g component supplier
ng g component supplier-detail
ng g component supplier-add
ng g component supplier-edit

Open src/app/app.module.ts then you will see those components imported and declared in @NgModule declarations. Next, open and edit src/app/app-routing.module.ts then add this imports.

import { SupplierComponent } from './supplier/supplier.component';
import { SupplierDetailComponent } from './supplier-detail/supplier-detail.component';
import { SupplierAddComponent } from './supplier-add/supplier-add.component';
import { SupplierEditComponent } from './supplier-edit/supplier-edit.component';

Add these arrays to the existing routes constant.

const routes: Routes = [
&nbsp; {
&nbsp; &nbsp; path: 'supplier',
&nbsp; &nbsp; component: SupplierComponent,
&nbsp; &nbsp; data: { title: 'List of Suppliers' }
&nbsp; },
&nbsp; {
&nbsp; &nbsp; path: 'supplier-details/:id',
&nbsp; &nbsp; component: SupplierDetailComponent,
&nbsp; &nbsp; data: { title: 'Supplier Details' }
&nbsp; },
&nbsp; {
&nbsp; &nbsp; path: 'supplier-add',
&nbsp; &nbsp; component: SupplierAddComponent,
&nbsp; &nbsp; data: { title: 'Add Supplier' }
&nbsp; },
&nbsp; {
&nbsp; &nbsp; path: 'supplier-edit/:id',
&nbsp; &nbsp; component: SupplierEditComponent,
&nbsp; &nbsp; data: { title: 'Edit Supplier' }
&nbsp; },
&nbsp; { path: '',
&nbsp; &nbsp; redirectTo: '/supplier',
&nbsp; &nbsp; pathMatch: 'full'
&nbsp; }
];

Open and edit src/app/app.component.html and you will see existing router outlet. Next, modify this HTML page to fit the CRUD page.

<div style="text-align:center">
&nbsp; <h1>
&nbsp; &nbsp; Welcome to {{ title }}!
&nbsp; </h1>
&nbsp; <img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>

<div class="container">
&nbsp; <router-outlet></router-outlet>
</div>

Open and edit src/app/app.component.scss then replace all SCSS codes with this.

.container {
&nbsp; padding: 20px;
}

11. Create Service for Accessing RESTful API

To access ASP.NET Core Web API from Angular 7 application, we have to create an Angular 7 Service first. Type this command to create it.

ng g service api

We have to register the HttpClient module before using it in the Service. Open and edit src/app/app.module.ts then add this import.

import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

Add them to the `@NgModule` imports array.

imports: [
&nbsp; BrowserModule,
&nbsp; FormsModule,
&nbsp; HttpClientModule,
&nbsp; AppRoutingModule
],

To specify the type of response object from the ASP.NET Core Web API, we have to create a class for it. Create a new file src/app/supplier.ts then add this codes.

export class Supplier {
&nbsp; &nbsp; supplierId: number;
&nbsp; &nbsp; companyName: string;
&nbsp; &nbsp; contactName: string;
&nbsp; &nbsp; contactTitle: string;
&nbsp; &nbsp; address: string;
&nbsp; &nbsp; city: string;
&nbsp; &nbsp; region: string;
&nbsp; &nbsp; postalCode: string;
&nbsp; &nbsp; country: string;
&nbsp; &nbsp; phone: string;
&nbsp; &nbsp; fax: string;
&nbsp; &nbsp; homePage: string;
}

Next, open and edit src/app/api.service.ts then add this imports.

import { Observable, of, throwError } from 'rxjs';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { catchError, tap, map } from 'rxjs/operators';
import { Supplier } from './supplier';

Add these constants before the @Injectable.

const httpOptions = {
&nbsp; headers: new HttpHeaders({'Content-Type': 'application/json'})
};
const apiUrl = 'http://localhost:5000/api/';

Inject HttpClient module to the constructor.

constructor(private http: HttpClient) { }

Add the error handler function.

private handleError<T> (operation = 'operation', result?: T) {
&nbsp; return (error: any): Observable<T> => {

&nbsp; &nbsp; // TODO: send the error to remote logging infrastructure
&nbsp; &nbsp; console.error(error); // log to console instead

&nbsp; &nbsp; // Let the app keep running by returning an empty result.
&nbsp; &nbsp; return of(result as T);
&nbsp; };
}

Add all CRUD (create, read, update, delete) functions of suppliers data.

getSuppliers (): Observable<Supplier[]> {
&nbsp; return this.http.get<Supplier[]>(apiUrl)
&nbsp; &nbsp; .pipe(
&nbsp; &nbsp; &nbsp; tap(heroes => console.log('fetched Suppliers')),
&nbsp; &nbsp; &nbsp; catchError(this.handleError('getSuppliers', []))
&nbsp; &nbsp; );
}

getSupplier(id: number): Observable<Supplier> {
&nbsp; const url = `${apiUrl}/${id}`;
&nbsp; return this.http.get<Supplier>(url).pipe(
&nbsp; &nbsp; tap(_ => console.log(`fetched Supplier id=${id}`)),
&nbsp; &nbsp; catchError(this.handleError<Supplier>(`getSupplier id=${id}`))
&nbsp; );
}

addSupplier (supplier: any): Observable<Supplier> {
&nbsp; return this.http.post<Supplier>(apiUrl, supplier, httpOptions).pipe(
&nbsp; &nbsp; tap((supplierRes: Supplier) => console.log(`added Supplier w/ id=${supplierRes.supplierId}`)),
&nbsp; &nbsp; catchError(this.handleError<Supplier>('addSupplier'))
&nbsp; );
}

updateSupplier (id: number, supplier: any): Observable<any> {
&nbsp; const url = `${apiUrl}/${id}`;
&nbsp; return this.http.put(url, supplier, httpOptions).pipe(
&nbsp; &nbsp; tap(_ => console.log(`updated Supplier id=${id}`)),
&nbsp; &nbsp; catchError(this.handleError<any>('updateSupplier'))
&nbsp; );
}

deleteSupplier (id: number): Observable<Supplier> {
&nbsp; const url = `${apiUrl}/${id}`;
&nbsp; return this.http.delete<Supplier>(url, httpOptions).pipe(
&nbsp; &nbsp; tap(_ => console.log(`deleted Supplier id=${id}`)),
&nbsp; &nbsp; catchError(this.handleError<Supplier>('deleteSupplier'))
&nbsp; );
}

12. Display List of Suppliers using Angular 7 Material

To display a list of suppliers to the Angular 7 template. First, open and edit src/app/supplier/supplier.component.ts then add this imports.

import { ApiService } from '../api.service';

Next, inject the API Service to the constructor.

constructor(private api: ApiService) { }

Next, for user interface (UI) we will use Angular 7 Material and CDK. There’s a CLI for generating a Material component like Table as a component, but we will create or add the Table component from scratch to existing component. Type this command to install Angular 7 Material.

ng add @angular/material

If there are some questions, answer them like below.

? Choose a prebuilt theme name, or "custom" for a custom theme: Purple/Green &nbsp; &nbsp; &nbsp; [ Preview: https://material.angular.i
o?theme=purple-green ]
? Set up HammerJS for gesture recognition? Yes
? Set up browser animations for Angular Material? Yes

Next, we have to register all required Angular Material components or modules to src/app/app.module.ts. Open and edit that file then add this imports.

import {
&nbsp; MatInputModule,
&nbsp; MatPaginatorModule,
&nbsp; MatProgressSpinnerModule,
&nbsp; MatSortModule,
&nbsp; MatTableModule,
&nbsp; MatIconModule,
&nbsp; MatButtonModule,
&nbsp; MatCardModule,
&nbsp; MatFormFieldModule } from '@angular/material';

Also, modify FormsModule import to add ReactiveFormsModule.

import { FormsModule, ReactiveFormsModule } from '@angular/forms';

Register the above modules to @NgModule imports.

imports: [
&nbsp; BrowserModule,
&nbsp; FormsModule,
&nbsp; HttpClientModule,
&nbsp; AppRoutingModule,
&nbsp; ReactiveFormsModule,
&nbsp; BrowserAnimationsModule,
&nbsp; MatInputModule,
&nbsp; MatTableModule,
&nbsp; MatPaginatorModule,
&nbsp; MatSortModule,
&nbsp; MatProgressSpinnerModule,
&nbsp; MatIconModule,
&nbsp; MatButtonModule,
&nbsp; MatCardModule,
&nbsp; MatFormFieldModule
],

Next, back to src/app/supplier/supplier.component.ts then add this imports.

import { Supplier } from '../supplier';

Declare the variables of Angular Material Table Data Source before the constructor.

displayedColumns: string[] = ['supplierId', 'companyName', 'contactName'];
data: Supplier[] = [];
isLoadingResults = true;

Modify the ngOnInit function to get list of suppliers immediately.

ngOnInit() {
&nbsp; this.api.getSuppliers()
&nbsp; .subscribe(res => {
&nbsp; &nbsp; this.data = res;
&nbsp; &nbsp; console.log(this.data);
&nbsp; &nbsp; this.isLoadingResults = false;
&nbsp; }, err => {
&nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; this.isLoadingResults = false;
&nbsp; });
}

Next, open and edit src/app/supplier/supplier.component.html then replace all HTML tags with this Angular 7 Material tags.

<div class="example-container mat-elevation-z8">
&nbsp; <div class="example-loading-shade"
&nbsp; &nbsp; &nbsp; &nbsp;*ngIf="isLoadingResults">
&nbsp; &nbsp; <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
&nbsp; </div>
&nbsp; <div class="button-row">
&nbsp; &nbsp; <a mat-flat-button color="primary" [routerLink]="['/supplier-add']"><mat-icon>add</mat-icon></a>
&nbsp; </div>
&nbsp; <div class="mat-elevation-z8">
&nbsp; &nbsp; <table mat-table [dataSource]="data" class="example-table"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;matSort matSortActive="CompanyName" matSortDisableClear matSortDirection="asc">

&nbsp; &nbsp; &nbsp; <!-- Supplier ID Column -->
&nbsp; &nbsp; &nbsp; <ng-container matColumnDef="supplierId">
&nbsp; &nbsp; &nbsp; &nbsp; <th mat-header-cell *matHeaderCellDef>Supplier ID</th>
&nbsp; &nbsp; &nbsp; &nbsp; <td mat-cell *matCellDef="let row">{{row.supplierId}}</td>
&nbsp; &nbsp; &nbsp; </ng-container>

&nbsp; &nbsp; &nbsp; <!-- Company Name Column -->
&nbsp; &nbsp; &nbsp; <ng-container matColumnDef="companyName">
&nbsp; &nbsp; &nbsp; &nbsp; <th mat-header-cell *matHeaderCellDef>Company Name</th>
&nbsp; &nbsp; &nbsp; &nbsp; <td mat-cell *matCellDef="let row">{{row.companyName}}</td>
&nbsp; &nbsp; &nbsp; </ng-container>

&nbsp; &nbsp; &nbsp; <!-- Contact Name Column -->
&nbsp; &nbsp; &nbsp; <ng-container matColumnDef="contactName">
&nbsp; &nbsp; &nbsp; &nbsp; <th mat-header-cell *matHeaderCellDef>Contact Name</th>
&nbsp; &nbsp; &nbsp; &nbsp; <td mat-cell *matCellDef="let row">{{row.contactName}}</td>
&nbsp; &nbsp; &nbsp; </ng-container>

&nbsp; &nbsp; &nbsp; <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
&nbsp; &nbsp; &nbsp; <tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['/supplier-details/', row.supplierId]"></tr>
&nbsp; &nbsp; </table>
&nbsp; </div>
</div>

Finally, to make a little UI adjustment, open and edit src/app/supplier/supplier.component.css then add this CSS codes.

/* Structure */
.example-container {
&nbsp; &nbsp; position: relative;
&nbsp; &nbsp; padding: 5px;
&nbsp; }

&nbsp; .example-table-container {
&nbsp; &nbsp; position: relative;
&nbsp; &nbsp; max-height: 400px;
&nbsp; &nbsp; overflow: auto;
&nbsp; }

&nbsp; table {
&nbsp; &nbsp; width: 100%;
&nbsp; }

&nbsp; .example-loading-shade {
&nbsp; &nbsp; position: absolute;
&nbsp; &nbsp; top: 0;
&nbsp; &nbsp; left: 0;
&nbsp; &nbsp; bottom: 56px;
&nbsp; &nbsp; right: 0;
&nbsp; &nbsp; background: rgba(0, 0, 0, 0.15);
&nbsp; &nbsp; z-index: 1;
&nbsp; &nbsp; display: flex;
&nbsp; &nbsp; align-items: center;
&nbsp; &nbsp; justify-content: center;
&nbsp; }

&nbsp; .example-rate-limit-reached {
&nbsp; &nbsp; color: #980000;
&nbsp; &nbsp; max-width: 360px;
&nbsp; &nbsp; text-align: center;
&nbsp; }

&nbsp; /* Column Widths */
&nbsp; .mat-column-number,
&nbsp; .mat-column-state {
&nbsp; &nbsp; max-width: 64px;
&nbsp; }

&nbsp; .mat-column-created {
&nbsp; &nbsp; max-width: 124px;
&nbsp; }

&nbsp; .mat-flat-button {
&nbsp; &nbsp; margin: 5px;
&nbsp; }

13. Show and Delete Supplier Details using Angular 7 Material

To show supplier details after click or tap on the one of a row inside the Angular 7 Material table, open and edit src/app/supplier-detail/supplier-detail.component.ts then add this imports.

import { ActivatedRoute, Router } from '@angular/router';
import { ApiService } from '../api.service';
import { Supplier } from '../supplier';

Inject above modules to the constructor.

constructor(private route: ActivatedRoute, private api: ApiService, private router: Router) { }

Declare the variables before the constructor for hold supplier data that get from the API.

supplier: Supplier = {
&nbsp; supplierId: null,
&nbsp; companyName: '',
&nbsp; contactName: '',
&nbsp; contactTitle: '',
&nbsp; address: '',
&nbsp; city: '',
&nbsp; region: '',
&nbsp; postalCode: '',
&nbsp; country: '',
&nbsp; phone: '',
&nbsp; fax: '',
&nbsp; homePage: ''
};
isLoadingResults = true;

Add a function for getting Supplier data from the API.

getSupplierDetails(id) {
&nbsp; this.api.getSupplier(id)
&nbsp; &nbsp; .subscribe(data => {
&nbsp; &nbsp; &nbsp; this.supplier = data;
&nbsp; &nbsp; &nbsp; console.log(this.supplier);
&nbsp; &nbsp; &nbsp; this.isLoadingResults = false;
&nbsp; &nbsp; });
}

Call that function when the component is initiated.

ngOnInit() {
&nbsp; this.getSupplierDetails(this.route.snapshot.params['id']);
}

Add this function for delete supplier.

deleteSupplier(id: number) {
&nbsp; this.isLoadingResults = true;
&nbsp; this.api.deleteSupplier(id)
&nbsp; &nbsp; .subscribe(res => {
&nbsp; &nbsp; &nbsp; &nbsp; this.isLoadingResults = false;
&nbsp; &nbsp; &nbsp; &nbsp; this.router.navigate(['/supplier']);
&nbsp; &nbsp; &nbsp; }, (err) => {
&nbsp; &nbsp; &nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; &nbsp; &nbsp; this.isLoadingResults = false;
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; );
}

For the view, open and edit src/app/supplier-detail/supplier-detail.component.html then replace all HTML tags with this.

<div class="example-container mat-elevation-z8">
&nbsp; <div class="example-loading-shade"
&nbsp; &nbsp; &nbsp; &nbsp;*ngIf="isLoadingResults">
&nbsp; &nbsp; <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
&nbsp; </div>
&nbsp; <div class="button-row">
&nbsp; &nbsp; <a mat-flat-button color="primary" [routerLink]="['/supplier']"><mat-icon>list</mat-icon></a>
&nbsp; </div>
&nbsp; <mat-card class="example-card">
&nbsp; &nbsp; <mat-card-header>
&nbsp; &nbsp; &nbsp; <mat-card-title><h2>Supplier ID: {{supplier.supplierId}}</h2></mat-card-title>
&nbsp; &nbsp; &nbsp; <mat-card-subtitle>Company Name: {{supplier.companyName}}</mat-card-subtitle>
&nbsp; &nbsp; </mat-card-header>
&nbsp; &nbsp; <mat-card-content>
&nbsp; &nbsp; &nbsp; <dl>
&nbsp; &nbsp; &nbsp; &nbsp; <dt>Contact Name:</dt>
&nbsp; &nbsp; &nbsp; &nbsp; <dd>{{supplier.contactName}}</dd>
&nbsp; &nbsp; &nbsp; &nbsp; <dt>Contact Title:</dt>
&nbsp; &nbsp; &nbsp; &nbsp; <dd>{{supplier.contactTitle}}</dd>
&nbsp; &nbsp; &nbsp; &nbsp; <dt>Address:</dt>
&nbsp; &nbsp; &nbsp; &nbsp; <dd>{{supplier.address}}</dd>
&nbsp; &nbsp; &nbsp; &nbsp; <dt>City:</dt>
&nbsp; &nbsp; &nbsp; &nbsp; <dd>{{supplier.city}}</dd>
&nbsp; &nbsp; &nbsp; &nbsp; <dt>Region:</dt>
&nbsp; &nbsp; &nbsp; &nbsp; <dd>{{supplier.region}}</dd>
&nbsp; &nbsp; &nbsp; &nbsp; <dt>Postal Code:</dt>
&nbsp; &nbsp; &nbsp; &nbsp; <dd>{{supplier.postalCode}}</dd>
&nbsp; &nbsp; &nbsp; &nbsp; <dt>Country:</dt>
&nbsp; &nbsp; &nbsp; &nbsp; <dd>{{supplier.country}}</dd>
&nbsp; &nbsp; &nbsp; &nbsp; <dt>Phone:</dt>
&nbsp; &nbsp; &nbsp; &nbsp; <dd>{{supplier.phone}}</dd>
&nbsp; &nbsp; &nbsp; &nbsp; <dt>Fax:</dt>
&nbsp; &nbsp; &nbsp; &nbsp; <dd>{{supplier.fax}}</dd>
&nbsp; &nbsp; &nbsp; &nbsp; <dt>Home Page:</dt>
&nbsp; &nbsp; &nbsp; &nbsp; <dd>{{supplier.homePage}}</dd>
&nbsp; &nbsp; &nbsp; </dl>
&nbsp; &nbsp; </mat-card-content>
&nbsp; &nbsp; <mat-card-actions>
&nbsp; &nbsp; &nbsp; <a mat-flat-button color="primary" [routerLink]="['/supplier-edit/', supplier.supplierId || 'all']"><mat-icon>edit</mat-icon></a>
&nbsp; &nbsp; &nbsp; <a mat-flat-button color="warn" (click)="deleteSupplier(supplier.supplierId)"><mat-icon>delete</mat-icon></a>
&nbsp; &nbsp; </mat-card-actions>
&nbsp; </mat-card>
</div>

Finally, open and edit src/app/supplier-detail/supplier-detail.component.css then add these lines of CSS codes.

/* Structure */
.example-container {
&nbsp; position: relative;
&nbsp; padding: 5px;
}

.example-loading-shade {
&nbsp; position: absolute;
&nbsp; top: 0;
&nbsp; left: 0;
&nbsp; bottom: 56px;
&nbsp; right: 0;
&nbsp; background: rgba(0, 0, 0, 0.15);
&nbsp; z-index: 1;
&nbsp; display: flex;
&nbsp; align-items: center;
&nbsp; justify-content: center;
}

.mat-flat-button {
&nbsp; margin: 5px;
}

14. Add a Supplier using Angular 7 Material

To add a new supplier, we have to create Angular 7 reactive form. Open and edit src/app/supplier-add/supplier-add.component.ts then add this imports.

import { Router } from '@angular/router';
import { ApiService } from '../api.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';

Inject above modules to the constructor.

constructor(private router: Router, private api: ApiService, private formBuilder: FormBuilder) { }

Declare variables for the Form Group and all of the required fields inside the form before the constructor.

supplierForm: FormGroup;
companyName = '';
contactName = '';
contactTitle = '';
address = '';
city = '';
region = '';
postalCode = '';
country = '';
phone = '';
fax = '';
homePage = '';
isLoadingResults = false;

Add initial validation for each field.

ngOnInit() {
&nbsp; this.supplierForm = this.formBuilder.group({
&nbsp; &nbsp; 'companyName' : [null, Validators.required],
&nbsp; &nbsp; 'contactName' : [null, null],
&nbsp; &nbsp; 'contactTitle' : [null, null],
&nbsp; &nbsp; 'address' : [null, null],
&nbsp; &nbsp; 'city' : [null, null],
&nbsp; &nbsp; 'region' : [null, null],
&nbsp; &nbsp; 'postalCode' : [null, null],
&nbsp; &nbsp; 'country' : [null, null],
&nbsp; &nbsp; 'phone' : [null, null],
&nbsp; &nbsp; 'fax' : [null, null],
&nbsp; &nbsp; 'homePage' : [null, null]
&nbsp; });
}

Create a function for submitting or POST supplier form.

onFormSubmit(form: NgForm) {
&nbsp; this.isLoadingResults = true;
&nbsp; this.api.addSupplier(form)
&nbsp; &nbsp; .subscribe((res: { [x: string]: any; }) => {
&nbsp; &nbsp; &nbsp; &nbsp; const supplier = res['supplierResponse'];
&nbsp; &nbsp; &nbsp; &nbsp; const id = supplier['supplierId'];
&nbsp; &nbsp; &nbsp; &nbsp; this.isLoadingResults = false;
&nbsp; &nbsp; &nbsp; &nbsp; this.router.navigate(['/supplier-details', id]);
&nbsp; &nbsp; &nbsp; }, (err) => {
&nbsp; &nbsp; &nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; &nbsp; &nbsp; this.isLoadingResults = false;
&nbsp; &nbsp; &nbsp; });
}

Next, add this import for implementing ErrorStateMatcher.

import { ErrorStateMatcher } from '@angular/material/core';

Create a new class after the end of this class bracket.

/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
&nbsp; isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
&nbsp; &nbsp; const isSubmitted = form && form.submitted;
&nbsp; &nbsp; return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
&nbsp; }
}

Instantiate that MyErrorStateMatcher as a variable in main class.

matcher = new MyErrorStateMatcher();

Next, open and edit src/app/supplier-add/supplier-add.component.html then replace all HTML tags with this.

<div class="example-container mat-elevation-z8">
&nbsp; <div class="example-loading-shade"
&nbsp; &nbsp; &nbsp; &nbsp;*ngIf="isLoadingResults">
&nbsp; &nbsp; <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
&nbsp; </div>
&nbsp; <div class="button-row">
&nbsp; &nbsp; <a mat-flat-button color="primary" [routerLink]="['/supplier']"><mat-icon>list</mat-icon></a>
&nbsp; </div>
&nbsp; <mat-card class="example-card">
&nbsp; &nbsp; <form [formGroup]="supplierForm" (ngSubmit)="onFormSubmit(supplierForm.value)">
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Company Name" formControlName="companyName"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; &nbsp; <mat-error>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span *ngIf="!supplierForm.get('companyName').valid && supplierForm.get('companyName').touched">Please enter Company Name</span>
&nbsp; &nbsp; &nbsp; &nbsp; </mat-error>
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Contact Name" formControlName="contactName"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Contact Title" formControlName="contactTitle"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Address" formControlName="address"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="City" formControlName="city"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Region" formControlName="region"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Postal Code" formControlName="postalCode"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Country" formControlName="country"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Phone" formControlName="phone"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Fax" formControlName="fax"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Home Page" formControlName="homePage"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <div class="button-row">
&nbsp; &nbsp; &nbsp; &nbsp; <button type="submit" [disabled]="!supplierForm.valid" mat-flat-button color="primary"><mat-icon>save</mat-icon></button>
&nbsp; &nbsp; &nbsp; </div>
&nbsp; &nbsp; </form>
&nbsp; </mat-card>
</div>

Finally, open and edit src/app/supplier-add/supplier-add.component.css then add this CSS codes.

/* Structure */
.example-container {
&nbsp; position: relative;
&nbsp; padding: 5px;
}

.example-form {
&nbsp; min-width: 150px;
&nbsp; max-width: 500px;
&nbsp; width: 100%;
}

.example-full-width {
&nbsp; width: 100%;
}

.example-full-width:nth-last-child() {
&nbsp; margin-bottom: 10px;
}

.button-row {
&nbsp; margin: 10px 0;
}

.mat-flat-button {
&nbsp; margin: 5px;
}

15. Edit a Supplier using Angular 7 Material

We have put an edit button inside the Supplier Detail component for call Edit page. Now, open and edit src/app/supplier-edit/supplier-edit.component.ts then add this imports.

import { Router, ActivatedRoute } from '@angular/router';
import { ApiService } from '../api.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material/core';

Inject above modules to the constructor.

constructor(private router: Router, private route: ActivatedRoute, private api: ApiService, private formBuilder: FormBuilder) { }

Declare the Form Group variable and all of the required variables for the supplier form before the constructor.

supplierForm: FormGroup;
companyName = '';
contactName = '';
contactTitle = '';
address = '';
city = '';
region = '';
postalCode = '';
country = '';
phone = '';
fax = '';
homePage = '';
isLoadingResults = false;
matcher = new MyErrorStateMatcher();

Next, add validation for all fields when the component is initiated.

ngOnInit() {
&nbsp; this.getSupplier(this.route.snapshot.params['id']);
&nbsp; this.supplierForm = this.formBuilder.group({
&nbsp; &nbsp; 'companyName' : [null, Validators.required],
&nbsp; &nbsp; 'contactName' : [null, null],
&nbsp; &nbsp; 'contactTitle' : [null, null],
&nbsp; &nbsp; 'address' : [null, null],
&nbsp; &nbsp; 'city' : [null, null],
&nbsp; &nbsp; 'region' : [null, null],
&nbsp; &nbsp; 'postalCode' : [null, null],
&nbsp; &nbsp; 'country' : [null, null],
&nbsp; &nbsp; 'phone' : [null, null],
&nbsp; &nbsp; 'fax' : [null, null],
&nbsp; &nbsp; 'homePage' : [null, null]
&nbsp; });
}

Create a function for getting supplier data that filled to each form fields.

getSupplier(id: number) {
&nbsp; this.api.getSupplier(id).subscribe(data => {
&nbsp; &nbsp; this.supplierId = data.supplierId;
&nbsp; &nbsp; this.supplierForm.setValue({
&nbsp; &nbsp; &nbsp; companyName: data.companyName,
&nbsp; &nbsp; &nbsp; contactName: data.contactName,
&nbsp; &nbsp; &nbsp; contactTitle: data.contactTitle,
&nbsp; &nbsp; &nbsp; address: data.address,
&nbsp; &nbsp; &nbsp; city: data.city,
&nbsp; &nbsp; &nbsp; region: data.region,
&nbsp; &nbsp; &nbsp; postalCode: data.postalCode,
&nbsp; &nbsp; &nbsp; country: data.country,
&nbsp; &nbsp; &nbsp; phone: data.phone,
&nbsp; &nbsp; &nbsp; fax: data.fax,
&nbsp; &nbsp; &nbsp; homePage: data.homePage
&nbsp; &nbsp; });
&nbsp; });
}

Create a function to update the supplier changes.

onFormSubmit(form: NgForm) {
&nbsp; this.isLoadingResults = true;
&nbsp; this.api.updateSupplier(this.supplierId, form)
&nbsp; &nbsp; .subscribe(res => {
&nbsp; &nbsp; &nbsp; &nbsp; this.isLoadingResults = false;
&nbsp; &nbsp; &nbsp; &nbsp; this.router.navigate(['/supplier']);
&nbsp; &nbsp; &nbsp; }, (err) => {
&nbsp; &nbsp; &nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; &nbsp; &nbsp; this.isLoadingResults = false;
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; );
}

Add a function for handling show supplier details button.

supplierDetails() {
&nbsp; this.router.navigate(['/supplier-details', this.supplierId]);
}

Add a class that implementing ErrorStateMatcher after the current class.

/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
&nbsp; isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
&nbsp; &nbsp; const isSubmitted = form && form.submitted;
&nbsp; &nbsp; return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
&nbsp; }
}

Next, open and edit src/app/supplier-edit/supplier-edit.component.html then replace all HTML tags with this.

<div class="example-container mat-elevation-z8">
&nbsp; <div class="example-loading-shade"
&nbsp; &nbsp; &nbsp; &nbsp;*ngIf="isLoadingResults">
&nbsp; &nbsp; <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
&nbsp; </div>
&nbsp; <div class="button-row">
&nbsp; &nbsp; <a mat-flat-button color="primary" [routerLink]="['/supplier']"><mat-icon>list</mat-icon></a>
&nbsp; </div>
&nbsp; <mat-card class="example-card">
&nbsp; &nbsp; <form [formGroup]="supplierForm" (ngSubmit)="onFormSubmit(supplierForm.value)">
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Company Name" formControlName="companyName"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; &nbsp; <mat-error>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span *ngIf="!supplierForm.get('companyName').valid && supplierForm.get('companyName').touched">Please enter Company Name</span>
&nbsp; &nbsp; &nbsp; &nbsp; </mat-error>
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Contact Name" formControlName="contactName"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Contact Title" formControlName="contactTitle"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Address" formControlName="address"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="City" formControlName="city"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Region" formControlName="region"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Postal Code" formControlName="postalCode"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Country" formControlName="country"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Phone" formControlName="phone"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Fax" formControlName="fax"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Home Page" formControlName="homePage"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <div class="button-row">
&nbsp; &nbsp; &nbsp; &nbsp; <button type="submit" [disabled]="!supplierForm.valid" mat-flat-button color="primary"><mat-icon>update</mat-icon></button>
&nbsp; &nbsp; &nbsp; </div>
&nbsp; &nbsp; </form>
&nbsp; </mat-card>
</div>

Finally, open and edit src/app/supplier-edit/supplier-edit.component.css then add this lines of CSS codes.

/* Structure */
.example-container {
&nbsp; position: relative;
&nbsp; padding: 5px;
}

.example-form {
&nbsp; min-width: 150px;
&nbsp; max-width: 500px;
&nbsp; width: 100%;
}

.example-full-width {
&nbsp; width: 100%;
}

.example-full-width:nth-last-child() {
&nbsp; margin-bottom: 10px;
}

.button-row {
&nbsp; margin: 10px 0;
}

.mat-flat-button {
&nbsp; margin: 5px;
}

16. Run and Test the ASP.NET Core Web API and Angular 7 CRUD Web Application

Before running the ASP.NET Core Web API that consumes by Angular 7 application, make sure ASP.NET Core Web API serve by HTTP only. Just open Properties\launchSettings.json then remove https from the applicationUrl. Next, type this command in the Visual Studio Code Terminal.

dotnet watch run

To run the Angular 7 application, type this command from the Node.js Command Prompt.

ng serve

Now, you will see this Angular 7 pages when pointing your browser to localhost:4200.

You can just navigate through the whole Angular 7 application.

That it’s, the tutorial of Building Web App using ASP.NET Web API Angular 7 and SQL Server. You can find the fully working source code from our GitHub.

Learn More

Angular 7 (formerly Angular 2) - The Complete Guide

Learn and Understand AngularJS

Angular Crash Course for Busy Developers

The Complete Angular Course: Beginner to Advanced

Angular (Angular 2+) & NodeJS - The MEAN Stack Guide

The Complete ASP.NET MVC 5 Course

Build a Real-world App with ASP.NET Core and Angular 2 (4+)

ASP NET Core (ASP.NET 5),MVC 6,C#,Angular2 & EF Crash Course

ASP Net Core, SQL Server, and Angular 7: Web App Authentication

ASP Net Core, SQL Server, and Angular 7: Web App Authentication

The comprehensive step by step tutorial on building Web Application Authentication using ASP.NET Core Web API, Microsoft SQL Server, and Angular 7

Originally published by Didin J at djamware.com

The comprehensive step by step tutorial on building Web Application Authentication using ASP.NET Core Web API, Microsoft SQL Server, and Angular 7. We will create our own Microsoft SQL Server Database and Tables (User and Book). Password in the User table will be encrypted using salted HMACSHA512. The authentication flow describes a sequence diagram below.

Table of contents


  • Create Microsoft SQL Server Database and Tables
  • Create and Configure a new ASP.NET Core Web API Application
  • Generate Models and Context from Microsoft SQL Server Database
  • Create DTO (Data Transfer Object) for Request Body and Response
  • Create Helpers Class for Mapping DTO with Model Classes
  • Create Controller for Books Data
  • Create a Repository for Authentication
  • Create Controller for Authentication
  • Test Secure API using Postman
  • Install or Update Angular 7 CLI and Create Application
  • Add Routes for Navigation between Angular Pages/Component
  • Create a custom Angular 7 HttpInterceptor
  • Create Services for Accessing Book and Authentication API
  • Display List of Book using Angular 7 Material
  • Create a Login and Register Page
  • Run and Test the ASP Net Core, SQL Server, and Angular 7: Web App Authentication

The book API is secure, so only authorized user can access that endpoint otherwise it will return 401 (Unauthorized) error and redirect the user to the login page. Next, the user will log in using email and password then after successful login (credentials validated successfully), it will response success status with JWT token. JWT token will be used to access book endpoint as Authorization headers. So, the token should be store in Angular 7 app and all access to book API securely and seamlessly using Angular 7 Http Interceptors.

The following tools, frameworks, package, and modules are required for this tutorial:

  1. ASP.NET Core SDK
  2. Visual Studio Code
  3. Node.js
  4. Angular 7
  5. SQL Server 2017 Express
  6. Postman
  7. Command prompt (CMD)

We assume that you already install all above required tools. Now, we have to check that DotNet SDK already installed and added to the path. Open command prompt then type this command to make sure DotNet installed and runnable via command prompt.

dotnet --version

You will get this result.

2.1.403

We are using the DotNet Framework SDK version 2.1.403. That's mean you're ready to move to the main steps.

Create Microsoft SQL Server Database and Tables

We will create a Database for this web application with User and Book tables. For that, open Microsoft SQL Server Management Studio then connects to your local SQL Server (we are using SQL Express 2017). Right-click Database on the left pane then clicks New Database menu.

Fill Database Name (we name it "Book Store"), leave owner as default because we will use sa user then click OK button. Next, expand the BookStore Database then right-click tables -> table. Fill the table as below then save as `TblUser`.

Or simply just run this SQL commands.

USE [BookStore]
GO

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[TblUser](
    [UserID] [int] IDENTITY(1,1) NOT NULL,
    [FullName] varchar NULL,
    [Email] varchar NULL,
    [Password] varbinary NULL,
    [Salt] varbinary NULL,
 CONSTRAINT [PK_TblUser] PRIMARY KEY CLUSTERED
(
    [UserID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

Next, create a Book table using these SQL commands.

USE [BookStore]
GO

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[TblBook](
    [BookID] [int] IDENTITY(1,1) NOT NULL,
    [ISBN] varchar NULL,
    [Title] varchar NULL,
    [Author] varchar NULL,
    [Description] varchar NULL,
    [Publisher] varchar NULL,
    [PublishedYear] [int] NULL,
    [Price] [decimal](18, 0) NULL,
 CONSTRAINT [PK_TblBook] PRIMARY KEY CLUSTERED
(
    [BookID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

Create and Configure a new ASP.NET Core Web API Application

A slightly different ASP.NET Core application creation, we will use the command prompt. Of course, you can use the more easiest way when using Microsoft Visual Studio. In the command prompt go to your projects folder then type this command.

dotnet  new webapi –o AspNetAngularAuth –n AspNetAngularAuth

You will the output like below in the Command Prompt.

The template "ASP.NET Core Web API" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on AspNetAngularAuth\AspNetAngularAuth.csproj...
  Restoring packages for C:\Users\DIDIN\Projects\AspNetAngularAuth\AspNetAngularAuth.csproj...
  Generating MSBuild file C:\Users\DIDIN\Projects\AspNetAngularAuth\obj\AspNetAngularAuth.csproj.nuget.g.props.
  Generating MSBuild file C:\Users\DIDIN\Projects\AspNetAngularAuth\obj\AspNetAngularAuth.csproj.nuget.g.targets.
  Restore completed in 5.8 sec for C:\Users\DIDIN\Projects\AspNetAngularAuth\AspNetAngularAuth.csproj.

Restore succeeded.

That's mean, you can open directly as a folder from Visual Studio Code as well as CSharp Project/Solution from Microsoft Visual Studio. Or you can call this new ASP.NET Core Web API Project by type this command.

cd AspNetAngularAuth
code .

The project will be opened by Visual Studio Code. Next, open terminal from the menu or press Ctrl+Shift+. Run these commands to installs all required packages.</p><pre class="ql-syntax" spellcheck="false">dotnet add package Microsoft.EntityFrameworkCore.Tools -v 2.1.2 dotnet add package Microsoft.EntityFrameworkCore.SqlServer -v 2.1.2 dotnet add package Microsoft.EntityFrameworkCore.SqlServer.Design -v 1.1.6 dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design -v 2.1.5 dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Tools -v 2.0.4 dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection -v 5.0.1 </pre><p>If you see a notification like below, just click Restore button.</p><p><img src="https://s3-ap-southeast-1.amazonaws.com/djamblog/article-270119063810.png"></p><p>Now, you will have these packages with the right version installed shown in <code>AspNetAngularAuth.csproj</code> file.</p><pre class="ql-syntax" spellcheck="false"> &lt;ItemGroup&gt; &nbsp; &lt;PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="5.0.1" /&gt; &nbsp; &lt;PackageReference Include="Microsoft.AspNetCore.App" /&gt; &nbsp; &lt;PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.1.2" PrivateAssets="All" /&gt; &nbsp; &lt;PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.1.2" /&gt; &nbsp; &lt;PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.6" /&gt; &nbsp; &lt;PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.1.2"&gt; &nbsp; &nbsp; &lt;IncludeAssets&gt;runtime; build; native; contentfiles; analyzers&lt;/IncludeAssets&gt; &nbsp; &nbsp; &lt;PrivateAssets&gt;all&lt;/PrivateAssets&gt; &nbsp; &lt;/PackageReference&gt; &nbsp; &lt;PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.1.5" /&gt; &nbsp; &lt;PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.4" /&gt; &lt;/ItemGroup&gt; </pre><p>Next, build the DotNet application to make sure there's no error in package dependencies.</p><pre class="ql-syntax" spellcheck="false">dotnet build </pre><p>You must see this output when everything on the right path.</p><pre class="ql-syntax" spellcheck="false">Build succeeded. &nbsp; &nbsp; 0 Warning(s) &nbsp; &nbsp; 0 Error(s) </pre><p>Next, we have to configure connections to Microsoft SQL Server Database. First, open and edit <code>appsettings.json</code> then add these lines before loggingJSON object.</p><pre class="ql-syntax" spellcheck="false"> "ConnectionStrings": { &nbsp; "SQLConnection": "Server=.;Database=BookStore;Trusted_Connection=True;User Id=sa;Password=q;Integrated Security=false;MultipleActiveResultSets=true" }, </pre><p>Add this configuration for Token secret.</p><pre class="ql-syntax" spellcheck="false">"AppSettings": { &nbsp;&nbsp; &nbsp;"Token": "MySecretTokenForJwt" }, </pre><p>Open and editStartup.csthen add this line insideConfigureServicesbracket.</p><pre class="ql-syntax" spellcheck="false">services.AddDbContext&lt;BookStoreContext&gt;(options =&gt; options.UseSqlServer(Configuration.GetConnectionString("SQLConnection"))); </pre><p>Don't worry <code>BookStoreContext</code> will be added later when generate context and data models. For now, you can comment out this line then uncomment after Models and Context generation and set the imports to it. Next, add this line to enable CORS after the above line.</p><pre class="ql-syntax" spellcheck="false"> services.AddCors(); </pre><p>Next, add these lines to configure JWT Authentication token.</p><pre class="ql-syntax" spellcheck="false">services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) &nbsp; .AddJwtBearer(options =&gt; { &nbsp; &nbsp; &nbsp; options.TokenValidationParameters = new TokenValidationParameters &nbsp; &nbsp; &nbsp; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ValidateIssuerSigningKey = true, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .GetBytes(Configuration.GetSection("AppSettings:Token").Value)), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ValidateIssuer = false, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ValidateAudience = false &nbsp; &nbsp; &nbsp; }; &nbsp; }); </pre><p>Also, add these lines inside <code>Configure</code> bracket.</p><pre class="ql-syntax" spellcheck="false"> app.UseCors(x =&gt; x.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()); app.UseAuthentication(); </pre><p>Don't forget to import the library or module that added in the Configuration.</p><h2><br></h2><h2><strong>Generate Models and Context from Microsoft SQL Server Database</strong></h2><p>We will use the Code Generation Tools package to generate all models that represent all tables in theBookStoreDatabase including theBookStoreContext. Run this command to generate the models and their context.</p><pre class="ql-syntax" spellcheck="false">dotnet ef dbcontext scaffold "Server=.;Database=BookStore;Trusted_Connection=True;User Id=sa;Password=q;Integrated Security=false;" Microsoft.EntityFrameworkCore.SqlServer -o Models </pre><p>You will see the generated Models and Context inside the Models folder.</p><p><img src="https://s3-ap-southeast-1.amazonaws.com/djamblog/article-270419111940.png"></p><p>Remove unused lines (without commenting out) to clear the error in BookStoreContext. If you're not using Models and Context generator, the contents of Models/TblUser.cs` looks like these.

using System;
using System.Collections.Generic;

namespace AspNetAngularAuth.Models
{
    public partial class TblUser
    {
        public int UserId { get; set; }
        public string FullName { get; set; }
        public string Email { get; set; }
        public byte[] Password { get; set; }
        public byte[] Salt { get; set; }
    }
}

The Models/TblBook.cs look like these.

using System;
using System.Collections.Generic;

namespace AspNetAngularAuth.Models
{
    public partial class TblBook
    {
        public int BookId { get; set; }
        public string Isbn { get; set; }
        public string Title { get; set; }
        public string Author { get; set; }
        public string Description { get; set; }
        public string Publisher { get; set; }
        public int? PublishedYear { get; set; }
        public decimal? Price { get; set; }
    }
}

And the Models/BookStoreContext.cs looks like these.

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;

namespace AspNetAngularAuth.Models
{
    public partial class BookStoreContext : DbContext
    {
        public BookStoreContext()
        {
        }

        public BookStoreContext(DbContextOptions<BookStoreContext> options)
            : base(options)
        {
        }

        public virtual DbSet<TblBook> TblBook { get; set; }
        public virtual DbSet<TblUser> TblUser { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
               optionsBuilder.UseSqlServer("Server=.;Database=BookStore;Trusted_Connection=True;User Id=sa;Password=q;Integrated Security=false;");
            }
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<TblBook>(entity =>
            {
                entity.HasKey(e => e.BookId);

                entity.Property(e => e.BookId).HasColumnName("BookID");

                entity.Property(e => e.Author)
                    .HasMaxLength(50)
                    .IsUnicode(false);

                entity.Property(e => e.Description)
                    .HasMaxLength(200)
                    .IsUnicode(false);

                entity.Property(e => e.Isbn)
                    .HasColumnName("ISBN")
                    .HasMaxLength(50)
                    .IsUnicode(false);

                entity.Property(e => e.Price).HasColumnType("decimal(18, 0)");

                entity.Property(e => e.Publisher)
                    .HasMaxLength(50)
                    .IsUnicode(false);

                entity.Property(e => e.Title)
                    .HasMaxLength(100)
                    .IsUnicode(false);
            });

            modelBuilder.Entity<TblUser>(entity =>
            {
                entity.HasKey(e => e.UserId);

                entity.Property(e => e.UserId).HasColumnName("UserID");

                entity.Property(e => e.Email)
                    .HasMaxLength(50)
                    .IsUnicode(false);

                entity.Property(e => e.FullName)
                    .HasMaxLength(50)
                    .IsUnicode(false);

                entity.Property(e => e.Password).HasMaxLength(128);

                entity.Property(e => e.Salt).HasMaxLength(128);
            });
        }
    }
}

Create DTO (Data Transfer Object) for Request Body and Response

To specify the request body and response fields, we will use a Data Transfer Object (DTO). For that, create a new DTOs folder and BookListDto.cs, LoginDto.cs, RegisterDto.cs files inside that folder. Next, open and edit Dtos/LoginDto.cs then replace all CSharp codes with these.

using System.ComponentModel.DataAnnotations;

namespace AspNetAngularAuth.Dtos
{
    public class LoginDto
    {
        [Required]
        public string Email { get; set; }
        [Required]
        public string Password { get; set; }
    }
}

Next, open and edit Dtos/RegisterDto.cs then replace all CSharp codes with these.

using System.ComponentModel.DataAnnotations;

namespace AspNetAngularAuth.Dtos
{
    public class RegisterDto
    {
        [Required]
        [StringLength(50, MinimumLength = 3, ErrorMessage = "Email must be at least 3 characters")]
        public string FullName { get; set; }
        [Required]
        [StringLength(50, MinimumLength = 3, ErrorMessage = "Email must be at least 3 characters")]
        public string Email { get; set; }
        [Required]
        [StringLength(64, MinimumLength = 8, ErrorMessage = "You must provide password between 8 and 20 characters")]
        public string Password { get; set; }
    }
}


Create Helpers Class for Mapping DTO with Model Classes

To create a helper for mapping DTO to Model classes, create the folder Helpers in the root of the project folder then create a file AutoMapperProfile.cs inside that new folder. Open and edit this new file then replace all codes with this.

using AspNetAngularAuth.Dtos;
using AspNetAngularAuth.Models;
using AutoMapper;

namespace AspNetAngularAuth.Helpers
{
    public class AutoMapperProfile: Profile
    {
        public AutoMapperProfile()
        {
            CreateMap<TblBook, BookListDto>();
            CreateMap<LoginDto, TblUser>();
            CreateMap<RegisterDto, TblUser>();
        }
    }
}

Next, open and edit again Startup.cs then add this line inside ConfigureServices.

services.AddAutoMapper();

Don't forget to auto import the library in ConfigureServices.


Create Controller for Books Data

To get a list of the book using API, we have to create a CSharp file Controllers/BookController.cs. Open and edit Controllers/BookController.cs then replace all CSharp codes with these.

using System.Threading.Tasks;
using AspNetAngularAuth.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace AspNetAngularAuth.Controllers
{
    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class BookController: ControllerBase
    {
        private readonly BookStoreContext _context;
        public BookController(BookStoreContext context)
        {
            _context = context;
        }

        [HttpGet]
        public async Task<IActionResult> GetBooks()
        {
            var data = await _context.TblBook.ToListAsync();
            return Ok(data);
        }
    }
}

There's an [Authorized] annotation which means this API resource is secured. So, only authorized user can access this API endpoint.

Create a Repository for Authentication

Before creating a file for Authentication repository, we have to create a repositories folder first with the name Repositories in the root of the project folder. Next, create a file with the name Repositories/AuthRepository.cs. Open and edit that file then replace all CSharp codes with these.

using System.Threading.Tasks;
using AspNetAngularAuth.Models;
using Microsoft.EntityFrameworkCore;

namespace AspNetAngularAuth.Repositories
{
    public class AuthRepository: IAuthRepository
    {
        private readonly BookStoreContext _context;

        public AuthRepository(BookStoreContext context)
        {
            _context = context;
        }

        public async Task<TblUser> Login(string email, string password)
        {
            var user = await _context.TblUser.FirstOrDefaultAsync(x => x.Email == email);
            if (user == null)
                return null;

            if (!VerifyPasswordHash(password, user.Password, user.Salt))
                return null;

            return user; // auth successful
        }

        public async Task<TblUser> Register(TblUser user, string password)
        {
            byte[] passwordHash, salt;
            CreatePasswordHash(password, out passwordHash, out salt);
            user.Password = passwordHash;
            user.Salt = salt;

            await _context.TblUser.AddAsync(user);
            await _context.SaveChangesAsync();

            return user;
        }

        private bool VerifyPasswordHash(string password, byte[] passwordHash, byte[] salt)
        {
            using (var hmac = new System.Security.Cryptography.HMACSHA512(salt))
            {
                var computedHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
                for (int i = 0; i < computedHash.Length; i++)
                {
                    if (computedHash[i] != passwordHash[i]) return false;
                }
            }
            return true;
        }

        private void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] salt)
        {
            using (var hmac = new System.Security.Cryptography.HMACSHA512())
            {
                salt = hmac.Key;
                passwordHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
            }
        }

        public async Task<bool> UserExists(string Username)
        {
            if (await _context.TblUser.AnyAsync(x => x.Email == Username))
                return true;
            return false;
        }
    }
}

Next, create an interface file with the name IAuthRepository.cs then replace all CSharp code with these.

using System.Threading.Tasks;
using AspNetAngularAuth.Models;

namespace AspNetAngularAuth.Repositories
{
     public interface IAuthRepository
     {
             Task<TblUser> Register(TblUser user, string password);
             Task<TblUser> Login(string username, string password);
             Task<bool> UserExists(string username);
     }
}

Next, open and edit again Startup.cs then add this line inside ConfigureServices bracket.

services.AddScoped<IAuthRepository, AuthRepository>();

Also, import the required classes and libraries.

Create Controller for Authentication

Now, we have to create an endpoint for user login and register. Create a CSharp file with the name Controllers/AuthController.cs then open that file and replace all CSharp codes with these.

using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using AspNetAngularAuth.Dtos;
using AspNetAngularAuth.Models;
using AspNetAngularAuth.Repositories;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;

namespace AspNetAngularAuth.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class AuthController: ControllerBase
    {
        private readonly IAuthRepository _repo;
        private readonly IConfiguration _config;
        private readonly IMapper _mapper;

        public AuthController(IAuthRepository repo, IConfiguration config, IMapper mapper)
        {
            _mapper = mapper;
            _config = config;
            _repo = repo;
        }

        [HttpPost("register")]
        public async Task<IActionResult> Register(RegisterDto registerDto)
        {
            registerDto.Email = registerDto.Email.ToLower();
            if (await _repo.UserExists(registerDto.Email))
                return BadRequest("Email already exists");

            var userToCreate = _mapper.Map<TblUser>(registerDto);
            var createdUser = await _repo.Register(userToCreate, registerDto.Password);
            return StatusCode(201, new { email = createdUser.Email, fullname = createdUser.FullName });
        }

        [HttpPost("login")]
        public async Task<IActionResult> Login(LoginDto loginDto)
        {
            var userFromRepo = await _repo.Login(loginDto.Email.ToLower(), loginDto.Password);
            if (userFromRepo == null)
                return Unauthorized();

            var claims = new[]
            {
                new Claim(ClaimTypes.NameIdentifier, userFromRepo.UserId.ToString()),
                new Claim(ClaimTypes.Name, userFromRepo.Email)
            };

            var key = new SymmetricSecurityKey(Encoding.UTF8
                .GetBytes(_config.GetSection("AppSettings:Token").Value));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(claims),
                Expires = DateTime.Now.AddDays(1),
                SigningCredentials = creds
            };

            var tokenHandler = new JwtSecurityTokenHandler();
            var token = tokenHandler.CreateToken(tokenDescriptor);

            return Ok(new {token = tokenHandler.WriteToken(token), email = userFromRepo.Email, fullname = userFromRepo.FullName});
        }
    }
}


Test Secure API using Postman

Now, we have to run the ASP.NET Core Web API from the Terminal by typing this command.

dotnet watch run

Watch keyword is the additional command for monitoring any change in the codes then reloading the ASP.NET Core Web API application. Next, open or run the Postman application. Use the GET method and fill the right column after the GET method with localhost:5000/api/Book, Headers key with Content-Type, Headers value with application/json. You will see 401 response after sending the request.

That's mean, only authorized user can access the /api/Book endpoint. Next, we have to register a user first before login and get the authentication token. In the Postman, change the Method to POST, change the address to /api/auth/Register and fill the body (raw) with this JSON data.


Next, login using above successfully registered email and password. Change the address to /api/auth/Login and body (raw) as below.

Now, you can get the Token from the response and put as Authorization -> Bearer Token value to GET book data.

Install or Update Angular 7 CLI and Create Application

Before installing the Angular 7 CLI, make sure you have installed Node.js https://nodejs.organd can open Node.js command prompt. Next, open the Node.js command prompt then type this command to install Angular 7 CLI.

npm install -g @angular/cli

Next, create an Angular 7 application by typing this command in the root of ASP.NET Core application/project directory.

ng new client

Where client is the name of the Angular 7 application. You can specify your own name, we like to name it client because it's put inside ASP.NET Core Project directory. If there's a question, we fill them with Y and SCSS. Next, go to the newly created Angular 7 application.

cd client

Run the Angular 7 application for the first time.

ng serve

Now, go to localhost:4200 and you should see this page.


Add Routes for Navigation between Angular Pages/Component

On the previous steps, we have to add Angular 7 Routes when answering the questions. Now, we just added the required pages for CRUD (Create, Read, Update, Delete) Supplier data. Type this commands to add the Angular 7 components or pages.

ng g component book
ng g component auth/login
ng g component auth/register

Open src/app/app.module.ts then you will see those components imported and declared in @NgModule declarations. Next, open and edit src/app/app-routing.module.ts then add these imports.

import { BookComponent } from './book/book.component';
import { LoginComponent } from './auth/login/login.component';
import { RegisterComponent } from './auth/register/register.component';

Add these arrays to the existing routes constant.

const routes: Routes = [
  {
    path: 'book',
    component: BookComponent,
    data: { title: 'List of Books' }
  },
  {
    path: 'login',
    component: LoginComponent,
    data: { title: 'Login' }
  },
  {
    path: 'register',
    component: RegisterComponent,
    data: { title: 'Register' }
  }
];

Open and edit src/app/app.component.html and you will see the existing router outlet. Next, modify this HTML page to fit the CRUD page.

<div style="text-align:center">
  <h1>
    Welcome to {{ title }}!
  </h1>
  <img width="150" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>
<div class="container">
  <router-outlet></router-outlet>
</div>

Open and edit src/app/app.component.scss then replace all SCSS codes with this.

.container {
  padding: 20px;
}
Create a custom Angular 7 HttpInterceptor

Before creating a custom Angular 7 HttpInterceptor, create a folder with the name client/src/app/interceptors. Next, create a file for the custom Angular 7 HttpInterceptor with the name client/src/app/interceptors/token.interceptor.ts. Open and edit that file the add these imports.

import { Injectable } from '@angular/core';
import {
    HttpRequest,
    HttpHandler,
    HttpEvent,
    HttpInterceptor,
    HttpResponse,
    HttpErrorResponse
} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { Router } from '@angular/router';

Create a class that implementing HttpInterceptor method.

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

}

Inject the required module to the constructor inside the class.

constructor(private router: Router) {}

Implement a custom Interceptor function.

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    const token = localStorage.getItem('token');
    if (token) {
      request = request.clone({
        setHeaders: {
          'Authorization': 'Bearer ' + token
        }
      });
    }
    if (!request.headers.has('Content-Type')) {
      request = request.clone({
        setHeaders: {
          'content-type': 'application/json'
        }
      });
    }
    request = request.clone({
      headers: request.headers.set('Accept', 'application/json')
    });
    return next.handle(request).pipe(
      map((event: HttpEvent<any>) => {
        if (event instanceof HttpResponse) {
          console.log('event--->>>', event);
        }
        return event;
      }),
      catchError((error: HttpErrorResponse) => {
        console.log(error);
        if (error.status === 401) {
          this.router.navigate(['login']);
        }
        if (error.status === 400) {
          alert(error.error);
        }
        return throwError(error);
      }));
}

Next, we have to register this custom HttpInterceptor and HttpClientModule. Open and edit client/src/app.module.ts then add these imports.

import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { TokenInterceptor } from './interceptors/token.interceptor';

Add HttpClientModule to the @NgModule imports array.

imports: [
  BrowserModule,
  AppRoutingModule,
  HttpClientModule
],

Add the Interceptor modules to the provider array of the @NgModule.

providers: [
  {
    provide: HTTP_INTERCEPTORS,
    useClass: TokenInterceptor,
    multi: true
  }
],

Now, the HTTP interceptor is ready to intercept any request to the API.

Create Services for Accessing Book and Authentication API

To accessing the ASP Net Core Web API from Angular 7 application, we have to create services for that. Type these commands to generate the Angular 7 services from the client folder.

ng g service auth
ng g service book

Next, open and edit client/src/app/auth.service.ts then add these imports.

import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';

Declare a variable as ASP Net Core Web API URL.

apiUrl = 'http://192.168.0.5:5000/api/auth/';

Inject the HttpClient module inside the constructor.

constructor(private http: HttpClient) { }

Create all required functions for Login, Logout, Register, and helper functions.

login(data: any): Observable<any> {
  return this.http.post<any>(this.apiUrl + 'login', data)
    .pipe(
      tap(_ => this.log('login')),
      catchError(this.handleError('login', []))
    );
}

register(data: any): Observable<any> {
  return this.http.post<any>(this.apiUrl + 'register', data)
    .pipe(
      tap(_ => this.log('login')),
      catchError(this.handleError('login', []))
    );
}

private handleError<T>(operation = 'operation', result?: T) {
  return (error: any): Observable<T> => {

    // TODO: send the error to remote logging infrastructure
    console.error(error); // log to console instead

    // TODO: better job of transforming error for user consumption
    this.log(${operation} failed: ${error.message});

    // Let the app keep running by returning an empty result.
    return of(result as T);
  };
}

/** Log a HeroService message with the MessageService */
private log(message: string) {
  console.log(message);
}

Next, create an object class that represents Book data client/src/app/book/book.ts then replace all file contents with these.

export class Book {
    bookId: number;
    isbn: string;
    title: string;
    author: string;
    description: string;
    publisher: string;
    publishedYear: number;
    price: number;
}

Next, open and edit client/src/app/services/book.service.ts then replace all codes with this.

import { Injectable } from '@angular/core';
import { Book } from './book/book';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class BookService {

  apiUrl = 'http://192.168.0.5:5000/api/book';

  constructor(private http: HttpClient) { }

  getBooks(): Observable<Book[]> {
    return this.http.get<Book[]>(this.apiUrl + 'book')
      .pipe(
        tap(_ => this.log('fetched books')),
        catchError(this.handleError('getBooks', []))
      );
  }

  private handleError<T>(operation = 'operation', result?: T) {
    return (error: any): Observable<T> => {

      // TODO: send the error to remote logging infrastructure
      console.error(error); // log to console instead

      // TODO: better job of transforming error for user consumption
      this.log(${operation} failed: ${error.message});

      // Let the app keep running by returning an empty result.
      return of(result as T);
    };
  }

  /** Log a HeroService message with the MessageService */
  private log(message: string) {
    console.log(message);
  }
}


Display List of Book using Angular 7 Material

To display a list of books to the Angular 7 template. First, open and edit client/src/app/book/book.component.ts then add these imports.

import { Book } from './book';
import { BookService } from '../book.service';
import { AuthService } from '../auth.service';
import { Router } from '@angular/router';

Next, inject the Book and Auth Services to the constructor.

constructor(private bookService: BookService, private authService: AuthService, private router: Router) { }

Declare these variables before the constructor.

data: Book[] = [];
displayedColumns: string[] = ['bookId', 'isbn', 'title'];
isLoadingResults = true;

Create a function for consuming or get a book list from the booking service.

getBooks(): void {
  this.bookService.getBooks()
    .subscribe(books => {
      this.data = books;
      console.log(this.data);
      this.isLoadingResults = false;
    }, err => {
      console.log(err);
      this.isLoadingResults = false;
    });
}

Call this function from ngOnInit.

ngOnInit() {
  this.getBooks();
}

Add a function for log out the current session.

logout() {
  localStorage.removeItem('token');
  this.router.navigate(['login']);
}

Next, for the user interface (UI) we will use Angular 7 Material and CDK. There's a CLI for generating a Material component like Table as a component, but we will create or add the Table component from scratch to existing component. Type this command to install Angular 7 Material.

ng add @angular/material

If there are some questions, answer them like below.

? Choose a prebuilt theme name, or "custom" for a custom theme: Purple/Green       [ Preview: https://material.angular.i
o?theme=purple-green ]
? Set up HammerJS for gesture recognition? Yes
? Set up browser animations for Angular Material? Yes

Next, we have to register all required Angular Material components or modules to src/app/app.module.ts. Open and edit that file then add this imports.

import {
  MatInputModule,
  MatPaginatorModule,
  MatProgressSpinnerModule,
  MatSortModule,
  MatTableModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule } from '@angular/material';

Also, modify FormsModule import to add ReactiveFormsModule.

import { FormsModule, ReactiveFormsModule } from '@angular/forms';

Register the above modules to @NgModule imports.

imports: [
  BrowserModule,
  FormsModule,
  HttpClientModule,
  AppRoutingModule,
  ReactiveFormsModule,
  BrowserAnimationsModule,
  MatInputModule,
  MatTableModule,
  MatPaginatorModule,
  MatSortModule,
  MatProgressSpinnerModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule
],

Next, open and edit client/src/app/book/book.component.html then replace all HTML tags with this Angular 7 Material tags.

<div class="example-container mat-elevation-z8">
  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
  </div>
  <div class="button-row">
    <a mat-flat-button color="primary" (click)="logout()">Logout</a>
  </div>
  <div class="mat-elevation-z8">
    <table mat-table [dataSource]="data" class="example-table">

      <!-- Book ID Column -->
      <ng-container matColumnDef="bookId">
        <th mat-header-cell *matHeaderCellDef>Book ID</th>
        <td mat-cell *matCellDef="let row">{{row.bookId}}</td>
      </ng-container>

      <!-- ISBN Column -->
      <ng-container matColumnDef="isbn">
        <th mat-header-cell *matHeaderCellDef>ISBN</th>
        <td mat-cell *matCellDef="let row">{{row.isbn}}</td>
      </ng-container>

      <!-- Title Column -->
      <ng-container matColumnDef="title">
        <th mat-header-cell *matHeaderCellDef>Title</th>
        <td mat-cell *matCellDef="let row">{{row.title}}</td>
      </ng-container>

      <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
      <tr mat-row matRowDef="let row; columns: displayedColumns;"></tr>
    </table>
  </div>
</div>

Finally, we have to align the style for this page. Open and edit client/src/app/book/book.component.scss then replace all SCSS codes with these.

/ Structure */
.example-container {
    position: relative;
    padding: 5px;
}

.example-table-container {
    position: relative;
    max-height: 400px;
    overflow: auto;
}

table {
    width: 100%;
}

.example-loading-shade {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 56px;
    right: 0;
    background: rgba(0, 0, 0, 0.15);
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
}

.example-rate-limit-reached {
    color: #980000;
    max-width: 360px;
    text-align: center;
}

/* Column Widths */
.mat-column-number,
.mat-column-state {
    max-width: 64px;
}

.mat-column-created {
    max-width: 124px;
}

.mat-flat-button {
    margin: 5px;
}


Create a Login and Register Page

This time for authentication part. Open and edit client/src/app/auth/login/login.component.ts then add these imports.

import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { AuthService } from '../../auth.service';
import { Router } from '@angular/router';
import { ErrorStateMatcher } from '@angular/material/core';

Declare these variables before the constructor.

loginForm: FormGroup;
email = '';
password = '';
matcher = new MyErrorStateMatcher();
isLoadingResults = false;

Inject the imported modules to the constructor.

constructor(private formBuilder: FormBuilder, private router: Router, private authService: AuthService) { }

Initialize NgForm to the NgOnInit function.

ngOnInit() {
  this.loginForm = this.formBuilder.group({
    'email' : [null, Validators.required],
    'password' : [null, Validators.required]
  });
}

Add a function to submit the login form.

onFormSubmit(form: NgForm) {
  this.authService.login(form)
    .subscribe(res => {
      console.log(res);
      if (res.token) {
        localStorage.setItem('token', res.token);
        this.router.navigate(['book']);
      }
    }, (err) => {
      console.log(err);
    });
}

Add a function to go to the Register page.

register() {
  this.router.navigate(['register']);
}

Add a class that handles the form validation below this class.

/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
  }
}

Next, open and edit client/src/app/auth/login/login.component.html then replace all HTML tags with these.

<div class="example-container mat-elevation-z8">
  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
  </div>
  <mat-card class="example-card">
    <form [formGroup]="loginForm" (ngSubmit)="onFormSubmit(loginForm.value)">
      <mat-form-field class="example-full-width">
        <input matInput type="email" placeholder="Email" formControlName="email"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!loginForm.get('email').valid && loginForm.get('email').touched">Please enter your email</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class="example-full-width">
        <input matInput type="password" placeholder="Password" formControlName="password"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span ngIf="!loginForm.get('password').valid && loginForm.get('password').touched">Please enter your password</span>
        </mat-error>
      </mat-form-field>
      <div class="button-row">
        <button type="submit" [disabled]="!loginForm.valid" mat-flat-button color="primary">Login</button>
      </div>
      <div class="button-row">
        <button type="button" mat-flat-button color="primary" (click)="register()">Register</button>
      </div>
    </form>
  </mat-card>
</div>

Next, give this page a style by open and edit client/src/app/auth/login/login.component.scss then applies these styles codes.

/ Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child() {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}

.mat-flat-button {
  margin: 5px;
}

Next, for register page, open and edit client/src/app/auth/register/register.component.ts then replace all Typescript codes with these.

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { AuthService } from '../../auth.service';
import { Router } from '@angular/router';
import { ErrorStateMatcher } from '@angular/material/core';

@Component({
  selector: 'app-register',
  templateUrl: './register.component.html',
  styleUrls: ['./register.component.scss']
})
export class RegisterComponent implements OnInit {

  registerForm: FormGroup;
  fullName = '';
  email = '';
  password = '';
  isLoadingResults = false;
  matcher = new MyErrorStateMatcher();

  constructor(private formBuilder: FormBuilder, private router: Router, private authService: AuthService) { }

  ngOnInit() {
    this.registerForm = this.formBuilder.group({
      'fullName' : [null, Validators.required],
      'email' : [null, Validators.required],
      'password' : [null, Validators.required]
    });
  }

  onFormSubmit(form: NgForm) {
    this.authService.register(form)
      .subscribe(res => {
        this.router.navigate(['login']);
      }, (err) => {
        console.log(err);
        alert(err.error);
      });
  }

}

/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
  }
}


Next, open and edit client/src/app/auth/register/register.component.html then replace all HTML tags with these.

<div class="example-container mat-elevation-z8">
  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
  </div>
  <mat-card class="example-card">
    <form [formGroup]="registerForm" (ngSubmit)="onFormSubmit(registerForm.value)">
      <mat-form-field class="example-full-width">
        <input matInput type="fullName" placeholder="Full Name" formControlName="fullName"
                [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!registerForm.get('fullName').valid && registerForm.get('fullName').touched">Please enter your Full Name</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class="example-full-width">
        <input matInput type="email" placeholder="Email" formControlName="email"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!registerForm.get('email').valid && registerForm.get('email').touched">Please enter your email</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class="example-full-width">
        <input matInput type="password" placeholder="Password" formControlName="password"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span ngIf="!registerForm.get('password').valid && registerForm.get('password').touched">Please enter your password</span>
        </mat-error>
      </mat-form-field>
      <div class="button-row">
        <button type="submit" [disabled]="!registerForm.valid" mat-flat-button color="primary">Register</button>
      </div>
    </form>
  </mat-card>
</div>

Finally, open and edit client/src/app/auth/register/register.component.scss then replace all SCSS codes with these.

/ Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child() {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}

.mat-flat-button {
  margin: 5px;
}


Run and Test the ASP Net Core, SQL Server, and Angular 7: Web App Authentication

Let's test the whole ASP Net Core and Angular 7 Web application. First, we have to run the ASP Net Core Web API from the Visual Studio Code Terminal.

dotnet watch run

Next, run this command from the CMD to run Angular 7 application.

cd client
ng serve

If you get this error in the Visual Studio Code terminal while running Angular 7 in the browser.

Failed to authenticate HTTPS connection.
System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception. ---> System.ComponentModel.Win32Exception: An unknown error occurred while processing the certificate
   --- End of inner exception stack trace ---
   at System.Net.Security.SslState.StartSendAuthResetSignal(ProtocolToken message, AsyncProtocolRequest asyncRequest, ExceptionDispatchInfo exception)
   at System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.PartialFrameCallback(AsyncProtocolRequest asyncRequest)
--- End of stack trace from previous location where exception was thrown ---
   at System.Net.Security.SslState.ThrowIfExceptional()
   at System.Net.Security.SslState.InternalEndProcessAuthentication(LazyAsyncResult lazyResult)
   at System.Net.Security.SslState.EndProcessAuthentication(IAsyncResult result)
   at System.Net.Security.SslStream.EndAuthenticateAsServer(IAsyncResult asyncResult)
   at System.Net.Security.SslStream.<>c.<AuthenticateAsServerAsync>b__51_1(IAsyncResult iar)
   at System.Threading.Tasks.TaskFactory1.FromAsyncCoreLogic(IAsyncResult iar, Func2 endFunction, Action1 endAction, Task1 promise, Boolean requiresSynchronization)
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionAdapter.InnerOnConnectionAsync(ConnectionAdapterContext context)

Just, open and edit Properties/launchSettings.json then replace the applicationUrl in AspNetAngularAuth object with this.

"applicationUrl": "http://192.168.0.5:5000;http://localhost:5000",

Then re-run again ASP Net Core Web API. And here they are, the Angular 7 authentication app looks like.

That it's, the ASP Net Core, SQL Server, and Angular 7: Web App Authentication. You can find the full source code in our GitHub.

That just the basic. If you need more deep learning about ASP.NET Core, Angular or related you can take the following cheap course:


Angular 8 (formerly Angular 2) - The Complete Guide

Angular Crash Course for Busy Developers

Originally published by Didin J at djamware.com

==========================================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter

Build a CRUD App with ASP.NET Core 2.2 and SQL Server

Build a CRUD App with ASP.NET Core 2.2 and SQL Server

​ I’ve always said that you can tell a lot about a person by the kind of music they listen to. Don’t tell me you haven’t had serious doubts about whether you can be friends with someone when you find out that they like a particular band or artist. In that spirit, I created *JudgeMyTaste*, an ASP.NET Core web application where people can enter their favorite band or artist so that people on the Internet can judge them openly. ​ The combination of ASP.NET and SQL Server is probably the most common pairing in the enterprises that use ASP.NET. With ASP.NET Core and SQL Server both being cross-platform, you don’t *have* to run this combination on Windows anymore! I’ll show you how to create a basic CRUD application using ASP.NET Core 2.2 and SQL Server 2017. I’ll be running on Linux, but with the free tools used here, it won’t matter what operating system you’re using! ​ The tools I’ll be using that are available for all platforms are: * SQL Server 2017 (I’ll be running on Ubuntu 18.04) * Visual Studio Code * Azure Data Studio * ASP.NET Core 2.2 ​ Once you’ve got all the tools installed for your platform, let’s rock and roll! ​ ​ ​ ## Scaffold Your ASP.NET Core 2.2 Application ​ No matter the platform you’re on, the ```dotnet``` CLI is available. The commands used here should be the same for everyone. To scaffold the ASP.NET Core 2.2 MVC application, create a new folder for it: I’ve always said that you can tell a lot about a person by the kind of music they listen to. Don’t tell me you haven’t had serious doubts about whether you can be friends with someone when you find out that they like a particular band or artist. In that spirit, I created JudgeMyTaste, an ASP.NET Core web application where people can enter their favorite band or artist so that people on the Internet can judge them openly.

I’ve always said that you can tell a lot about a person by the kind of music they listen to. Don’t tell me you haven’t had serious doubts about whether you can be friends with someone when you find out that they like a particular band or artist. In that spirit, I created JudgeMyTaste, an ASP.NET Core web application where people can enter their favorite band or artist so that people on the Internet can judge them openly.

The combination of ASP.NET and SQL Server is probably the most common pairing in the enterprises that use ASP.NET. With ASP.NET Core and SQL Server both being cross-platform, you don’t have to run this combination on Windows anymore! I’ll show you how to create a basic CRUD application using ASP.NET Core 2.2 and SQL Server 2017. I’ll be running on Linux, but with the free tools used here, it won’t matter what operating system you’re using!

The tools I’ll be using that are available for all platforms are:

  • SQL Server 2017 (I’ll be running on Ubuntu 18.04)
  • Visual Studio Code
  • Azure Data Studio
  • ASP.NET Core 2.2

Once you’ve got all the tools installed for your platform, let’s rock and roll!

Scaffold Your ASP.NET Core 2.2 Application

No matter the platform you’re on, the dotnet CLI is available. The commands used here should be the same for everyone. To scaffold the ASP.NET Core 2.2 MVC application, create a new folder for it:

mkdir JudgeMyTaste

Change into that new directory:

cd JudgeMyTaste

Then run the following command:

dotnet new mvc

Then open the new application in VS Code.

code .

When you open the new application in VS Code, you should get a warning in the bottom right corner asking to add some missing assets. Go ahead and add the missing assets. You’ll see the .vscode folder added with a launch.json and a tasks.json file.

These will allow you to run the application from VS Code. To verify that everything scaffolded properly, run the base application by typing F5. This will build the application, run it, and open it in a new browser window.

You may notice a strange error page come up, if you’ve never run an ASP.NET Core 2.x application before. By default ASP.NET Core wants to run on HTTPS. This is a recommended practice for web applications. You could avoid this message by removing the redirect to HTTPS in your Startup.cs or by generating a certificate for your local machine, but this error screen only comes up once in a great while, so I just side step it by clicking on Advanced and telling the browser that it’s okay to visit this site even though there is no certificate for it.

For your daily work, it will probably behoove you to create a local certificate for development so that you never have to see this message again.

Create Your SQL Server Database

Open Azure Data Studio and connect to your localhost server with the SA password you created when installing SQL Server on your machine. You’ll notice it is arranged very much like VS Code. In the Connections Explorer, you will see localhost as a connection. Right-click on the connection and choose New Query, which will open a new query window on the right side. Start typing the word CREATE and an intellisense drop down will open and one of the choices will be sqlCreateDatabase. Choose that option and a query will be scaffolded with the database name highlighted in the three places that it occurs in the query. You can just start typing the database name “JudgeMyTaste” and it will be replaced in all three places so that the final query looks like this.

-- Create a new database called 'JudgeMyTaste'
-- Connect to the 'master' database to run this snippet
USE master
GO
-- Create the new database if it does not exist already
IF NOT EXISTS (
  SELECT [name]
    FROM sys.databases
    WHERE [name] = N'JudgeMyTaste'
)
CREATE DATABASE JudgeMyTaste
GO

Now you can just click the green Run arrow at the top of the window to create the database. Simple, no?

Now when you expand the Databases folder in the Connection Explorer, you will see the JudgeMyTaste database in the list. Right-click on the new database and choose New Query again. Start typing CREATE again and this time choose sqlCreateTable from the options presented. Again, you can start typing the table name FavoriteBands and it will be filled in all the places it occurs in the query.

You’ll also need to add some other columns to the table. Add the columns for Id, Name, EnteredBy, and EnteredOn so that the query looks like this:

-- Create a new table called '[FavoriteBands]' in schema '[dbo]'
-- Drop the table if it already exists
IF OBJECT_ID('[dbo].[FavoriteBands]', 'U') IS NOT NULL
DROP TABLE [dbo].[FavoriteBands]
GO
-- Create the table in the specified schema
CREATE TABLE [dbo].[FavoriteBands](
  [Id] [int] IDENTITY(1,1) NOT NULL,
  [Name] [varchar](255) NULL,
  [EnteredBy] [varchar](255) NULL,
  [EnteredOn] [date] NULL
);
GO

Then run the query by clicking the green Run arrow as before.

It’s good practice to create a user specifically for your application to connect with the database. One that only has the permissions that it will need to interact with your database. Here’s a script to create a login and a user for the database and assign that user dbo permissions to the database.

USE master

GO

CREATE LOGIN webapp WITH PASSWORD=N'[email protected]!', DEFAULT_DATABASE=JudgeMyTaste

GO

ALTER LOGIN webapp ENABLE

GO

USE JudgeMyTaste

GO

CREATE USER webapp FOR LOGIN webapp
EXEC sp_addrolemember 'db_owner', 'webapp'

GO

It might seem like a lot going on here, but it simply creates a login for SQL Server, makes that login a user for the JudgeMyTaste database, and add it to the db_owner role for the database. This will allow that login to do all the CRUD operations that the application will need. Now your database is ready to be used by your application!

Connect SQL Server to Your ASP.NET Core 2.2 MVC Application

Before anything else, you’ll need the Entity Framework Core NuGet package. To install it, run the following command in the terminal.

dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 2.2.4

Start by adding the connection string to your appsettings.json file in the root of your MVC project, so that it looks like this:

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "JudgeMyTasteDatabase": "Server=.;Database=JudgeMyTaste;user id=webapp;[email protected]!"
  }
}

In the Models folder, create a class file called FavoriteBand.cs.

using System;
using System.ComponentModel.DataAnnotations;

namespace JudgeMyTaste.Models
{
  public class FavoriteBand
  {
    public int Id { get; set; }
    public string Name { get; set; }
    public string EnteredBy { get; set; }
    public DateTime EnteredOn { get; set; }
  }
}

This class will be used to work with the FavoriteBand entries.

In the root of the project, create a folder called Data to house the database context for the application. Create a C# file called JudgeMyTasteContext.cs with the following contents:

using JudgeMyTaste.Models;
using Microsoft.EntityFrameworkCore;

namespace JudgeMyTaste.Data
{
  public class JudgeMyTasteContext : DbContext
  {
    public JudgeMyTasteContext(DbContextOptions<JudgeMyTasteContext> options) : base(options)
    {
    }

    public DbSet<FavoriteBand> FavoriteBands { get; set; }
  }
}

In your Startup.cs file, in the ConfigureServices() method, right before the services.AddMvc()... line, add the newly created context with the connection string.

services.AddDbContext<JudgeMyTasteContext>(options => options.UseSqlServer(Configuration.GetConnectionString("JudgeMyTasteDatabase")));

Now your database is all hooked into your application. All you need to do is create some way for the user to enter their favorite bands. To get some more scaffolding goodness for the CLI, install the Code Generation tool.

dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design

Now you can scaffold a controller to handle all the CRUD operations for the FavoriteBand class by running the following command from the terminal.

dotnet aspnet-codegenerator controller -name FavoriteBandsController -async -m JudgeMyTaste.Models.FavoriteBand -dc JudgeMyTaste.Data.JudgeMyTasteContext -namespace Controllers -outDir Controllers -udl

This is a long one but if you break it down into its component pieces, it’s easier to understand.

The first part just calls the dotnet CLI’s new aspnet-codegenerator command for a controller. You want the controller’s name to be “FavoriteBandsController” and for the controller actions to all be -async. The model being used to generate the controller is the JudgeMyTaste.Models.FavoriteBand class, and the database context will be the JudgeMyTaste.Data.JudgeMyTasteContext class you just created. The namespace and output directory for the controller will be Controllers and the -udl switch tells the generator to use the default layout for the views it will generate (yeah, it’s going to generate views for everything too!). Pretty cool, right?

Once you run the command, you should see the controller and all it’s views show up. The only thing left is to create a link so that users can get to the favorite bands section of the site easily.

In the Views/Shared folder open the Layout.cshtml file and add a link to the menu to get to the new section of the site.

<li class="nav-item">
  <a class="nav-link text-dark" asp-area="" asp-controller="FavoriteBands" asp-action="Index">Favorite Bands</a>
</li>

Now when you run the application, you can click on the Favorite Bands menu item and see a list of all the favorite bands that have been entered. Of course there aren’t any right now, so add one using the Create New link at the top of the page and see it show up in the listing.

Now it’s a little cumbersome to add the EnteredOn value manually, and the code generator you used can’t know that you can just add that field to the entry as it’s being saved, so change the Create() method of the FavoriteBandController to add it automatically.

// POST: FavoriteBands/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Name,EnteredBy")] FavoriteBand favoriteBand)
{
  if (ModelState.IsValid)
  {
    favoriteBand.EnteredOn = DateTime.Now;
    _context.Add(favoriteBand);
    await _context.SaveChangesAsync();
    return RedirectToAction(nameof(Index));
  }
  return View(favoriteBand);
}

The only things that have changed is that I removed the EnteredOn field from the Bind statement in the method signature, and I added the value DateTime.Now as the value right before saving it to the database.

Add Authentication to Your ASP.NET Core 2.2 MVC + SQL Server Application

What you have now is okay, but there’s currently no way to keep users from editing other user’s entries. We want to make sure to judge people for their favorite band that they actually entered, right?

No reason to write this yourself. You can easily integrate Okta to handle the authentication for you and easily:

Sign up for a forever-free developer account (or log in if you already have one).

Once you have signed up and logged in, you’ll be taken to your dashboard. Make note of your Org URL in the top right corner.

Click on the Applications menu item at the top, click Add Application, and from the first page of the wizard choose Web and click Next.

On the next screen, change the application name to “Judge My Taste App” and update the Base URIs value and the Login Redirect URIs to reflect to the correct port and the fact that you’re running on the HTTPS scheme.

Then click Done and you’re taken to the application page. On the General Settings tab click Edit and add a URL to the Logout Redirect URIs with a value of <a href="https://localhost:5001/signout/callback" target="_blank">https://localhost:5001/signout/callback</a>. This is where Okta will redirect back to after the logout call. This is handled by the ASP.NET OIDC Middleware.

Configure Your ASP.NET Core 2.2 MVC Application for Authentication

Now you need to tell your application how to use Okta for authentication. The easiest way is to use the ASP.NET SDK from Okta. You can install it from NuGet using the following command:

dotnet add package Okta.AspNetCore --version 1.1.5

Add some configuration values to your appsettings.json file to that the final file looks like this:

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "JudgeMyTasteDatabase": "Server=.;Database=JudgeMyTaste;user id=webapp;[email protected]!"
  },
  "Okta": {
    "ClientId": "{yourClientId}",
    "ClientSecret": "{yourClientSecret}",
    "OktaDomain": "https://{yourOktaDomain}",
    "PostLogoutRedirectUri": "https://localhost:5001/"
  }
}

This PostLogoutRedirectUri is the URL that the middleware will redirect to once Okta has redirected back to the signout/callback URL. You can use any valid URL in the MVC application. Here, I am just redirecting to the root of the application.

Back in the Startup.cs file, add the following using statements:

using Okta.AspNetCore;
using Microsoft.AspNetCore.Authentication.Cookies;

Then at the very beginning of the ConfigureServices() method add:

var oktaMvcOptions = new OktaMvcOptions();
Configuration.GetSection("Okta").Bind(oktaMvcOptions);
oktaMvcOptions.Scope = new List<string> { "openid", "profile", "email" };
oktaMvcOptions.GetClaimsFromUserInfoEndpoint = true;

services.AddAuthentication(options =>
{
  options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
  options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
  options.DefaultChallengeScheme = OktaDefaults.MvcAuthenticationScheme;
})
.AddCookie()
.AddOktaMvc(oktaMvcOptions);

This is a pretty dense chunk of code, but most of it is boilerplate for the OIDC middleware that the Okta SDK is built on. The first part just binds all of those configuration values you just added in appsettings.json to the oktaMvcOptions. It also adds the scopes you want to receive (which are the OpenID information, the user’s profile, and the user’s email address). It also tells the middleware that it can get the claims from the user info endpoint, which all OIDC identity providers have.

When the code adds authentication, it tells the OIDC provider to use cookies for storing tokens and that you’ll be sending users to Okta from an MVC application.

To actually wire up authentication, you need to tell the Configure() method to use this service you just configured. Right before the app.UseMvc(...) line, add:

app.UseAuthentication();

Okta is now configured in your application! You still need to set up your application to challenge the user (send them to Okta to authenticate).

Create a new controller in the Controllers folder called AccountController with the following code:

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Mvc;
using Okta.AspNetCore;

namespace JudgeMyTaste.Controllers
{
  public class AccountController : Controller
  {
    public IActionResult Login()
    {
      if (!HttpContext.User.Identity.IsAuthenticated)
      {
        return Challenge(OktaDefaults.MvcAuthenticationScheme);
      }
      return RedirectToAction("Index", "Home");
    }

    public IActionResult Logout()
    {
      return new SignOutResult(new[]
      {
        OktaDefaults.MvcAuthenticationScheme,
        CookieAuthenticationDefaults.AuthenticationScheme
      });
    }
  }
}

This will give you a Login() and Logout() method to wire up some menu items. Speaking of which, add a new view in Views/Shared called _LoginPartial.cshtml. This will house all the code for the login menu items.

@if (User.Identity.IsAuthenticated)
{
  <ul class="navbar-nav ml-auto">
    <li>
      <span class="navbar-text">Hello, @User.Identity.Name</span> &nbsp;
      <a onclick="document.getElementById('logout_form').submit();" style="cursor: pointer;">Log out</a>
    </li>
  </ul>
  <form asp-controller="Account" asp-action="Logout" method="post" id="logout_form"></form>
}
else
{
  <ul class="navbar-nav">
    <li><a asp-controller="Account" asp-action="Login">Log in</a></li>
  </ul>
}

Change the main menu in Views/Shared/_Layout.cshtml to add this in and move the main menu to the left and have the login menu on the far right. The final div that houses the menu should look like this:

<div class="navbar-collapse collapse justify-content-between">
  <ul class="navbar-nav mr-auto">
    <li class="nav-item">
      <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
    </li>
    <li class="nav-item">
      <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
    </li>
    <li class="nav-item">
      <a class="nav-link text-dark" asp-area="" asp-controller="FavoriteBands" asp-action="Index">Favorite Bands</a>
    </li>
  </ul>
  <partial name="_LoginPartial" />
</div>

The class list for the navbar-collapse has changed to add the justify-content-between class that will keep the menus apart. The ul’s class also changed to mr-auto which will help keep it left. Lastly, the login partial is added at the end of the menu.

Don’t just sit there, fire this thing up and judge me for liking Nickleback!

Now you have a complete CRUD slice built in an ASP.NET Core 2.2 MVC application saving data to a SQL Server database! Now you can take the same path to add things like favorite movie, favorite food, and favorite beverage so that you can easily and completely judge people for their taste online!!!

Angular and ASP.NET Core

Angular and ASP.NET Core

​ The&nbsp;[Angular CLI](https://cli.angular.io/ "Angular CLI")&nbsp;provides a way to develop front-end applications using angular that hides a lot of details. For example there's no requirement to understand how&nbsp;[Webpack](https://webpack.js.org/ "Webpack")&nbsp;or&nbsp;[SystemJS](https://github.com/systemjs/systemjs "SystemJS")&nbsp;work. ​ In fact, if you don't know a little bit about Webpack, which is what is used to build the latest version of Angular applications, the CLI almost looks like magic. You just need to do a&nbsp;ng new&nbsp;and&nbsp;ng serve --open&nbsp;and you have a working Angular application open in your web browser. ​ The fact that the CLI hides all the plumbing might lead to questions like: "How do I use Angular with ASP.NET Core?". ​ ![](https://res.cloudinary.com/practicaldev/image/fetch/s--dKBafg3O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://www.blinkingcaret.com/wp-content/uploads/2018/01/asp_net_core_and_angular_logo.png) ​ I hope that by the end of this blog post it will be clear to you how you can answer that question (and not only with ASP.NET Core, with whichever technology you want to use your Angular app with). ​ You see, an angular app is an app in and of itself, it does need to be "served" somehow by a web server. ​ When you compile an angular application you are producing a set of JavaScript, CSS and one index.html file. That's it. ​ The default folder where those "artifacts" get copied to is&nbsp;yourApplicationFolder/dist. You can check it out by going to your Angular application and doing an&nbsp;ng build. ​ Go on, I'll wait. ​ When you do&nbsp;ng serve --open&nbsp;you are actually using a stand-alone web server ([webpack-dev-server](https://github.com/webpack/webpack-dev-server "webpack-dev-server")) to serve that index.html file in the dist folder. ​ The rest of this blog post will describe several approaches that you can take for using Angular with ASP.NET Core. The first is to have ASP.NET Core serve the Angular files. ​ The second approach is to have Angular and ASP.NET Core as different applications. There's an example of how to achieve this using Nginx where both Angular and ASP.NET Core are served using port 80 and in IIS where each application is served from its own port. ​ The final part of the post describes a setup that I consider ideal where you can use Angular's&nbsp;ng serve&nbsp;during development. ​ This post is quite long but the sections are fairly independent. If your are only interested in the last section and you are using Windows I recommend also reading the section on how to configure Angular in IIS. ## Using ASP.NET Core to serve the Angular application ​ It can be argued that serving an Angular application "within" ASP.NET Core is wasteful in terms of resources. In the end the Angular application is just a set of static files, there's no need to have the request for those files go through the ASP.NET Core middleware pipeline. ​ There might be some good reasons for doing it though, also there's no harm in knowing how to do it and since it seems to be a common approach, being familiar with it might be useful. ​ One important thing to know in order to understand how we can serve an ASP.NET Core and Angular application together is to understand how a request is processed in ASP.NET Core. ​ When you run an ASP.NET Core application your request goes through a "pipeline" of&nbsp;[middlewares](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware?tabs=aspnetcore2x "middlewares"). Every time a request comes in it goes through the middlewares in the order they are defined, and then in reverse order. ​ Every middleware has an opportunity to change the request or response two times, once before the other middlewares have been executed, and then after the other middlewares have executed. This allows for a middleware at the top of the pipeline to handle for example, a 401 response set by a middleware further down in the pipeline. ​ An example of this are the authentication middlewares that change a 401 response to a 302 redirect to a login page. The Angular CLI provides a way to develop front-end applications using angular that hides a lot of details. For example there’s no requirement to understand how Webpack or SystemJS work.

The Angular CLI provides a way to develop front-end applications using angular that hides a lot of details. For example there's no requirement to understand how Webpack or SystemJS work.

In fact, if you don't know a little bit about Webpack, which is what is used to build the latest version of Angular applications, the CLI almost looks like magic. You just need to do a ng new and ng serve --open and you have a working Angular application open in your web browser.

The fact that the CLI hides all the plumbing might lead to questions like: "How do I use Angular with ASP.NET Core?".

I hope that by the end of this blog post it will be clear to you how you can answer that question (and not only with ASP.NET Core, with whichever technology you want to use your Angular app with).

You see, an angular app is an app in and of itself, it does need to be "served" somehow by a web server.

When you compile an angular application you are producing a set of JavaScript, CSS and one index.html file. That's it.

The default folder where those "artifacts" get copied to is yourApplicationFolder/dist. You can check it out by going to your Angular application and doing an ng build.

Go on, I'll wait.

When you do ng serve --open you are actually using a stand-alone web server (webpack-dev-server) to serve that index.html file in the dist folder.

The rest of this blog post will describe several approaches that you can take for using Angular with ASP.NET Core. The first is to have ASP.NET Core serve the Angular files.

The second approach is to have Angular and ASP.NET Core as different applications. There's an example of how to achieve this using Nginx where both Angular and ASP.NET Core are served using port 80 and in IIS where each application is served from its own port.

The final part of the post describes a setup that I consider ideal where you can use Angular's ng serve during development.

This post is quite long but the sections are fairly independent. If your are only interested in the last section and you are using Windows I recommend also reading the section on how to configure Angular in IIS.

Using ASP.NET Core to serve the Angular application

It can be argued that serving an Angular application "within" ASP.NET Core is wasteful in terms of resources. In the end the Angular application is just a set of static files, there's no need to have the request for those files go through the ASP.NET Core middleware pipeline.

There might be some good reasons for doing it though, also there's no harm in knowing how to do it and since it seems to be a common approach, being familiar with it might be useful.

One important thing to know in order to understand how we can serve an ASP.NET Core and Angular application together is to understand how a request is processed in ASP.NET Core.

When you run an ASP.NET Core application your request goes through a "pipeline" of middlewares. Every time a request comes in it goes through the middlewares in the order they are defined, and then in reverse order.

Every middleware has an opportunity to change the request or response two times, once before the other middlewares have been executed, and then after the other middlewares have executed. This allows for a middleware at the top of the pipeline to handle for example, a 401 response set by a middleware further down in the pipeline.

An example of this are the authentication middlewares that change a 401 response to a 302 redirect to a login page.

You can find the definition of this pipeline on the Startup.cs file, in the Configure method. For example, here's the pipeline that you get when you do a dotnet new mvc:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseStaticFiles();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

Every time a request comes in to this ASP.NET Core application it can go through at most three middlewares. First the DeveloperExceptionPage/ExceptionHandler middleware depending if the ASP.NET Core application is running in development mode or not. Then the StaticFiles middleware and then finally the Mvc middleware.

The middleware that is key here is StaticFiles. This is the middleware that serves files contained in the wwwroot folder, i.e. if a request comes in for index.html and there's an index.html file at wwwroot/index.html then that file is sent to the client. StaticFiles middleware won't call the middlewares below it after this (in this case it would be Mvc).

You can probably already see how this could work with an Angular application. Just put it under wwwroot.

That's absolutely correct, however there's a detail about StaticFiles that is important to know. StaticFiles won't try to do any guesses for you, i.e. if your request is for /, StaticFiles won't look for /index.html. It will just assume that this request isn't supposed to be handled by it and it will call the next middleware in the pipeline, in this case Mvc.

For this approach to work you need another middleware named DefaultFiles which must come before StaticFiles in the pipeline:

//...
app.UseDefaultFiles();
app.UseStaticFiles();
//...

DefaultFiles will cause cause StaticFiles to look for index.html if the url ends with /.

Now the only thing left to do is to configure your Angular CLI to compile to your ASP.NET Core application's wwwroot folder.

If you look in your Angular's application folder you'll find a .angular-cli.json file. In that file look for the outDir property:

...
"apps": [
{
    ...
    "outDir": "dist",
...

Change it from "dist" to the path of your ASP.NET Core's wwwroot folder. Run ng build in your Angular application and now if you run your ASP.NET Core web application you should see your Angular application in the browser.

A nice development workflow is to run the Angular CLI build in watch mode: In a console window do ng build --watch or ng build -w if you want to save a few key strokes, and leave it running. Now every time you make a change in your Angular application you can just refresh the browser and see the change (you also need to have your ASP.NET Core application running).

There is one thing missing from this approach, though. Deep-linking support, i.e. if your Angular application uses routing and you send a user a url with a valid Angular route (e.g http://yourapplication.com/products/5) the receiving user won't be able to open it. Trying to get to that route will result in a 404 Not Found response.

That's because the request will go all the way through your ASP.NET Core application's pipeline and when it reaches the MVC middleware it won't know what to do with it and will set the response's status code to 404 Page Not Found.

What we can do is at the top of the pipeline we look for a 404 response that is about to be sent and change its path to our Angular application's index.html file (that way what gets served is the Angular application which will know what to do with the url in terms of routing). After this we make the request go through the pipeline again:

//add this at the start of Configure
app.Use(async (HttpContext context, Func<Task> next) =>
{
    await next.Invoke();

    if (context.Response.StatusCode == 404)
    {
        context.Request.Path = new PathString("/index.html");
        await next.Invoke();
    }
});

That fixes deep links but introduces a new problem. What if your web api (that you've implemented in your ASP.NET Core application) needs to send a 404 response. That's something more than reasonable to do. Instead of a 404, the service call will receive a 200 response with index.html.

The solution here is to look at the url and decide if it's intended for the web api or an Angular route. Usually a call to the web api will have /api in the url. That's a simple test to perform and it will solve this problem. Here's the revised version of a custom middleware that solves this problem:

//add this at the start of Configure
app.Use(async (HttpContext context, Func<Task> next) =>
{
    await next.Invoke();

    if (context.Response.StatusCode == 404 && !context.Request.Path.Value.Contains("/api")))
    {
        context.Request.Path = new PathString("/index.html");
        await next.Invoke();
    }
});

One last note about this approach. I've seen examples where the Angular application is in the same Visual Studio solution as the ASP.NET application. Visual Studio (not VS Code) will try to compile the typescript files. If you are using ng build -w you'll want Visual Studio to leave your Typescript files alone. To do that open your project's .csproj and add in any PropertyGroup:

<TypescriptCompileBlocked>true</TypescriptCompileBlocked>

Nginx

Nginx is a web server that can act as a reverse proxy for ASP.NET Core applications and which is also very good at serving static content.

The setup for having an Angular application work with ASP.NET Core is much simpler in Nginx. You just need a configuration similar to this:

server {
    listen 80;        

    location / {
        root /pathToYourAngularApplication/dist;
        index index.html;
        try_files $uri $uri/ /index.html;
    }

    location /api/ {
        proxy_pass http://localhost:5000;
    }
}

This is how a typical Nginx configuration file looks like. If you are not familiar with Nginx and ASP.NET Core I recommend my blog post: HTTPS in ASP.NET Core from Scratch. It has a section with instructions on how to install and setup websites using Nginx.

This configuration allows us to have both the Angular and ASP.NET Core application on port 80. Let's look at the important parts in it.

The listen 80 statement establishes that Nginx will respond to requests coming in on port 80.

The location blocks are where we are going to define how our two applications will be served (Angular and ASP.NET). Each time a request comes in, Nginx will look at the URL and try to find the location block that best matches it. In this case the location blocks urls act like a "prefix match", i.e., the first block will match every URL (every url that starts with a /). The second location block matches URLs that start with /api/.

Nginx picks the most "specific" location block, so even though a request for /api/users would match both location blocks, since the second one (/api/) is more specific, it will be the one that would be used to handle the request.

In the first location block (/):

root /pathToYourAngularApplication/dist sets the path where static content will be looked for as the location where your compiled Angular application files are (dist is the CLI's default output folder).

index index.html specifies which file should be served for URLs that end in /.

try_files $uri $uri/ /index.html can be read this way: check if there's a file that matches the normalized URL (e.g. http://www.yourwebsite.com/assets/image.jpg -> /assets/image.jpg), if that file does not exist try the normalized URL plus a / (e.g. http://www.yourwebsite.com/documents -> /documents/ -> /documents/index.html because of the index rule). If all of that fails serve the file /index.html.

Serving /index.html if no match is found is what enables us to use deep linking. For example a URL such as http://www.yourwebsite.com/documentswith no math in the file system will be served with the Angular application's index.html. Index.html will load all the necessary files for the Angular application to run, specifically the routing module. The routing module will then look at the url, and according to the routes defined in the angular app will decide which component to load.

Finally, the last location block. It instructs Nginx to forward the requests that start with /api/ to a webserver that is listening on port 5000 on localhost. That will be your ASP.NET Core's application.

One note about the Nginx's syntax for proxy_pass. It matters a lot if the URL for the application has a / at the end or not. The url in proxy_pass is treated differently if it has what is described in [Nginx's documentation as a "optional URI"](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass "Nginx's documentation as a "optional URI"") (optional URI isn't a great name, since in the end a URL is a URI).

An example of a URL with an optional URI is: http://localhost:5000/optionalURI/. If the location's path is /api/, then a request for http://yourwebsite.com/api/users will be forwarded to your ASP.NET Core's application as http://localhost:5000/optionalURI/users.

That's why not adding the / at the end in proxy_pass is so important, because if you do (e.g.: proxy_pass http://localhost:5000/;) it falls into the "optional URI" category (it will be interpreted as an empty optional URI), and a request for http://yourwebsite.com/api/users will be seen in your ASP.NET Core's application as a request for http://localhost:5000/users.

If you don't add the / at the end (e.g.: proxy_pass http://localhost:5000;) then a request for http://yourwebsite.com/api/users will be seen in the ASP.NET Core application as a request for http://localhost:5000/api/userswhich is probably what you want.

If you need a more complete example that explains how you can make this work outside a development-time scenario (i.e. have your ASP.NET Core application auto start and remain online even if there's an exception) check out HTTPS in ASP.NET Core from Scratch where there's an example describing how you can use Supervisor to keep the ASP.NET application running even in the event of errors (by auto restarting it).

IIS

With IIS it becomes very cumbersome to have a configuration similar to what we can do with Nginx where both the Angular and ASP.NET Core applications are served on port 80.

To understand why it makes it easier if we understand the IIS concepts of Website and Application. When you create a website you define (among other settings) the port (e.g. 80) where it will be served from. A website can then have several applications "inside" it, all of which will share the website configuration (and therefore be served on the same port).

We could for example put our Angular application inside the "Default Web Site" and the ASP.NET Core one as an IIS Application under it, and call it for example "api".

If the "Default Web Site" responds at http://localhost, then the ASP.NET Core application could be at http://localhost/api. Which seems to be exactly what we want. However, the requests for http://localhost/api would be seen in ASP.NET Core without the api in the url.

As far as I know there's no way to change this behavior.

This means your ASP.NET Core application will behave differently when running inside IIS vs when executed directly (either in Visual Studio or with dotnet run).

To make matters worse an ASP.NET Core application needs to be published (dotnet publish) for it to work in IIS. It's not like a non-Core ASP.NET application where you can just point an IIS Application to the folder that contains the ASP.NET application's files .

So when using IIS the reasonable options are to either have ASP.NET Core serve the angular application as it was described in the first section of this article or have two separate Websites.

Lets walk-though the process of creating two separate websites. First a website for the Angular project and then for ASP.NET Core.

Angular in IIS

We'll be adding a Website named MyNgWebSite on port 80. That means that if you have a "Default Web Site", which in all likelihood you'll have, you need to stop it or change its bindings since the default for it is port 80.

But before we get there we need to create an application pool for our Angular application. Right click on Application Pools in IIS Manager:

The Application Pool for an Angular application does not require Managed Code (we only need to serve static files). We should choose "No Managed Code" in the .NET CLR Version:

We can now add a new IIS web site and set the new application pool we created as its application pool:

The physical path should be set to where your Angular project is being compiled to, usually this is the dist folder.

If you were to try to access http://localhost right now (and assuming that you stopped the "Default Web Site" or used a different port than 80) you would get a permissions error. That's because when you create an application pool a ["virtual" user is created](https://docs.microsoft.com/en-us/iis/manage/configuring-security/application-pool-identities ""virtual" user is created"). That user is a local user and must have permissions to access the folder that contains the files you are trying to serve.

That user's name is IIS AppPool\ApplicationPoolName, in this example it's IIS AppPool\ApplicationPoolForAngular.

Go to the folder that contains the compiled Angular project, right click on it and select properties, go to the security tab, click edit, then add and finally add the application pool user:

We should now be able to access your Angular application if you go to http://localhost.

We still need to do one more thing though. Enable deep-linking support.

If you have routes in your Angular application these won't work if someone tries to access them from "outside" the Angular app. What this means is that if navigating to http://localhost/documents is valid inside the Angular application and you send that url to someone else, when that someone else clicks the link they will be greeted with a 404 page from IIS.

That's because there is no documents folder nor index file inside it for IIS to serve. We need to tell IIS that it must serve the file index.html when someone tries to access a URL that does not exists.

We are going to use the same mechanism used for having a custom 404 page, but instead of a 404 page we'll serve the Angular application.

To achieve this we need to create a web.config file and put it in the src folder of the Angular application with this inside:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <httpErrors errorMode="Custom" existingResponse="Replace">
            <remove statusCode="404"/>
            <error statusCode="404" responseMode="ExecuteURL" path="/index.html"/>
        </httpErrors>
    </system.webServer>
</configuration>

A very quick explanation of what's going on. We are using httpErrors with an errorMode="Custom" and existingResponse="Replace". This instructs IIS to replace the default error pages with the one we are about to specify.

remove statusCode="404" will remove any custom settings for 404 pages if they already exist.

error statusCode="404" responseMode="ExecuteURL" path="/index.html" will configure IIS to execute the /index.html url if there's a 404 error. This will effectively serve your Angular application and won't change the URL seen by the client.

Now we need to edit the .angular-cli.json file so that web.config gets copied to the output folder as an asset when the application is compiled. The assets section is under "app", here's an example:

{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"project": {
    "name": "your-app"
},
"apps": [
    {
    "root": "src",
    "outDir": "dist",
    "assets": [
        "assets",
        "favicon.ico", 
        "web.config"
    ],
    "index": "index.html",
...

ASP.NET Core in IIS

The process for the configuring an ASP.NET Core application in IIS is similar, although we need to select a different port.

But before you start you need to make sure you have the ASP.NET Core Module for IIS installed. It might already be installed if you installed the .Net Core SDK, however the best way to make sure is to go to IIS Manager and see if it's in the modules' list:

If you don't have it you can find more information about it here and a direct link to download it here.

This module takes care of starting and keeping an ASP.NET Core application running.

Before we create the website in IIS we need the published version of the ASP.NET Core application. You can do that in the command line with dotnet publish or, in full Visual Studio, right click on the project and select Publish, then click publish to folder.

Create a new Website and point it to the ASP.NET Core project published folder, give it a different port number (for example 8080) and create an Application Pool for it.

An application pool for an ASP.NET Core application is also unmanaged (No Managed Code). Although this might seem odd, it's because IIS is actually just acting as a reverse proxy.

Before we're able to run the ASP.NET Project using IIS we need to changed the published folder's permissions so that the Application Pool user can access it. If you don't you'll get this moderately unhelpful error message:

HTTP Error 500.19 - Internal Server Error> HTTP Error 500.19 - Internal Server Error

If you look at the Config Error section you'll see "Cannot read configuration file due to insufficient permissions", which pretty much says it all.

Go to the published folder and add the application pool user to the list of users with permissions over that folder.

Your ASP.NET Core application should now be available on the port you've selected when you created the website in IIS. However, if you try to call it from the Angular application you'll get this error "Failed to load ... No 'Access-Control-Allow-Origin' header is present on the requested resource...". Here's an example of how that would look like in the developer tools console tab:

That's because even though both our our Angular and ASP.NET Core applications are on the same domain, they are in different ports, and that's enough to qualify the request as a Cross Origin Resource Sharing (CORS) request in all browsers except IE.

We need to enable CORS on the ASP.NET Core application. To do that we need to add the package Microsoft.AspNetCore.Cors and in ConfigureServices(IServiceCollection services... method in Startup.csadd services.AddCors():

public void ConfigureServices(IServiceCollection services)
{
    //...
    services.AddCors();
    //...
}

And in the Configure method we need to create a "policy" that says that we are expecting requests from http://localhost. We should do that before the MVC middleware:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    //...
    app.UseCors(builder => builder.WithOrigins("http://localhost"));
    app.UseMvc();
}

You should be good to go. Your Angular and ASP.NET Core should both be working now.

Platform Agnostic Development Setup

Both Angular and ASP.NET Core applications provide ways to detect if they are running in development or production mode. That can be leveraged to create a setup that works both in Linux, Windows or Mac.

The easiest way to run an Angular application is to use run ng serve. That spins up a webpack development server that serves the Angular application on port 4200 by default.

This also has the advantage of having hot module replacing, which means you can see your changes to the Angular application as soon as you make then without even having to refresh the browser.

So ideally we want to run the Angular application this way.

For the ASP.NET Core application we want to run it without having to publish it which you would have to if it is being served by IIS.

This is the ideal development scenario, ng serve for Angular and dotnet run or running the ASP.NET Core from Visual Studio without having to publish it.

In this ideal scenario when developing we could have the Angular application running on port 4200 (through ng serve) and the ASP.NET Core application running on port 5000. When in production the Angular application would typically be served from port 80 and the ASP.NET Core application for port 8080 for example (or from a different server on port 80).

On the ASP.NET Core side of things we'd have to configure CORS to accept requests from port 4200 when in development and from port 80 when in production. In Startup.cs that would look like this:

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors();
    //...        
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    //...
    if (env.IsDevelopment())
    {
        //...
        app.UseCors(builder => builder.WithOrigins("http://localhost:4200"));
    }else 
    {
        app.UseCors(builder => builder.WithOrigins("http://localhost"));
    }

    app.UseMvc();
}

That takes care of the ASP.NET Core application.

For Angular we need to leverage the environemnt.ts and environemnt.prod.ts files. You can find then under a folder name environemnts under the src folder on an Angular project.

What you put on environment.ts will be available when you run in development mode (the default) and the values in environment.prod.ts will be used when in production. To compile the Angular project with the environment set to production use the --env=prod flag (e.g. ng build --env=prod).

Here's a simple example of how the environment files could be configured to support our hypothetical scenario, environment.ts:

export const environment = {
    production: false,
    apiBaseUrl: "http://localhost:4200/"
};

environment.prod.ts:

export const environment = {
    production: true,
    apiBaseUrl: "http://localhost/"
};

In your Angular services, to get to the environment values you just need to import the environment (always environment and not environment.prod):

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from '../environments/environment';

@Injectable()
export class MyServiceService {

    constructor(private httpClient: HttpClient) { }

    getStuff(){
        return this.httpClient.get(`${environment.apiBaseUrl}/api/suff`);
    }  
}

This approach would work even if you host on Nginx or IIS so probably the best option if you need/want to support having developers using different platforms of if you just want to compare performance between them.