Full Stack CRUD web API with Angular ASP.NET
Hello again, everyone! ??
It's time to share my experience try exploring create a Web Apps with .NET and Angular
Prerequisites
Step 1
First we will need to make our database, On this project I use SQL Server, I already make a Database with "Basketball" name.
then just run this Query to make new Table
CREATE TABLE Positions
(
Id INT IDENTITY(1,1) PRIMARY KEY,
Name VARCHAR(50) NOT NULL,
DisplayOrder INT NOT NULL
);
CREATE TABLE Players
(
Id INT IDENTITY(1,1) PRIMARY KEY,
ShirtNo INT NOT NULL,
Name VARCHAR(50) NOT NULL,
PositionId INT NOT NULL,
Appearances INT NOT NULL,
Goals INT NOT NULL
);
it will make a Table with this relationship
we can add the data into the table with this querry
INSERT INTO Players (ShirtNo, Name, PositionId, Appearances, Goals)
VALUES
(4, 'Tetsuya Kuroko', 1, 100, 10),
(5, 'Taiga Kagami', 4, 90, 40),
(6, 'Ryota Kise', 3, 80, 30),
(7, 'Shintaro Midorima', 2, 85, 45),
(8, 'Daiki Aomine', 4, 95, 50),
(9, 'Seijuro Akashi', 1, 75, 20),
(10, 'Murasakibara Atsushi', 5, 70, 25),
(11, 'Tatsuya Himuro', 2, 60, 15),
(12, 'Atsushi Nijimura', 4, 65, 10),
(13, 'Riko Aida', 5, 50, 5);
INSERT INTO Positions (Name, DisplayOrder)
VALUES
('Point Guard', 1),
('Shooting Guard', 2),
('Shooting Guard', 3),
('Power Forward', 4),
('Center', 5);
Step 2
Let's make our Project, open Visual Studio and create New Project then search for ASP.NET Core with Angular template
We can set Name and Directory where we want our project installed
then wait until it build our project, then we will get the structure of our project, the angular project in on ClientApp folder
we can test how this Web App works by pressing F5 it will showing the dafault page.
Step 3
let's work on Back End first, go to Dependencies>Packages Right Click the Manage NuGet Packages to search and install the required packages
we can use EF Core approach to reverse engineer the entity models and DbContext using the Scaffold-DbContext.
go to Tools>NuGet Package Manager>Package Manager Console
put this command, change [SERVER NAME], and [DATABASE NAME] with your Server and database name
Scaffold-DbContext -Connection "Server=[SERVER NAME]; Database=[DATABASE NAME]; Trusted_Connection=True; MultipleActiveResultSets=true;" -Provider Microsoft.EntityFrameworkCore.SqlServer -OutputDir "Models" -ContextDir "Data" -Context "BasketballDbContext" -NoOnConfiguring
it will generate entity classes on Models Folder and BasketballDbContext class on Data Folder
then we need to Specify the connection string on the appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=[SERVER NAME]; Database=[DATABASE NAME]; Trusted_Connection=True; MultipleActiveResultSets=true"
},
...
after that we need to configure the Configure Service on the startup.cs
...
services.AddDbContext<BasketballDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")
));
services.AddControllersWithViews()
.AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
services.AddScoped<IPlayersService, PlayersService>();
services.AddScoped<IPositionsService, PositionsService>();
...
after we set the connection to the database we need to create the following two services to perform CRUD operations.
Create a?Services?folder in the project and create :
IPlayersService.cs
using Basketball.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Basketball.Services
{
public interface IPlayersService
{
Task<IEnumerable<Player>> GetPlayersList();
Task<Player> GetPlayerById(int id);
Task<Player> CreatePlayer(Player player);
Task UpdatePlayer(Player player);
Task DeletePlayer(Player player);
}
}
IPositionsService.cs
...
public interface IPositionsService
{
Task<IEnumerable<Position>> GetPositionsList();
}
PlayersService.cs
...
public class PlayersService : IPlayersService
{
private readonly BasketballDbContext _context;
public PlayersService(BasketballDbContext context)
{
_context = context;
}
public async Task<IEnumerable<Player>> GetPlayersList()
{
return await _context.Players
.Include(x => x.Position)
.ToListAsync();
}
public async Task<Player> GetPlayerById(int id)
{
return await _context.Players
.Include(x => x.Position)
.FirstOrDefaultAsync(x => x.Id == id);
}
public async Task<Player> CreatePlayer(Player player)
{
_context.Players.Add(player);
await _context.SaveChangesAsync();
return player;
}
public async Task UpdatePlayer(Player player)
{
_context.Players.Update(player);
await _context.SaveChangesAsync();
}
public async Task DeletePlayer(Player player)
{
_context.Players.Remove(player);
await _context.SaveChangesAsync();
}
}
PositionsService.cs
public class PositionsService : IPositionsService
{
private readonly BasketballDbContext _context;
public PositionsService(BasketballDbContext context)
{
_context = context;
}
public async Task<IEnumerable<Position>> GetPositionsList()
{
return await _context.Positions
.OrderBy(x => x.DisplayOrder)
.ToListAsync();
}
}
next we need to add the PlayersControllers and PositionsControllers to the Controllers Folder
领英推荐
PlayersControllers.cs
...
[ApiController]
[Route("api/[controller]")]
public class PlayersController : ControllerBase
{
private readonly IPlayersService _playerService;
public PlayersController(IPlayersService playerService)
{
_playerService = playerService;
}
// GET: api/Players
[HttpGet]
public async Task<IEnumerable<Player>> Get()
{
return await _playerService.GetPlayersList();
}
// GET: api/Players/5
[HttpGet("{id}")]
public async Task<ActionResult<Player>> Get(int id)
{
var player = await _playerService.GetPlayerById(id);
if (player == null)
{
return NotFound();
}
return Ok(player);
}
// POST: api/Players
[HttpPost]
public async Task<ActionResult<Player>> Post(Player player)
{
await _playerService.CreatePlayer(player);
return CreatedAtAction("Post", new { id = player.Id }, player);
}
// PUT: api/Players/5
[HttpPut("{id}")]
public async Task<IActionResult> Put(int id, Player player)
{
if (id != player.Id)
{
return BadRequest("Not a valid player id");
}
await _playerService.UpdatePlayer(player);
return NoContent();
}
// DELETE: api/Players/5
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
if (id <= 0)
return BadRequest("Not a valid player id");
var player = await _playerService.GetPlayerById(id);
if (player == null)
{
return NotFound();
}
await _playerService.DeletePlayer(player);
return NoContent();
}
}
PositionsControllers.cs
using Basketball.Models;
using Basketball.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Basketball.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class PositionsController : ControllerBase
{
private readonly IPositionsService _positionService;
public PositionsController(IPositionsService positionService)
{
_positionService = positionService;
}
// GET: api/Positions
[HttpGet]
public async Task<IEnumerable<Position>> Get()
{
return await _positionService.GetPositionsList();
}
}
}
The back end structure then will look like this
try save then run the Apps, we can check it using Postman or just open the url to get the data
Step 4
after make sure our end points of our backend is indeed working we can start do our front end.
go to ClientApp then Open in Terminal to generate angular module
use command below to generate it
ng generate module players --routing
That command will create a players folder inside the app folder and will generate the players.module.ts and players-routing.module.ts typescript files inside the players module.
To generate another angular components to perform CRUD operations. Run the following command in the command window.
ng generate component players/list
ng generate component players/details
ng generate component players/create
ng generate component players/edit
we can also generate the players and positions angular services and interfaces using the following commands.
ng generate service players/players
ng generate service players/positions
ng generate interface players/player
ng generate interface players/position
the result is kinda looks like this:
Open the players.module.ts file and import the components we generated using the import statement. We also need to specify all four components in the declarations section.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { PlayersRoutingModule } from './players-routing.module';
import { ListComponent } from './list/list.component';
import { DetailsComponent } from './details/details.component';
import { CreateComponent } from './create/create.component';
import { EditComponent } from './edit/edit.component';
@NgModule({
declarations: [ListComponent, DetailsComponent, CreateComponent, EditComponent],
imports: [
CommonModule,
PlayersRoutingModule,
FormsModule,
ReactiveFormsModule
]
})
export class PlayersModule { }
The next step is to define the routes of the list, create, edit, and details components in players-routing.module.ts file
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ListComponent } from './list/list.component';
import { DetailsComponent } from './details/details.component';
import { CreateComponent } from './create/create.component';
import { EditComponent } from './edit/edit.component';
const routes: Routes = [
{ path: 'players', redirectTo: 'players/list', pathMatch: 'full' },
{ path: 'players/list', component: ListComponent },
{ path: 'players/:playerId/details', component: DetailsComponent },
{ path: 'players/create', component: CreateComponent },
{ path: 'players/:playerId/edit', component: EditComponent }];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class PlayersRoutingModule { }
to make Players list page to be accessible from the top navigation bar so add the new Players link in nav-menu.component.html file.
...
<li class="nav-item" [routerLinkActive]="['link-active']">
<a class="nav-link text-dark" [routerLink]="['/players']">Players</a>
</li>
we also need to import our players module in the app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { RouterModule } from '@angular/router';
import { PlayersModule } from './players/players.module';
import { AppComponent } from './app.component';
import { NavMenuComponent } from './nav-menu/nav-menu.component';
import { HomeComponent } from './home/home.component';
import { CounterComponent } from './counter/counter.component';
import { FetchDataComponent } from './fetch-data/fetch-data.component';
import { ListComponent } from "./players/list/list.component";
@NgModule({
declarations: [
AppComponent,
NavMenuComponent,
HomeComponent,
CounterComponent,
FetchDataComponent
],
imports: [
BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
HttpClientModule,
FormsModule,
PlayersModule,
RouterModule.forRoot([
{ path: '', component: HomeComponent, pathMatch: 'full' },
{ path: 'counter', component: CounterComponent },
{ path: 'fetch-data', component: FetchDataComponent },
{ path: 'players', component: ListComponent },
])
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
if we try to run the application and we should be able to navigate to the empty Players list page using the Players navigation link in the top bar.
Step 5
Now let's implementing Position and Player interfaces and services.
Position.ts
export interface Position {
id: number;
name: string;
displayOrder?: number;
}
Player.ts
import { Position } from "./position";
export interface Player {
id: number;
shirtNo: number;
name: string;
positionId?: number;
appearances?: number;
goals?: number;
goalsPerMatch?: number;
position?: Position;
}
now we will create the PositionsService and it has only one method getPositions that will call our back-end Positions Web API and return all positions.
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { Position } from "./position";
@Injectable({
providedIn: 'root'
})
export class PositionsService {
private apiURL = "https://localhost:44384/api"; // CHANGE THIS WITH YOUR BACKEND URL
constructor(private httpClient: HttpClient) { }
getPositions(): Observable<Position[]> {
return this.httpClient.get<Position[]>(this.apiURL + '/positions')
.pipe(
catchError(this.errorHandler)
);
}
errorHandler(error) {
let errorMessage = '';
if (error.error instanceof ErrorEvent) {
errorMessage = error.error.message;
} else {
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
}
return throwError(errorMessage);
}
}
and now we will make a PlayersService that will define all CRUD methods related to players.
All of these methods will call a specific back-end Web API endpoint using the HttpClient object.
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { Player } from "./player";
@Injectable({
providedIn: 'root'
})
export class PlayersService {
private apiURL = "https://localhost:44384/api"; // CHANGE THIS WITH YOUR BACKEND URL
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};
constructor(private httpClient: HttpClient) { }
getPlayers(): Observable<Player[]> {
return this.httpClient.get<Player[]>(this.apiURL + '/players')
.pipe(
catchError(this.errorHandler)
);
}
getPlayer(id): Observable<Player> {
return this.httpClient.get<Player>(this.apiURL + '/players/' + id)
.pipe(
catchError(this.errorHandler)
);
}
createPlayer(player): Observable<Player> {
return this.httpClient.post<Player>(this.apiURL + '/players/', JSON.stringify(player), this.httpOptions)
.pipe(
catchError(this.errorHandler)
);
}
updatePlayer(id, player): Observable<Player> {
return this.httpClient.put<Player>(this.apiURL + '/players/' + id, JSON.stringify(player), this.httpOptions)
.pipe(
catchError(this.errorHandler)
);
}
deletePlayer(id) {
return this.httpClient.delete<Player>(this.apiURL + '/players/' + id, this.httpOptions)
.pipe(
catchError(this.errorHandler)
);
}
errorHandler(error) {
let errorMessage = '';
if (error.error instanceof ErrorEvent) {
errorMessage = error.error.message;
} else {
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
}
return throwError(errorMessage);
}
}
After me we make a Interface and Services we need to implements it into the components.
open list folder then modify the list.components.ts
import { Component, OnInit } from '@angular/core';
import { Player } from "../player";
import { PlayersService } from "../players.service";
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css']
})
export class ListComponent implements OnInit {
players: Player[] = [];
constructor(public playersService: PlayersService) { }
ngOnInit(): void {
this.playersService.getPlayers().subscribe((data: Player[]) => {
this.players = data;
});
}
deletePlayer(id) {
this.playersService.deletePlayer(id).subscribe(res => {
this.players = this.players.filter(item => item.id !== id);
});
}
}
we also need to modify the list.components.html to showing our data
<div class="container">
<br />
<div class="row">
<div class="col">
<h3>Players</h3>
</div>
<div class="col text-right">
<a href="#" routerLink="/players/create/" class="btn btn-success btn-sm">Create New</a>
</div>
</div>
<br />
<table class="table table-bordered table-sm">
<tr>
<th>Id</th>
<th>Shirt No</th>
<th>Name</th>
<th>Position</th>
<th>Appearances</th>
<th>Goals</th>
<th>Goals per match</th>
<th width="200px"></th>
</tr>
<tr *ngFor="let player of players">
<td>{{ player.id }}</td>
<td>{{ player.shirtNo }}</td>
<td>{{ player.name }}</td>
<td>{{ player.position?.name }}</td>
<td>{{ player.appearances }}</td>
<td>{{ player.goals }}</td>
<td>{{ player.goalsPerMatch }}</td>
<td class="text-right">
<a href="#" [routerLink]="['/players/', player.id, 'details']" class="btn btn-info btn-sm">View</a>
<a href="#" [routerLink]="['/players/', player.id, 'edit']" class="btn btn-primary btn-sm">Edit</a>
<button type="button" (click)="deletePlayer(player.id)" class="btn btn-danger btn-sm">Delete</button>
</td>
</tr>
</table>
</div>
try save and run the program
there you will see that our page is generated nicely, we can implements the details, edit and delete with similar way, you can check my Github repo for the complete code ?
Thank you for your attention.
I hope this toturial is useful See you ??