Angular CRUD Operation Using Entity Framework Core and .NET Core

What is CRUD?

CRUD is an acronym that stands for Create, Read, Update, and Delete. It represents the four basic operations that can be performed on data in most software development applications. These operations are the fundamental building blocks for interacting with persistent data in a database or any data storage system.

  1. Create (C): This operation refers to the action of adding new data to the system. It involves creating and inserting a new record or entry into the database with the specified information.
  2. Read (R): The read operation allows developers to retrieve or fetch existing data from the database. It involves querying the database to find and display the data that meets certain criteria or conditions.
  3. Update (U): With the update operation, you can modify or change the existing data in the database. Developers use this operation to update the attributes or fields of a specific record.
  4. Delete (D): The delete operation is used to remove data from the database. It involves permanently deleting a record or entry from the database, ensuring it is no longer accessible.

Tools

  • Visual Studio Community 2022
  • Visual Studio Code
  • SQL Server Management Studio

Application Setup

1. Select => ASP.NET CORE WEB API => Click on the next button in the bottom right.

Application Setup

2. Enter the project name PaymentCard.API.

Configure the project

3. I am using dotnet core 7.0 for web api => click on create button.

additional information

4. Install nuget packages:

  • 1) Microsoft.EntityFrameworkCore
  • 2)Microsoft.EntityFrameworkCore.SqServer
  • 3)Microsoft.EntityFrameworkCore.Tools

5. Right-click on the project => click on Add => create new folder, "Models".

6. Create a Model class in the Models folder => "PaymentDetail.cs".

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

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

    [Column(TypeName = "nvarchar(16)")]
    public string cardNumber { get; set; } = "";

    [Column(TypeName = "nvarchar(5)")]
    public string expirationDate { get; set; } = "";

    [Column(TypeName = "nvarchar(3)")]
    public string securityCode { get; set; } = "";

}

7. Create a data context folder in the project, then create the "PaymentDbContext" class.

public class PaymentDbContext : DbContext
{
    public PaymentDbContext(DbContextOptions options) : base(options)
    {
    }

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

Open the "appsettings.json" file like below.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "PaymentConnection": "your connection string"
  }
}

Now we going to confirm Paymentdbcontext in the program.cs file.

using Microsoft.EntityFrameworkCore;
using PaymentCard.API.DataContext;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<PaymentDbContext>(o => o.UseSqlServer(builder.Configuration.GetConnectionString("PaymentConnection")));
//services cors
builder.Services.AddCors(p => p.AddPolicy("corsapp", builder =>
{
    builder.WithOrigins("*").AllowAnyMethod().AllowAnyHeader();
}));

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseRouting();
app.UseCors("corsapp");
app.UseAuthorization();

app.MapControllers();

app.Run();

Click on Tools in the top menu => click  Nuget package manager => click on Package Manager Console

for migration, use two commands.

  • Add-Migration "Initial"
  • Update-Database 

Create Controller class in Controllers folder: PaymentDetailsController.cs.

namespace PaymentCard.API.Controllers;

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

    public PaymentDetailsController(PaymentDbContext context)
    {
        _context = context;
    }

    // GET: api/PaymentDetails
    [HttpGet]
    public async Task<ActionResult<IEnumerable<PaymentDetail>>> GetPaymentDetails()
    {
      if (_context.PaymentDetails == null)
      {
          return NotFound();
      }
        return await _context.PaymentDetails.ToListAsync();
    }

    // GET: api/PaymentDetails/5
    [HttpGet("{id}")]
    public async Task<ActionResult<PaymentDetail>> GetPaymentDetail(int id)
    {
      if (_context.PaymentDetails == null)
      {
          return NotFound();
      }
        var paymentDetail = await _context.PaymentDetails.FindAsync(id);

        if (paymentDetail == null)
        {
            return NotFound();
        }

        return paymentDetail;
    }

    // PUT: api/PaymentDetails/5

    [HttpPut("{id}")]
    public async Task<IActionResult> PutPaymentDetail(int id, PaymentDetail paymentDetail)
    {
        if (id != paymentDetail.paymentId)
        {
            return BadRequest();
        }

        _context.Entry(paymentDetail).State = EntityState.Modified;

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

        return NoContent();
    }


    // POST: api/PaymentDetails
    [HttpPost]
    public async Task<ActionResult<PaymentDetail>> PostPaymentDetail(PaymentDetail paymentDetail)
    {
      if (_context.PaymentDetails == null)
      {
          return Problem("Entity set 'PaymentDbContext.PaymentDetails'  is null.");
      }
        _context.PaymentDetails.Add(paymentDetail);
        await _context.SaveChangesAsync();

        return Ok(await _context.PaymentDetails.ToListAsync());
    }

    // DELETE: api/PaymentDetails/5
    [HttpDelete("{id}")]
    public async Task<IActionResult> DeletePaymentDetail(int id)
    {
        if (_context.PaymentDetails == null)
        {
            return NotFound();
        }
        var paymentDetail = await _context.PaymentDetails.FindAsync(id);
        if (paymentDetail == null)
        {
            return NotFound();
        }

        _context.PaymentDetails.Remove(paymentDetail);
        await _context.SaveChangesAsync();

        return NoContent();
    }

    private bool PaymentDetailExists(int id)
    {
        return (_context.PaymentDetails?.Any(e => e.paymentId == id)).GetValueOrDefault();
    }
}

