Blazor HTTP 클라이언트 완벽 가이드 — HttpClient로 REST API 호출하기

HTTP 클라이언트
📚
핵심 요약

HTTP 클라이언트

HttpClient를 주입받아 외부 REST API를 호출하고 JSON 데이터를 처리합니다.

📌 학습 목표

  • Blazor 프로젝트에서 HttpClient를 의존성 주입(의존성 주입(DI))으로 등록하는 방법을 이해합니다.
  • 컴포넌트 내에서 HttpClient를 주입받아 외부 REST API에 GET·POST 요청을 비동기로 수행합니다.
  • API 응답 JSON을 C# 모델 클래스로 역직렬화하여 화면에 데이터를 바인딩합니다.
  • HttpResponseMessage를 직접 다루어 상태 코드 확인과 오류를 처리합니다.
  • Blazor WebAssembly와 Blazor Server에서 HttpClient 등록 방식이 다른 이유를 파악합니다.

📝 개념 설명

1. HttpClient란?

HttpClient는 .NET에서 HTTP 요청을 보내고 응답을 받는 클래스입니다. Blazor 앱에서는 이 클래스를 사용해 백엔드 API나 공개 REST API에 데이터를 요청하고, JSON 형태로 받아 C# 객체로 변환합니다.

 

Blazor에서 HttpClient는 싱글턴 또는 스코프드 서비스로 의존성 주입(DI) 컨테이너에 등록한 뒤, 컴포넌트에서 @inject로 주입받아 사용합니다. 직접 new HttpClient()를 생성하는 방식은 소켓 고갈(Socket Exhaustion) 문제가 발생하므로 절대 권장하지 않습니다.

2. Blazor WebAssembly vs Blazor Server — 등록 방식 차이

Blazor WebAssembly는 브라우저 안에서 실행되므로 HttpClient도 브라우저의 Fetch API를 통해 동작합니다. 따라서 CORS 정책의 영향을 받으며, Program.cs에서 BaseAddress를 지정해야 합니다.

Blazor Server는 서버 측에서 실행되므로 일반적인 ASP.NET Core의 IHttpClientFactory를 사용하거나 AddHttpClient()로 등록합니다.
Blazor WebAssembly — Program.cs 등록 예시:
// Program.cs (Blazor WebAssembly)
builder.Services.AddScoped(sp => new HttpClient
{
    BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
});
Blazor Server — Program.cs 등록 예시:
// Program.cs (Blazor Server)
builder.Services.AddHttpClient();

Named Client로 등록하면 BaseAddress를 포함해 관리하기 편리합니다:

builder.Services.AddHttpClient("MyApi", client =>
{
    client.BaseAddress = new Uri("https://api.example.com/");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
});

3. 컴포넌트에서 HttpClient 주입받기

Razor 컴포넌트에서는 @inject 지시어로 주입받습니다.

@inject HttpClient Http

code-behind 파일(.cs)에서는 [Inject] 속성을 사용합니다:

[Inject]
private HttpClient Http { get; set; } = default!;

4. JSON 역직렬화 — System.Text.Json

Blazor는 기본적으로 System.Text.Json을 사용합니다. GetFromJsonAsync<T> 메서드는 HTTP GET 요청과 JSON 역직렬화를 한 번에 처리하는 편의 메서드입니다. 이 메서드를 사용하려면 System.Net.Http.Json 네임스페이스가 필요합니다(Blazor 기본 포함).

// 모델 클래스 정의
public class Todo
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;
    public bool Completed { get; set; }
}

// GET + 역직렬화 한 번에 처리
var todo = await Http.GetFromJsonAsync<Todo>("https://jsonplaceholder.typicode.com/todos/1");

 

 

🔄 Blazor HTTP 요청 전체 흐름

 

컴포넌트
@inject HttpClient
HttpClient
메서드 호출
REST API
JSON 응답 수신
역직렬화
C# 모델 변환
UI 렌더링
StateHasChanged

 

 

⚖️ HttpClient 주요 메서드 비교

 

메서드용도반환 타입
GetFromJsonAsync<T>GET 요청 + JSON 역직렬화T?
PostAsJsonAsyncPOST 요청 + JSON 직렬화HttpResponseMessage
PutAsJsonAsyncPUT 요청 + JSON 직렬화HttpResponseMessage
DeleteAsyncDELETE 요청HttpResponseMessage
GetAsyncGET 요청 (수동 처리)HttpResponseMessage
SendAsync커스텀 HttpRequestMessage 전송HttpResponseMessage

💡 예제 & 실습

실습 1 — 공개 API에서 Todo 목록 가져오기 (GET)

JSONPlaceholder(jsonplaceholder.typicode.com)는 테스트용 무료 REST API입니다. 이 API에서 Todo 목록을 가져와 화면에 표시하는 컴포넌트를 작성합니다.

① 모델 클래스 정의
// Models/Todo.cs
public class Todo
{
    public int Id { get; set; }
    public int UserId { get; set; }
    public string Title { get; set; } = string.Empty;
    public bool Completed { get; set; }
}
② Razor 컴포넌트 작성 (Pages/TodoList.razor)
@page "/todos"
@inject HttpClient Http

