REST ( Representational State Trasnfer) 공식표준 스펙은 아닌데
원래 있던 HTTP 통신에서 재사용 하여 데이터 송수신 규칙을 만든것 => CRUD
CRUD 를 Blazor API 서버를 활용하여 작업 한것
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SharedData.Models;
using System.Collections.Generic;
using System.Linq;
using WebAPI.Data;
namespace WebAPI.Controllers
{
//www.google.com 이건 전체 주소
//www.google.com/api/ranking 이건 세부 라우팅 주소로 전체 주소에서 구분지어 들어갈 때 사용됨
//그 다음 어떤걸 하는지 지정 할 수 있다 GET, POST, PUT...)
//Create : POST 방식 api/ranking => body 에 실제 정보를 담는다 , 아이템 생성 요청
//Read 시에는 보통 Get 을 사용한다
//GET : api/ranking => 전체 아이템을 가져온다 , api/ranking/1 id가 1인 아이템을 가져온다
//get /api/ranking 모든 id를 주세요
//get /api/ranking/1 id 중에서 1전을 주세요 라는 요청
//get 은 body 에 정보를 넣지 않고 이 요청 하나로 처리 된다
//update : put 으로 사용됨
//PUT : api/ranking (put은 보안 무제로 일반적인 웹에서 사용되지 않는다)
//put 은 body 에 정보를 넣어서 요청을 보낸다
//delete /api/ranking/1 이렇게 삭제 할 수 있는데 보안 문제로 웹에서 쓰지 않는다
//id=1 번인 아이템 삭제 요청
//api controller 는 c# 객체를 반환하는 것이 가능하다
//null 반환하면 클라에서 204 Response (No Content) 를 받는다
//String을 반호나하면 => text/plain 타입으로 반환한다
//나머지는 json 형태로 반환한다
//localhost:portnumber/api/ranking 이 요청이라는 얘기인데 RankingController 이것이 [controller] 부분에서 ranking 으로 변환됨
[Route("api/[controller]")]
[ApiController]
public class RankingController : ControllerBase
{
ApplicationDbContext _context;
public RankingController(ApplicationDbContext context)
{
_context = context;
}
//create, body 에 보낼때는 [frombody 를 넣어주면 된다]
[HttpPost]
public GameResult AddGameResult([FromBody] GameResult gameResult)
{
_context.GameResultList.Add(gameResult);
_context.SaveChanges();
return gameResult;
}
//update
[HttpPut]
public bool UpdateGameResult([FromBody] GameResult gameResult)
{
var findResult = _context.GameResultList.Where(item => item.Id == gameResult.Id).FirstOrDefault();
if(findResult == null)
{
return false;
}
findResult.UserName = gameResult.UserName;
findResult.Score = gameResult.Score;
_context.SaveChanges();
return true;
}
//read
//ranking
[HttpGet]
public List<GameResult> GetGameResults()
{
List<GameResult> results = _context.GameResultList.OrderByDescending(item=> item.Score).ToList();
return results;
}
//ranking/1
[HttpGet("{id}")]
public GameResult GetGameResult(int id)
{
GameResult result = _context.GameResultList.Where(item => item.Id == id).FirstOrDefault();
return result;
}
//delete
[HttpDelete("{id}")]
public bool DeleteGameResult(int id)
{
var findResult = _context.GameResultList.Where(item => item.Id == id).FirstOrDefault();
if (findResult == null)
{
return false;
}
_context.GameResultList.Remove(findResult);
_context.SaveChanges();
return true;
}
}
}
Console.WriteLine(default(int)); // output: 0
Console.WriteLine(default(object) is null); // output: True
void DisplayDefaultOf<T>()
{
var val = default(T);
Console.WriteLine($"Default value of {typeof(T)} is {(val == null ? "null" : val.ToString())}.");
}
DisplayDefaultOf<int?>();
DisplayDefaultOf<System.Numerics.Complex>();
DisplayDefaultOf<System.Collections.Generic.List<int>>();
// Output:
// Default value of System.Nullable`1[System.Int32] is null.
// Default value of System.Numerics.Complex is (0, 0).
// Default value of System.Collections.Generic.List`1[System.Int32] is null.
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Migrations;
using RankingApp.Data.Models;
using System;
using System.Collections.Generic;
using System.Text;
namespace RankingApp.Data
{
public class ApplicationDbContext : IdentityDbContext
{
//DbSet 으로 추가해줘야지 GameReesult.cs 에서 GameResultList 내용을 DB에 저장할 수 있다. ORM
//즉 서버를띄울때 자동으로 DB를 갱신하여 띄워주게 된다
//PM> add-migration RankingService 명령어르 NeGet 콘솔에서 실행하여 DB를 갱신해준다
//PM> update-database 명령어르 NeGet 콘솔에서 실행하여 DB를 갱신해준다
//주의!! GameResultList 이 테이블 명과 동일해야 한다 dbo.GameResultList 에서 dbo 빼고 GameResultList 만
public DbSet<GameResult> GameResultList { get; set; }
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
}
그런다음 패키지 관리자 콘솔에서 다음 처럼 입력한다음 다음 처럼 적용한다
PM> Add-Migration GameResultList
PM> Update-Database
이 처럼 마이그레이션이 변경된다
테이블명도 자동으로 바뀌게 된다
서비스 쪽에 갱신과 삭제에 대한 로직을 추가 하고 테이블에도 값이 삭제/갱신이 적용 될수 있게 처리 한다
public Task<bool> UpdateGameResult(GameResult gameResult)
{
var result = _context.GameResultList.Where(x => x.Id == gameResult.Id).FirstOrDefault();
if(result==null)
{
return Task.FromResult(false);
}
result.UserName = gameResult.UserName;
result.Score = gameResult.Score;
_context.SaveChanges();
return Task.FromResult(true);
}
public Task<bool> DeleteGameResult(GameResult gameResult)
{
var result = _context.GameResultList.Where(x => x.Id == gameResult.Id).FirstOrDefault();
if (result == null)
{
return Task.FromResult(false);
}
_context.GameResultList.Remove(gameResult);
_context.SaveChanges();
return Task.FromResult(true);
}
Ranking.razor
갱신과 삭제 버튼이 보여 내용을 수정 할수 있도록 하고
추가팝업과 갱신 팝업은 동일한 형태로 사용한다
그리고 로그인이 되어 있지 않다면 유저 정보는 보이지 않도록 Autorized, NotAutorized 처리를 해 놓았다
@page "/ranking"
@using RankingApp.Data.Models;
@using RankingApp.Data.Services;
@inject RankingService RankingService
<h3>Ranking</h3>
<AuthorizeView>
<Authorized>
@if (_gameResultList == null)
{
<p>Loading...</p>
}else{
<table class="table">
<thead>
<tr>
<th>UserName</th>
<th>Score</th>
<th>Date</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var gameResult in _gameResultList)
{
<tr>
<td>@gameResult.UserName</td>
<td>@gameResult.Score</td>
<td>@gameResult.Date</td>
<td>
<button class="btn btn-primary" @onclick="() => EditGameResult(gameResult)">
Edit
</button>
</td>
<td>
<button class="btn btn-primary" @onclick="() => DeleteGameResult(gameResult)">
Delete
</button>
</td>
</tr>
}
</tbody>
</table>
<p>
<!-- make button to add gameresult -->
<button class="btn btn-primary" @onclick="AddGameResult">
Add
</button>
</p>
@if(_showPopup)
{
//dialog to show gameresult
<div class="modal" style="display:block" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header"></div>
<h3 class="modal-title">Add/Update Game Result</h3>
<button type="button" class="close" @onclick="ClosePopup">
<span aria-hidden="true">X</span>
</button>
<div class="modal-body">
<label for="UserName"> UserName</label>
<input class="form-control" type="text" placeholder="UserName" @bind-value="_gameResult.UserName" />
<label for="Score"> Score</label>
<input class="form-control" type="text" placeholder="Score" @bind-value="_gameResult.Score" />
<button class="btn-primary" @onclick="SaveGameResult">Save</button>
</div>
</div>
</div>
</div>
}
}
</Authorized>
<NotAuthorized>
인증 안됨(로그인 안됨)
</NotAuthorized>
</AuthorizeView>
@code {
List<GameResult> _gameResultList;
GameResult _gameResult;
bool _showPopup = false;
protected override async Task OnInitializedAsync()
{
//db 에서 데이터 긁어와서 _gameResults 에 넣어줌
await readGameResultList();
}
public async Task<List<GameResult>> readGameResultList()
{
//db 에서 데이터 긁어와서 _gameResultList 에 넣어줌
_gameResultList = await RankingService.GetGameResultAsync();
return _gameResultList;
}
public void AddGameResult()
{
_showPopup = true;
//Add new gameresult to
_gameResult = new GameResult()
{
Id = 0
};
}
//ClosePopup
void ClosePopup()
{
_showPopup = false;
}
void EditGameResult(GameResult gameResult)
{
_showPopup = true;
_gameResult = gameResult;
}
async Task DeleteGameResult(GameResult gameResult)
{
var result = RankingService.DeleteGameResult(gameResult);
await readGameResultList();
}
async Task SaveGameResult()
{
//새로 만드는 상태
if(_gameResult.Id==0)
{
//save to db
_gameResult.Date = DateTime.Now;
var result = RankingService.AddGameResult(_gameResult);
//close popup
ClosePopup();
}
else
{
//수정하고 있는 상태
var result = RankingService.UpdateGameResult(_gameResult);
}
await readGameResultList();
_showPopup = false;
}
}
@page "/ranking"
@using RankingApp.Data.Models;
@using RankingApp.Data.Services;
@inject RankingService RankingService
<h3>Ranking</h3>
<AuthorizeView>
<Authorized>
@if (_gameResultLIst == null)
{
<p>Loading...</p>
}else{
<table class="table">
<thead>
<tr>
<th>UserName</th>
<th>Score</th>
<th>Date</th>
</tr>
</thead>
<tbody>
@foreach (var gameResult in _gameResultLIst)
{
<tr>
<td>@gameResult.UserName</td>
<td>@gameResult.Score</td>
<td>@gameResult.Date</td>
</tr>
}
</tbody>
</table>
<p>
<!-- make button to add gameresult -->
<button class="btn btn-primary" @onclick="AddGameResult">
Add
</button>
</p>
@if(_showPopup)
{
//dialog to show gameresult
<div class="modal" style="display:block" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header"></div>
<h3 class="modal-title">Add/Update Game Result</h3>
<button type="button" class="close" @onclick="ClosePopup">
<span aria-hidden="true">X</span>
</button>
<div class="modal-body">
<label for="UserName"> UserName</label>
<input class="form-control" type="text" placeholder="UserName" @bind-value="_gameResult.UserName" />
<label for="Score"> Score</label>
<input class="form-control" type="text" placeholder="Score" @bind-value="_gameResult.Score" />
<button class="btn-primary" @onclick="SaveGameResult">Save</button>
</div>
</div>
</div>
</div>
}
}
</Authorized>
<NotAuthorized>
인증 안됨(로그인 안됨)
</NotAuthorized>
</AuthorizeView>
@code {
List<GameResult> _gameResultLIst;
GameResult _gameResult;
bool _showPopup = false;
protected override async Task OnInitializedAsync()
{
//db 에서 데이터 긁어와서 _gameResults 에 넣어줌
await readGameResults();
}
public async Task<List<GameResult>> readGameResults()
{
//db 에서 데이터 긁어와서 _gameResults 에 넣어줌
_gameResultLIst = await RankingService.GetGameResultAsync();
return _gameResultLIst;
}
public void AddGameResult()
{
_showPopup = true;
//Add new gameresult to
_gameResult = new GameResult()
{
Id = 0
};
}
//ClosePopup
void ClosePopup()
{
_showPopup = false;
}
async Task SaveGameResult()
{
//새로 만드는 상태
if(_gameResult.Id==0)
{
//save to db
_gameResult.Date = DateTime.Now;
var result = RankingService.AddGameResult(_gameResult);
//close popup
ClosePopup();
}
else
{
//수정하고 있는 상태
}
_gameResultLIst = await readGameResults();
}
}
RankingService.cs
using RankingApp.Data.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace RankingApp.Data.Services
{
public class RankingService
{
ApplicationDbContext _context;
public RankingService(ApplicationDbContext context)
{
_context = context;
}
public Task<List<GameResult>> GetGameResultAsync()
{
//DB 에서 GameResult 테이블의 모든 데이터를 가져온다
List<GameResult> results = _context.GameResult.ToList();
return Task.FromResult(results);
}
public Task<GameResult> AddGameResult(GameResult gameResult)
{
_context.GameResult.Add(gameResult);
_context.SaveChanges(); //db 에 실제 저장이 됨
return Task.FromResult(gameResult);
}
}
}
AddGameResult() 함수가 추가 되었고
_context.GameResult.Add(gameResult);
_context.SaveChanges(); //db 에 실제 저장이 됨
이 코드가 실제 DB 에 저장하는 부분이 된다
이후 결과는 Task 로 리턴해주고 결과를 담을때는 FromResult 로 결과를 담아줘서 리턴하면 await 으로 결과를 기다 릴 수 있다
public async Task<List<GameResult>> readGameResults()
{
//db 에서 데이터 긁어와서 _gameResults 에 넣어줌
_gameResultLIst = await RankingService.GetGameResultAsync();
return _gameResultLIst;
}
PM> add-migration anyName 으로 DB를 마이그레이션 할때 DefaultConnection 에 잇는 Database=RankingDB
RankingDB 이 이름으로 DB 가 생성되게 된다
먼저 최종 작업한 화면은 다음과 같다
여기서 Entity = EntityFramework 는 ORM 같은 것이다
기본 Blazor Server 에서 Ranking 부분을 추가 한것이도 Fetch data 와는 다르게 데이터는 db 에서 읽어온다 그래서 미리 DB 에 데이터를 넣어 놓어 놓는 것으로 시작한다
오른쪽 상단 Register 를 눌러 가입을 하나 하면 dbo.AspNetUsers 에 추저가 하나 추가 된다
그런 다음 EmailConfirmed 를 수동으로 true 로 변경한다 (이 부분은 이메일 확인 부분이다)
유저는 기본으로 제공된느 db 를 사용한다
GameResult.cs 를 Modes 폴더를 하나 만들어추가한다(Services 로 마찬가지로 추가한다)
using System;
namespace RankingApp.Data.Models
{
public class GameResult
{
public int Id { get; set; }
public int UserID { get; set; }
public string UserName { get; set; }
public int Score { get; set; }
public DateTime Date { get; set; }
}
}
원래는 GameResult 테이블을 하나 추가해 다음 처럼 데이터를 추가해야 하지만
이렇게 하지 않고 Entity 를 활용해 자동으로 테이블을 등록하게한다 => db에 대한 버전 관리도 되고 .cs 와 코드적으로 자동 연동 됨으로 이것이 편하다
ApplicationDbContext.cs 가 db 와 .cs 같에 연결해주는 핵심적인 부분이다
먼저 IdentityDbContext 를 상속받는다
DbSet<GameResult> 이 부분이 DB와 코드의 연결 부분이다
IdentityDbContext: Base class for the Entity Framework database context used for identity.
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Migrations;
using RankingApp.Data.Models;
using System;
using System.Collections.Generic;
using System.Text;
namespace RankingApp.Data
{
public class ApplicationDbContext : IdentityDbContext
{
//DbSet 으로 추가해줘야지 GameReesult.cs 에서 GameResult 내용을 DB에 저장할 수 있다. ORM
//즉 서버를띄울때 자동으로 DB를 갱신하여 띄워주게 된다
//PM> add-migration RankingService 명령어르 NeGet 콘솔에서 실행하여 DB를 갱신해준다
//PM> update-database 명령어르 NeGet 콘솔에서 실행하여 DB를 갱신해준다
public DbSet<GameResult> GameResult { get; set; }
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
}
이렇게 추가해주고
Startup.cs 에서
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
이 부분을 통해 DB 로 연결하게 된다 (Identity 를 사용하기 때문에 이 부분이 자동으로 등록 되어 있긴 하지만 안되어 있으면 추가해준다)
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace RankingApp.Data.Migrations
{
public partial class RankingService : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "GameResult",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
UserID = table.Column<int>(nullable: false),
UserName = table.Column<string>(nullable: true),
Score = table.Column<int>(nullable: false),
Date = table.Column<DateTime>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_GameResult", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "GameResult");
}
}
}
즉 GameResult 를 알아서 해석하여 테이블을 만드는 코드가 된다
Up 일때는 들어가고 Down 일때는 Drop 하게된다 (버전 관리 개념)
이렇게 하지 않으면 DB 에서 Field 를 변경하거나 코드에서 Field 명등 수정 사항이 안맞는 상황들이 발생 할수 있기 때문에 번거로운 작업이 계속 될 수 있는데 이것을 코드수준에서 DB로 맞춰만들어주기 때문에 작업의 효율을 높여준다
이에 더해 버전 관리 또한 가능해지게 된다
데이터는 우선 수동으로 다음처럼 추가한다
데이터를 읽어오는 코드를 먼저 작업하자
그러기 위해서 ApplicationDbContext.cs 에서 다음 처럼
DbSet 을 통해서 DB 와 연결된 부분을 읽어와야 하는데 이미 위에서 다음과 같은 코드를 통해 연결을 해놨으니
public DbSet<GameResult> GameResult { get; set; }
RankingService.cs 서비스를 만들어 데이터를 가져올 수 있는 코드를 만들어 준면 된다
using RankingApp.Data.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace RankingApp.Data.Services
{
public class RankingService
{
ApplicationDbContext _context;
public RankingService(ApplicationDbContext context)
{
_context = context;
}
public Task<List<GameResult>> GetGameResultAsync()
{
//DB 에서 GameResult 테이블의 모든 데이터를 가져온다
List<GameResult> results = _context.GameResult.ToList();
return Task.FromResult(results);
}
}
}
이 코드를 통해 db 에 있는 GameResult 테이블 내용을 읽어올 수 있게 된다
그리고 RankingService 를 사용하기 위해서 Startup.cs 에 AddScoped 로 RankigService 를 추가해준다