Finally, we created CRUD of asp.net core web API, now move to Angular application to consume API.

Open the command prompt and run the below command.

run below command

Select CSS, then press enter.

Select CSS

Install the toaster notification.

It is installed in the latest version, as we using angular 16.

npm i ngx-toastr

Configure toastr.css in angular.json file and add below code:

Just replace it with your code in the style.css file.

@import 'ngx-toastr/toastr';

Add the below code in your app.module.ts file.

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ToastrModule } from 'ngx-toastr';

@NgModule({
  declarations: [
    AppComponent,
    PaymentDetailsComponent,
    PaymentDetailFormComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule, // required animations module
    ToastrModule.forRoot(), // ToastrModule added
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

How to Create Components, model classes, env Files, and service classes in Angular?

  • ng generate component name-of-component.
  • ng generate s service-name
  • ng generate class ModelName
  •  ng generate interface ModelName

Now create a component for the Payment card application.

  • ng generate component PaymentDetails
  • ng generate component PaymentDetails/PaymentDetailForm
  • ng generate service shared/PaymentDetail
  • ng generate class shared/PaymentDetail
  • ng generate environments

I have created the required files.

Open the Model class inside of the shared folder and paste the below code.

export class PaymentDetail {
    paymentId:number=0;
    cardOwnerName:string=""
    cardNumber:string=""
    expirationDate:string=""
    securityCode:string=""
}

Open the index.html file and add bootstrap cdn in the head.

Open the app.module.ts file and import the FormsModule.

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

//inside of import bracketet

imports: [
    BrowserModule,
    HttpClientModule,
    FormsModule, //forms module
    ],
  providers: [],
  bootstrap: [AppComponent]
})

Open the app.component.html file and paste the below code into your file.

<div class="container-fluid">
  <div class="row">
    <div class="col-6 offset-2">
      <app-payment-details />
    </div>
  </div>
</div>

Open payment-detail-form.component.html and paste the below code.


<form #form="ngForm" (submit)="onSubmit(form)" [class.submitted]="paymentService.formSubmitted" novalidate autocomplete="off">
    <input type="hidden" name="paymentId" [value]="paymentService.formData.paymentId" />
    <div class="mb-3">
        <label for="cardOwnerName">Card Owner Name</label>
        <input class="form-control form-control-lg" placeholder="Name on Card" type="text" #cardOwnerName="ngModel" name="cardOwnerName"
        [(ngModel)]="paymentService.formData.cardOwnerName" id="cardOwnerName" required />
    </div>

    <div class="mb-3">
        <label for="cardNumber">Card Number</label>
        <input class="form-control form-control-lg" placeholder="16 digit card number" type="text" #cardNumber="ngModel" name="cardNumber"
        [(ngModel)]="paymentService.formData.cardNumber" id="cardNumber" minlength="16" maxlength="16" required />
    </div>

<div class="row">
    <div class="mb-3 col-6">
        <label for="expirationDate">Valid</label>
        <input class="form-control form-control-lg" placeholder="MM/YY" type="text" #expirationDate="ngModel" name="expirationDate"
        [(ngModel)]="paymentService.formData.expirationDate" id="expirationDate" required />
    </div>

    <div class="mb-3 col-6">
        <label for="securityCode">Security Code</label>
        <input class="form-control form-control-lg" placeholder="CVV" type="password" #securityCode="ngModel" name="securityCode"
        [(ngModel)]="paymentService.formData.securityCode" id="securityCode" minlength="3" maxlength="3" required />
    </div>
</div>
   

    <div class="d-grid">
        <button class="btn btn-lg btn-success" type="submit">
            Submit
        </button>
    </div>
</form>

Explanation

  1. #form="ngForm": This is a template reference variable named form that is assigned the value ngForm. It is used to reference the Angular NgForm directive, which provides access to the form's state and controls.
  2. (submit)="onSubmit(form)": This is an event binding that listens for the submit event of the form and calls the onSubmit method when the event is triggered. The form variable (referenced using the template reference variable) is passed as an argument to the onSubmit method. This allows you to access the form's data and controls within the method.
  3. [class.submitted]="paymentService.formSubmitted": This is a property binding that adds the CSS class submitted to the form element when the paymentService.formSubmitted property evaluates to true. This can be useful for applying styles or showing certain visual cues based on the form submission state.
  4. novalidate: This attribute disables the default HTML5 form validation behavior. It prevents the browser from performing its native form validation and allows you to handle validation using Angular's mechanisms.
  5. autocomplete="off": This attribute disables the autocomplete feature for form inputs. This can be useful in scenarios where you want to prevent browsers from suggesting previously entered values for form fields.
  6. #cardOwnerName="ngModel": This is a template reference variable named cardOwnerName and associated with Angular's ngModel directive. It allows you to reference this input field in your component code.
  7. name="cardOwnerName": This attribute specifies the name of the input field. It is used for form data binding and submission.
  8. [(ngModel)]="paymentService.formData.cardOwnerName": This is two-way data binding using Angular's ngModel directive. It binds the value of the input field to the cardOwnerName property in the paymentService.formData object. This means any changes in the input field will update the property value and vice versa.
  9. id="cardOwnerName": This attribute provides a unique identifier for the input field, which is used to associate the label with the input field using the for attribute.
  10. required: This attribute specifies that the input field is required and must be filled out before submitting the form.

Open the environments folder and click on the environment.development.ts file.

export const environment = {
    apiBaseUrl:'https://localhost:7213/api'
};
Open service class inside of the shared folder.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from 'src/environments/environment.development';
import { PaymentDetail } from './payment-detail.model';
import { NgForm } from '@angular/forms';

@Injectable({
  providedIn: 'root'
})
export class PaymentDetailService {
  //api url
  url: string = environment.apiBaseUrl + '/PaymentDetails';
  // stored object
  list: PaymentDetail[] = [];
  //model class
  formData:PaymentDetail=new PaymentDetail();
  formSubmitted:boolean=false;
  constructor(private http: HttpClient) { }

  refreshList() {
    this.http.get(this.url)
      .subscribe({
        next: res => {
          this.list = res as PaymentDetail[];
        },
        error: err => {
          console.log(err);
        }
      })
  }

  postPayment(){
   return this.http.post(this.url,this.formData)
  }

  putPayment(){
    return this.http.put(this.url+'/'+this.formData.paymentId,this.formData);
   }

   deletePayment(id:number){
    return this.http.delete(this.url+'/'+id);
   }

  resetForm(form:NgForm){
    form.form.reset();
    this.formData=new PaymentDetail();
    this.formSubmitted=false;
  }
}
Explanation
  1. refreshList(): This method sends an HTTP GET request to the API using the HttpClient. It retrieves a list of payment details and updates the list property with the response.
  2. postPayment(): This method sends an HTTP POST request to create a new payment detail by using the formData.
  3. putPayment(): This method sends an HTTP PUT request to update an existing payment detail, targeting the URL with the specific paymentId.
  4. deletePayment(id): This method sends an HTTP DELETE request to remove a payment detail with the specified id.
  5. resetForm(form): This method resets an Angular form (NgForm instance), clears formData, and resets the formSubmitted flag.

Open PaymentDetailFormComponent of .ts file.

import { Component } from '@angular/core';
import { NgForm } from '@angular/forms';
import { PaymentDetail } from 'src/app/shared/payment-detail.model';
import { PaymentDetailService } from 'src/app/shared/payment-detail.service';
import { ToastrService } from 'ngx-toastr';

@Component({
  selector: 'app-payment-detail-form',
  templateUrl: './payment-detail-form.component.html',
  styleUrls: ['./payment-detail-form.component.css']
})
export class PaymentDetailFormComponent {
  constructor(public paymentService: PaymentDetailService, private toastr: ToastrService) { }


  onSubmit(form: NgForm) {
    this.paymentService.formSubmitted = true;
    if (form.valid) {
      if (this.paymentService.formData.paymentId == 0)
        this.insertRecord(form);
      else
        this.updateRecord(form);
    }
  }

  insertRecord(form: NgForm) {
    this.paymentService.postPayment()
      .subscribe({
        next: res => {
          this.paymentService.list = res as PaymentDetail[]
          this.paymentService.resetForm(form);
          this.toastr.success('Inserted successfully!', 'Payment Card');
          //console.log(res);
        },
        error: err => {
          console.log(err);
        }
      })
  }

  updateRecord(form: NgForm) {
    this.paymentService.putPayment()
      .subscribe({
        next: res => {
          this.paymentService.list = res as PaymentDetail[]
          this.paymentService.resetForm(form);
          this.toastr.info('Updated successfully!', 'Payment Card');
          //console.log(res);
        },
        error: err => {
          console.log(err);
        }
      })
  }
}

Explanation

onSubmit(form: NgForm) { ... }:

  1. This method is called when the form is submitted.
  2. It sets the formSubmitted flag in the PaymentDetailService to true to indicate that a form has been submitted.
  3. It checks if the form is valid and, based on the validity, decides whether to call the insertRecord or updateRecord method.

insertRecord(form: NgForm) { ... }:

  1. This method is called to insert a new payment record.
  2. It calls the postPayment method of  PaymentDetailService sending an HTTP POST request.
  3. If successful, it updates the list property in the service, resets the form using resetForm, and displays a success toast notification.

updateRecord(form: NgForm) { ... }:

  1. This method is called to update an existing payment record.
  2. It calls the putPayment method of  PaymentDetailService sending an HTTP PUT request.
  3. If successful, it updates the list property in the service, resets the form using resetForm, and displays an info toast notification.

Open PaymentDetailsComponent of .ts file.

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

@Component({
  selector: 'app-payment-details',
  templateUrl: './payment-details.component.html',
  styleUrls: ['./payment-details.component.css']
})
export class PaymentDetailsComponent implements OnInit {
  constructor(public paymentService: PaymentDetailService, private toastr: ToastrService) {
  }

  ngOnInit(): void {
    this.paymentService.refreshList();
  }

  populateForm(selectedRecord: PaymentDetail) {
    this.paymentService.formData = Object.assign({}, selectedRecord);
  }

  onDelete(id: number) {
    if (confirm('Are you sure to delete this record?')) {
      this.paymentService.deletePayment(id)
        .subscribe({
          next: res => {
            this.paymentService.list = res as PaymentDetail[]
            this.toastr.error("Deleted successfull!", "Payment Card");
          },
          error: err => { console.log(err) }
        })
    }
  }
}

Open PaymentDetailsComponent of .html file.

<div class="row mt-5">
    <div class="col-md-6">
        <div class="card p-4">
            <h2 class="mb-4">Payment Details</h2>
            <app-payment-detail-form></app-payment-detail-form>
        </div>
    </div>
    <div class="col-md-6">
        <div class="card p-4">
            <h2 class="mb-4">List of Payments</h2>
            <div class="table-responsive">
                <table class="table table-striped text-center">
                    <thead>
                        <tr>
                            <th>Card Owner Name</th>
                            <th>Card Number</th>
                            <th>Expiration Date</th>
                            <th>Action</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr *ngFor="let pay of paymentService.list">
                            <td>{{pay.cardOwnerName}}</td>
                            <td>{{pay.cardNumber}}</td>
                            <td>{{pay.expirationDate}}</td>
                            <td class="text-center">
                                <div class="btn-group" role="group">
                                    <button class="btn btn-danger btn-sm me-2" (click)="onDelete(pay.paymentId)">
                                        <i class="fa-solid fa-trash"></i>
                                    </button>
                                    <button class="btn btn-warning btn-sm" (click)="populateForm(pay)">
                                        <i class="fa-solid fa-pen-to-square"></i>
                                    </button>
                                </div>
                            </td>
                        </tr>
                    </tbody>
                </table>
            </div>
        </div>
    </div>    
</div>
Screenshots- Form and listing

Form listing

Create payment- Validation

Payment details

Payment