Blazor 소개
Blazor는 HTML, CSS, C#을 기반으로 웹 애플리케이션을 구축할 수 있는 프론트엔드 프레임워크입니다. 클라이언트와 서버 양쪽에서 동작하는 재사용 가능한 컴포넌트를 사용해 반응형 웹 경험을 제공할 수 있습니다.
프로젝트 구성 요소
- wwwroot: 정적 자산(이미지, CSS, JS 등)을 저장하는 디렉터리입니다.
- Components: 공통 Razor 컴포넌트를 포함하며, 레이아웃과 페이지 외부의 재사용 가능한 UI 요소를 위치시킵니다.
- Layout: 레이아웃 파일 및 스타일 시트를 관리합니다.
- Pages: 라우팅 가능한 페이지 컴포넌트가 위치합니다. 각 페이지는
@page지시어로 경로를 설정합니다. - _Imports.razor: 자주 사용하는 네임스페이스를 전역으로 포함합니다. 예:
@using System.Net.Http. - App.razor: 앱의 최상위 컴포넌트로, 라우팅 및 스크립트 로딩을 담당합니다.
- Routes.razor:
Router컴포넌트를 통해 요청된 URL에 맞는 페이지를 매핑합니다. - Program.cs: 앱 시작점으로, 서비스 등록, 미들웨어 구성, 호스팅 설정 등을 수행합니다.
주의: 모든 Razor 컴포넌트 파일은 반드시 대문자로 시작해야 하며, 그렇지 않으면 인식되지 않습니다.
렌더링 모드 종류
각 컴포넌트는 렌더링 방식을 결정하는 렌더링 모드(Render Mode)를 가집니다. 이는 해당 컴포넌트가 어디서 실행되며 상호작용이 가능한지를 정의합니다.
| 모드 | 설명 | 렌더링 위치 | 인터랙티브 여부 |
|---|---|---|---|
| 정적 서버 (Static Server) | 서버에서 HTML 생성 후 전송. 이후 상호작용 불가 | 서버 | 아니요 |
| 인터랙티브 서버 (Interactive Server) | Blazor Server 기반 실시간 상호작용 지원 | 서버 | 예 |
| 인터랙티브 WebAssembly | 클라이언트에서 .NET 런타임 실행 (CSR) | 클라이언트 | 예 |
| 자동 인터랙티브 (Interactive Auto) | 초기 요청은 서버, 이후 자동으로 WebAssembly 전환 | 서버 → 클라이언트 | 예 |
인터랙티브 모드 활성화
인터랙티브 기능을 사용하려면 Program.cs에서 적절한 서비스를 등록해야 합니다.
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// 인터랙티브 서버 및 WebAssembly 지원 추가
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();
var app = builder.Build();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode();
컴포넌트 내에서 렌더링 모드 지정
특정 페이지나 컴포넌트에 대해 렌더링 모드를 명시적으로 설정할 수 있습니다.
@page "/dashboard"
@rendermode InteractiveServer
또는 앱 전체에 적용하고 싶다면 App.razor에서 루트 컴포넌트에 모드를 부여합니다.
<Routes @rendermode="InteractiveServer" />
<HeadOutlet @rendermode="InteractiveServer" />
기본 컴포넌트 작성법
Blazor 컴포넌트는 .razor 확장자를 가지며 HTML 마크업과 C# 코드를 결합합니다.
<!-- Counter.razor -->
<h1>카운터</h1>
<p>현재 값: @count</p>
<button class="btn btn-primary" @onclick="Increment">증가</button>
@code {
private int count = 0;
private void Increment() => count++;
}
1. 데이터 바인딩
@bind를 사용하면 양방향 데이터 바인딩이 가능합니다. 입력 필드 변경 시 변수도 자동 갱신됩니다.
<input @bind="name" placeholder="이름 입력" />
<p>입력된 이름: @name</p>
@code {
private string name = string.Empty;
}
@bind:event를 사용하면 특정 이벤트에 따라 동기화 시점을 조정할 수 있습니다.
<input @bind="inputText" @bind:event="oninput" />
<p>실시간 입력: @inputText</p>
2. 날짜 형식 지정
@bind:format을 이용해 DateTime 값을 원하는 형식으로 표시할 수 있습니다.
<input type="date" @bind="selectedDate" @bind:format="yyyy-MM-dd" />
@code {
private DateTime selectedDate = DateTime.Today;
}
3. 속성 확장(Splatting)
딕셔너리를 사용해 여러 속성을 한 번에 바인딩할 수 있습니다.
<input @attributes="InputAttrs" />
@code {
private Dictionary<string, object> InputAttrs = new()
{
{ "maxlength", 20 },
{ "placeholder", "입력하세요" },
{ "required", true },
{ "size", 40 }
};
}
4. 객체 바인딩
복잡한 객체의 프로퍼티에도 바인딩이 가능합니다.
@page "/student"
@rendermode InteractiveServer
<label>
이름:
<input @bind="Student.Name" />
</label>
<p>학생 이름: @Student.Name</p>
@code {
public StudentModel Student { get; set; } = new() { Name = "김철수" };
public class StudentModel
{
public string? Name { get; set; }
}
}
이벤트 처리 및 컴포넌트 통신
부모-자식 컴포넌트 간 데이터 전달은 [Parameter] 속성과 EventCallback을 활용합니다.
<!-- ChildComponent.razor -->
<div class="card">
<h4>자식 컴포넌트</h4>
<p>년도: @Year</p>
<button @onclick="ChangeYear">년도 변경</button>
</div>
@code {
[Parameter]
public int Year { get; set; }
[Parameter]
public EventCallback<int> YearChanged { get; set; }
private async Task ChangeYear()
{
var newYear = Random.Shared.Next(1980, 2025);
await YearChanged.InvokeAsync(newYear);
}
}
<!-- ParentComponent.razor -->
@page "/parent"
@rendermode InteractiveServer
<h3>부모 컴포넌트</h3>
<p>현재 년도: @currentYear</p>
<button @onclick="UpdateYear">직접 업데이트</button>
<ChildComponent @bind-Year="currentYear" />
@code {
private int currentYear = 2020;
private void UpdateYear() => currentYear = Random.Shared.Next(1980, 2025);
}
라우팅 및 네비게이션
1. 기본 라우팅
@page 지시어로 경로를 정의합니다. 라우터는 어셈블리 스캔을 통해 경로 정보를 수집합니다.
@page "/profile/{userId:int}"
2. 라우트 파라미터
URL 세그먼트 값을 컴포넌트에 전달할 수 있습니다. 대소문자 구분 없음.
@page "/greeting/{message}"
<p>메시지: @Message</p>
@code {
[Parameter]
public string? Message { get; set; }
}
3. 라우트 제약 조건
타입 안전성을 보장하기 위해 제약 조건을 설정할 수 있습니다.
@page "/user/{id:int}/{active:bool?}"
<p>사용자 ID: @Id</p>
<p>활성 상태: @Active</p>
@code {
[Parameter]
public int Id { get; set; }
[Parameter]
public bool Active { get; set; } = true;
}
4. NavigationManager 사용
C# 코드 내에서 프로그래밍 방식으로 페이지 이동이 가능합니다.
@inject NavigationManager NavManager
<button @onclick="GoToCounter">카운터로 이동</button>
@code {
private void GoToCounter() => NavManager.NavigateTo("/counter");
}
5. NavLink 컴포넌트
내비게이션 링크는 일반 <a> 태그보다 NavLink를 사용하는 것이 좋습니다. 현재 경로와 일치할 경우 자동으로 활성 클래스를 추가합니다.
<NavLink href="/home" Match="NavLinkMatch.All">홈</NavLink>
<NavLink href="/products" Match="NavLinkMatch.Prefix">제품</NavLink>
NavLinkMatch.All: 정확한 전체 URL 일치 시 활성화NavLinkMatch.Prefix: URL 접두사 일치 시 활성화 (기본값)
폼 검증 (Validation)
Blazor는 Data Annotations 기반 검증을 지원합니다. EditForm, DataAnnotationsValidator, ValidationSummary 등을 조합해 사용합니다.
@page "/register"
@using System.ComponentModel.DataAnnotations
@inject ILogger<RegistrationForm> Logger
<EditForm Model="User" OnValidSubmit="HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<label>
사용자명:
<InputText @bind-Value="User.Username" />
<ValidationMessage For="() => User.Username" />
</label>
<button type="submit">제출</button>
</EditForm>
@code {
public UserModel User { get; set; } = new();
private void HandleValidSubmit()
{
Logger.LogInformation("유효한 폼 제출: {Username}", User.Username);
}
public class UserModel
{
[Required(ErrorMessage = "사용자명은 필수입니다.")]
[StringLength(10, MinimumLength = 3, ErrorMessage = "3~10자 사이로 입력하세요.")]
public string? Username { get; set; }
}
}
EditForm: 폼 컨텍스트를 제공하고 제출 이벤트를 처리합니다.DataAnnotationsValidator: 모델의 특성 기반 유효성 검사를 연결합니다.ValidationSummary: 전체 오류 메시지를 목록으로 표시합니다.ValidationMessage: 특정 필드의 오류만 표시합니다.
폼 제출 콜백은 다음과 같이 세 가지로 나뉩니다:
OnValidSubmit: 유효할 때 실행OnInvalidSubmit: 무효일 때 실행OnSubmit: 항상 실행 (내부에서 수동 검증 필요)