HI WELCOME TO KANSIRIS

Angular 10 CRUD with ASP.NET Core Web API

Leave a Comment

 In this article, we’ll implement Asp.Net Core 3.1 Web API CRUD Operations with Angular 10. To demonstrate the topic, we’ll build a project from scratch with payment details like Credit/ Debit Card.

Sub-topics discussed.

  • ASP.NET Core Web API
    • Create .Net Core Web API
    • Setup Database with EF Core
    • API Controller for CRUD Web Methods
  • Angular Client Side
    • Create Angular 9 Project
    • Consume ASP.NET Core API From Anguar
    • Form Design and Validation
    • Insert/ Create Record by Form Submission
    • Retrive and Display Inserted Records
    • Update and Delete Operation

Create ASP.NET Core Web API

In Visual Studio 2019, go to File > New > Project (Ctrl + Shift + N). From new project window, select Asp.Net Core Web Application.

Image showing how to create ASP.NET Core Web API project in Visual Studio.

Once you provide the project name and location. A new window will be opened as follows, Select API. The above steps will create a brand new ASP.NET Core Web API project.

Select API Project Template

Setup Database

Let’s create a Database for this project. Inside this project, we’ll be using Entity Framework Core to do DB Operations. So first of all we’ve to install corresponding NuGet packages. Right-click on Project Name from Solution Explorer, Click on Manage NuGet Packages, From Browse Tab, install following 3 packages.

Showing list of NuGet Packages for Entity Framework Core

Now, let’s define DB model class file –PaymentDetail.cs in a new folder Models.

public class PaymentDetail
{
    [Key]
    public int PMId { get; set; }

    [Required]
    [Column(TypeName = "nvarchar(100)")]
    public string CardOwnerName { get; set; }

    [Required]
    [Column(TypeName = "varchar(16)")]
    public string CardNumber { get; set; }

    [Required]
    [Column(TypeName = "varchar(5)")]
    public string ExpirationDate { get; set; }

    [Required]
    [Column(TypeName = "varchar(3)")]
    public string CVV { get; set; }
}
C#

Now let’s define DbContextclass file-  /Models/PaymentDetailContext.cs.

public class PaymentDetailContext : DbContext
{
    public PaymentDetailContext(DbContextOptions<PaymentDetailContext> options):base(options)
    { }

    public DbSet<PaymentDetail> PaymentDetails { get; set; }
}
C#

DbContext Class- PaymentDetailContext decides what should be added to actual physical database during DB Migration. So we have added property for PaymentDetailModel class, after migration PaymentDetails table will be created in SQL Server Database.

Into this model class constructor parameter- options, we have to pass which DbProvider (SQL Server, MySQL, PostgreSQL, etc) to use and corresponding DB connection string also. For that we’ll be using Dependency Injection in ASP.NET Core with Startup.cs file as follows.

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddDbContext<PaymentDetailContext>(optionns =>
    optionns.UseSqlServer(Configuration.GetConnectionString("DevConnection")));
}
C#

Here we’ve used Dependency Injection for DbContext class, by passing SQL Server as a DbProvider with Connection String, Now let’s save connections in appsettings.json file using DevConnectionkey as follows.

{
  ....

  "ConnectionStrings": {
    "DevConnection": "Server=(local)\\sqlexpress;Database=PaymentDetailDB;Trusted_Connection=True;MultipleActiveResultSets=True;"
  }
}
JSON

Now let’s do the migration. Select project from solution explorer, then go to Tools > NuGet Package Manager > Package Manager Console. Then execute following commands one by one.

Add-Migration "InitialCreate"
Update-Database
none

After successful migration, as per the connection string, a new database – PaymentDetailDB will be created with PaymentDetails table. Also, there will be a new Migrations folder created with corresponding C# files.

Create API Controller for CRUD Operations

To create a new API Controller, right-click on Controllers folder Add > Controller, Select API Controller with actions, using Entity Framework. then we can create PaymentDetailController for CRUD operations.

With the help of Scaffolding Mechanism, new controller will be created.

[Route("api/[controller]")]
[ApiController]
public class PaymentDetailController : ControllerBase
{
    private readonly PaymentDetailContext _context;

