다음을 통해 공유


탐색

이 콘텐츠는 ‘.NET MAUI를 사용하는 엔터프라이즈 애플리케이션 패턴’ eBook에서 발췌한 것으로, .NET Docs에서 제공되거나 오프라인으로 읽을 수 있는 다운로드 가능한 무료 PDF로 제공됩니다.

Enterprise Application Patterns Using .NET MAUI eBook cover thumbnail.

.NET MAUI에는 일반적으로 사용자가 UI와 상호 작용하여 발생하거나 앱 자체에서 논리 기반 상태 변경으로 인해 발생하는 페이지 탐색에 대한 지원이 포함되어 있습니다. 그러나 MVVM(Model-View-ViewModel) 패턴을 사용하는 앱에서는 다음과 같은 어려운 문제를 해결해야 하므로 탐색이 복잡할 수 있습니다.

  • 뷰 간에 긴밀한 결합 및 종속성을 도입하지 않는 방법을 사용하여 탐색할 뷰를 식별.
  • 탐색할 뷰가 인스턴스화 및 초기화되는 프로세스를 조정 MVVM을 사용하는 경우 뷰 및 뷰 모델을 인스턴스화하고 뷰의 바인딩 컨텍스트를 통해 서로 연결해야 합니다. 앱이 종속성 주입 컨테이너를 사용하는 경우 뷰 및 뷰 모델의 인스턴스화에는 특정 생성 메커니즘이 필요할 수 있습니다.
  • 뷰 우선 탐색 또는 뷰 모델 우선 탐색을 수행할지 여부 뷰 우선 탐색을 사용하면 탐색할 페이지가 뷰 형식의 이름을 참조합니다. 탐색하는 동안 지정된 뷰는 해당 뷰 모델 및 기타 종속 서비스와 함께 인스턴스화됩니다. 또 다른 방법은 뷰 모델 우선 탐색을 사용하는 것입니다. 여기서는 탐색할 페이지가 뷰 모델 형식의 이름을 참조합니다.
  • 뷰 및 뷰 모델에서 앱의 탐색 동작을 깔끔하게 구분하는 방법을 결정. MVVM 패턴은 앱의 UI와 해당 프레젠테이션 및 비즈니스 논리를 구분하지만 이러한 패턴을 함께 연결하기 위한 직접적인 메커니즘을 제공하지는 않습니다. 그러나 앱의 탐색 동작은 앱의 UI 부분과 프레젠테이션 부분에 걸쳐 있는 경우가 많습니다. 사용자는 뷰에서 탐색을 시작하는 경우가 많으며 탐색의 결과로 뷰가 대체됩니다. 그러나 뷰 모델 내에서 탐색을 시작하거나 조정해야 하는 경우도 많습니다.
  • 초기화를 위해 탐색하는 동안 매개 변수를 전달하는 방법을 결정. 예를 들어 사용자가 뷰로 이동하여 주문 세부 정보를 업데이트하는 경우 올바른 데이터를 표시할 수 있도록 주문 데이터를 뷰에 전달해야 합니다.
  • 특정 비즈니스 규칙이 준수되도록 탐색을 조정. 예를 들어 뷰에서 벗어나기 전에 잘못된 데이터를 수정하거나 뷰 내에서 변경한 데이터를 제출 또는 삭제하라는 메시지가 사용자에게 표시될 수 있습니다.

이 장에서는 뷰 모델 우선 페이지 탐색을 수행하는 데 사용되는 MauiNavigationService라는 탐색 서비스 클래스를 제공하여 이러한 문제를 해결합니다.

참고

앱에서 사용하는 MauiNavigationService는 단순하며 가능한 모든 탐색 유형을 다루지는 않습니다. 애플리케이션에 필요한 탐색 유형에는 추가 기능이 필요할 수 있습니다.

탐색 논리는 뷰의 코드 숨김 또는 데이터 바인딩된 뷰 모델에 상주할 수 있습니다. 뷰에 탐색 논리를 배치하는 것이 가장 간단한 방법일 수 있지만 단위 테스트를 통해 쉽게 테스트할 수 없습니다. 뷰 모델 클래스에 탐색 논리를 배치하면 단위 테스트를 통해 논리를 확인할 수 있습니다. 또한 뷰 모델은 특정 비즈니스 규칙이 적용되도록 탐색을 제어하는 논리를 구현할 수 있습니다. 예를 들어 앱은 사용자가 입력한 데이터가 유효한지 확인하지 않으면 페이지에서 벗어나는 것을 허용하지 않을 수 있습니다.

