오늘 작업 목표
이번에는 Blazor 기본 페이지인 Home.razor를 직접 수정하면서 간단한 로그인 폼을 만들었다.
실제 로그인 기능을 완성한 것은 아니다. 아직 DB도 붙이지 않았고, 서버 인증도 하지 않았다. 오늘 한 작업은 로그인 화면에서 가장 앞단에 해당하는 부분이다.
아이디 입력
↓
비밀번호 입력
↓
로그인 버튼 클릭
↓
입력값 검증
↓
성공 / 오류 메시지 출력
↓
입력값을 LoginRequest 클래스로 묶기
처음에는 그냥 변수 두 개를 만들고 입력값을 받으면 된다고 생각했다. 그런데 조금만 진행해도 아이디, 비밀번호, 메시지, CSS class 같은 값들이 한 파일에 계속 늘어났다.
그래서 이번 글에서는 아주 작은 로그인 폼을 만들면서도, 나중에 실제 로그인 처리로 확장하기 좋게 입력값을 클래스로 묶는 데까지 진행했다.
현재 상황
이전 글에서는 Blazor Web App 프로젝트를 만들고, 첫 화면이 어떻게 뜨는지 확인했다.
흐름은 대략 이렇게 정리했다.
Program.cs
↓
App.razor
↓
Routes.razor
↓
Home.razor
이번에는 그중 실제 첫 화면인 Home.razor를 수정했다.
처음 기본 코드는 이런 형태였다.
@page "/"
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
여기서부터 화면 문구를 바꾸고, C# 변수를 연결하고, 입력창과 버튼 이벤트를 붙이는 방식으로 진행했다.
오늘 만든 최종 형태
오늘 작업이 끝난 뒤 구조는 이렇게 됐다.
프로젝트 루트
├── Components
│ └── Pages
│ ├── Home.razor
│ └── Home.razor.css
│
├── Models
│ └── LoginRequest.cs
│
└── wwwroot
└── app.css
이번 글에서 새로 만든 파일은 LoginRequest.cs와 Home.razor.css다.
Home.razor는 화면 구조와 버튼 클릭 흐름을 담당한다.
Home.razor.css는 Home 화면 전용 스타일을 담당한다.
LoginRequest.cs는 로그인 입력값을 하나의 객체로 묶는 역할을 한다.
1. Home.razor에 C# 변수 출력하기
처음에는 단순히 화면에 문자열을 직접 적었다.
<h1>Blazor Study Web</h1>
<p>Blazor와 C# 기본 문법을 학습하는 페이지입니다.</p>
그 다음 이 값을 C# 변수로 옮겼다.
<h1>@title</h1>
<p>@description</p>
@code
{
private string title = "Blazor Study Web";
private string description = "Blazor와 C# 기본 문법을 학습하는 페이지입니다.";
}
여기서 @title, @description은 C# 변수 값을 HTML 안에 출력하는 Razor 문법이다.
처음에는 HTML 안에 C# 변수가 들어오는 것이 조금 어색했는데, Blazor에서는 이게 기본이다.
C# 변수
↓
Razor 문법
↓
HTML 화면 출력
2. @code 블록
@code는 해당 Razor 컴포넌트에서 사용할 C# 코드를 작성하는 영역이다.
@code
{
private string title = "Blazor Study Web";
}
위쪽에는 HTML 구조를 작성하고, 아래쪽 @code 안에는 C# 변수나 메서드를 작성한다.
처음에는 한 파일에 HTML과 C#이 같이 있는 것이 낯설었다. 그런데 작은 화면을 만들 때는 오히려 어떤 데이터가 어디에 출력되는지 바로 보여서 이해하기 쉬웠다.
3. 버튼 클릭 이벤트 만들기
다음으로 버튼을 만들었다.
<button @onclick="Login">회원로그인</button>
HTML 버튼에 Blazor 이벤트를 연결한 것이다.
일반 HTML이나 JavaScript에서는 보통 이런 느낌으로 생각한다.
<button onclick="login()">회원로그인</button>
Blazor에서는 JavaScript 함수 대신 C# 메서드를 연결한다.
<button @onclick="Login">회원로그인</button>
그리고 C# 쪽에는 메서드를 만든다.
private void Login()
{
loginMessage = "로그인 버튼을 클릭했습니다.";
}
흐름은 이렇게 된다.
버튼 클릭
↓
Login 메서드 실행
↓
C# 변수 값 변경
↓
화면 자동 갱신
여기서 중요한 점은 화면을 직접 조작하지 않았다는 것이다.
JavaScript였다면 특정 DOM 요소를 찾아서 텍스트를 바꿀 수도 있다.
document.querySelector("#message").innerText = "변경된 메시지";
하지만 Blazor에서는 상태 값을 바꾸면 화면이 다시 그려진다.
loginMessage = "변경된 메시지";
이 감각이 생각보다 중요했다.
4. input과 @bind
다음으로 아이디 입력창을 만들었다.
<input @bind="userId" />
여기서 input은 일반 HTML 태그다.
새로운 부분은 @bind다.
@bind="userId"
이건 입력창의 값과 C# 변수를 연결한다.
사용자가 입력창에 입력
↓
C# 변수에 값 저장
처음에는 아이디 하나만 받았다.
<div>
<label>아이디</label>
<input @bind="userId" />
</div>
C# 변수는 이렇게 만들었다.
private string userId = "";
버튼을 클릭하면 입력한 값을 메시지로 출력했다.
private void ShowWelcomeMessage()
{
welcomeMessage = $"{userId}님 환영합니다.";
}
여기서 $"" 문법도 사용했다.
5. 문자열 보간
welcomeMessage = $"{userId}님 환영합니다.";
C#에서 $""는 문자열 보간이다.
문자열 안에 변수 값을 바로 넣을 수 있다.
예를 들어 userId 값이 testuser라면 아래 문자열은
$"{userId}님 환영합니다."
이렇게 만들어진다.
testuser님 환영합니다.
JavaScript의 템플릿 문자열과 비슷한 느낌이다.
`${userId}님 환영합니다.`
6. 입력값 검증 추가
아이디를 비워둔 상태로 버튼을 누르면 문제가 생겼다.
현재 코드가 아래와 같다면
welcomeMessage = $"{userId}님 환영합니다.";
아이디가 비어 있을 때 결과가 이렇게 나온다.
님 환영합니다.
그래서 입력값 검증을 넣었다.
if (string.IsNullOrWhiteSpace(userId))
{
welcomeMessage = "아이디를 입력하세요.";
return;
}
if 문은 C, C++, Java에서 쓰던 것과 거의 같다.
if (조건)
{
처리할 코드
}
조건이 참이면 중괄호 안의 코드가 실행된다.
return은 메서드를 바로 끝낸다.
아이디가 비어 있음
↓
메시지 출력
↓
return으로 메서드 종료
↓
아래 성공 메시지 코드는 실행되지 않음
7. IsNullOrWhiteSpace와 IsNullOrEmpty 차이
작업 중에 IsNullOrWhiteSpace와 IsNullOrEmpty 차이가 궁금했다.
둘 다 문자열이 비어 있는지 검사하는 데 쓰지만, 공백 처리에서 차이가 있다.
string.IsNullOrEmpty(value)
이건 아래 두 경우만 true다.
null
""
하지만 공백만 있는 문자열은 false다.
" "
반면 아래 코드는
string.IsNullOrWhiteSpace(value)
다음 경우를 모두 true로 본다.
null
""
" "
"\t"
"\n"
표로 정리하면 이렇다.
| 값 | IsNullOrEmpty | IsNullOrWhiteSpace |
|---|---:|---:|
| null | true | true |
| "" | true | true |
| " " | false | true |
| "test" | false | false |
아이디나 이름 같은 일반 입력값 검증에서는 보통 IsNullOrWhiteSpace가 더 자연스럽다.
사용자가 스페이스바만 입력한 경우도 입력하지 않은 것으로 처리할 수 있기 때문이다.
8. 비밀번호 입력창 추가
아이디 검증이 된 뒤에는 비밀번호 입력창을 추가했다.
<div>
<label>비밀번호</label>
<input type="password" @bind="password" />
</div>
type="password"는 Blazor 문법이 아니라 HTML 속성이다.
입력한 값이 화면에 그대로 보이지 않고 비밀번호 입력 형태로 표시된다.
C# 쪽에는 비밀번호 변수를 추가했다.
private string password = "";
그리고 로그인 메서드에서 아이디와 비밀번호를 순서대로 검사했다.
private void Login()
{
if (string.IsNullOrWhiteSpace(userId))
{
welcomeMessage = "아이디를 입력하세요.";
return;
}
if (string.IsNullOrWhiteSpace(password))
{
welcomeMessage = "비밀번호를 입력하세요.";
return;
}
welcomeMessage = $"{userId}님 환영합니다. 로그인 시도 완료.";
}
처리 순서는 이렇게 된다.
로그인 버튼 클릭
↓
아이디 검사
↓
아이디가 없으면 메시지 출력 후 종료
↓
아이디가 있으면 비밀번호 검사
↓
비밀번호가 없으면 메시지 출력 후 종료
↓
둘 다 있으면 로그인 시도 완료 메시지 출력
9. HTML 구조 정리
처음에는 태그를 단순히 위에서 아래로 나열했다.
<h1>@title</h1>
<p>@description</p>
<div>
<label>아이디</label>
<input @bind="userId" />
</div>
<div>
<label>비밀번호</label>
<input type="password" @bind="password" />
</div>
기능은 되지만 화면이 커지면 구조가 잘 안 보일 것 같았다.
그래서 영역을 나눴다.
<div class="home-page">
<header class="page-header">
<h1>@title</h1>
<p>@description</p>
</header>
<main class="page-content">
<section class="login-section">
<h2>회원 로그인</h2>
...
</section>
</main>
<footer class="page-footer">
...
</footer>
</div>
header는 상단 영역이다.
main은 페이지의 주요 내용 영역이다.
section은 하나의 의미 있는 구역이다.
footer는 하단 정보 영역이다.
처음부터 모든 HTML을 완벽하게 짤 필요는 없지만, 최소한 영역을 나누는 습관은 필요하다고 느꼈다.
10. label의 for와 input의 id
입력창에는 id를 붙이고, label에는 for를 붙였다.
<label for="user-id">아이디</label>
<input id="user-id" @bind="userId" />
<label for="password">비밀번호</label>
<input id="password" type="password" @bind="password" />
label의 for 값과 input의 id 값이 같으면, 라벨 텍스트를 클릭했을 때 해당 입력창으로 포커스가 이동한다.
작은 부분이지만 폼을 만들 때 좋은 습관이라고 생각했다.
11. CSS 분리 방식 결정
Blazor에서도 일반 HTML처럼 app.css 하나에서 전체 CSS를 관리할 수 있다.
하지만 Blazor에는 컴포넌트별 CSS 파일도 만들 수 있다.
Home.razor
Home.razor.css
이런 식으로 만들면 Home.razor.css는 Home.razor 전용 스타일처럼 사용할 수 있다.
이 방식을 CSS isolation이라고 한다.
처음에는 CSS를 한 파일에서 관리하는 것이 더 단순해 보였다. 하지만 나중에 페이지가 많아질 것을 생각하면 처음부터 역할을 나눠두는 편이 낫다고 판단했다.
이번 프로젝트에서는 이런 기준으로 가기로 했다.
wwwroot/app.css
→ 전체 공통 스타일
Home.razor.css, Login.razor.css 등
→ 각 화면 또는 컴포넌트 전용 스타일
그래서 이번에는 Home.razor.css를 만들어서 Home 화면 전용 스타일을 따로 작성했다.
12. Home.razor.css 작성
Home.razor와 같은 위치에 Home.razor.css를 만들었다.
Components
└── Pages
├── Home.razor
└── Home.razor.css
작성한 CSS는 아래와 같다.
.home-page {
max-width: 720px;
margin: 0 auto;
padding: 32px;
}
.page-header {
margin-bottom: 32px;
}
.page-header h1 {
margin: 0 0 8px;
font-size: 32px;
font-weight: 700;
}
.page-header p {
margin: 0;
font-size: 16px;
}
.page-content {
margin-bottom: 40px;
}
.login-section {
padding: 24px;
border: 1px solid #ddd;
border-radius: 8px;
}
.login-section h2 {
margin: 0 0 24px;
font-size: 22px;
}
.form-row {
margin-bottom: 16px;
}
.form-row label {
display: block;
margin-bottom: 6px;
font-weight: 600;
}
.form-row input {
width: 100%;
padding: 10px 12px;
border: 1px solid #ccc;
border-radius: 4px;
}
.login-button {
padding: 10px 16px;
border: 1px solid #333;
border-radius: 4px;
cursor: pointer;
}
.login-message {
margin-top: 16px;
min-height: 24px;
font-weight: 600;
}
.error-message {
color: #b00020;
}
.success-message {
color: #14532d;
}
.page-footer {
margin-top: 40px;
padding-top: 16px;
border-top: 1px solid #ddd;
font-size: 14px;
}
.page-footer p {
margin: 4px 0;
}
이 CSS의 목적은 예쁜 디자인 완성이 아니다.
지금은 영역이 나뉘고, 입력창과 버튼이 정리되어 보이는 정도면 충분하다.
13. 메시지 스타일 분리
처음에는 성공 메시지와 오류 메시지가 모두 같은 스타일로 나왔다.
<p>@welcomeMessage</p>
그런데 실제 화면에서는 오류와 성공을 구분하는 것이 좋다.
그래서 메시지 내용뿐 아니라 CSS class도 C# 변수로 관리했다.
<p class="@messageClass">@loginMessage</p>
C# 쪽에는 메시지와 class 변수를 만들었다.
private string loginMessage = "";
private string messageClass = "login-message";
아이디가 비어 있으면 오류 메시지 class를 넣었다.
loginMessage = "아이디를 입력하세요.";
messageClass = "login-message error-message";
비밀번호가 비어 있어도 똑같이 오류 class를 넣었다.
loginMessage = "비밀번호를 입력하세요.";
messageClass = "login-message error-message";
둘 다 입력되어 있으면 성공 class를 넣었다.
loginMessage = $"{userId}님 환영합니다. 로그인 시도 완료.";
messageClass = "login-message success-message";
이렇게 하면 C# 상태에 따라 HTML class가 바뀌고, CSS 스타일도 같이 바뀐다.
C# 변수 변경
↓
HTML class 변경
↓
CSS 스타일 변경
14. 로그인 입력값을 클래스로 묶기
처음에는 입력값을 각각 변수로 가지고 있었다.
private string userId = "";
private string password = "";
이 방식도 지금 단계에서는 문제가 없다.
하지만 실제 로그인 요청을 생각하면 아이디와 비밀번호는 따로 노는 값이 아니라 하나의 요청 데이터다.
로그인 요청
├── 아이디
└── 비밀번호
그래서 입력값을 LoginRequest 클래스로 묶었다.
먼저 프로젝트 루트에 Models 폴더를 만들었다.
Models
└── LoginRequest.cs
그리고 LoginRequest.cs를 작성했다.
namespace <PROJECT_NAMESPACE>.Models;
public class LoginRequest
{
public string UserId { get; set; } = "";
public string Password { get; set; } = "";
}
블로그에는 네임스페이스를 <PROJECT_NAMESPACE>로 적어둔다. 실제 프로젝트에서는 본인 프로젝트의 네임스페이스에 맞춰 작성하면 된다.
예를 들어 프로젝트 기본 네임스페이스가 MyBlazorApp이라면 이렇게 된다.
namespace MyBlazorApp.Models;
15. C# property와 get; set;
LoginRequest 클래스 안에는 이런 코드가 있다.
public string UserId { get; set; } = "";
public string Password { get; set; } = "";
C#에서는 이런 형태를 property라고 부른다.
Java에서는 보통 필드를 private으로 두고 getter/setter 메서드를 따로 만든다.
public class LoginRequest {
private String userId;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
}
C#에서는 같은 느낌의 코드를 훨씬 짧게 쓴다.
public string UserId { get; set; } = "";
처음에는 낯설지만 C#에서는 정말 자주 보게 될 문법이다.
16. Home.razor에서 LoginRequest 사용하기
LoginRequest를 만들었으니 Home.razor에서 사용했다.
먼저 상단에 using을 추가했다.
@using <PROJECT_NAMESPACE>.Models
그리고 기존 변수 두 개를 제거했다.
private string userId = "";
private string password = "";
대신 LoginRequest 객체를 만들었다.
private LoginRequest loginRequest = new();
원래는 이렇게 쓸 수도 있다.
private LoginRequest loginRequest = new LoginRequest();
C#에서는 왼쪽에 타입이 이미 있기 때문에 오른쪽 타입을 생략해서 new()로 쓸 수 있다.
17. 객체 속성에 @bind 연결하기
기존에는 입력창을 각각 변수에 연결했다.
<input @bind="userId" />
<input type="password" @bind="password" />
이제는 loginRequest 객체의 속성에 연결한다.
<input id="user-id" @bind="loginRequest.UserId" />
<input id="password" type="password" @bind="loginRequest.Password" />
구조가 이렇게 바뀐 것이다.
기존
userId
password
변경
loginRequest
├── UserId
└── Password
이렇게 해두면 나중에 로그인 서비스를 만들 때 객체 하나를 그대로 넘길 수 있다.
Login(loginRequest)
아직 서비스는 만들지 않았지만, 이 구조가 다음 단계로 넘어가기 좋다.
최종 코드 - LoginRequest.cs
Models/LoginRequest.cs
namespace <PROJECT_NAMESPACE>.Models;
public class LoginRequest
{
public string UserId { get; set; } = "";
public string Password { get; set; } = "";
}
최종 코드 - Home.razor
Components/Pages/Home.razor
@page "/"
@using <PROJECT_NAMESPACE>.Models
<PageTitle>Home</PageTitle>
<div class="home-page">
<header class="page-header">
<h1>@title</h1>
<p>@description</p>
</header>
<main class="page-content">
<section class="login-section">
<h2>회원 로그인</h2>
<div class="form-row">
<label for="user-id">아이디</label>
<input id="user-id" @bind="loginRequest.UserId" />
</div>
<div class="form-row">
<label for="password">비밀번호</label>
<input id="password" type="password" @bind="loginRequest.Password" />
</div>
<button class="login-button" @onclick="Login">@loginButtonText</button>
<p class="@messageClass">@loginMessage</p>
</section>
</main>
<footer class="page-footer">
<p>학습용 Blazor 웹</p>
<p>Ver: 0.0.1</p>
</footer>
</div>
@code
{
private string title = "Blazor Study Web";
private string description = "Blazor와 C# 기본 문법을 학습하는 페이지입니다.";
private string loginButtonText = "회원로그인";
private LoginRequest loginRequest = new();
private string loginMessage = "";
private string messageClass = "login-message";
private void Login()
{
if (string.IsNullOrWhiteSpace(loginRequest.UserId))
{
loginMessage = "아이디를 입력하세요.";
messageClass = "login-message error-message";
return;
}
if (string.IsNullOrWhiteSpace(loginRequest.Password))
{
loginMessage = "비밀번호를 입력하세요.";
messageClass = "login-message error-message";
return;
}
loginMessage = $"{loginRequest.UserId}님 환영합니다. 로그인 시도 완료.";
messageClass = "login-message success-message";
}
}
최종 코드 - Home.razor.css
Components/Pages/Home.razor.css
.home-page {
max-width: 720px;
margin: 0 auto;
padding: 32px;
}
.page-header {
margin-bottom: 32px;
}
.page-header h1 {
margin: 0 0 8px;
font-size: 32px;
font-weight: 700;
}
.page-header p {
margin: 0;
font-size: 16px;
}
.page-content {
margin-bottom: 40px;
}
.login-section {
padding: 24px;
border: 1px solid #ddd;
border-radius: 8px;
}
.login-section h2 {
margin: 0 0 24px;
font-size: 22px;
}
.form-row {
margin-bottom: 16px;
}
.form-row label {
display: block;
margin-bottom: 6px;
font-weight: 600;
}
.form-row input {
width: 100%;
padding: 10px 12px;
border: 1px solid #ccc;
border-radius: 4px;
}
.login-button {
padding: 10px 16px;
border: 1px solid #333;
border-radius: 4px;
cursor: pointer;
}
.login-message {
margin-top: 16px;
min-height: 24px;
font-weight: 600;
}
.error-message {
color: #b00020;
}
.success-message {
color: #14532d;
}
.page-footer {
margin-top: 40px;
padding-top: 16px;
border-top: 1px solid #ddd;
font-size: 14px;
}
.page-footer p {
margin: 4px 0;
}
결과 확인
작업 후에는 세 가지를 확인했다.
1. 아이디와 비밀번호를 둘 다 비우고 로그인 클릭
예상 결과는 아래와 같다.
아이디를 입력하세요.
오류 메시지 스타일이 적용되어야 한다.
2. 아이디만 입력하고 로그인 클릭
예상 결과는 아래와 같다.
비밀번호를 입력하세요.
이것도 오류 메시지 스타일이 적용되어야 한다.
3. 아이디와 비밀번호를 둘 다 입력하고 로그인 클릭
예상 결과는 아래와 같다.
입력한아이디님 환영합니다. 로그인 시도 완료.
성공 메시지 스타일이 적용되어야 한다.
최종 구조
오늘 작업이 끝난 뒤의 구조는 이렇게 볼 수 있다.
Blazor 화면
Home.razor
├── HTML 구조
├── @bind 입력 연결
├── @onclick 버튼 이벤트
├── Login 메서드
└── LoginRequest 객체 사용
Home.razor.css
├── Home 화면 전용 스타일
├── 입력창 스타일
├── 버튼 스타일
└── 메시지 성공 / 오류 스타일
LoginRequest.cs
├── UserId
└── Password
처음에는 Home.razor 하나만 수정하면 될 것 같았는데, 작업을 하다 보니 역할이 조금씩 나뉘었다.
화면 구조
↓
화면 전용 스타일
↓
입력 데이터 모델
이렇게 나눠두니까 나중에 로그인 로직을 서비스로 분리하기도 훨씬 쉬울 것 같다.
오늘 느낀 점
이번 작업은 화면만 보면 굉장히 단순하다.
아이디 입력창 하나, 비밀번호 입력창 하나, 로그인 버튼 하나가 전부다.
그런데 이 작은 화면 안에 Blazor의 기본 흐름이 꽤 많이 들어 있었다.
@bind
@onclick
@code
if
return
문자열 보간
CSS class 동적 변경
컴포넌트별 CSS
C# class
property
new()
처음에는 그냥 입력창과 버튼만 만들면 된다고 생각했는데, 막상 해보니 입력값 검증, 메시지 처리, CSS 분리, 모델 분리까지 자연스럽게 이어졌다.
아직 실제 로그인은 아니지만, 진짜 로그인 기능으로 넘어가기 전에 필요한 앞단의 흐름은 어느 정도 잡힌 것 같다.
특히 입력값을 LoginRequest 클래스로 묶은 부분이 중요했다.
지금은 단순히 아이디와 비밀번호만 있지만, 나중에 사업자번호나 로그인 옵션이 추가되더라도 요청 객체에 속성을 추가하는 방식으로 확장할 수 있다.
오늘 배운 점
@bind는 입력값과 C# 변수를 연결한다
<input @bind="loginRequest.UserId" />
입력창의 값이 C# 객체의 속성과 연결된다.
@onclick은 버튼 클릭과 C# 메서드를 연결한다
<button @onclick="Login">회원로그인</button>
버튼을 누르면 Login() 메서드가 실행된다.
IsNullOrWhiteSpace는 공백만 입력한 경우도 막는다
string.IsNullOrWhiteSpace(loginRequest.UserId)
아이디 검증에는 IsNullOrEmpty보다 더 자연스럽다.
메시지 class도 C# 변수로 바꿀 수 있다
<p class="@messageClass">@loginMessage</p>
텍스트뿐 아니라 HTML 속성도 C# 값으로 제어할 수 있다.
화면 전용 CSS는 .razor.css로 분리할 수 있다
Home.razor
Home.razor.css
Blazor에서는 컴포넌트별 CSS를 분리할 수 있다.
로그인 입력값은 클래스로 묶는 편이 낫다
public class LoginRequest
{
public string UserId { get; set; } = "";
public string Password { get; set; } = "";
}
나중에 서비스나 API로 넘기기 좋은 구조가 된다.
다음 작업
다음에는 Home.razor 안에 있는 로그인 처리 코드를 조금 더 분리할 예정이다.
아직은 Login() 메서드 안에서 직접 검증하고 메시지를 만들고 있다.
다음 단계에서는 이런 구조로 갈 수 있다.
Home.razor
↓
LoginService
↓
LoginResult
예상 작업은 아래와 같다.
LoginResult 클래스 만들기
↓
LoginService 클래스 만들기
↓
입력값 검증 로직 일부 이동
↓
Home.razor는 화면 처리에 집중
아직 DB는 붙이지 않는다.
바로 DB로 가기 전에, 먼저 C# 클래스와 서비스 분리 구조를 익히는 것이 좋을 것 같다.
'language > blazor' 카테고리의 다른 글
| [Blazor Web App] 01. 시작하기 - 프로젝트 생성부터 첫 화면이 출력되는 과정 이해하기 (0) | 2026.07.05 |
|---|