    public PaymentDetailController(PaymentDetailContext context)
    {
        _context = context;
    }

    // GET: api/PaymentDetail
    [HttpGet]
    public async Task<ActionResult<IEnumerable<PaymentDetail>>> GetPaymentDetails()
    { ... }

    // GET: api/PaymentDetail/5
    [HttpGet("{id}")]
    public async Task<ActionResult<PaymentDetail>> GetPaymentDetail(int id)
    { ... }

    // PUT: api/PaymentDetail/5
    [HttpPut("{id}")]
    public async Task<IActionResult> PutPaymentDetail(int id, PaymentDetail paymentDetail)
    { ... }

    // POST: api/PaymentDetail
    [HttpPost]
    public async Task<ActionResult<PaymentDetail>> PostPaymentDetail(PaymentDetail paymentDetail)
    { ... }

    // DELETE: api/PaymentDetail/5
    [HttpDelete("{id}")]
    public async Task<ActionResult<PaymentDetail>> DeletePaymentDetail(int id)
    { ... }

    private bool PaymentDetailExists(int id)
    { ... }
}
C#

It contains web methods POST, GET, PUT and DELETE for Create, Retrieve, Update and Delete operations respectively. As a constructor parameter we’ve context of the type PaymentDetailContextthe instance/value for this parameter will be passed from Dependency Injection from StartUpclass.

For this project, we don’t have to change anything in web methods. You can test any of the CRUD operations using softwares like postman. And if you want to do so, you’ve to disable SSL certificate verification (Since we checked HTTPS while creating this project).

Web methods with the corresponding URL are given below.

GET/api/PaymentDetail/Retrieve all records
GET/api/PaymentDetail/idRetrieve a record with given id
POST/api/PaymentDetail/Insert/ Create a new record
PUT/api/PaymentDetail/idUpdate a record with given id
DELETE/api/PaymentDetail/idDelete a record with given id

Create Angular App

Now let’s create front-end client-side app in Angular 10. For that execute following Angular-CLI command one by one.

ng new app_name
# after project creation.
# navigate inside project folder
cd app_name
# open in vs code
code .
# from vs code terminal
# command to open the app in default web browser
ng serve --o
none

Before moving forward, let’s look at the structure of the app that we want to build.

● src
+---● app
|   +--● payment-details
|   |  |--payment-details.component.ts|.html|.css
|   |  |
|   |  +--● payment-detail-form
|   |     |--payment-detail-form.component.ts|.html|.css
|   |
|   +--● shared
|   |     |--payment-detail.service.ts
|   |     |--payment-detail.model.ts
|   |
|   |--app.module.ts
|
|--index.html (cdn path for bootstrap & fa icons)
None

we’ve two components, list of records will be shown in  payment-details component, it has a child component payment-detail-form for designing the form.

To create these 2 components, you can execute the following commands.

ng g c payment-details -s --skipTests
ng g c payment-details/payment-detail-form -s --skipTests
none

options used

–inlineStyle(Aliase : -s) = skip seperate component specific style sheet
–skipTests = skip test files with extension .spec.ts .

Now let’s replace the default component file- app.component.html as follows.

<div class="container">
  <app-payment-details></app-payment-details>
</div>
HTML

To show list of records and it’s form side by side, update payment-details.component.html as bellow.

<div class="jumbtron">
    <h1 class="display-4 text-center">Payment Detail Register</h1>

    <div class="row">
        <div class="col-md-5">
            <app-payment-detail-form></app-payment-detail-form>
        </div>
        <div class="col-md-7">
            <div>table with list of records from the table</div>
        </div>
    </div>
</div>
HTML

For this app developement, we’ll be using Bootstrap and Font Awesome Icons. so let’s add their stylesheet reference in index.html.

