language/blazor

[Blazor Web App] 05. Blazor에서는 Controller를 왜 잘 사용하지 않을까

렁치 2026. 7. 15. 13:11

Blazor로 로그인 화면을 만들면서 LoginRequest, LoginResult, LoginService를 차례대로 만들었다.

처음에는 자연스럽게 이런 의문이 들었다.

C# 웹 개발에서는 MVC와 Controller를 자주 이야기하는데,
왜 지금 Blazor 프로젝트에서는 Controller를 거의 안 쓰고 있을까?

C# 웹 개발을 공부하다 보면 MVC, Controller, Model, View 같은 개념을 자주 보게 된다.

ASP.NET Core MVC에서는 Controller가 사용자의 요청을 받고, 필요한 데이터를 준비한 뒤, View를 반환하는 중요한 역할을 한다.

그런데 Blazor를 공부하다 보면 구조가 조금 다르다.

Blazor 프로젝트에도 Controller를 추가할 수는 있다.
하지만 일반적인 화면 개발에서는 MVC처럼 Controller를 중심에 두기보다는, Razor Component가 화면과 이벤트를 담당하고 Service가 기능 로직을 담당하는 구조를 더 많이 사용하는 것 같다.

그래서 이번에는 Blazor에서는 왜 Controller를 잘 사용하지 않는지 정리해보려고 한다.


MVC에서 Controller는 요청 흐름의 중심이다

먼저 MVC의 기본 흐름을 간단히 보면 이렇다.

Browser
    ↓
Controller
    ↓
Model 또는 Service
    ↓
View
    ↓
HTML 반환

ASP.NET Core MVC에서는 사용자가 특정 URL로 접속하면 Controller의 Action 메서드가 실행된다.

예를 들어 /Home/Index 요청이 들어오면 HomeControllerIndex() 메서드가 실행되고, 그 결과로 View가 반환된다.

public class HomeController : Controller
{
    public IActionResult Index()
    {
        return View();
    }
}

이 구조에서는 Controller가 웹 요청 흐름의 중심에 있다.

Controller는 요청을 받고, 어떤 데이터를 가져올지 결정하고, 어떤 화면을 보여줄지도 결정한다.

즉 MVC에서 Controller는 단순히 중간에 있는 파일이 아니라, 브라우저 요청과 화면 반환 사이의 흐름을 잡아주는 역할을 한다.


Blazor에서는 화면을 Razor Component로 만든다

Blazor에서는 화면을 .razor 컴포넌트로 만든다.

예를 들어 이런 파일들이 화면 단위가 된다.

Home.razor
Login.razor
Member.razor
ProductList.razor

Blazor의 기본 흐름은 MVC와 조금 다르게 볼 수 있다.

Browser
    ↓
Razor Component
    ↓
Service
    ↓
Database

MVC에서는 Controller가 요청을 받아 View를 반환했다면, Blazor에서는 Razor Component가 화면 표시와 사용자 이벤트 처리를 함께 담당한다.

예를 들어 버튼 클릭 처리는 이렇게 작성할 수 있다.

<button @onclick="Login">로그인</button>

그리고 같은 Razor 컴포넌트의 @code 안에 이벤트 처리 메서드를 작성한다.

private void Login()
{
    LoginResult loginResult;

    loginResult = LoginService.Login(loginRequest);

    if (loginResult.IsSuccess)
    {
        LoginStateService.Login(loginRequest.UserId);

        NavigationManager.NavigateTo("/member");

        return;
    }

    loginMessage = loginResult.Message;
    messageClass = "app-message error-message";
}

여기서 Login() 메서드는 사용자의 버튼 클릭 이벤트를 처리한다.

MVC로 보면 Controller가 담당하던 일부 흐름 제어 역할을 Razor Component 내부에서 처리하는 셈이다.

그래서 Blazor에서는 Controller가 완전히 사라졌다기보다는, 화면 제어 역할이 Razor Component 쪽으로 이동했다고 이해하는 게 자연스러워 보인다.


Blazor에서 Controller가 완전히 사라진 것은 아니다

Blazor 프로젝트에서도 Controller를 추가할 수 있다.

ASP.NET Core 기반 프로젝트라면 Controllers 폴더를 만들고 API Controller를 작성할 수 있다.

다만 일반적인 Blazor 화면 개발에서는 다음과 같은 구조를 더 많이 사용한다.

Razor Component
    ↓
Service
    ↓
Repository 또는 DbContext
    ↓
Database

예를 들어 로그인 화면이라면 이런 흐름으로 볼 수 있다.

Home.razor
    ↓
LoginService
    ↓
LoginStateService

나중에 DB가 붙으면 더 확장될 수 있다.

