[블레이저] 페이지 요소 사이 데이터 전달.

블레이저에서는 페이지에 기능을 추가하고, 페이지의 요소가 너무 복잡하지 않도록 기능을 쪼개 자식 요소에게 위임하는 경우가 많습니다.

이때, 부모(페이지) 요소가 가진 데이터를 자식 요소로 전달하는 수단은 잘 발달되어 있습니다.

  1. 요소 파라미터
  2. Cascading 파라미터
// SchoolsPage.razor
@page "/schools"

<PageTitle>학교기관</PageTitle>
<!--파리미터 전달-->
<ShoolList Model="_schools" />
<!--캐스케이딩-->
<CascadingValue Value="_selectedSchool">
   <SchoolDetails />
   <SchoolInput />
</CasCadingValue>

@code {
  // ...
}

그런데, 페이지 요소의 기능이 복잡하다면, 자식 요소로 파편화하는 게 한계가 있기 마련입니다.

이 경우, 자식 요소 성격의 새로운 페이지 요소를 도입하게 되는 게 일반적인데, 문제는 페이지 요소간 데이터 전달 수단이 매우 한정적이라는 점입니다.

  1. 파라미터
  2. 쿼리 스트링
// StudentsPage.razor
@page "/schools/{shooldId}/students"

<h2>@_shoolName 학생들</h2>

// ...

@code {
   [Parameter]
   public int SchooldId { get; set; }

   [SupplyParameterFromQuery]
   public string? Gender { get; set; }

   string _schoolName = "";
}

그런데, _schoolNameSchoolsPage 에서 이미 획득한 데이터입니다.
아래와 같이, 파라미터로 받을 수도 있지만,

   [SupplyParameterFromQuery]
   public string? SchoolName { get; set; }

이 것이 항상 쉽게 가능하지는 않습니다.
예를 들면, 비 영어 문자열 데이터인데, 이 데이터들은 엔코딩 문제를 해결해야 한다는 제한이 있습니다. 쿼리 엔코딩은 어렵다면 어렵고, 쉽다면 쉬우나, 언제나 마음을 조리게 하는 문제입니다.

사설이 길었습니다.

우선, 페이지 간 데이터 전달을 위해, 캐시서비스(UserDataCache)를 정의합니다.
이 글의 목적은 캐시가 아니라서, 구현은 생략하지만, Duration 과 Sliding 을 지원하는 캐시라고 가정합니다.

데이터 송신자

다른 페이지에게 데이터를 전달하고자 하는 페이지 요소는 아래와 같이 네비게이션이 일어 날 때, 캐시를 등록하게 끔 처리합니다.

// SchoolsPage.razor
@page "/schools"
@inject NavigationManager Navigation
@inject UserDataCache DataCache
// ..

@code {
   Shool[] _schools = [];
   IDisposable? _navigatingHandler;
   public static string CachKey = "schoolsPageCache"; 
   protected override async Task OnInitializedAsync()
   {
      _navigatingHandler = Navigation.RegisterLocationChangingHandler( 
        async (context) =>  {
            await DataCache.Set(CacheKey, _shools);            
            _navigatingHandler?.Dispose();
        });
// ...

보시다시피, 캐싱의 시점은 이 페이지에서 나가는 순간입니다.

참고로, 블레이저에는 브라우저 창이 닫히는 이벤트를 받는 수단은 없습니다.

따라서,나가기 전에 이뤄진 데이터에 대한 조작은 캐시에 그대로 반영됩니다.
또한, 캐시된 데이터의 Duration은 이 페이지를 빠져 나가는 순간부터 시작됩니다.

데이터 수신자

캐시된 데이터를 받고자 하는 페이지는 아래와 같이 처리합니다.

// StudentsPage.razor
@page "/schools/{shooldId}/students"

@inject UserDataCache DataCache
@inject NavigationManager Navigation
@inject IMessageBox MessageBox
// ..

@code {
   // ...
   [Parameter]
   public int SchooldId { get; set; }
   string? _schoolName;

   protected override async Task OnInitializedAsync()
   {
      var schools = await DataCache.Get<School[]>(SchoolsPage.CacheKey);
      _schoolName = schools?.FirstOrDefault(s => s.Id == SchoolId)?.Name;
     
     if (_schoolName is null)
     {
         await MessageBox.AlertAsync("잘못된 접근입니다.");
         Navigation.NavigateTo("/schools");
     }      
// ...

이 페이지는 브라우저에 아래와 같이 주소를 입력해도 접근을 하지 못합니다.

https://schoolsystem.net/schools/11/students

반드시 schools 를 먼저 방문해야 하는데, 이는 이 페이지가 SchoolsPage 의 자식 요소처럼 동작하기 원했기 때문입니다. (이러한 행태가 필요없다면, 그렇게 하지 않아도 됩니다.)

데이터 송신자의 캐시 이용

참고로, 데이터 송신자도 캐시 고유의 장점을 향유할 수 있습니다.

// SchoolsPage.razor
// ...
@inject ISchoolService SchoolService
@inject UserDataCache DataCache
// ..

@code {
   School[] _schools = [];
   // ...
   protected override async Task OnInitializedAsync()
   {
      //...
      _schools = (await DataCache.Get<School[]>(CacheKey)) ??
         (await SchoolService.GetallAsync()) ?? [];     
// ...

이 페이지에서 나갔다가 금방 돌아오는 경우나 사용자가 의미 없이 네비게이션을 반복하는 경우, 외부 자원에 대한 접근이 불필요하게 일어나는 것을 예방할 수 있는 장점이 있습니다.

1개의 좋아요