In this article, we will learn How to Create CRUD operation using Angular and ASP.NET Core Web API. To demonstrate the topic, we will build a project from scratch.
We will take a systematic step-by-step approach, applying the principles of clean architecture, to develop CRUD operation using Angular and ASP.NET Core Web API
Prerequisites
- Install .NET SDK 7
- VS Code or Visual Studio
- Angular 16
Table of Contents
ASP.NET Web API Project
Create Web API
dotnet new project_Name
Create Classlib project
# Create Domain layer
dotnet new classlib -n Domain
# Create Application layer
dotnet new classlib -n Application
# Create Infrastructure layer
dotnet new classlib -n Infrastructure
Install Packages
Install packages in the Infrastructure project
- Microsoft.EntityFrameworkCore
- Microsoft.EntityFrameworkCore.Design
- Microsoft.EntityFrameworkCore.Sqlite
- Microsoft.EntityFrameworkCore.Tools
Reference the projects
Now that we understand the principles of Clean Architecture, let’s see how we can reference it in an ASP.NET Core Web API project
Application Layer
<ItemGroup>
<ProjectReference Include="..\Domain\Domain.csproj" />
</ItemGroup>
Infrastructure Layer
<ItemGroup>
<ProjectReference Include="..\Domain\Domain.csproj" />
<ProjectReference Include="..\Application\Application.csproj" />
</ItemGroup>
Web API
<ItemGroup>
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
</ItemGroup>
Create Entities in the Domain Layer
path:/Domain/Entities
Employee.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Domain.Entities
{
public class Employee : BaseEntity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Street {get;set;}
public string City { get; set; }
public string State { get; set; }
}
}
BaseEntity.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Domain.Entities
{
public class BaseEntity
{
public BaseEntity()
{
CreateAt = DateTime.UtcNow;
}
public int Id { get; set; }
public DateTime CreateAt { get; set; }
}
}
Create an Interface in the Application Layer
path:/Application/Interfaces
IEmployeeRepo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Domain.Entities;
namespace Application.Interfaces
{
public interface IEmployeeRepo
{
Task<IReadOnlyList<Employee>> GetEmployeeListAsync();
Employee GetEmployeeById(int id);
Employee CreateEmployee(Employee employee);
Employee UpdateEmployee(Employee employee);
void DeleteEmployee(int id);
}
}
Create DB Context, Concrete Class, and Services in the Infrastructure Layer
path:/Infrastructure/Data
AppDbContext.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Domain.Entities;
using Microsoft.EntityFrameworkCore;
namespace Infrastructure.Data
{
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<Employee> Employees { get; set;}
}
}
path:/Infrastructure/Repository
EmployeeRepository.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Application.Interfaces;
using Domain.Entities;
using Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
namespace Infrastructure.Repository
{
public class EmployeeRepository : IEmployeeRepo
{
private readonly AppDbContext _context;
public EmployeeRepository(AppDbContext context)
{
_context = context;
}
public Employee CreateEmployee(Employee employees)
{
_context.Employees.Add(employees);
var result = _context.SaveChangesAsync();
if (result.IsCompletedSuccessfully) { return employees; }
return null;
}
public Employee GetEmployeeById(int id)
{
return _context.Employees.FindAsync(id).Result;
}
public async Task<IReadOnlyList<Employee>> GetEmployeeListAsync()
{
return await _context.Employees.ToListAsync();
}
public Employee UpdateEmployee(Employee employee)
{
this._context.Employees.Update(employee);
_context.SaveChanges();
return employee;
}
public void DeleteEmployee(int id)
{
var result = GetEmployeeById(id);
_context.Employees.Remove(result);
_context.SaveChanges();
}
}
}
path:/Infrastructure/Services
ServicesConfig.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Application.Interfaces;
using Infrastructure.Data;
using Infrastructure.Repository;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Infrastructure.Services
{
public static class ServicesConfig
{
public static IServiceCollection AddApplication(this IServiceCollection services, IConfiguration config)
{
services.AddDbContext<AppDbContext>(opt =>
{
opt.UseSqlite(config.GetConnectionString("connection"));
});
services.AddScoped<IEmployeeRepo, EmployeeRepository>();
return services;
}
}
}
Update Program.cs
using Infrastructure.Services;
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.AddApplication(builder.Configuration);
//builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
builder.Services.AddCors(Opt => Opt.AddDefaultPolicy(opt =>
{
opt.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin();
}));
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.UseCors();
app.Run();
Migration
dotnet ef migrations add initialcreate -p .\Infrastructure\ -s API -o Migrations
dotnet ef database update -p Infrastructure -s API
Create Account Controller
EmployeeController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Application.Interfaces;
using Domain.Entities;
using Microsoft.AspNetCore.Mvc;
namespace API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class EmployeeController : ControllerBase
{
private readonly IEmployeeRepo _employeeRepo;
public EmployeeController(IEmployeeRepo employeeRepo)
{
_employeeRepo = employeeRepo;
}
[HttpGet]
public async Task<IReadOnlyList<Employee>> GetEmployees()
{
return await this._employeeRepo.GetEmployeeListAsync();
}
[HttpPost]
public ActionResult EmployeePost(Employee employees)
{
var result = _employeeRepo.CreateEmployee(employees);
return Ok(result);
}
[HttpPut]
public ActionResult EmployeePut(Employee employee)
{
var result = _employeeRepo.UpdateEmployee(employee);
return Ok(result);
}
[HttpGet("{id}")]
public Employee EmployeeGetById(int id)
{
var result = this._employeeRepo.GetEmployeeById(id);
return result;
}
[HttpDelete("{id}")]
public ActionResult DeleteEmployee(int id)
{
this._employeeRepo.DeleteEmployee(id);
return Ok("Employee Deleted");
}
}
}
Create Client-side Angular Project
To create the client-side application, we’ll be using Angular. This section will guide you through setting up an Angular project and creating the necessary components.
# Create a client side Application
ng new project_name
# Navigate the inside project folder
cd project_name
# open the VS Code
code .
# Run the App
ng serve
Create Module | Services | Routing | Shared Module
# Create a module
ng g m employee/employee --flat
# Create a Routing
ng g m employee/employee-routing --flat
# Create a Service
ng g s employee/employee --flat
# Shared module
Employee.ts
Update Routing
import { NgModule, createComponent } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Routes, RouterModule } from '@angular/router';
import { ReadComponent } from './read/read.component';
import { UpdateComponent } from './update/update.component';
import { DeleteComponent } from './delete/delete.component';
import { CreateComponent } from './create/create.component';
const routes : Routes=[
{path:'', component:ReadComponent},
{path:'Create', component:CreateComponent},
{path:'update/:id', component:UpdateComponent},
{path:'delete/:id', component:DeleteComponent},
]
@NgModule({
declarations: [],
imports: [
RouterModule.forChild(routes),
CommonModule
],
exports:[
RouterModule
]
})
export class EmployeeRoutingModule { }
Update Module
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CreateComponent } from './create/create.component';
import { UpdateComponent } from './update/update.component';
import { ReadComponent } from './read/read.component';
import { DeleteComponent } from './delete/delete.component';
import { EmployeeRoutingModule } from './employee-routing.module';
import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
declarations: [
CreateComponent,
UpdateComponent,
ReadComponent,
DeleteComponent
],
imports: [
EmployeeRoutingModule,
ReactiveFormsModule,
CommonModule
]
})
export class EmployeeModule { }
Create Shared Module
Employee.ts
export interface Employee {
id : number,
firstName : string,
lastName : string,
street : string,
city : string,
state : string,
createAt : Date
}
Update Service
import { Injectable } from '@angular/core';
import { Router, Routes } from '@angular/router';
import { HttpClient } from '@angular/common/http'
import { Employee } from '../shared/modules/Employee';
import { map } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class EmployeeService {
baseUrl = 'https://localhost:8080/api';
constructor(private http : HttpClient ,private router : Router ) { }
CreateEmployee(values:any){
return this.http.post<Employee>(this.baseUrl + '/Employee/',values).pipe(
map(employee=> {
alert("Employee Created Successfully...!" + employee);
})
)
}
GetEmployee(){
return this.http.get<Employee>(this.baseUrl + '/Employee/')
}
GetEmployeeById(id:number){
return this.http.get<Employee>(this.baseUrl + '/Employee/'+ id)
}
UpdateEmployee(values:any){
return this.http.put<Employee>(this.baseUrl + '/Employee/', values).pipe(
map(emp=>{
alert("Employee Updated Successfully...!" + emp);
})
)
}
RemoveEmployee(id:number){
return this.http.delete(this.baseUrl + '/Employee/' + id);
}
}
Create Component
# Create a component
ng g c Employee/Read --skip-tests
ng g c Employee/Create--skip-tests
ng g c Employee/Update --skip-tests
ng g c Employee/Delete --skip-tests
Update Read Component
read.component.ts
import { Component, OnInit } from '@angular/core';
import { EmployeeService } from '../employee.service';
import { ActivatedRoute } from '@angular/router';
import { Employee } from 'src/app/shared/modules/Employee';
@Component({
selector: 'app-read',
templateUrl: './read.component.html',
styleUrls: ['./read.component.scss']
})
export class ReadComponent implements OnInit {
employee? :any;
constructor(private employeeServices: EmployeeService, private router:ActivatedRoute){}
ngOnInit(): void {
this.loadEmployee();
}
loadEmployee(){
this.employeeServices.GetEmployee().subscribe({
next : emp => this.employee=emp,
error: error=> alert(error)
});
}
}
read.component.html
<div>
<table class="table table-success table-striped">
<thead>
<tr>
<th>Id</th>
<th>First Name</th>
<th>Last Name</th>
<th>Street</th>
<th>City</th>
<th>State</th>
<th>Created At</th>
<th>Action</th>
</tr>
</thead>
<tbody *ngFor="let emp of employee">
<tr>
<td>{{emp.id}}</td>
<td>{{emp.firstName}}</td>
<td>{{emp.lastName}}</td>
<td>{{emp.street}}</td>
<td>{{emp.city}}</td>
<td>{{emp.state}}</td>
<td>{{emp.createAt}}</td>
<td>
<h5 class="mb-0">
<a routerLink="/delete/{{emp.id}}" class="text-dark text-decoration-none">
<span class="fa fa-trash"></span>
</a>
</h5>
<h5 class="mb-0">
<a routerLink="/update/{{emp.id}}" class="text-dark text-decoration-none">
<span class="fa fa-edit"></span>
</a>
</h5>
</td>
</tr>
</table>
</div>
Update Create Component
create.component.ts
import { Component } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { EmployeeService } from '../employee.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-create',
templateUrl: './create.component.html',
styleUrls: ['./create.component.scss']
})
export class CreateComponent {
title : string ='Employee Registeration Form';
EmpForm = new FormGroup({
firstName : new FormControl('',Validators.required),
lastName: new FormControl('',Validators.required),
street: new FormControl('',Validators.required),
city: new FormControl('',Validators.required),
state: new FormControl('',Validators.required)
});
constructor(private employeeService: EmployeeService, private router: Router){}
onSubmit(){
this.employeeService.CreateEmployee(this.EmpForm.value).subscribe({
next:()=> this.router.navigateByUrl('/'),
error:()=> alert("Please check ...!")
})
}
}
create.component.html
<div class="d-flex justify-content-center mt-5">
<div class="col-3">
<form [formGroup]="EmpForm" (ngSubmit)="onSubmit()">
<div class="text-center mb-4">
<h1 class="mb-3">
Employee
</h1>
</div>
<div class="form-floating mb-3">
<input type="firstName" formControlName="firstName" class="form-control" id="floatingInput" placeholder="first Name">
<label for="floatingInput">firstName</label>
</div>
<div class="form-floating">
<input type="lastName" formControlName="lastName" class="form-control" id="floatingPassword" placeholder="last Name">
<label for="floatingPassword">lastName</label>
</div>
<div class="form-floating">
<input type="street" formControlName="street" class="form-control" id="floatingPassword" placeholder="street">
<label for="floatingPassword">street</label>
</div>
<div class="form-floating">
<input type="city" formControlName="city" class="form-control" id="floatingPassword" placeholder="city">
<label for="floatingPassword">city</label>
</div>
<div class="form-floating">
<input type="state" formControlName="state" class="form-control" id="floatingPassword" placeholder="state">
<label for="floatingPassword">state</label>
</div>
<div class="d-grid">
<button class="btn btn-lg btn-primary mt-3" type="submit">
Submit
</button>
</div>
</form>
</div>
</div>
Update Edit Component
update.component.ts
import { Component, OnInit } from '@angular/core';
import { EmployeeService } from '../employee.service';
import { ActivatedRoute, Router } from '@angular/router';
import { FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-update',
templateUrl: './update.component.html',
styleUrls: ['./update.component.scss']
})
export class UpdateComponent implements OnInit{
employee?:any;
id?:any;
constructor(private employeeService:EmployeeService, private router:ActivatedRoute,private route:Router)
{
this.id = this.router.snapshot.paramMap.get('id');
}
EmpForm = new FormGroup({
firstName : new FormControl('',Validators.required),
lastName: new FormControl('',Validators.required),
street: new FormControl('',Validators.required),
city: new FormControl('',Validators.required),
state: new FormControl('',Validators.required),
id: new FormControl('',Validators.required)
});
ngOnInit(): void {
this.getEmployee();
}
getEmployee(){
if(this.id) this.employeeService.GetEmployeeById(this.id).subscribe({
next : emp => this.employee = emp,
error : error=>alert(error)
});
}
onSubmit(){
this.employeeService.UpdateEmployee(this.EmpForm.value).subscribe({
next:()=>this.route.navigateByUrl('/'),
error:()=>alert("Please check...!")
})
}
}
update.component.html
<p>update works!</p>
<div class="d-flex justify-content-center mt-10">
<div class="col-10">
<div >
<form *ngIf="employee" [formGroup]="EmpForm" (ngSubmit)="onSubmit()">
<fieldset>
<div class="mb-3">
<label for="disabledTextInput" class="form-label">First Name</label>
<input type="text" id="firstName" formControlName="firstName" class="form-control" [(ngModel)]="employee.firstName" >
</div>
<div class="mb-3">
<label for="disabledTextInput" class="form-label">Last Name</label>
<input type="text" id="lastName" formControlName="lastName" class="form-control" [(ngModel)]="employee.lastName">
</div>
<div class="mb-3">
<label for="disabledTextInput" class="form-label">Street</label>
<input type="text" id="street" formControlName="street" class="form-control" [(ngModel)]="employee.street">
</div>
<div class="mb-3">
<label for="disabledTextInput" class="form-label">City</label>
<input type="text" id="city" formControlName="city" class="form-control" [(ngModel)]="employee.city">
</div>
<div class="mb-3">
<label for="disabledTextInput" class="form-label">State</label>
<input type="text" id="state" formControlName="state" class="form-control" [(ngModel)]="employee.state">
</div>
<input type="hidden" id="id" formControlName="id" class="form-control" [(ngModel)]="employee.id" >
<div class="d-grid">
<button class="btn btn-lg btn-primary mt-3" type="submit">
Submit
</button>
</div>
</fieldset>
</form>
</div>
Update Delete Component
delete.component.ts
import { Component, OnInit } from '@angular/core';
import { EmployeeService } from '../employee.service';
import { ActivatedRoute, Router } from '@angular/router';
@Component({
selector: 'app-delete',
templateUrl: './delete.component.html',
styleUrls: ['./delete.component.scss']
})
export class DeleteComponent implements OnInit {
employee?:any;
id?:any;
constructor(private employeeService:EmployeeService, private router : ActivatedRoute, private route:Router)
{
this.id =this.router.snapshot.paramMap.get('id');
}
ngOnInit(): void {
this.getEmployee();
}
getEmployee(){
if(this.id) this.employeeService.GetEmployeeById(this.id).subscribe({
next : emp => this.employee = emp,
error : error=>alert(error)
});
}
onSubmit(){
this.employeeService.RemoveEmployee(this.id).subscribe({
next : ()=>this.route.navigateByUrl('/'),
error :()=>alert("Please check..!")
});
}
}
delete.component.html
<div class="d-flex justify-content-center mt-10">
<div class="col-10">
<div *ngIf="employee"><form>
<fieldset disabled>
<div class="mb-3">
<label for="disabledTextInput" class="form-label">First Name</label>
<input type="text" id="" class="form-control" placeholder="{{ employee.firstName }}">
</div>
<div class="mb-3">
<label for="disabledTextInput" class="form-label">Last Name</label>
<input type="text" id="" class="form-control" placeholder="{{ employee.lastName }}">
</div>
<div class="mb-3">
<label for="disabledTextInput" class="form-label">Street</label>
<input type="text" id="" class="form-control" placeholder="{{ employee.street }}">
</div>
<div class="mb-3">
<label for="disabledTextInput" class="form-label">City</label>
<input type="text" id="" class="form-control" placeholder="{{ employee.city }}">
</div>
<div class="mb-3">
<label for="disabledTextInput" class="form-label">State</label>
<input type="text" id="" class="form-control" placeholder="{{ employee.state }}">
</div>
</fieldset>
</form>
<div class="d-grid">
<button class="btn btn-lg btn-primary mt-3" type="submit" (click)="onSubmit()">
Submit
</button>
</div>
</div>
Root App Updating
Update app-routing. module
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
{path:'',loadChildren:()=>import('./Employee/employee.module').then(emp=>emp.EmployeeModule)},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Update app.component.html
<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css'>
<div class="d-flex justify-content-center mt-5">
<div class="col-0">
<h5 class="mb-0">
<a routerLink="/Create/" class="text-dark text-decoration-none">
<button class="btn btn-primary">Add Employee</button>
</a>
</h5>
<br>
<router-outlet></router-outlet>
</div>
</div>
Related Article – How to Create a Complete Login Page using Angular and .NET Core
Get List
Update record
Delete Record