일반적으로 탐색 서비스는 테스트 가능성을 높이기 위해 뷰 모델에서 호출됩니다. 그러나 뷰 모델에서 뷰로 이동하려면 뷰를 참조하는 뷰 모델, 특히 활성 뷰 모델이 연결되지 않은 뷰가 필요하므로 이는 권장되지 않습니다. 따라서 여기에 표시된 MauiNavigationService는 탐색할 대상으로 뷰 모델 형식을 지정합니다.

eShopOnContainers 다중 플랫폼 앱은 MauiNavigationService 클래스를 사용하여 뷰 모델 우선 탐색을 제공합니다. 이 클래스는 다음 코드 예제에 표시된 INavigationService 인터페이스를 구현합니다.

public interface INavigationService
{
    Task InitializeAsync();

    Task NavigateToAsync(string route, IDictionary<string, object> routeParameters = null);

    Task PopAsync();
}

이 인터페이스는 구현 클래스가 다음 메서드를 제공해야 한다고 지정합니다.

메서드 용도
InitializeAsync 앱이 시작될 때 두 페이지 중 하나로 이동합니다.
NavigateToAsync(string route, IDictionary<string, object> routeParameters = null) 등록된 탐색 경로를 사용하여 지정된 페이지에 대한 계층적 탐색을 수행합니다. 필요에 따라 대상 페이지에서 처리에 사용할 명명된 경로 매개 변수를 전달할 수 있습니다.
PopAsync 탐색 스택에서 현재 페이지를 제거합니다.

참고

INavigationService 인터페이스는 일반적으로 탐색 스택의 이전 페이지로 프로그래밍 방식으로 돌아가는 데 사용되는 GoBackAsync 메서드도 지정합니다. 그러나 이 메서드는 필수가 아니므로 eShopOnContainers 다중 플랫폼 앱에서는 누락되었습니다.

MauiNavigationService 인스턴스 만들기

INavigationService 인터페이스를 구현하는 MauiNavigationService 클래스는 다음 코드 예제에 설명된 대로 MauiProgram.CreateMauiApp() 메서드의 종속성 주입 컨테이너를 사용하여 싱글톤으로 등록됩니다.

mauiAppBuilder.Services.AddSingleton<INavigationService, MauiNavigationService>();;

그러면 다음 코드 예제에 설명된 대로 INavigationService 인터페이스를 뷰 및 뷰 모델의 생성자에 추가하여 인터페이스를 확인할 수 있습니다.

public AppShell(INavigationService navigationService)

종속성 주입 컨테이너에 MauiNavigationService 저장된 개체에 대한 참조를 반환합니다.

ViewModelBase 클래스는 MauiNavigationService 인스턴스를 INavigationService 형식의 NavigationService 속성에 저장합니다. 따라서 ViewModelBase 클래스에서 파생되는 모든 뷰 모델 클래스는 NavigationService 속성을 사용하여 INavigationService 인터페이스에 지정된 메서드에 액세스할 수 있습니다.

탐색 요청 처리

.NET MAUI는 애플리케이션 내에서 탐색하는 여러 가지 방법을 제공합니다. 일반적인 탐색 방법은 NavigationPage 클래스를 사용하여 사용자가 원하는 대로 페이지를 앞뒤로 탐색할 수 있는 계층적 탐색 환경을 구현하는 것입니다. eShopOnContainers 앱은 Shell 구성 요소를 애플리케이션의 루트 컨테이너로 사용하고 탐색 호스트로 사용합니다. 셸 탐색에 대한 자세한 내용은 Microsoft 개발자 센터의 셸 탐색을 참조하세요.

탐색은 다음 코드 예제에 설명된 대로 탐색 중인 페이지의 라우팅 경로를 지정하여 NavigateToAsync 메서드 중 하나를 호출해 뷰 모델 클래스 내에서 수행됩니다.

await NavigationService.NavigateToAsync("//Main");

다음 코드 예제에서는 MauiNavigationService 클래스에서 제공하는 NavigateToAsync 메서드를 보여 줍니다.

public Task NavigateToAsync(string route, IDictionary<string, object> routeParameters = null)
{
    return
        routeParameters != null
            ? Shell.Current.GoToAsync(route, routeParameters)
            : Shell.Current.GoToAsync(route);
}

.NET MAUIShell 컨트롤은 경로 기반 탐색에 이미 익숙하므로 NavigateToAsync 메서드는 이 기능을 마스킹하도록 작동합니다. NavigateToAsync 메서드를 사용하면 탐색 데이터를 탐색 중인 뷰 모델에 전달되는 인수로 지정할 수 있으며, 이 경우 일반적으로 초기화를 수행하는 데 사용됩니다. 자세한 내용은 탐색 중 매개 변수 전달을 참조하세요.

중요