<h3>Todo 목록</h3>

@if (isLoading)
{
    <p>⏳ 데이터 로딩 중...</p>
}
else if (todos is null || todos.Count == 0)
{
    <p>데이터가 없습니다.</p>
}
else
{
    <ul>
        @foreach (var todo in todos)
        {
            <li>
                @todo.Id. @todo.Title
                <span>@(todo.Completed ? "✅" : "⬜")</span>
            </li>
        }
    </ul>
}

@code {
    private List<Todo>? todos;
    private bool isLoading = true;

    protected override async Task OnInitializedAsync()
    {
        todos = await Http.GetFromJsonAsync<List<Todo>>(
            "https://jsonplaceholder.typicode.com/todos?_limit=10"
        );
        isLoading = false;
    }
}
단계별 해설:
  1. @inject HttpClient Http: 의존성 주입(DI) 컨테이너에 등록된 HttpClient를 Http라는 이름으로 주입받습니다.
  2. OnInitializedAsync: 컴포넌트가 초기화될 때 자동으로 호출되는 생명주기 메서드입니다. 비동기 데이터 로드에 적합합니다.
  3. GetFromJsonAsync<List<Todo>>: GET 요청을 보내고 응답 JSON을 List<Todo>로 역직렬화합니다.
  4. isLoading 플래그: 데이터가 로드되기 전까지 로딩 메시지를 표시해 사용자 경험을 향상시킵니다.

실습 2 — POST 요청으로 새 데이터 전송

PostAsJsonAsync를 사용해 C# 객체를 JSON으로 직렬화하여 서버에 전송하고, 응답 상태 코드를 확인합니다.

@page "/create-todo"
@inject HttpClient Http

<h3>새 Todo 추가</h3>

<div>
    <input @bind="newTitle" placeholder="할 일을 입력하세요" />
    <button @onclick="CreateTodo">추가</button>
</div>

@if (!string.IsNullOrEmpty(resultMessage))
{
    <p>@resultMessage</p>
}

@code {
    private string newTitle = string.Empty;
    private string resultMessage = string.Empty;

    private async Task CreateTodo()
    {
        if (string.IsNullOrWhiteSpace(newTitle)) return;

        var newTodo = new Todo
        {
            Title = newTitle,
            UserId = 1,
            Completed = false
        };

        var response = await Http.PostAsJsonAsync(
            "https://jsonplaceholder.typicode.com/todos",
            newTodo
        );

        if (response.IsSuccessStatusCode)
        {
            var created = await response.Content
                .ReadFromJsonAsync<Todo>();
            resultMessage = $"생성 완료! 새 Todo ID: {created?.Id}";
            newTitle = string.Empty;
        }
        else
        {
            resultMessage = $"오류 발생: {(int)response.StatusCode} {response.StatusCode}";
        }
    }
}
단계별 해설:
  1. PostAsJsonAsync: C# 객체를 자동으로 JSON으로 직렬화하여 POST 요청을 보냅니다. Content-Type 헤더도 자동으로 application/json으로 설정됩니다.
  2. response.IsSuccessStatusCode: HTTP 200~299 상태 코드이면 true를 반환합니다.
  3. ReadFromJsonAsync<T>: 응답 본문 JSON을 C# 객체로 역직렬화합니다.
  4. 문자열 보간($): 응답 상태 코드를 숫자와 이름 모두 표시합니다.

실습 3 — BaseAddress 활용 (상대 경로 사용)

Program.cs에서 BaseAddress를 설정했다면, 이후 모든 요청에서 상대 경로만 사용할 수 있어 코드가 간결해집니다.

// Program.cs — BaseAddress 설정
builder.Services.AddScoped(sp => new HttpClient
{
    BaseAddress = new Uri("https://jsonplaceholder.typicode.com/")
});

// 컴포넌트에서 상대 경로만 사용
var todos  = await Http.GetFromJsonAsync<List<Todo>>("todos");
var todo   = await Http.GetFromJsonAsync<Todo>("todos/1");
var posts  = await Http.GetFromJsonAsync<List<Post>>("posts?userId=1");

실습 4 — JSON 프로퍼티 이름 매핑 ([JsonPropertyName])

API가 반환하는 JSON 키와 C# 프로퍼티 이름이 다를 경우 [JsonPropertyName] 속성으로 명시적으로 매핑합니다.

using System.Text.Json.Serialization;

// API 응답 JSON:
// { "user_id": 1, "product_name": "노트북", "unit_price": 1200000 }

public class Product
{
    [JsonPropertyName("user_id")]
    public int UserId { get; set; }

    [JsonPropertyName("product_name")]
    public string ProductName { get; set; } = string.Empty;

    [JsonPropertyName("unit_price")]
    public decimal UnitPrice { get; set; }
}

// 사용 예시
var product = await Http.GetFromJsonAsync<Product>("api/products/1");

⚠️ 자주 틀리는 것 / 주의사항

⚠️ 1. Blazor WebAssembly에서 CORS 오류

