공부/인프런 - Rookiss

Part 6-7-4. WebAPI와 REST 서비스 : Blazor와 WebApi 연동

셩잇님 2024. 10. 16. 12:57
반응형

 

 

🌀 WebAPI와 REST 서비스

 

 지난 시간에는 CRUD 중 R을 제외한 Create, Update, Delete에 대해서 구현하고 학습해보았다.이 후 마찬가지로 Postman 툴을 사용하여 구현한 스크립트가 정상적으로 작동하는지 확인하였다. 이번 시간에는 일전에 사용하였던 RankingApp Blazor 프로젝트와 WebApi 프로젝트를 연동시켜 보는 시간을 가져보도록 한다.

 


 

🐊 Blazor와 WebApi 연동

 

 이번 시간에는 Blazor 프로젝트와 WebApi를 연동할 것이다. 먼저 RankingApp에 산하에 있는 Models 폴더와 gameResult를 삭제해주도록 한다. 왜냐하면 일전의 WebApi 프로젝트를 진행할 때 GameResult를 공용 데이터인 SharedData 프로젝트에서 사용하기로 했기 때문이다. 따라서 RankingApp에 산하에 있는 Models 폴더와 gameResult를 삭제해주도록 하자.

 

 이 후,  WebApi 프로젝트와 마찬가지로 RankingApp 프로젝트를 클락하여 추가 - 프로젝트 참조 - SharedData 프로젝트를 선택하여 SharedData 프로젝트 밑에 있는 GameResult를 참조할 수 있도록 설정하자.

 

