Consuming REST Web APIs
1. 프로젝트 생성(MVC UI)
ASP.Net Core Web App (MVC)
2. GET Controller 생성
HttpClient 적용
.Net에서 제공하는 HttpClient Class
https://learn.microsoft.com/ko-kr/dotnet/api/system.net.http.httpclient?view=net-8.0
HTTP 요청을 보내고 URI로 식별된 리소스에서 HTTP 응답을 수신하기 위한 클래스를 제공합니다.
public class HttpClient : System.Net.Http.HttpMessageInvoker
사용 예제
// HttpClient는 매번 사용될 때마다 인스턴스화되지 않고, 애플리케이션 전체에서 한 번만 인스턴스화되어야 합니다. 참고 사항을 참조하세요. static readonly HttpClient client = new HttpClient(); static async Task Main() { // 비동기 네트워크 메서드를 try/catch 블록 내에서 호출하여 예외를 처리합니다. try { // 비동기적으로 GET 요청을 보냅니다. using HttpResponseMessage response = await client.GetAsync("http://www.contoso.com/"); // 요청이 성공적으로 완료되었는지 확인합니다. response.EnsureSuccessStatusCode(); // 응답 본문을 문자열로 읽어옵니다. string responseBody = await response.Content.ReadAsStringAsync(); // 위의 세 줄은 아래 새로운 헬퍼 메서드로 대체할 수 있습니다. // string responseBody = await client.GetStringAsync(uri); // 응답 본문을 콘솔에 출력합니다. Console.WriteLine(responseBody); } catch (HttpRequestException e) { // 예외가 발생했을 때 예외 메시지를 콘솔에 출력합니다. Console.WriteLine("\nException Caught!"); Console.WriteLine("Message :{0} ", e.Message); } }
HttpClient를 사용하려면, Program.cs 파일에 HttpClient Factory를 설정
이를 통해 효율적으로 HttpClient 인스턴스를 관리하고, 특히 성능 문제를 해결할 수 있음
Program.cs
var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllersWithViews(); // HttpClient 삽입 builder.Services.AddHttpClient(); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run();
RegionsController.cs 수정
using Microsoft.AspNetCore.Mvc; namespace NZWalksUI.Controllers { public class RegionsController : Controller { private readonly IHttpClientFactory httpClientFactory; // Http 클라이언트 팩토리를 삽입하기 위한 생성자를 생성 public RegionsController(IHttpClientFactory httpClientFactory) { this.httpClientFactory = httpClientFactory; } // Index 액션 메서드 public async Task<IActionResult> Index() { try { // Get All Regions From Web API // 새로운 Http 클라이언트가 생성 var client = httpClientFactory.CreateClient(); // Web API로 GET 요청을 보냄 var httpResponseMessage = await client.GetAsync("https://localhost:7256/api/regions"); // 요청이 성공적으로 완료되었는지 확인 httpResponseMessage.EnsureSuccessStatusCode(); // 응답 본문을 문자열로 읽어옴 var stringResponse = await httpResponseMessage.Content.ReadAsStringAsync(); // 응답 내용을 ViewBag에 저장 ViewBag.Response = stringResponse; // 응답 메시지를 반환 return Ok(httpResponseMessage); } catch (Exception ex) { // 예외를 로깅 (로그 기록) } // View를 반환 return View(); } } }
3. Index.cshtml , _Layout.cshtml 생성 및 수정
Index.cshtml
@* For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 *@ @{ } <h1 class="mt-3">Regions</h1> @if (ViewBag.Respose is not null) { <p>ViewBag.Respose</p> }
_Layout.cshtml
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"] - NZWalksUI</title> <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" /> <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" /> <link rel="stylesheet" href="~/NZWalksUI.styles.css" asp-append-version="true" /> </head> <body> <header> <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3"> <div class="container-fluid"> <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">NZWalksUI</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between"> <ul class="navbar-nav flex-grow-1"> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="Regions" asp-action="Index">Regions</a> </li> </ul> </div> </div> </nav> </header> <div class="container"> <main role="main" class="pb-3"> @RenderBody() </main> </div> <footer class="border-top footer text-muted"> <div class="container"> © 2024 - NZWalksUI - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a> </div> </footer> <script src="~/lib/jquery/dist/jquery.min.js"></script> <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script> <script src="~/js/site.js" asp-append-version="true"></script> @await RenderSectionAsync("Scripts", required: false) </body> </html>
4. RegionDto.cs 생성 및 Index.cshtml 적용
RegionDto.cs
namespace NZWalksUI.Models.DTO { public class RegionDto { public Guid Id { get; set; } public string Code { get; set; } public string Name { get; set; } public string? RegionImageUrl { get; set; } // Nullable } }
Index.cshtml
@model IEnumerable<NZWalksUI.Models.DTO.RegionDto> <!-- 뷰 모델을 IEnumerable<RegionDto>로 설정 --> @* MVC를 빈 프로젝트에 대해 활성화하는 방법에 대한 추가 정보는 https://go.microsoft.com/fwlink/?LinkID=397860를 참조 *@ @{} <!-- 블록 코드, 현재 비어 있음 --> <h1 class="mt-3">Regions</h1> <!-- 페이지 제목 --> <table> <thead> <tr> <th>Id</th> <!-- Id 열 --> <th>Code</th> <!-- Code 열 --> <th>Name</th> <!-- Name 열 --> </tr> </thead> <tbody> @foreach (var walk in Model) <!-- Model을 반복하여 각 walk에 대해 테이블 행 생성 --> { <tr> <th>@walk.Id</th> <!-- 현재 walk의 Id 표시 --> <th>@walk.Code</th> <!-- 현재 walk의 Code 표시 --> <th>@walk.Name</th> <!-- 현재 walk의 Name 표시 --> </tr> } </tbody> </table>
5. POST Method 생성
Index.cshtml 수정
@model IEnumerable<NZWalksUI.Models.DTO.RegionDto> @* For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 *@ @{ } <h1 class="mt-3">Regions</h1> <div class="d-flex justify-content-end"> <a class="btn btn-secondary" asp-controller="Regions" asp-action="Add">Add Region</a> </div> <table class="table-bordered"> <thead> <tr> <th>Id</th> <th>Code</th> <th>Name</th> </tr> </thead> <tbody> @foreach (var walk in Model) { <tr> <td>@walk.Id</td> <td>@walk.Code</td> <td>@walk.Name</td> </tr> } </tbody> </table>
AddRegionViewModel.cs 생성
namespace NZWalksUI.Models { public class AddRegionViewModel { public string Code { get; set; } public string Name { get; set; } public string RegionImageUrl { get; set; } } }
RegionsController.cs 수정
using Microsoft.AspNetCore.Mvc; using NZWalksUI.Models; using NZWalksUI.Models.DTO; using System.Net.Http; using System.Text; using System.Text.Json; namespace NZWalksUI.Controllers { public class RegionsController : Controller { private readonly IHttpClientFactory httpClientFactory; // Http 클라이언트 팩토리를 삽입하기 위한 생성자를 생성 public RegionsController(IHttpClientFactory httpClientFactory) { this.httpClientFactory = httpClientFactory; } [HttpGet] // Index 액션 메서드 public async Task<IActionResult> Index() { List<RegionDto> response = new List<RegionDto>(); try { // Get All Regions From Web API // 새로운 Http 클라이언트가 생성 var client = httpClientFactory.CreateClient(); // Web API로 GET 요청을 보냄 var httpResponseMessage = await client.GetAsync("https://localhost:7256/api/regions"); // 요청이 성공적으로 완료되었는지 확인 httpResponseMessage.EnsureSuccessStatusCode(); // 응답 본문을 JSON으로 읽어와 리스트에 추가 response.AddRange(await httpResponseMessage.Content.ReadFromJsonAsync<IEnumerable<RegionDto>>()); } catch (HttpRequestException e) { // 예외 발생 시 예외 메시지를 로그로 기록 Console.WriteLine($"Request error: {e.Message}"); } catch (Exception ex) { // 기타 예외 발생 시 예외 메시지를 로그로 기록 Console.WriteLine($"An error occurred: {ex.Message}"); } // View를 반환 return View(response); } [HttpGet] public IActionResult Add() { return View(); // Add View 반환 } [HttpPost] public async Task<IActionResult> Add(AddRegionViewModel model) { var client = httpClientFactory.CreateClient(); var httpRequestMessage = new HttpRequestMessage() { Method = HttpMethod.Post, RequestUri = new Uri("https://localhost:7256/api/regions"), Content = new StringContent(JsonSerializer.Serialize(model), Encoding.UTF8, "application/json") // JSON 요청 본문 설정 }; var httpResponseMessage = await client.SendAsync(httpRequestMessage); httpResponseMessage.EnsureSuccessStatusCode(); // 요청이 성공적으로 완료되었는지 확인 var response = await httpResponseMessage.Content.ReadFromJsonAsync<RegionDto>(); if (response is not null) { return RedirectToAction("Index", "Regions"); // Index 액션으로 리디렉션 } return View(); // View 반환 } } }
Add.cshtml
@model NZWalksUI.Models.AddRegionViewModel; @* *@ @{ } <h1 class="mt-3">Add Region</h1> <form method="post"> <div class="mt-3"> <label class="form-label">Code</label> <input type="text" class="form-control" asp-for="Code" /> </div> <div class="mt-3"> <label class="form-label">Name</label> <input type="text" class="form-control" asp-for="Name" /> </div> <div class="mt-3"> <label class="form-label">Image URL</label> <input type="text" class="form-control" asp-for="RegionImageUrl" /> </div> <div class="mt-3"> <button type="submit" class="btn btn-primary">Save</button> </div> </form>
6. 단일 편집 기능
RegionsController.cs 수정
using Microsoft.AspNetCore.Mvc; using NZWalksUI.Models; using NZWalksUI.Models.DTO; using System.Net.Http; using System.Text; using System.Text.Json; namespace NZWalksUI.Controllers { public class RegionsController : Controller { private readonly IHttpClientFactory httpClientFactory; // Http 클라이언트 팩토리를 삽입하기 위한 생성자를 생성 public RegionsController(IHttpClientFactory httpClientFactory) { this.httpClientFactory = httpClientFactory; } [HttpGet] // Index 액션 메서드 public async Task<IActionResult> Index() { List<RegionDto> response = new List<RegionDto>(); try { // Get All Regions From Web API // 새로운 Http 클라이언트가 생성 var client = httpClientFactory.CreateClient(); // Web API로 GET 요청을 보냄 var httpResponseMessage = await client.GetAsync("https://localhost:7256/api/regions"); // 요청이 성공적으로 완료되었는지 확인 httpResponseMessage.EnsureSuccessStatusCode(); // // 응답 본문을 문자열로 읽어옴 // var stringResponse = await httpResponseMessage.Content.ReadAsStringAsync(); response.AddRange(await httpResponseMessage.Content.ReadFromJsonAsync<IEnumerable<RegionDto>>()); } catch (HttpRequestException e) { // 예외 발생 시 예외 메시지를 로그로 기록 Console.WriteLine($"Request error: {e.Message}"); } catch (Exception ex) { // 기타 예외 발생 시 예외 메시지를 로그로 기록 Console.WriteLine($"An error occurred: {ex.Message}"); } // View를 반환 return View(response); } [HttpGet] public IActionResult Add() { return View(); } [HttpPost] public async Task<IActionResult> Add(AddRegionViewModel model) { var client = httpClientFactory.CreateClient(); var httpRequestMessage = new HttpRequestMessage() { Method = HttpMethod.Post, RequestUri = new Uri("https://localhost:7256/api/regions"), Content = new StringContent(JsonSerializer.Serialize(model), Encoding.UTF8, "application/json") }; var httpResponseMessage = await client.SendAsync(httpRequestMessage); httpResponseMessage.EnsureSuccessStatusCode(); var respose = await httpResponseMessage.Content.ReadFromJsonAsync<RegionDto>(); if (respose is not null) { return RedirectToAction("Index", "Regions"); } return View(); } [HttpGet] public async Task<IActionResult> Edit(Guid id) { ViewBag.Id = id; return View(); } } }
Edite View 생성
Edit.cshtml
@* For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 *@ @{ } <h1 class="mt-3">Edit Region</h1> <p> @ViewBag.Id </p>
Index.cshtml 수정
@model IEnumerable<NZWalksUI.Models.DTO.RegionDto> @* For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 *@ @{ } <h1 class="mt-3">Regions</h1> <div class="d-flex justify-content-end"> <a class="btn btn-secondary" asp-controller="Regions" asp-action="Add">Add Region</a> </div> <table class="table-bordered"> <thead> <tr> <th>Id</th> <th>Code</th> <th>Name</th> <th> </th> </tr> </thead> <tbody> @foreach (var walk in Model) { <tr> <td>@walk.Id</td> <td>@walk.Code</td> <td>@walk.Name</td> <td> <a asp-controller="Regions" asp-action="Edit" asp-route-id="@walk.Id" class="btn btn-light">Eidt</a> </td> </tr> } </tbody> </table>
Error.cshtml 수정
@model NZWalksUI.Models.DTO.RegionDto @{ } <h1 class="mt-3">Edit Region</h1> @if (Model is not null) { <form method="post"> <div class="mt-3"> <label class="form-label">Id</label> <input type="text" class="form-control" asp-for="Id" readonly/> </div> <div class="mt-3"> <label class="form-label">Code</label> <input type="text" class="form-control" asp-for="Code" /> </div> <div class="mt-3"> <label class="form-label">Name</label> <input type="text" class="form-control" asp-for="Name" /> </div> <div class="mt-3"> <label class="form-label">Image URL</label> <input type="text" class="form-control" asp-for="RegionImageUrl" /> </div> <div class="mt-3"> <button type="submit" class="btn btn-primary">Save</button> </div> </form> }
RegionsController.cs 수정
using Microsoft.AspNetCore.Mvc; using NZWalksUI.Models; using NZWalksUI.Models.DTO; using System.Net.Http; using System.Reflection; using System.Text; using System.Text.Json; using static System.Net.WebRequestMethods; namespace NZWalksUI.Controllers { public class RegionsController : Controller { private readonly IHttpClientFactory httpClientFactory; // Http 클라이언트 팩토리를 삽입하기 위한 생성자를 생성 public RegionsController(IHttpClientFactory httpClientFactory) { this.httpClientFactory = httpClientFactory; } [HttpGet] // Index 액션 메서드 public async Task<IActionResult> Index() { List<RegionDto> response = new List<RegionDto>(); try { // Get All Regions From Web API // 새로운 Http 클라이언트가 생성 var client = httpClientFactory.CreateClient(); // Web API로 GET 요청을 보냄 var httpResponseMessage = await client.GetAsync("https://localhost:7256/api/regions"); // 요청이 성공적으로 완료되었는지 확인 httpResponseMessage.EnsureSuccessStatusCode(); // // 응답 본문을 문자열로 읽어옴 // var stringResponse = await httpResponseMessage.Content.ReadAsStringAsync(); response.AddRange(await httpResponseMessage.Content.ReadFromJsonAsync<IEnumerable<RegionDto>>()); } catch (HttpRequestException e) { // 예외 발생 시 예외 메시지를 로그로 기록 Console.WriteLine($"Request error: {e.Message}"); } catch (Exception ex) { // 기타 예외 발생 시 예외 메시지를 로그로 기록 Console.WriteLine($"An error occurred: {ex.Message}"); } // View를 반환 return View(response); } [HttpGet] public IActionResult Add() { return View(); } [HttpPost] public async Task<IActionResult> Add(AddRegionViewModel model) { var client = httpClientFactory.CreateClient(); var httpRequestMessage = new HttpRequestMessage() { Method = HttpMethod.Post, RequestUri = new Uri("https://localhost:7256/api/regions"), Content = new StringContent(JsonSerializer.Serialize(model), Encoding.UTF8, "application/json") }; var httpResponseMessage = await client.SendAsync(httpRequestMessage); httpResponseMessage.EnsureSuccessStatusCode(); var respose = await httpResponseMessage.Content.ReadFromJsonAsync<RegionDto>(); if (respose is not null) { return RedirectToAction("Index", "Regions"); } return View(); } [HttpGet] public async Task<IActionResult> Edit(Guid id) { var client = httpClientFactory.CreateClient(); var response = await client.GetFromJsonAsync<RegionDto>($"https://localhost:7256/api/regions/{id.ToString()}"); if (response is not null) { return View(response); } return View(null); } [HttpPost] public async Task<IActionResult> Edit(RegionDto request) { var client = httpClientFactory.CreateClient(); var httpRequestMessage = new HttpRequestMessage() { Method = HttpMethod.Put, RequestUri = new Uri($"https://localhost:7256/api/regions/{request.Id}"), Content = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json") }; var httpResponseMessage = await client.SendAsync(httpRequestMessage); httpResponseMessage.EnsureSuccessStatusCode(); var respose = await httpResponseMessage.Content.ReadFromJsonAsync<RegionDto>(); if (respose is not null) { return RedirectToAction("Edit", "Regions"); } return View(); } } }
7. 삭제 기능
Edit.cshtml 수정
@model NZWalksUI.Models.DTO.RegionDto @{ } <h1 class="mt-3">Edit Region</h1> @if (Model is not null) { <form method="post"> <div class="mt-3"> <label class="form-label">Id</label> <input type="text" class="form-control" asp-for="Id" readonly/> </div> <div class="mt-3"> <label class="form-label">Code</label> <input type="text" class="form-control" asp-for="Code" /> </div> <div class="mt-3"> <label class="form-label">Name</label> <input type="text" class="form-control" asp-for="Name" /> </div> <div class="mt-3"> <label class="form-label">Image URL</label> <input type="text" class="form-control" asp-for="RegionImageUrl" /> </div> <div class="mt-3 d-flex justify-content-between"> <button type="submit" class="btn btn-primary">Save</button> <button type="submit" asp-controller="Regions" asp-action="Delete" class="btn btn-danger">Delete</button> </div> </form> }
RegionsController.cs 수정
using Microsoft.AspNetCore.Mvc; using NZWalksUI.Models; using NZWalksUI.Models.DTO; using System.Text; using System.Text.Json; namespace NZWalksUI.Controllers { public class RegionsController : Controller { private readonly IHttpClientFactory httpClientFactory; // Http 클라이언트 팩토리를 삽입하기 위한 생성자를 생성 public RegionsController(IHttpClientFactory httpClientFactory) { this.httpClientFactory = httpClientFactory; } [HttpGet] // Index 액션 메서드 public async Task<IActionResult> Index() { List<RegionDto> response = new List<RegionDto>(); try { // Get All Regions From Web API // 새로운 Http 클라이언트가 생성 var client = httpClientFactory.CreateClient(); // Web API로 GET 요청을 보냄 var httpResponseMessage = await client.GetAsync("https://localhost:7256/api/regions"); // 요청이 성공적으로 완료되었는지 확인 httpResponseMessage.EnsureSuccessStatusCode(); // // 응답 본문을 문자열로 읽어옴 // var stringResponse = await httpResponseMessage.Content.ReadAsStringAsync(); response.AddRange(await httpResponseMessage.Content.ReadFromJsonAsync<IEnumerable<RegionDto>>()); } catch (HttpRequestException e) { // 예외 발생 시 예외 메시지를 로그로 기록 Console.WriteLine($"Request error: {e.Message}"); } catch (Exception ex) { // 기타 예외 발생 시 예외 메시지를 로그로 기록 Console.WriteLine($"An error occurred: {ex.Message}"); } // View를 반환 return View(response); } [HttpGet] public IActionResult Add() { return View(); } [HttpPost] public async Task<IActionResult> Add(AddRegionViewModel model) { var client = httpClientFactory.CreateClient(); var httpRequestMessage = new HttpRequestMessage() { Method = HttpMethod.Post, RequestUri = new Uri("https://localhost:7256/api/regions"), Content = new StringContent(JsonSerializer.Serialize(model), Encoding.UTF8, "application/json") }; var httpResponseMessage = await client.SendAsync(httpRequestMessage); httpResponseMessage.EnsureSuccessStatusCode(); var respose = await httpResponseMessage.Content.ReadFromJsonAsync<RegionDto>(); if (respose is not null) { return RedirectToAction("Index", "Regions"); } return View(); } [HttpGet] public async Task<IActionResult> Edit(Guid id) { var client = httpClientFactory.CreateClient(); var response = await client.GetFromJsonAsync<RegionDto>($"https://localhost:7256/api/regions/{id.ToString()}"); if (response is not null) { return View(response); } return View(null); } [HttpPost] public async Task<IActionResult> Edit(RegionDto request) { var client = httpClientFactory.CreateClient(); var httpRequestMessage = new HttpRequestMessage() { Method = HttpMethod.Put, RequestUri = new Uri($"https://localhost:7256/api/regions/{request.Id}"), Content = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json") }; var httpResponseMessage = await client.SendAsync(httpRequestMessage); httpResponseMessage.EnsureSuccessStatusCode(); var respose = await httpResponseMessage.Content.ReadFromJsonAsync<RegionDto>(); if (respose is not null) { return RedirectToAction("Edit", "Regions"); } return View(); } [HttpPost] public async Task<IActionResult> Delete(RegionDto request) { try { var client = httpClientFactory.CreateClient(); var httpResponseMessage = await client.DeleteAsync($"https://localhost:7256/api/regions/{request.Id}"); httpResponseMessage.EnsureSuccessStatusCode(); return RedirectToAction("Index", "Regions"); } catch (Exception ex) { } return View("Edit"); } } }