.NET MAUI에서 탐색을 수행하는 방법에는 여러 가지가 있습니다. MauiNavigationServiceShell에서 작동하도록 특별히 빌드되었습니다. NavigationPage 또는 TabbedPage를 사용하거나 다른 탐색 메커니즘을 사용하는 경우 이러한 구성 요소를 사용하여 작동하도록 이 라우팅 서비스를 업데이트해야 합니다.

MauiNavigationService에 대한 경로를 등록하려면 XAML 또는 코드 숨김에서 경로 정보를 제공해야 합니다. 다음 예제에서는 XAML을 통한 경로 등록을 보여줍니다.

<?xml version="1.0" encoding="UTF-8" ?>
<Shell
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:views="clr-namespace:eShopOnContainers.Views"
    x:Class="eShopOnContainers.AppShell">

    <!-- Omitted for brevity -->

    <FlyoutItem >
        <ShellContent x:Name="login" ContentTemplate="{DataTemplate views:LoginView}" Route="Login" />
    </FlyoutItem>

    <TabBar x:Name="main" Route="Main">
        <ShellContent Title="CATALOG" Route="Catalog" Icon="{StaticResource CatalogIconImageSource}" ContentTemplate="{DataTemplate views:CatalogView}" />
        <ShellContent Title="PROFILE" Route="Profile" Icon="{StaticResource ProfileIconImageSource}" ContentTemplate="{DataTemplate views:ProfileView}" />
    </TabBar>
</Shell>

이 예제에서 ShellContentTabBar 사용자 인터페이스 개체는 해당 Route 속성을 설정합니다. 이는 Shell에 의해 제어되는 사용자 인터페이스 개체에 대한 경로를 등록하는 기본 방법입니다.

나중에 탐색 스택에 추가될 개체가 있는 경우 코드 숨김을 통해 추가해야 합니다. 다음 예제에서는 코드 숨김의 경로 등록을 보여줍니다.

Routing.RegisterRoute("Filter", typeof(FiltersView));
Routing.RegisterRoute("Basket", typeof(BasketView));

코드 숨김에서는 Routing.RegisterRoute 메서드를 호출합니다. 이 메서드는 경로 이름을 첫 번째 매개 변수로 사용하고 뷰 형식을 두 번째 매개 변수로 사용합니다. 뷰 모델이 NavigationService 속성을 사용하여 탐색하는 경우 애플리케이션의 Shell 개체는 등록된 경로를 찾아 탐색 스택에 푸시합니다.

뷰를 만들고 탐색한 후 뷰에 연결된 뷰 모델의 ApplyQueryAttributesInitializeAsync 메서드가 실행됩니다. 자세한 내용은 탐색 중 매개 변수 전달을 참조하세요.

앱이 시작되면 Shell 개체가 애플리케이션의 루트 뷰로 설정됩니다. 루트 뷰로 설정된 Shell는 경로 등록을 제어하는 데 사용되며 이후로 애플리케이션의 루트에 표시됩니다. Shell이 만들어지면 탐색 경로를 초기화하기 위해 OnParentSet 메서드를 사용하여 애플리케이션에 연결될 때까지 기다릴 수 있습니다. 다음 코드 예제에서는 이 메서드를 보여줍니다.

protected override async void OnParentSet()
{
    base.OnParentSet();

    if (Parent is not null)
    {
        await _navigationService.InitializeAsync();
    }
}

이 메서드는 종속성 주입 생성자가 제공하고 해당 InitializeAsync 메서드를 호출하는 INavigationService 인스턴스를 사용합니다.

다음 코드 예제에서는 MauiNavigationService.InitializeAsync 메서드의 구현을 보여 줍니다.

public Task InitializeAsync()
{
    return NavigateToAsync(string.IsNullOrEmpty(_settingsService.AuthAccessToken)
        ? "//Login"
        : "//Main/Catalog");
}

앱에 인증에 사용되는 캐시된 액세스 토큰이 있는 경우 //Main/Catalog 경로가 탐색됩니다. 그렇지 않으면 //Login 경로가 탐색됩니다.

탐색 중에 매개 변수 전달

INavigationService 인터페이스에서 지정한 NavigateToAsync 메서드를 사용하면 탐색 데이터를 탐색 중인 뷰 모델에 전달되는 데이터의 IDictionary<string, object>로 지정할 수 있으며, 이 경우 일반적으로 초기화를 수행하는 데 사용됩니다.

예를 들어 ProfileViewModel 클래스에는 사용자가 ProfileView 페이지에서 주문을 선택할 때 실행되는 OrderDetailCommand가 포함됩니다. 그러면 다음 코드 예제에 표시된 OrderDetailAsync 메서드가 실행됩니다.

private async Task OrderDetailAsync(Order order)
{
    if (order is null)
    {
        return;
    }

    await NavigationService.NavigateToAsync(
        "OrderDetail",
        new Dictionary<string, object>{ { "OrderNumber", order.OrderNumber } });
}