프로젝트 참조 설정

 

 이 후 RankingService 스크립트로 돌아갈 경우, 기존에 삭제한 Models의 Gameresult를 참조하고 있기 때문에 이를 지워주어야 한다. 해당 내용을 지워주고 using SharedData.Models를 작성하여 참조할 수 있도록 설정해주자.

 

 마찬가지로 Ranking.razor 스크립트도 @using RankingApp.Data.Models를 지워주고 using SharedData.Models 작성하여 참조할 수 있도록 설정해주자. 마지막으로 ApplicationDbContext 스크립트도 또한 @using RankingApp.Data.Models을 지워준다. 해당 스크립트에서는 Dbset을 통해 GameResults를 설정하고 있는데 우리는 이제 WebApi를 사용할 것이므로 해당 내용 역시 삭제하여 준다.

 


 

 이 후 RankingService 스크립트로 돌아가 그동안 사용하였던 AddGameResult, GetGameResultsAsync, UpdateGameResult, DeleteGameResult 함수의 내용을 모두 지워준다. 왜냐하면 이제 WebApi랑 연동을 할 것 이기 때문이다. 

 

 여태까지는 서버 사이드 블레이저를 통해 클라이언트와 서버를 동시에 운영하고 있는 [C ↔ S] 상태였다. 하지만 이제 서버만 담당하고 있는 [C ↔ S] ↔ [S(WebApi] 상태로 변경하여 처리할 것이다. 이 후 [S(WebApi)]가 DB를 물고 있는 상태로 변경되는 것이다. 따라서 [C ↔ S] ↔ [S(WebApi)] - DB 이와 같은 상태로 변경되는 것이다.

 

 그렇다면 DB에 직접 접근하여서 잘 작업하고 있는데 왜 API 서버와 통신을 해야하는 걸까? 하지만 이런 구조가 유용할 수 있는 상황이 존재하는데 이는 이미 완성된 API 서버가 있다면 코드를 다시 작성하기보다는 양쪽으로 이를 분리하여 각자의 역할을 수행할 수 있도록 처리하는 것이다.

 

 예를 들어 롤 전적을 관리하는 fow.kr이나, op.gg와 같은 사이트들은 실제 데이터는 라이엇에서 관리하는 정보를 기반으로 이루어 질 것이다. 이 때에 라이엇에서 관리하는 데이터를 우리의 UI로 표시하고 싶을 경우 이런 상황이 생길 수 있는 것이다. 

 

 결국 이로인해 ApplicationDbContext는 더 이상 사용하지 않지만 여전히 우리는 Web Api와 통신을 해야하기 때문에 이제부터는 Http Client를 사용해줄 것이다. Http Client를 사용하기 위해 using System.Net.Http를 사용한다. 이 후 구 - Startup, 현 - Program 스크립트로 돌아가 아래와 같이 AddScoped 랭킹 서비스 내용을 지우고 AddHttpClient를 등록해준다.

 

using Microsoft.AspNetCore.Components;
...

namespace RankingApp
{
	public class Program
	{
		public static void Main(string[] args)
		{
			var builder = WebApplication.CreateBuilder(args);

			...
            
            // HttpClient 서비스를 등록한다.
			builder.Services.AddHttpClient<RankingService>(c =>
			{
				c.BaseAddress = new Uri("https://localhost:44360");
			});

			var app = builder.Build();

			...
		}
	}
}

 

 이 떄의 localhost 옆의 포트 번호를 설정해주어야 하는데 이를 확인하기 위해서는 WebApi 프로젝트 내 Properties 폴더 산하에 있는 launchSetting.json 파일을 확인해주면 된다. 해당 json 파일에서 sslPort 정보를 가져와 포트 번호를 설정해주도록 하자. 이제 Create 부터 하나씩 만들어보도록 하자.

 

using Newtonsoft.Json;
using SharedData.Models;
using System.Text;

namespace RankingApp.Data.Services
{
    public class RankingService
    {
        HttpClient _httpClient;

        public RankingService(HttpClient client)
        {
            _httpClient = client;
        }

        // CREATE
        public async Task<GameResult> AddGameResult(GameResult gameResult)
        {
            // 게임 결과를 JSON 문자열로 변환
            string jsonStr = JsonConvert.SerializeObject(gameResult);
            var content = new StringContent(jsonStr, Encoding.UTF8, "application/json");
            var result = await _httpClient.PostAsync("api/ranking", content);

            if (result.IsSuccessStatusCode == false)
            {
                throw new Exception("AddGameResult Failed");
            }

            // 응답 내용을 문자열로 읽는다.
            var resultContent = await result.Content.ReadAsStringAsync();

            // 응답 문자열을 GameResult 객체로 변환
            GameResult resGameResult = JsonConvert.DeserializeObject<GameResult>(resultContent);   
            return resGameResult;
        }
    }
}

 

 먼저 HttpClient를 _httpClient;로 설정하고, 랭킹서비스 생성자 설정 시 이를 할당해주도록 하자. 만약 HttpClient 사용 시 문제가 생긴다면 using Newtonsoft.Json;를 작성하여 이를 참조하도록 설정해주자.

 

 Create 함수는 먼저 JsonConvert를 통해 gameResult를 JOSN 문자열로 변환시킨다. 이 때 받아온 jsonStr을 Encoding.UTF8 형식으로 변경하면서 동시에 "application/json" 형태로 처리한다. 마찬가지로 이 때에도 Encodeing이 에러가 날 경우 using System.Text;를 활성화 해주도록 한다. 이 후 기존의 WebApi에서 Create는 Post를 사용한 것처럼 result를 _httpclient의 'Post'Async를 통해 처리한다.

 

 만약 result의 결과 값이 실패일 경우, Exception을 통해 에러 결과를 처리하고 그렇지 않을 경우 ReadAsStringAsync를 통해 응답 내용을 문자열로 읽어준다. 이 후 해당 문자열을 DeserializeObject를 통해 GameResult 형태로 변경한다. 다음으로는 Read를 만들어주도록 하자.

 

using Newtonsoft.Json;
using SharedData.Models;
using System.Text;

namespace RankingApp.Data.Services
{
    public class RankingService
    {
        HttpClient _httpClient;

        public RankingService(HttpClient client)
        {
            _httpClient = client;
        }

        // READ
        public async Task<List<GameResult>> GetGameResultsAsync()
        {
            var result = await _httpClient.GetAsync("api/ranking");

            var resultContent = await result.Content.ReadAsStringAsync();
            List<GameResult> gameResults = JsonConvert.DeserializeObject<List<GameResult>>(resultContent);
            return gameResults;
        }
    }
}

 

 Read 역시 모든 데이터를 다 읽는 것이기 때문에 일전에 WebApi에서 사용했던 Get 메서드를 통해 이를 처리한다. 이 후 응답 내용을 문자열로 읽어와서 DeserializeObject를 통해 List<GameResult>로 반환하여 처리해준다. 다음으로는 Update를 만들어주도록 하자.

 

using Newtonsoft.Json;
using SharedData.Models;
using System.Text;

namespace RankingApp.Data.Services
{
    public class RankingService
    {
        HttpClient _httpClient;

        public RankingService(HttpClient client)
        {
            _httpClient = client;
        }

        // UPDATE
        public async Task<bool> UpdateGameResult(GameResult gameResult)
        {
            // 게임 결과를 JSON 문자열로 변환
            string jsonStr = JsonConvert.SerializeObject(gameResult);
            var content = new StringContent(jsonStr, Encoding.UTF8, "application/json");
            var result = await _httpClient.PutAsync("api/ranking", content);

            if (result.IsSuccessStatusCode == false)
            {
                throw new Exception("UpdateGameResult Failed");
            }

            return true;
        }
    }
}

 

 다음으로는 Update이다. Update는 Post 메서드를 사용했기 때문에 PutAsync를 이용해주고, 성공적으로 코드를 받아오지 못할 경우에는 Exception을 통해 예외처리를 하고, 해당 로직을 지나칠 경우 수정이 완료되었기 때문에 true 값을 반환해준다. 이 경우 특별히 업데이트 한 후에 필요한 정보가 없으므로 따로 끝나도 문제가 되지 않는다. 마지막으로 delete를 만들어주도록 하자.

 

using Newtonsoft.Json;
using SharedData.Models;
using System.Text;

namespace RankingApp.Data.Services
{
    public class RankingService
    {
        HttpClient _httpClient;

        public RankingService(HttpClient client)
        {
            _httpClient = client;
        }

        // DELETE
        public async Task<bool> DeleteGameResult(GameResult gameResult)
        {
            var result = await _httpClient.DeleteAsync($"api/ranking/{gameResult.Id}");

            if (result.IsSuccessStatusCode == false)
            {
                throw new Exception("DeleteGameResult Failed");
            }

            return true;
        }
    }
}

 

 Delete는 Id 값을 비교하여 확인 후에 정보를 삭제하기 때문에 인자값으로 받은 gameResult의 id를 Delete 메서드를 통해 보내주어야 한다. Update와 마찬가지로 성공적으로 코드를 받아오지 못할 경우에는 Exception을 통해 예외처리를 하고, 해당 로직을 지나칠 경우 삭제가 완료되었기 때문에 true 값을 반환해준다. 이 경우 특별히 삭제한 후에 필요한 정보가 없으므로 따로 끝나도 문제가 되지 않는다.

 

 마지막으로 Ranking.razor로 이동하여 SaveGameResult 함수에 await를 붙여 수정해준 뒤, 컴파일을 진행해보자.

 

@page "/ranking"
@using SharedData.Models
@using RankingApp.Data.Services

@inject RankingService RankingService

...

@code {
    List<GameResult> _gameResults;

    async Task SaveGameResult()
    {
        if (_gameResult.Id == 0)
		{
            _gameResult.Date = DateTime.Now;
			var result = await RankingService.AddGameResult(_gameResult);
		}
		else
		{
			var result = await RankingService.UpdateGameResult(_gameResult);
		}

        _showPopup = false;
        // 데이터 갱신
        _gameResults = await RankingService.GetGameResultsAsync();
    }
}

 

 이 후 RankingApp 프로젝트를 우클릭하여 시작 프로젝트 구성을 선택한 뒤 '여러 개의 시작 프로젝트'를 설정한 뒤 RankingApp과 WebApi를 시작으로 설정한 뒤 테스트를 진행해보자.

 

 

 Ranking 메뉴로 이동 할 경우 테스트로 만들어 준 데이터가 정상적으로 뜨는 것을 볼 수 있으며, 이 후 Add, Edit, Delete 기능들을 다 눌러보며 정상적으로 작동하는지 확인해보자.

 

 

반응형