개요
- 블레이저로 산출된 웹은 `웹앱`이라고 명명함
- CSR, SSR 모두 가능
- 대화형 SSR..?
ㄴ 대화형 페이지의 페이지 콘텐츠는 미리 렌더링된다.
- 대화형 렌더링 모드(대화형 SSR)
ㄴ 기본적으로 서버 옵션을 사용하도록 설정된다.
ㄴ CSR에서만 대화형 작업을 사용하도록 설정하려면 웹 어셈블리 옵션을 사용한다.
쓸만한 블레이저 UI 라이브러리
1. MudBlazor
https://mudblazor.com/components/drawer#usage
2. Fluentui-Blazor by Microsoft
https://www.fluentui-blazor.net/Tabs
3. Blazor Bootstrap
https://docs.blazorbootstrap.com/getting-started/blazor-webassembly-net-8
웹 어셈블리 (WASM)
- 2015에 처음 나온 기술
- 웹 브라우저에서 C, C++, C#, RUST를 사용가능하게 만들었음
- 고성능을 추구할 수 있고, JS를 보완할 수도 있다.
- 속도는 자바스크립트와 비슷하다.(?. 이라고 의문이 남는건 하단에서 설명)
- 스택 기반 가상머신에서만 동작한다.
장점
- 게임이나 물리 시뮬레이션 같은 경우, JS보다 성능이 좋음.
- 웹 어셈블리는 특정 언어에 구애받지 않는다.
단점
- 디버깅이 어렵다. 출시된지 얼마되지 않아서 개발 생태계가 완전히 조성되지 않아 디버깅 툴이 발전하지 않음.
- C++ / RUST...등등에서 웹어셈블리로 컴파일하는 건, JS를 작성하는 것보다 어려움
이하 `WASM`으로 이야기 하겠다.
웹 어셈블리의 작동 과정은 아래와 같다.
소스(c/c++/rust) --------------> LLVM(low level virtual machine) 컴파일러(ir/wasm) ------------> 사용자 브라우저(실행)
`wasm`은 여기까지 알아보아도 될듯하다.
도구
- 일단 `ant design`같은 쓸만한 UI 라이브러리를 쓸 수 있다는게 큰 장점 같다.
- 근데 많이 불안정하다. js를 쓸 수 있다면 그럼 js 라이브러리도 사용이 가능한건가..?
- `ant chart`도 마찬가지
렌더모드
```js
@rendermode InteractiveServer
```
에서 `@rendermode`와 같은 것들을 `디렉티브`라고 한다.
렌더모드 옵션 같은 경우에는 2가지가 있다.
1. InteractiveServer
- 페이지가 서버에서 처음 렌더링 되고, 클라이언트와 서버 간에 signalR을 통해 지속적인 상호작용이 유지된다.
- 실시간 상호작용을 위해서 서버와의 지속적인 연결이 필요
- 이걸 대화형 SSR이라고 한다.
2. InteractiveWebAssembly
- 페이지가 클라이언트에서 WASM을 통해 실행되고 렌더링 된다.
- 클라이언트에서 모든 처리가 이루어지며, 서버와의 지속적인 연결이 필요하지 않다.
SignalR
- 어플리케이션에 실시간 웹 기능을 추가하는 프로세스를 간소화하는 asp.net 개발자를 위한 라이브러리이다.
- 서버가 클라이언트가 새 데이터를 요청할때까지 기다리지 않고, 서버코드가 연결된 클라이언트에 콘텐츠를 즉시 푸시하도록 하는 기능이다.
- 모든 종류의 `실시간` 웹 기능을 asp.net 어플리케이션에 추가하는데 사용할 수 있다.
- 예시 : 채팅 / 문서동시편집 / 대화형 SSR / C# <-> JS간의 함수호출
blazor에서의 렌더링
- CSR : Blazor WebAssembly라고 한다.
- SSR : SSR은 다음 두가지 종류일 수 있음.
ㄴ 정적 SSR : 서버는 사용자 대화형 또는 기본 구성 요소 상태를 제공하지 않는 정적 HTML을 Razor 생성한다.
ㄴ 대화형 SSR : Blazor 이벤트는 사용자 대화형 작업을 허용하고, Razor 구성요소상태는 프레임워크에서 Blazor 기본
- 미리렌더링 : 렌더링된 컨트롤에 대한 이벤트 처리기를 사용하도록 설정하지 않고, 서버에서 페이지 콘텐츠를 처음 렌더링하는 프로세스이다. 미리 렌더링은 검색 엔진이 페이지 순위를 계산하는데 사용하는 초기 http 응답에 대한 콘텐츠를 렌더링해서 seo를 향상 시킬 수도 있다. 미리 렌더링 뒤에는 항상 서버 또는 클라이언트에서 최종렌더링이 수행된다.
좀만 깊이 들어가니까 꽤 어렵네.. 용어가 이상하다. CSR이면 CSR인데, WASM까지 언급되니까 머릿속이 복잡해진다. 그런데, 기술문서에서 언급하는 것들을 굳이 전부다 훑을 필요는 없다.
공식문서에는 너무 여러 내용이 혼재되어있어서 https://www.dotnetnote.com/docs/blazor/ 여기를 참고하기로 하였다.
프로젝트 구조 및 주요파일 설명
- wwwroot : 정적파일 (이미지, CSS, Javascript파일 등)을 저장하는 폴더
- Pages : Razor 컴포넌트 페이지를 저장하는 폴더
- Shared : 공유 Razor 컴포넌트를 저장하는 폴더
- `_Imports.razor`: 전역적으로 사용할 네임스페이스를 정의하는 파일
- App.razor : 라우팅 및 메인 레이아웃을 정의
- Program.cs : 어플리케이션의 시작지점 정의
컴포넌트 구조 및 생명주기 이해하기
- Blazor에서 컴포넌트는 독집적인 ui요소로 화면에 표시되는 각각의 부분을 나타낸다. 각 컴포넌트는 렌더링, 이벤트 처리, 데이터 바인딩 등의 기능을 수행한다.
- Blazor에서의 컴포넌트의 생명주기는 다음과 같이 이루어진다.
1. 초기화
2. 매개변수 설정
3. 렌더링 및 UI 업데이트
4. 컴포넌트 소멸
- 생명주기를 이용한 동작은 각 생명 주기에 해당하는 메서드를 오버라이드 해서 정의한다.
데이터 바인딩 및 이벤트 처리
- Blazor에서는 `@bind`속성을 사용하여 데이터 바인딩을 설정한다.
- 양방향 바인딩인듯.. 이건 리액트보다 편하다.
라우팅 및 상태관리(?)
라우팅
- `@page` 지시문을 사용하여 컴포넌트에 지정할 수 있다.
ㄴ 진짜 편한듯
```js
@page "/counter"
```
페이지 라우팅 및 매개변수 전달
- 페이지 간 이동 시에, 매개변수를 전달하려면 라우트 템플릿에 매개변수를 지정하고, 컴포넌트에 `[Parameter]`속성을 사용하여 정의한다.
- @page "/product/{id}"
-
[Parameter]
public int Id{get;set;}
이후 id 매개변수를 통해 페이지에 전달된 값이 Id 프로퍼티에 저장된다.
#### 네비게이션 매니저 사용
- 네비게이션 매니저를 사용해서도 프로그래밍 방식으로 페이지 간 이동을 수행할 수 있다.
- 이를 위해서 `NavigationManager`클래스를 사용한다.
```js
@inject NavigationManager NavigationManager
private void NavigateToProduct(int productId){
NavigationManager.NavigateTo($"/product/{productId}");
}
```
상태관리
- 리액트의 그것과 비슷하련지..?!
- Blazor에서는 전역 상태와 컴포넌트 간 상태를 관리할 수 있다.
Cascading Parameters
- 캐스케이딩 매개변수를 사용하여 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달할 수 있다.
<CascadingValue Value="UserName">
<ChildComponent/>
</CascadingValue>
[CascadingParameter]
public string UserName{get;set;}
위 예제에서 `UserName`값을 하위 컴포넌트로 전달한다.
DI(Dependency Injection)을 사용한 서비스 구현 (feat: React 상태관리 )
- 의존성 주입을 사용하여 어플리케이션 전체에서 서비스를 공유하고 상태를 관리할 수 있다.
- Blazor에서는 Program.cs메서드를 사용해서 의존성 주입을 설정한다.
builders.services.AddSingleton<IUserServices, UserService>();
위 예제에서 IUserService 인터페이스와 해당 구현체인 UserService를 등록하여 어플리케이션에서 사용할 수 있게 한다.
1. 먼저 서비스의 인터페이스를 정의한다. ( 꼭 필요하지는 않지만 하는게 좋다 )
public interface IUserService{
User GetUserById(int id);
IEnumerable<User> GetAllUsers();
}
2. 위의 인터페이스를 구현한 서비스 구현
위의 인터페이스를 구현한다. 실제 로직에 대한 코드가 들어가고, 데이터 가져오기나 더미데이터 정의를 여기에서 수행한다.
public class UserService : IUserService
{
private readonly List<User> _users;
public UserService()
{
// 예시로 사용할 사용자 데이터
_users = new List<User>
{
new User { Id = 1, Name = "John Doe" },
new User { Id = 2, Name = "Jane Smith" }
};
}
public User GetUserById(int id)
{
return _users.FirstOrDefault(u => u.Id == id);
}
public IEnumerable<User> GetAllUsers()
{
return _users;
}
}
3. 서비스 등록
의존성 주입(Dependency Injection, DI)은 소프트웨어 개발에서 객체 간의 의존 관계를 설정하는 디자인 패턴입니다. 이를 통해 객체 간의 결합도를 낮추고 코드의 재사용성을 높일 수 있습니다. Blazor와 같은 프레임워크에서는 DI를 통해 서비스나 데이터 접근 계층을 설정하고 사용할 수 있습니다. 다음은 DI를 사용하여 Blazor 애플리케이션에서 서비스를 구현하는 방법을 쉽게 설명한 것입니다.
Blazor 애플리케이션의 Program.cs 파일에서 서비스를 등록합니다. 이 작업은 애플리케이션이 시작될 때 수행됩니다.
using BlazorApp2.Interfaces;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
namespace BlazorApp2
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
// 서비스 등록
builder.Services.AddSingleton<IUserService, UserService>();
await builder.Build().RunAsync();
}
}
}
잠깐..!
ASP.NET에서 의존성 주입에 의해 추가되는 Service는 크게 3가지 생명주기를 지닌다.
1. AddSingleton
ㄴ 클라이언트(웹브라우저)의 접속상태와 관계없이 웹 서비스 시작 때 생성되서 웹 서비스가 종료될때까지 유지된다. Singleton이라는 이름에 걸맞게, 클라이언트가 아무리 많이 붙어도 오직 1개의 서비스만 존재하게 된다.
2. AddScoped
ㄴ 클라이언트의 Request 시작부터, Response 종료까지 유지된다. 각 클라이언트마다 존재하므로, 연결되는 클라이언트 수만큼 존재하게 될 수 있다.
3. AddTransient
ㄴ의존성 주입한 객체마다 독립적인데, 이건 `AddScoped`와 구분이 힘들다고 한다.
4. 서비스 사용
이제 Blazor 컴포넌트에서 서비스를 주입받아 사용할 수 있습니다. 다음은 UserComponent.razor 파일에서 서비스를 사용하는 예제입니다.
namespace BlazorApp2.Interfaces;
@page "/test"
@inject NavigationManager NavigationManager
@inject BlazorApp2.Interfaces.IUserService IUserService
<PageTitle>Test Page</PageTitle>
<main style="display:flex; width:100%; border:1px solid black; border-radius:20px; height:50vh;">
<aside style="display:flex; flex-flow:column nowrap; align-items:center;">
<CascadingValue Value="sendedValue">
<ShowBox />
</CascadingValue>
<button @onclick="()=>{NavigateToCounter(2);}">Counter</button>
</aside>
<section>
<ul>
@foreach (var user in IUserService.GetAllUsers())
{
<li>@user.Name</li>
}
</ul>
</section>
</main>
@code {
private string? sendedValue = "test String";
private void NavigateToCounter(int productId)
{
NavigationManager.NavigateTo($"/counter");
}
}
폼, 유효성 검사 및 데이터 처리
폼 구성
- Blazor에서의 폼 구성은 `EditForm` 컴포넌트를 사용한다.
<EditForm Model="Product" OnValidSubmit="HandleSubmit">
</EditForm>
입력 유효성 검사
- Blazor에서는 데이터 주석(`data annotation`)을 사용하여 입력 유효성 검사를 할 수 있다.
- 참고로 이렇게 데이터 형식을 정의하는 클래스를 정의한다고 하더라도, 의존성 주입을 `Program.cs`에서 해줘야 사용이 가능하다.
public class Product{
[Required]
public string Name{get;set;}
[Range(1, 100)]
public int Quantity{get;set;}
}
(부록) 데이터 주석(Data Annotation) 살펴보기
1. 필수 데이터 지정
`[Required]` : 해당 데이터는 null일 수 없고 꼭 값을 넣어주어야 한다.
2. 정규식 패턴 (`RegularExpression`)
속성 값이 지정된 정규식 패턴과 일치하는지 여부 체크
3. 문자열 속성의 최대길이 지정 및 체크
`[StringLength(10)]`
4. 범위 체크
`[Range(1,100)]`
5. 오류메세지에서의 속성 이름 재지정
`[DisplayName("Price")]`
폼 유효성 검사 활성화하기
<EditForm Model="Product" OnValidSubmit="HandleSubmit">
<DataAnnotationsValidator/>
</EditForm>
(부록) @inject
- 일반적으로 C#에서는 `using`으로 네임스페이스를 가져왔다.
- 하지만, Blazor 에서는 `@inject`로 `서비스`나 `객체`를 주입한다.
- 이것은 Blazor의 DI(의존성 주입)의 일부이다.
(부록) @onclick
- (Blazor문법 어지럽다..)
- 아무래도 c#이다 보니까 메서드 시그니쳐에 때문에 파라미터, 반환형에 민감하다.
- @onclick을 사용하려면
- @onclick="파라미터가 없는 메서드"
- @onclick=()=>파라미터가 있는 메서드
이렇게 사용한다..
js의 함수를 c# 코드에서 실행시키려면?
1. `IJSRuntime` 인터페이스를 사용해야 한다.
@inject IJSRuntime JSRuntime
2. `<Script>`는 `@code` 다음에 선언한다.
3. 그리고 나서 사용
```js
await JSRuntime.InvokeVoidAsync("함수명", 인수.....)
```
c#의 메서드를 js 코드에서 실행시키려면?
- 이 경우는 사실 거의 사용할 일이 없다.
- 왜냐하면, dom 요소에서 바로 js 코드를 실행시킬 수가 없기 때문이다.
- 클릭했을 때 js 요소를 실행시키고 싶다면 다음 로직을 따라야 하는데
ㄴ 클릭 -> c# 메서드 실행 -> js 메서드 실행
ㄴ 많이 비효율적이라 사용할 일이 거의 없을 것 같다.
- 그래도 살펴보긴하자.
1. C# 코드에서 메서드를 정의하고 `[JSInvokable]`어트리뷰트를 추가한다.
```js
[JSInvokable]
public static Task<string> YourDotNetMethod(string parameter){
return Task.FromResult($"Hello, {parameter}");
}
```
2. js에서 .net 메서드를 호출한다.
```js
async function callDotnetMethod(){
const result = await DotNet.invokeMethodAsync('네임스페이스명', '닷넷메서드명', '인수');
}
```
3. js 함수를 호출하여 닷넷 메서드 실행
```js
protected override async Task OnAfterRenderAsync(bool firstRender){
if(firstRender){
await JSRuntime.InvokeVoidAsync("calldotnetmethod");
}
}
```
좀 애매모호한게 있는데 cs 프로그램에서 기본적으로 모든 동작은 UI스레드에서 동작한다. 문제는 어떠한 연산을 할때도 UI스레드에서 기본적으로 동작한다는 것인데, 좀 무거운 동작을 하게 되면 UI스레드가 멈춰버린다. 다른 큰 할일을 줘서 원래 해야할일을 못하게 되는 것이다. 이를 `UI프리징`이라고 한다. 이 경우에 `Unmanaged Threading`이나 `Managed Threading`(`스레드풀`)을 사용했었다.
Blazor도 비슷한 방식으로 동작을 하는 것 같다. 그래서 JS 코드를 수행할때 간단한 동작은 상관이 없지만 조금만 무겁다면 비동기로 동작하도록 해야한다.
그래도 무거운 동작이 있을텐데 이 경우에도 닷넷 cs 프로그램에서의 스레드 풀 그것과 비슷한 동작을 하는 기능이 존재한다.
Web Worker
- Web Worker를 사용하면 js 작업을 백그라운드에서 실행할 수 있고, 이를 Blazor WebAssembly 어플리케이션에서도 사용할 수 있다.
(이건 나중에 더해서 별첨하게따)
스타일링 및 JS 상호작용
Blazor 에서 css 파일 사용하기
- `wwwroot` 폴더 내부의 css 폴더 내부에 css 파일을 위치시키고 `index.html`에 링크를 추가한다.
```html
<link href="css/custom.css" rel="stylesheet" />
```
- `SweetAlert2` 사용하기
마찬가지로 `wwwroot` 내의 `index.html`의 헤더에 cdn을 추가하고 해당 페이지의 `<script>`태그 안에서 사용하면 된다.
생명주기
#### 1. OnInitialized / OnInitializedAsync
- 역할 :
컴포넌트가 초기화 될 때 호출된다.
- 호출시점 :
컴포넌트가 처음 인스턴스화될 때 호출된다. 이 시점에서 DOM은 아직 완전히 로드되지 않았다.
- 주요용도:
1. 컴포넌트의 초기상태 설정
2. 비동기 작업을 통해 초기 데이터를 가져오는 작업
3. 매개변수 초기화
- 비동기 작업을 처리할 경우에 `OnInitializedAsync`를 사용한다.
protected override void OnInitialized()
{
// 초기화 로직
}
protected override async Task OnInitializedAsync()
{
// 비동기 초기화 로직
}
#### 2. OnParametersSet / OnParametersSetAsync
- 역할 :
컴포넌트의 매개변수가 설정되거나 변경될 때 호출된다.
- 호출시점 :
컴포넌트가 처음 렌더링 될때와 이후 매개변수가 변경될 때마다 호출된다.
- 주요용도 :
1. 매개변수에 따라서 컴포넌트의 상태를 초기화하거나 업데이트
2. 매개변수 변경에 따라서 비동기 작업 수행
- 비동기 작업을 처리할 경우 `OnParametersSetAsync`를 사용한다.
protected override void OnParametersSet()
{
// 매개변수 설정 로직
}
protected override async Task OnParametersSetAsync()
{
// 비동기 매개변수 설정 로직
}
#### 3. OnAfterRender / OnAfterRenderAsync
- useEffect(()=>{}, [])
- 역할 :
컴포넌트가 렌더링된 후에 호출된다.
- 호출시점 :
컴포넌트가 처음 렌더링 된 후, 또는 매 렌더링 사이클 후 호출된다. 이 시점에서 DOM은 완전히 로드된 상태이다.
- 주요용도 :
1. DOM조작 (JS 호출)
2. 애니메이션 초기화
3. 처음 렌더링 후 수행해야하는 추가작업
4. 첫번째 렌더링 이후에만 실행할 작업을 `firstRender 매개변수를 통해서 구분 가능
- 비동기 작업을 처리할 경우 `OnAfterRenderAsync`를 사용한다.
- `firstRender` 매개변수는 컴포넌트가 처음 렌더링된 것인지 여부를 나타낸다.
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
// 초기 렌더링 후 작업
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
// 비동기 초기 렌더링 후 작업
}
}
#### 4. Dispose
- 컴포넌트가 소멸될 때 호출된다.
#### 생명주기 정리
되게 혼란스럽다. 일단 이름이 직관적이지 않고, 설명된 기능이 비슷하고, 모호하다. 예시로 OnInitialized 같은 경우는 Dom 이 로드된 이후일 수도 있고 아닐 수도 있다고 이야기한다.(?이게 설명이야?!)
- 다시 정리해보자.
구성요소가 요청 시에 처음 렌더링 되는 경우에
1. 구성요소의 인스턴스를 만들고
2. 속성 삽입을 수행하고, `SetParametersAsync`실행
3. `OnInitialized / OnInitializedAsync` 호출
4. `OnParametersSet / OnParametersSetAsync`를 호출하고 구성요소를 렌더링
### IJSRuntime
- IJSRuntime은 blazor에서 js와 닷넷 간의 상호 운용성을 제공하는 인터페이스이다.
ㄴ 이를 통해서 C# 코드에서 JS 함수를 호출하거나, JS에서 C# 메서드를 호출할 수 있다.
'Dotnet > Blazor' 카테고리의 다른 글
Blazor에서 카카오 맵을 쓰려면 (0) | 2024.07.25 |
---|---|
SweetAlert를 Blazor에서 썼는데 껍데기만 온다면? (0) | 2024.07.25 |
Blazor의 UI 업데이트 방식 ( feat : 리액트, CSR ) (0) | 2024.07.25 |
Ado.net 그리고 DataTable, DataSet, DataRow에 대해서 알아봅시다. (0) | 2024.07.22 |
LINQ(C#)과 Blazor를 활용한 사용자 검색 웹페이지 (공부를 위한 예시코드) (0) | 2024.07.22 |