이 메서드는 OrderDetail 경로에 대한 탐색을 호출하여 주문 번호 정보를 사용자가 선택한 순서로 전달합니다. 종속성 주입 프레임워크가 뷰의 BindingContext에 할당된 OrderDetailViewModel 클래스와 함께 OrderDetail 경로에 대한 OrderDetailView를 만드는 경우 OrderDetailViewModel에는 아래 코드 예제와 같이 탐색 서비스에서 데이터를 수신할 수 있는 특성이 추가된 것입니다.

[QueryProperty(nameof(OrderNumber), "OrderNumber")]
public class OrderDetailViewModel : ViewModelBase
{
    public int OrderNumber { get; set; }
}

QueryProperty 특성을 사용하면 값을 매핑할 속성의 매개 변수와 쿼리 매개 변수 사전에서 값을 찾을 수 있는 키를 제공할 수 있습니다. 이 예제에서는 NavigateToAsync 호출 중에 키 "OrderNumber" 및 주문 번호 값이 제공되었습니다. 뷰 모델은 "OrderNumber" 키를 발견하고 값을 OrderNumber 속성에 매핑했습니다. 그러면 나중에 OrderNumber 속성을 사용하여 OrderService 인스턴스에서 전체 주문 세부 정보를 검색할 수 있습니다.

동작을 사용하여 탐색 호출

탐색은 일반적으로 사용자 상호 작용에 의해 뷰에서 트리거됩니다. 예를 들어 LoginView는 인증 성공 후 탐색을 수행합니다. 다음 코드 예제에서는 동작에 의해 탐색이 호출되는 방법을 보여줍니다.

<WebView>
    <WebView.Behaviors>
        <behaviors:EventToCommandBehavior
            EventName="Navigating"
            EventArgsConverter="{StaticResource WebNavigatingEventArgsConverter}"
            Command="{Binding NavigateCommand}" />
    </WebView.Behaviors>
</WebView>

런타임 시 EventToCommandBehaviorWebView와의 상호 작용에 응답합니다. WebView가 웹 페이지를 탐색하면 Navigating 이벤트가 발생하며, 이 이벤트는 LoginViewMode에서 NavigateCommand를 실행합니다. 기본적으로 이벤트에 대한 이벤트 인수는 명령에 전달됩니다. 이 데이터는 WebNavigatingEventArgs에서 Url를 반환하는 EventArgsConverter 속성에 지정된 변환기를 통해 원본과 대상 간에 전달될 때 변환됩니다. 따라서 NavigationCommand가 실행되면 웹 페이지의 Url이 등록된 작업에 매개 변수로 전달됩니다.

그러면 NavigationCommand이 다음 코드 예제에 표시된 NavigateAsync 메서드를 실행합니다.

private async Task NavigateAsync(string url)
{
    // Omitted for brevity.
    if (!string.IsNullOrWhiteSpace(accessToken))
    {
        _settingsService.AuthAccessToken = accessToken;
        _settingsService.AuthIdToken = authResponse.IdentityToken;
        await NavigationService.NavigateToAsync("//Main/Catalog");
    }
}

이 메서드는 NavigationService를 호출하여 애플리케이션을 //Main/Catalog로 라우팅합니다.

탐색 확인 또는 취소

사용자가 탐색을 확인 또는 취소할 수 있도록 앱이 탐색 작업 중에 사용자와 상호 작용해야 할 수 있습니다. 예를 들어 사용자가 데이터 입력 페이지를 완전히 작성하기 전에 탐색을 시도하는 경우에 필요할 수 있습니다. 이 경우 앱은 사용자가 페이지를 벗어나도록 허용하거나 탐색 작업이 발생하기 전에 해당 작업을 취소할 수 있는 알림을 제공해야 합니다. 이 작업은 뷰 모델 클래스에서 알림의 응답을 사용하여 탐색이 호출되는지 여부를 제어하여 수행할 수 있습니다.

요약

.NET MAUI에는 일반적으로 사용자가 UI와 상호 작용하여 발생하거나 앱 자체에서 논리 기반 상태 변경으로 인해 발생하는 페이지 탐색에 대한 지원이 포함되어 있습니다. 그러나 탐색은 MVVM 패턴을 사용하는 앱에서 구현하기 복잡할 수 있습니다.

이 장에서는 뷰 모델에서 뷰 모델 우선 탐색을 수행하는 데 사용되는 NavigationService 클래스를 제공했습니다. 뷰 모델 클래스에 탐색 논리를 배치하면 자동화된 테스트를 통해 논리를 실행할 수 있습니다. 또한 뷰 모델은 특정 비즈니스 규칙이 적용되도록 탐색을 제어하는 논리를 구현할 수 있습니다.