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