Full Stack CRUD web API with Angular ASP.NET

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

  • .NET SDK for this project I use v5.0
  • Node.js for this project I use v16.5.1
  • A IDE such as Visual Studio, for this project we need Visual Studio 2019 version 17.7
  • Database Management System such as SMSS

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.

  1. PlayersService – To implement methods related to Players
  2. PositionsService? – To implement methods related to Positions

Create a?Services?folder in the project and create :

  • IPlayersService?interface (standard methods to perform CRUD operations on Players)
  • PlayersService Class (using injected DbContext in the service class to perform database operations)
  • IPositionsService interface (interface to GET Positions)
  • PositionsService Class

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



要查看或添加评论,请登录

Feri Ginanjar的更多文章

  • Workflow Engine with .NET

    Workflow Engine with .NET

    Hello, I'm Feri, and today I'm excited to share my hands-on experience with workflow engines. In the dynamic world of…

  • Event-Driven ASP.NET Core Microservice Architectures

    Event-Driven ASP.NET Core Microservice Architectures

    Hello again, everyone! ?? It's time to share my experience try to explore a Microservices Web Apps with .NET on this…

  • ASP.NET Core MVC with React

    ASP.NET Core MVC with React

    Hello this time I will try to share my exploration make Application ASP.NET Core MVC with React, Prerequisites .

  • Create Simple CRUD with .NET Core and SQL Server

    Create Simple CRUD with .NET Core and SQL Server

    Hello I'm Feri ?? on this article I want to share my experience creating simple CRUD with .NET Core and SQL Server.

社区洞察

其他会员也浏览了