Blazor WebAssembly는 브라우저에서 실행되므로, 요청하는 외부 API 서버가 CORS를 허용해야 합니다. 자체 ASP.NET Core 백엔드라면 AddCors()로 허용 도메인을 설정해야 합니다. 서드파티 공개 API는 대부분 CORS를 허용하지만, 그렇지 않은 경우 백엔드 프록시를 통해 우회해야 합니다.

⚠️ 2. new HttpClient() 직접 생성 금지

매 요청마다 new HttpClient()를 생성하면 TCP 소켓이 TIME_WAIT 상태로 누적되어 소켓 고갈 문제가 발생합니다. 반드시 의존성 주입(DI)로 주입받아 사용하세요.

// ❌ 잘못된 방법 — 소켓 고갈 위험
var http = new HttpClient();
var data = await http.GetFromJsonAsync<Todo>("todos/1");

// ✅ 올바른 방법 — @inject로 주입받은 인스턴스 재사용
var data = await Http.GetFromJsonAsync<Todo>("todos/1");

⚠️ 3. null 처리 누락

GetFromJsonAsync<T>는 응답이 204(No Content)이거나 역직렬화에 실패할 경우 null을 반환합니다. null 체크를 생략하면 런타임에 NullReferenceException이 발생합니다.

var todo = await Http.GetFromJsonAsync<Todo>("todos/1");
if (todo is null)
{
    Console.WriteLine("데이터를 불러오지 못했습니다.");
    return;
}
Console.WriteLine(todo.Title); // 안전하게 접근 가능

⚠️ 4. Blazor Server에서 .Result 또는 .Wait() 사용 금지

Blazor Server는 SynchronizationContext가 있어 .Result.Wait()를 사용하면 교착 상태(Deadlock)가 발생합니다. 반드시 await를 사용하세요.

// ❌ 교착 위험 (Blazor Server)
todos = Http.GetFromJsonAsync<List<Todo>>("todos").Result;

// ✅ 올바른 비동기 사용
todos = await Http.GetFromJsonAsync<List<Todo>>("todos");

⚠️ 5. BaseAddress 끝에 슬래시(/) 누락

BaseAddress를 설정할 때 끝에 /가 없으면 상대 경로 결합이 예상과 다르게 동작합니다. 반드시 /로 끝내야 합니다.

// ❌ 슬래시 누락 — "https://api.example.com" + "todos" = "https://api.example.com/todos" (우연히 동작)
//    하지만 "https://api.example.com/v1" + "todos" = "https://api.example.com/todos" (v1 경로 손실!)
new Uri("https://api.example.com/v1")  // 잘못된 예

// ✅ 슬래시 포함
new Uri("https://api.example.com/v1/")  // 올바른 예

🎯 마무리

이 글에서는 Blazor에서 HttpClient를 사용해 외부 REST API와 통신하는 전 과정을 살펴보았습니다. 의존성 주입으로 HttpClient를 등록하고, GetFromJsonAsyncPostAsJsonAsync 같은 편의 메서드를 활용하면 API 호출과 JSON 처리를 매우 간결하게 구현할 수 있습니다.

실전 활용 팁:
  • 반복되는 API 호출 로직은 서비스 클래스로 분리하여 컴포넌트를 깔끔하게 유지하세요. 예를 들어 TodoService를 만들어 HttpClient 로직을 캡슐화할 수 있습니다.
  • Blazor Server에서는 IHttpClientFactory와 Named Client를 조합하면 여러 API 엔드포인트를 독립적으로 관리할 수 있습니다.
  • API 통신 중 isLoading 플래그를 별도로 관리하면 사용자에게 진행 상태를 명확히 알릴 수 있습니다.
  • 프로덕션 환경에서는 반드시 try-catchHttpRequestExceptionJsonException을 처리하여 네트워크 오류와 파싱 오류에 대비하세요.
✅ 핵심 정리
  • HttpClient는 의존성 주입(DI)로 등록하고 @inject로 주입받아 사용한다. 직접 new HttpClient() 생성은 소켓 고갈 위험이 있어 금지.
  • Blazor WebAssembly는 BaseAddress를 지정한 스코프드(Scoped) HttpClient를 등록한다.
  • GetFromJsonAsync<T>는 GET 요청과 JSON 역직렬화를 한 번에 처리하는 편의 메서드다.
  • PostAsJsonAsync는 C# 객체를 JSON으로 직렬화하여 POST 요청을 보낸다.
  • HttpResponseMessage.IsSuccessStatusCode로 HTTP 200~299 성공 여부를 확인한다.
  • JSON 키와 C# 프로퍼티 이름이 다를 때는 [JsonPropertyName] 속성으로 매핑한다.
  • Blazor Server에서 .Result·.Wait() 사용은 교착 상태를 유발하므로 반드시 await를 사용한다.
  • BaseAddress 끝에는 반드시 /를 붙여야 상대 경로 결합이 올바르게 동작한다.

댓글 남기기

Wordpress Social Share Plugin powered by Ultimatelysocial
Copy link
URL has been copied successfully!
THREADS
RSS
error: 저작권 콘텐츠보호를 부탁드립니다.