KeiStory

반응형

MudBlazor 다이얼로그가 떠 있는지 전역으로 추적하기 (Blazor SSR)

작업을 하다 보면 "지금 화면에 다이얼로그가 하나라도 떠 있는가?"를 알아야 할 때가 있다. 단축키를 막거나, 백그라운드 동작을 멈추거나, 특정 UI를 숨기는 등 용도는 다양하다. 문제는 이 상태를 어느 화면에서든 알고 싶다는 점이다. 컴포넌트마다 플래그를 들고 다니는 건 답이 아니다.

이번 글에서는 MudBlazor의 IDialogService를 활용해 다이얼로그 열림 상태를 전역으로 추적하는 방법을 정리한다. 특히 Blazor SSR 환경에서 마주치는 두 가지 함정과 그 해결책을 함께 다룬다.

 

IDialogService의 이벤트

MudBlazor의 IDialogService는 다이얼로그 생명주기에 대한 이벤트를 제공하는데 이를 이용하면 된다.

public class DialogTracker : IDisposable
{
    private readonly IDialogService _dialogService;
    // 현재 열려 있는 다이얼로그 개수
    private int _count;

    public DialogTracker(IDialogService dialogService)
    {
        _dialogService = dialogService;
        _dialogService.DialogInstanceAddedAsync += OnAddedAsync;
    }

    // 하나라도 떠 있으면 true
    public bool IsOpen => _count > 0;

    private Task OnAddedAsync(IDialogReference reference)
    {
        // 다이얼로그 열림 → 카운트 증가 (스레드 안전)
        Interlocked.Increment(ref _count);

        // 닫힘 감지는 Result Task에 맡김 (await 결과는 무시)
        _ = WaitForCloseAsync(reference);

        // 비동기 이벤트지만 여기서 기다릴 게 없으므로 완료된 Task 반환
        return Task.CompletedTask;
    }

    private async Task WaitForCloseAsync(IDialogReference reference)
    {
        // 닫히는 방식과 무관하게 다이얼로그가 닫히면 Result가 완료됨
        await reference.Result;

        // 닫힘 → 카운트 감소
        Interlocked.Decrement(ref _count);
    }

    public void Dispose()
    {
        // 이벤트 구독 해제
        _dialogService.DialogInstanceAddedAsync -= OnAddedAsync;
    }
}

 

Blazor SSR의 DI 스코프 분리

먼저 짚어야 할 사실. 정적(static) SSR에서는 MudBlazor 다이얼로그가 동작하지 않는다. 다이얼로그를 열고 닫는 건 런타임 상호작용이므로 InteractiveServer 같은 인터랙티브 렌더 모드가 필요하다. 즉, 다이얼로그가 실제로 뜬다는 건 그 영역이 이미 인터랙티브로 돌고 있다는 뜻이다.

여기서 SSR 특유의 함정이 나온다. 정적 SSR 스코프와 인터랙티브 회로(circuit) 스코프는 서로 다른 DI 스코프다. Scoped 서비스라도 두 영역에서 인스턴스가 따로 생성된다.

그래서 이런 일이 벌어진다.

  • MainLayout이 정적 SSR로 렌더되면, 거기서 inject한 DialogTracker는 HTTP 요청 스코프 인스턴스
  • 다이얼로그는 인터랙티브 회로에서 열림 → 이벤트는 회로 스코프 인스턴스에서 발생
  • 두 인스턴스가 달라서 IsOpen이 항상 false

해결의 핵심은 간단하다. 다이얼로그 이벤트가 발생하는 곳과 IsOpen을 읽는 곳이 같은 인터랙티브 렌더 모드 영역 안에 있어야 한다. 앱 전체를 글로벌 인터랙티브로 구성했다면 회로 스코프가 세션 내내 유지되므로 신경 쓸 일이 거의 없다. 부분 인터랙티브를 섞어 쓴다면, 추적기를 inject하는 컴포넌트가 다이얼로그와 동일한 인터랙티브 영역에 있는지 확인해야 한다.

등록은 Scoped로 한다.

builder.Services.AddScoped<DialogTracker>();

 

화면에서 사용하기

다이얼로그를 여는 코드는 전혀 바꿀 필요없이 평소처럼 호출하면 추적기가 알아서 잡는다.

@inject IDialogService DialogService

<MudButton OnClick="OpenDialog">열기</MudButton>

@code {
    private async Task OpenDialog()
    {
        // 평소대로 호출하면 끝. 추적은 DialogTracker가 알아서 함
        await DialogService.ShowAsync<MyDialog>("제목");
    }
}

상태를 확인하고 싶은 곳에서는 DialogTracker를 inject해서 IsOpen만 읽으면 된다.

@inject DialogTracker Tracker

@if (Tracker.IsOpen)
{
    <MudText>다이얼로그가 떠 있습니다</MudText>
}

 

딱 하나만 챙기자

DialogTracker는 한 번은 inject되어 인스턴스화돼야 생성자에서 이벤트 구독이 걸린다. 어디서도 inject하지 않으면 객체 자체가 생성되지 않아 추적이 시작되지 않는다. 그래서 회로 내내 살아있는 MainLayout 같은 컴포넌트에 inject해 두는 것을 권장한다. 값을 직접 쓰지 않아도 인스턴스화 자체가 목적이다.

@* MainLayout.razor *@
@inject DialogTracker Tracker

@* 인스턴스화만 시키면 되므로 화면에서 직접 쓰지 않아도 됨 *@

 

728x90
반응형

공유하기

facebook twitter kakaoTalk kakaostory naver band