Home.razor
    ↓
LoginService
    ↓
UserRepository 또는 DbContext
    ↓
Database

이 구조에서는 화면 처리를 위해 Controller를 한 번 더 거칠 필요가 없다.

같은 Blazor 애플리케이션 내부에서 사용할 기능이라면 Component에서 Service를 직접 호출하는 방식이 더 단순하다.

이번 로그인 예제에서도 그래서 Controller를 만들지 않았다.

Home.razor
→ LoginService
→ LoginStateService

Home.razor는 화면과 버튼 이벤트를 담당하고, LoginService는 로그인 검증을 담당한다.
로그인 상태는 이후 LoginStateService로 따로 분리했다.

즉, 지금 단계에서는 Controller를 생략한 것이 아니라, Blazor 화면 구조에 맞게 Component와 Service 중심으로 역할을 나눈 것이다.


Blazor에서 Controller를 사용하는 경우

그렇다고 Blazor에서 Controller를 전혀 사용하지 않는 것은 아니다.

Controller를 사용하는 대표적인 경우는 API를 제공해야 할 때다.

예를 들어 이런 요청을 처리해야 한다고 해보자.

GET /api/users
POST /api/login
GET /api/products

이 요청들은 화면을 반환하는 요청이 아니라 데이터를 반환하는 API 요청이다.

이럴 때는 Controller를 사용할 수 있다.

[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
    [HttpGet]
    public IEnumerable<User> GetUsers()
    {
        // 사용자 목록 반환
    }
}

이런 Controller는 View를 반환하지 않는다.

대신 JSON 같은 데이터를 반환한다.

즉 Blazor에서 Controller는 화면을 만들기 위한 중심 구조라기보다는, 외부에 데이터를 제공하는 API 계층으로 사용하는 경우가 많다.


Blazor Server에서는 왜 Service를 직접 호출할까?

Blazor Server에서는 화면의 이벤트가 서버와 연결된 상태에서 처리된다.

사용자가 버튼을 클릭하면 서버 쪽 C# 코드가 실행되고, 변경된 화면 상태가 다시 사용자에게 반영된다.

그래서 같은 서버 애플리케이션 내부의 기능을 사용할 때는 Component에서 Service를 바로 호출하는 구조가 자연스럽다.

Component
    ↓
Service
    ↓
Database

반대로 Controller를 거치면 흐름이 길어진다.

Component
    ↓
HttpClient
    ↓
Controller
    ↓
Service
    ↓
Database

이 방식이 항상 틀린 것은 아니다.

하지만 같은 Blazor Server 프로젝트 내부에서만 사용할 기능이라면 불필요하게 복잡해질 수 있다.

예를 들어 지금 만든 로그인 학습 구조에서는 굳이 Home.razorHttpClient/api/login을 호출하고, 그 요청을 다시 Controller가 받아 LoginService를 호출하게 만들 필요가 없다.

지금 단계에서는 아래 구조가 더 단순하다.

Home.razor
    ↓
LoginService

따라서 Blazor Server에서는 보통 화면 로직은 Component에서 처리하고, 핵심 기능은 Service로 분리한다.

Controller는 별도의 API가 필요할 때 추가하는 식으로 설계하는 경우가 많다.


언제 Controller를 추가하는 것이 좋을까?

Controller를 추가할지 말지는 기능의 사용 범위를 기준으로 생각하면 이해하기 쉽다.

Blazor 화면 내부에서만 사용하는 기능이라면 보통 Controller 없이 Service를 직접 호출하는 방식이 단순하다.

Blazor Component
    ↓
Service

반대로 여러 클라이언트가 같은 기능을 사용해야 한다면 API Controller를 두는 것이 좋다.

Blazor Web
Mobile App
External Client
    ↓
API Controller
    ↓
Service

예를 들어 웹 화면뿐 아니라 모바일 앱, 외부 시스템, 다른 프론트엔드에서도 같은 로그인 기능이나 상품 조회 기능을 사용해야 한다면 API Controller를 분리해 두는 편이 좋다.

이렇게 하면 기능을 외부에서도 사용할 수 있다.

내부 Blazor 화면에서만 사용
→ Component에서 Service 직접 호출

외부 클라이언트도 사용
→ API Controller 추가

결국 Controller를 쓸지 말지는 “이 기능이 어디에서 호출되는가?”를 기준으로 판단하는 게 좋을 것 같다.


MVC와 Blazor의 차이

MVC와 Blazor의 차이를 단순히 비교하면 다음과 같다.

ASP.NET Core MVC
- Controller가 요청을 받는다.
- Controller가 View를 반환한다.
- View는 .cshtml 파일이다.
- 요청마다 서버가 HTML을 만들어 반환하는 흐름이다.