<head>
	...
	<link data-minify="1" rel="preload" href="https://www.codaffection.com/wp-content/cache/min/1/bootstrap/4.5.2/css/bootstrap.min-74e743e4d2986f75e3e383c818a6a36f.css" data-rocket-async="style" as="style" onload="this.onload=null;this.rel='stylesheet'" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
    <link data-minify="1" rel="preload" href="https://www.codaffection.com/wp-content/cache/min/1/ajax/libs/font-awesome/5.14.0/css/all.min-75e468e63b40f76b58f6c2c8108d8dc0.css" data-rocket-async="style" as="style" onload="this.onload=null;this.rel='stylesheet'" integrity="sha512-1PKOgIY59xJ8Co8+NE6FZ+LOAZKjy+KY8iq0G4B3CyeY6wYHN3yt9PW0XpSriVlkMXe40PTKnXrLnZ9+fkDaog==" crossorigin="anonymous" />
</head>
...
HTML

I’ve few custom CSS to add in global style sheet – styles.css.

.form-group label {
  color: grey;
}

.form-group input {
  font-weight: 500;
}

.form-group input::placeholder {
  color: #c0bdbd;
  font-weight: 300;
}

thead th{
  font-weight: 400;
}

table tr:hover {
  background-color: #fffbf2;
  cursor: pointer;
}

//for invalid form controlls
input.invalid {
  border-color: red;
}
CSS

How to Consume .Net Core API from Angular

first of let’s create a model class for payment details – shared/payment-detail.model.ts. You could manually create the file or execute following CLI command

ng g class shared/payment-detail --type=model --skipTests
//there is no seperat command for model
//hence we use class creation command with type option
//type is used to name the file like '.model.ts'
none

Update the model class with corresponding properties similar to .Net Core API model properties.

export class PaymentDetail {
    PMId :number;
    CardOwnerName: string;
    CardNumber: string;
    ExpirationDate: string;
    CVV: string;
}
C#

Now let’s create a service class to interact with ASP.NET Core Web API- shared/payment-detail.service.tsHere is the CLI command to create the service class.

ng g s shared/payment-detail --skipTests
none

Update the service class as below.

import { PaymentDetail } from './payment-detail.model';
import { Injectable } from '@angular/core';
import { HttpClient } from "@angular/common/http";

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

  formData: PaymentDetail= new PaymentDetail();
  readonly rootURL = 'https://localhost:65067/api';
  list : PaymentDetail[];

  constructor(private http: HttpClient) { }

  postPaymentDetail() {
    return this.http.post(`${this.rootURL}/PaymentDetail`, this.formData);
  }
  putPaymentDetail() {
    return this.http.put(`${this.rootURL}/PaymentDetail/${this.formData.PMId}`, this.formData);
  }
  deletePaymentDetail(id) {
    return this.http.delete(`${this.rootURL}/PaymentDetail/${id}`);
  }

  refreshList() {
    this.http.get(`${this.rootURL}/PaymentDetail/`)
      .toPromise()
      .then(res => this.list = res as PaymentDetail[]);
  }
}
JavaScript

formData property can be used for designing the form for CRUD Operations, list array is used to store all of the retrieved records from the API. rootUrl contains the base URL of the Web API. Now lets run the API from Visual Studio – Debug > Start Debugging(F5)HttpClient is used to make Http Request to the server. Along with methods for CRUD operations, we’ve refreshList function to populate existing records into list property.

To use HttpClient, we also need to import HttpClientModule. So update app/app.module.ts as follows.

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

@NgModule({
  ...
  imports: [HttpClientModule, ...],
  ...
})
TypeScript

Form Design and Validation

Now let’s design the form in payment-detail-form component. first of all we’ve to inject the service class.

import { PaymentDetailService } from './../../shared/payment-detail.service';
import { Component, OnInit } from '@angular/core';
import { NgForm } from '@angular/forms';

@Component({
  selector: 'app-payment-detail-form',
  templateUrl: './payment-detail-form.component.html',
  styles: []
})
export class PaymentDetailFormComponent implements OnInit {

  constructor(public service: PaymentDetailService) { }

  ngOnInit() {
    this.resetForm();
  }

  resetForm(form?: NgForm) {
    if (form != null)
      form.form.reset();
    this.service.formData = {
      PMId: 0,
      CardOwnerName: '',
      CardNumber: '',
      ExpirationDate: '',
      CVV: ''
    }
  }
}
TypeScript

Since we’ve the service injected here. we can access the service property formDatato design the form. function resetForm can be used to initialize model property or reset form based on parameter formInside ngOnInitlife-cycle event, we’ve called the function to initialize the model property.

As a first step towards designing the form, we’ve to import FormsModulein app/app.module.ts. 

...
import { FormsModule } from '@angular/forms';

@NgModule({
  ...
  imports: [FormsModule, ...],
  ...
})
TypeScript

Now let’s design the form. so update payment-detail-form.component.html as shown below.

<form #form="ngForm" autocomplete="off">
    <input type="hidden" name="PMId" [value]="service.formData.PMId">
    <div class="form-group">
        <label>CARD OWNER NAME</label>
        <input name="CardOwnerName" #CardOwnerName="ngModel" [(ngModel)]="service.formData.CardOwnerName"
            class="form-control form-control-lg" placeholder="Full Name" required
            [class.invalid]="CardOwnerName.invalid && CardOwnerName.touched">
    </div>
    <div class="form-group">
        <label>CARD NUMBER</label>
        <input name="CardNumber" #CardNumber="ngModel" [(ngModel)]="service.formData.CardNumber"
            class="form-control form-control-lg" placeholder="16 Digit Card Number" required maxlength="16"
            minlength="16" [class.invalid]="CardNumber.invalid && CardNumber.touched">
    </div>
    <div class="form-row">
        <div class="form-group col-md-3">
            <label>CVV</label>
            <input type="password" name="CVV" #CVV="ngModel" [(ngModel)]="service.formData.CVV"
                class="form-control form-control-lg" placeholder="CVV" required maxlength="3" minlength="3"
                [class.invalid]="CVV.invalid && CVV.touched">
        </div>
        <div class="form-group col-md-9">
            <label>VALID THROUGH</label>
            <input name="ExpirationDate" #ExpirationDate="ngModel" [(ngModel)]="service.formData.ExpirationDate"
                class="form-control form-control-lg" placeholder="MM/YY" required maxlength="5" minlength="5"
                [class.invalid]="ExpirationDate.invalid && ExpirationDate.touched">
        </div>

    </div>
    <div class="form-group">
        <button class="btn btn-info btn-lg btn-block" type="submit" [disabled]="form.invalid">SUBMIT</button>
    </div>
</form>
HTML

It might be confusing for you, because here we’ve put everything related to the form – design and form validation. we have input field for all model properties including PMId in a hidden field. Each input field is bound to its respective property through 2 way data-binding.

Inside this form, all field has the requiredvalidation and number of characters is restricted to all field except Card Owner NameFor Angular field validation, we can use auto-generated classes/attributes for showing validation error indications. Auto-generated classes/ attribute by Angular

    • ng-invalid (class)/ invalid (property) X ng-valid (class)/ valid (property)
    • ng-untouched (class)/ untouched (property) X ng-touched (class)/ touched (property)

To indicate validation error, we conditionally applied CSS class –  invalid. Finally the submit button is conditionally disabled based on whether the form as a whole is valid or not.

Currently our Angular Form in payment-detail-form component looks like this.

Image showing screen shot of Angular Form for Insert/ update operation.

Insert a new Record

let’s wire up submitevent to the form.

<form ...
	(submit)="onSubmit(form)">
...
</form>
HTML

Now define the function – onSubmitinside payment-detail-form.component.ts. 

onSubmit(form: NgForm) {
  this.insertRecord(form);
}

insertRecord(form: NgForm) {
  this.service.postPaymentDetail().subscribe(
    res => {
      this.resetForm(form);
      this.service.refreshList();
    },
    err => { console.log(err); }
  )
}
TypeScript

a separate function insertRecordis defined to insert a new record into the SQL server table.

Before testing this operation, we have to do a few more things in ASP.NET Core Web API.

  1. .Net Core Web API will block request from another application which is hosted in another domain or in another port number. by default, Angular is running at port number 4200 and Web API is hosted at a different port number. to make Http Request, we’ve to Enable-CORS (Cross Origin Resource Sharing) in Web API.
  2. By default, ASP.Net Core API use camel casing for response object. (eg: CardNumber to cardNumber). so we’ve to avoid this default json formatting.

Now let’s do required steps to solve these two issues. First of all install latest NuGet Package of Microsoft.AspNetCore.Cors. Then you can update Startup class like this.