Blazor
- Razor Component가 화면을 담당한다.
- Component가 사용자 이벤트를 처리한다.
- 필요한 기능은 Service로 분리한다.
- Controller는 주로 API가 필요할 때 사용한다.

MVC에서는 Controller가 화면 흐름의 중심이고, Blazor에서는 Component와 Service가 중심에 가깝다.

이번 로그인 학습 프로젝트도 같은 흐름으로 정리했다.

Home.razor
→ 화면과 사용자 입력 처리

LoginRequest
→ 로그인 입력값 모델

LoginResult
→ 로그인 처리 결과 모델

LoginService
→ 로그인 검증

LoginStateService
→ 로그인 상태 관리

여기까지는 Controller 없이 Component와 Service만으로 흐름을 만들었다.


지금 프로젝트에서는 왜 Controller를 만들지 않았나?

이번 로그인 예제에서는 아직 외부 API가 필요하지 않았다.

현재 필요한 것은 다음 정도였다.

사용자가 아이디와 비밀번호 입력
→ 버튼 클릭
→ 로그인 검증
→ 성공하면 /member 이동
→ 로그인 상태 저장

이 흐름은 같은 Blazor 앱 안에서만 사용된다.

그래서 Controller를 추가하지 않고, Home.razor에서 LoginService를 직접 호출했다.

Home.razor
    ↓
LoginService
    ↓
LoginStateService

만약 나중에 모바일 앱이나 외부 시스템에서도 같은 로그인 기능을 호출해야 한다면 그때는 API Controller를 고려할 수 있다.

예를 들어 이런 구조가 될 수 있다.

Mobile App
    ↓
POST /api/login
    ↓
LoginController
    ↓
LoginService

하지만 지금은 Blazor 화면 학습이 우선이기 때문에 Controller보다 Component와 Service 구조를 먼저 익히는 것이 더 맞다고 판단했다.


정리

Blazor 프로젝트에서도 Controller를 추가할 수 있다.

하지만 일반적인 Blazor 화면 개발에서는 MVC처럼 Controller를 중심으로 화면을 구성하지 않는다.

Blazor의 .razor 컴포넌트는 단순한 View가 아니라, 화면 표시와 사용자 이벤트 처리를 함께 담당한다.

그래서 MVC의 Controller가 하던 일부 역할이 Component 쪽으로 이동했다고 볼 수 있다.

일반적인 Blazor 구조는 다음과 같다.

Razor Component
    ↓
Service
    ↓
Repository 또는 DbContext
    ↓
Database

Controller는 주로 API가 필요할 때 사용한다.

정리하면 이렇게 볼 수 있다.

Blazor 내부에서만 사용하는 기능
→ Component에서 Service 직접 호출

외부 클라이언트도 사용하는 기능
→ API Controller 추가

화면을 반환하는 전통적인 MVC 구조
→ ASP.NET Core MVC에서 주로 사용

중요한 것은 Controller를 무조건 쓰거나 쓰지 않는 것이 아니다.

기능이 어디에서 사용되는지, 외부 API가 필요한지, 프로젝트 구조를 얼마나 분리해야 하는지를 기준으로 판단하는 것이 더 중요하다.

지금 단계에서는 Blazor 화면을 만들면서 C#과 서비스 구조를 익히는 것이 목표이므로, Controller보다 Component와 Service 중심으로 먼저 쌓아가는 흐름이 자연스러워 보인다.


오늘 배운 점

  • ASP.NET Core MVC에서는 Controller가 요청 흐름의 중심이다.
  • Blazor에서는 Razor Component가 화면 표시와 사용자 이벤트 처리를 함께 담당한다.
  • Blazor 화면 내부에서만 사용하는 기능은 Component에서 Service를 직접 호출하는 방식이 단순하다.
  • Controller는 Blazor에서 사라진 것이 아니라, 주로 API가 필요할 때 사용된다.
  • 같은 Blazor Server 앱 내부 기능을 위해 Component → HttpClient → Controller → Service 구조를 만들면 불필요하게 복잡할 수 있다.
  • 외부 클라이언트나 모바일 앱에서도 같은 기능을 사용해야 한다면 API Controller를 고려할 수 있다.
  • 지금 프로젝트에서는 Controller 대신 Component와 Service 중심으로 로그인 흐름을 구성했다.

다음 작업

다음에는 로그인 입력값을 단순 문자열 변수로 두지 않고, LoginRequest 모델로 묶어볼 예정이다.

지금은 화면에서 입력받은 값이 각각 따로 존재하지만, 로그인 요청이라는 하나의 데이터로 묶으면 구조가 더 명확해질 것 같다.