public void ConfigureServices(IServiceCollection services)
{
	...

	//remove default json formatting
	services.AddControllers().AddJsonOptions(options =>
     {
         options.JsonSerializerOptions.PropertyNamingPolicy = null;
         options.JsonSerializerOptions.DictionaryKeyPolicy = null;
     });

	//add cors package
	services.AddCors();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    //configurations to cosume the Web API from port : 4200 (Angualr App)
    app.UseCors(options =>
    options.WithOrigins("http://localhost:4200")
    .AllowAnyMethod()
    .AllowAnyHeader());

	...
}
C#

Inside the Configure function, it is better to keep the function call UseCors before any other lines. Now you can try the insert operation. for me, it is working fine. Comment if you face any problem.

Retrieve and Display all Inserted Records

Inserted records can be retrieved and displayed in payment-details component. for that let’s update the component typescript file as follows.

import { PaymentDetailService } from './../../shared/payment-detail.service';
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-payment-details',
  templateUrl: './payment-details.component.html',
  styles: []
})
export class PaymentDetailListComponent implements OnInit {

  constructor(public service: PaymentDetailService) { }

  ngOnInit() {
    this.service.refreshList();
  }
}
TypeScript

Inside list component ngOnInit lifecycle hook, we’ve called refreshListfunction to populate listarray in service class. using service listproperty, we can render all of the inserted records in payment-details component html. So update the table div as follow.

<table class="table">
    <thead class="thead-light">
        <tr>
            <th>Card Owner</th>
            <th>Card Number</th>
            <th>Exp.</th>
            <th></th>
        </tr>
    </thead>
    <tr *ngFor="let pd of service.list">
        <td (click)="populateForm(pd)">{{pd.CardOwnerName}}</td>
        <td (click)="populateForm(pd)">{{pd.CardNumber}}</td>
        <td (click)="populateForm(pd)">{{pd.ExpirationDate}}</td>
        <td>
            <i class="far fa-trash-alt fa-lg text-danger" (click)="onDelete(pd.PMId)"></i>
        </td>
    </tr>
</table>
HTML

Update and Delete Operation

Tol implement update operation, for that we’ve added click event for all tdcells as shown below.

<td (click)="populateForm(pd)">...</td>
HTML

Inside the click event function, we have to populate the corresponding selected record inside the form. so add the following function to payment-detailscomponent.

populateForm(selectedRecord) {
	this.service.formData = Object.assign({}, selectedRecord);
}
TypeScript

inside the function, we just set the selected record object to formDataproperty in service class. since the form is bound to formDataproperties, the form field will get populated corresponding details.

After making required changes in these populated value fields, user can submit the form for the update operation. so we have to handle both insert and update operation inside the same form submit event in payment-detail-form component. hence update the payment-detail-form.component.ts.

onSubmit(form: NgForm) {
  if (this.service.formData.PMId == 0)
    this.insertRecord(form);
  else
    this.updateRecord(form);
}

updateRecord(form: NgForm) {
  this.service.putPaymentDetail().subscribe(
    res => {
      this.resetForm(form);
      this.toastr.info('Submitted successfully', 'Payment Detail Register');
      this.service.refreshList();
    },
    err => {
      console.log(err);
    }
  )
}
TypeScript

Inside the form, we have a hidden field for PMId based on its value in submit function, we can decide whether we’ve got an insert/ update operation. insertRecordis already defined. with updateRecordfunction, we will update the corresponding payment-detail record.

Now let’s implement DeleteOperation in parent component. For that add delete button inside that table rows.

file: payment-details.component.html

...
  <td>
    <i class="far fa-trash-alt fa-lg text-danger" (click)="onDelete(pd.PMId)"></i>
  </td>
 </tr>
</table>
HTML

Now lets define onDeletefunction in payment-details.component.ts as below.

onDelete(PMId) {
  if (confirm('Are you sure to delete this record ?')) {
    this.service.deletePaymentDetail(PMId)
      .subscribe(res => {
        this.service.refreshList();
      },
      err => { console.log(err); })
  }
}
TypeScript

So that’s all about Angular CRUD operations with ASP.NET Core Web API.

0 comments:

Post a Comment

Note: only a member of this blog may post a comment.