Compartilhar via


Adicionar injeção de dependência

A DI (injeção de dependência) ajuda você a gerenciar o ciclo de vida de seus ViewModels e serviços. Isso torna seu código mais testável e mais fácil de manter. Nesta etapa, você configura o DI em seu aplicativo e atualiza seus modelos para usar um serviço de arquivo para operações de arquivo.

Para obter mais informações sobre a estrutura de injeção de dependência do .NET, consulte injeção de dependência do .NET e o tutorial Use a injeção de dependência no .NET.

Instalar pacotes Microsoft.Extensions:

Adicione suporte de DI aos seus projetos.

  1. Instale Microsoft.Extensions.DependencyInjection nos projetos WinUINotes e WinUINotes.Bus :

    dotnet add WinUINotes package Microsoft.Extensions.DependencyInjection
    dotnet add WinUINotes.Bus package Microsoft.Extensions.DependencyInjection
    

Criar uma interface e implementação do serviço de arquivo

  1. No projeto WinUINotes.Bus , crie uma nova pasta chamada Serviços.

  2. Adicionar um arquivo IFileService.csde interface:

    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Windows.Storage;
    
    namespace WinUINotes.Services
    {
        public interface IFileService
        {
            Task<IReadOnlyList<IStorageItem>> GetStorageItemsAsync();
            Task<IReadOnlyList<IStorageItem>> GetStorageItemsAsync(IStorageFolder storageFolder);
            Task<string> GetTextFromFileAsync(IStorageFile file);
            Task CreateOrUpdateFileAsync(string filename, string contents);
            Task DeleteFileAsync(string filename);
            bool FileExists(string filename);
            IStorageFolder GetLocalFolder();
        }
    }
    

    A interface do serviço de arquivo define métodos para operações de arquivo. Ele abstrai dos ViewModels e Modelos os detalhes do tratamento de arquivos. Os parâmetros e os valores retornados são todos tipos básicos do .NET ou interfaces. Esse design garante que o serviço possa ser facilmente ridicularizado ou substituído em testes de unidade, promovendo acoplamento flexível e testabilidade.

  3. Adicione o arquivo WindowsFileService.csde implementação:

    using System;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Windows.Storage;
    
    namespace WinUINotes.Services
    {
        public class WindowsFileService : IFileService
        {
             public StorageFolder storageFolder;
    
             public WindowsFileService(IStorageFolder storageFolder)
             {
                 this.storageFolder = (StorageFolder)storageFolder;
    
                 if (this.storageFolder is null)
                 {
                     throw new ArgumentException("storageFolder must be of type StorageFolder", nameof(storageFolder));
                 }
             }
    
             public async Task CreateOrUpdateFileAsync(string filename, string contents)
             {
                 // Save the note to a file.
                 StorageFile storageFile = (StorageFile)await storageFolder.TryGetItemAsync(filename);
                 if (storageFile is null)
                 {
                     storageFile = await storageFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
                 }
                 await FileIO.WriteTextAsync(storageFile, contents);
             }
    
         public async Task DeleteFileAsync(string filename)
         {
             // Delete the note from the file system.
             StorageFile storageFile = (StorageFile)await storageFolder.TryGetItemAsync(filename);
             if (storageFile is not null)
             {
                 await storageFile.DeleteAsync();
             }
         }
    
         public bool FileExists(string filename)
         {
             StorageFile storageFile = (StorageFile)storageFolder.TryGetItemAsync(filename).AsTask().Result;
             return storageFile is not null;
         }
    
         public IStorageFolder GetLocalFolder()
         {
             return storageFolder;
         }
    
         public async Task<IReadOnlyList<IStorageItem>> GetStorageItemsAsync()
         {
             return await storageFolder.GetItemsAsync();
         }
    
         public async Task<IReadOnlyList<IStorageItem>> GetStorageItemsAsync(IStorageFolder folder)
         {
             return await folder.GetItemsAsync();
         }
    
         public async Task<string> GetTextFromFileAsync(IStorageFile file)
         {
             return await FileIO.ReadTextAsync(file);
         }
        }
    }
    

A WindowsFileService implementação fornece operações de arquivo concretas usando as APIs de armazenamento do WinRT (Windows Runtime) e do .NET:

  • Injeção de construtor: o serviço aceita um IStorageFolder em seu construtor. Essa abordagem permite que você configure o local de armazenamento ao instanciar o serviço. Essa abordagem torna o serviço flexível e testável.
  • CreateOrUpdateFileAsync(): esse método usa TryGetItemAsync() para verificar se já existe um arquivo. Se isso acontecer, o método atualizará o arquivo existente. Caso contrário, ele criará um novo arquivo usando CreateFileAsync(). Essa abordagem manipula cenários de criação e atualização em um único método.
  • DeleteFileAsync(): antes de excluir um arquivo, esse método verifica se o arquivo existe usando TryGetItemAsync(). Essa verificação impede que exceções sejam geradas ao tentar excluir arquivos inexistentes.
  • FileExists(): esse método síncrono verifica a existência do arquivo chamando o assíncrono TryGetItemAsync() e bloqueando com .Result. Embora essa abordagem geralmente não seja recomendada, ela é usada aqui para dar suporte ao CanDelete() método de validação no ViewModel, que deve ser síncrono.
  • Métodos de item de armazenamento: os métodos GetStorageItemsAsync() e GetTextFromFileAsync() fornecem acesso a arquivos e seu conteúdo usando as APIs de armazenamento do WinRT. Esses métodos permitem que os Modelos carreguem e enumerem anotações.

Ao implementar a IFileService interface, você pode facilmente substituir essa classe por uma implementação simulada para teste ou um provedor de armazenamento diferente, se necessário.

Saiba mais nos documentos:

Configurar a injeção de dependência no App.xaml.cs

Antes de atualizar os modelos e ViewModels para usar o serviço de arquivo, configure a injeção de dependência para que o serviço possa ser resolvido e injetado nos construtores.

Atualize o App.xaml.cs arquivo para configurar o contêiner de DI:

using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
using WinUINotes.ViewModels;

namespace WinUINotes;

public partial class App : Application
{
    private readonly IServiceProvider _serviceProvider;

    public App()
    {
        Services = ConfigureServices();
        this.InitializeComponent();
    }

    private static IServiceProvider ConfigureServices()
    {
        var services = new ServiceCollection();

        // Services
        services.AddSingleton<Services.IFileService>(x =>
            ActivatorUtilities.CreateInstance<Services.WindowsFileService>(x,
                            Windows.Storage.ApplicationData.Current.LocalFolder)
        );

        // ViewModels
        services.AddTransient<AllNotesViewModel>();
        services.AddTransient<NoteViewModel>();

        return services.BuildServiceProvider();
    }

    protected override void OnLaunched(LaunchActivatedEventArgs args)
    {
        m_window = new MainWindow();
        m_window.Activate();
    }

    public IServiceProvider Services { get; }

    private Window? m_window;

    public new static App Current => (App)Application.Current;
}

Essa configuração configura o contêiner de injeção de dependência com todos os serviços necessários:

  • ConfigureServices() método: um método estático que cria e configura a coleção de serviços. Separar esse método torna a configuração mais mantenedível e mais fácil de testar.
  • Services Propriedade: Uma propriedade de instância que contém o IServiceProvider. O construtor define essa propriedade chamando ConfigureServices().
  • App.Current propriedade estática: fornece acesso conveniente à instância atual App , o que é útil quando modelos ou outras classes precisam acessar o provedor de serviços.
  • IFileService registro: usa ActivatorUtilities.CreateInstance para criar uma instância de WindowsFileService com ApplicationData.Current.LocalFolder como parâmetro. Essa abordagem permite que o parâmetro do construtor seja injetado no momento do registro. Registre o serviço como um singleton, pois as operações de arquivo são sem estado e uma única instância pode ser compartilhada dentro do aplicativo.
  • Cadastro de ViewModels: registre ambos os ViewModels como transitórios, ou seja, uma nova instância é criada sempre que uma for solicitada. Essa abordagem garante que cada página obtenha sua própria instância ViewModel com estado limpo.

Modelos e outras classes podem acessar o provedor de serviços através de App.Current.Services.GetService() para recuperar serviços registrados quando necessário.

Saiba mais nos documentos:

Atualizar modelos para usar o serviço de arquivo

Agora que o serviço de arquivo está disponível por meio da injeção de dependência, atualize as classes de modelo para usá-lo. Os modelos recebem o serviço de arquivo e o utilizam para todas as operações de arquivos.

Atualizar o modelo Note

Atualize a Note classe para aceitar o serviço de arquivo e usá-lo para salvar, excluir e operações de existência de arquivo:

using System;
using System.Threading.Tasks;
using WinUINotes.Services;

namespace WinUINotes.Models;

public class Note
{
    private IFileService fileService;
    public string Filename { get; set; } = string.Empty;
    public string Text { get; set; } = string.Empty;
    public DateTime Date { get; set; } = DateTime.Now;

    public Note(IFileService fileService)
    {
        Filename = "notes" + DateTime.Now.ToBinary().ToString() + ".txt";
        this.fileService = fileService;
    }

    public async Task SaveAsync()
    {
        await fileService.CreateOrUpdateFileAsync(Filename, Text);
    }

    public async Task DeleteAsync()
    {
        await fileService.DeleteFileAsync(Filename);
    }

    public bool NoteFileExists()
    {
        return fileService.FileExists(Filename);
    }
}

O Note modelo agora recebe o serviço de arquivo por meio de injeção de construtor:

  • Construtor: aceita um IFileService parâmetro, tornando a dependência explícita e necessária. Esse design promove a testabilidade e garante que o modelo sempre tenha acesso ao serviço de arquivo de que precisa.
  • Geração de nome de arquivo: o construtor gera automaticamente um nome de arquivo exclusivo usando o timestamp atual, garantindo que cada anotação tenha um nome de arquivo distinto.
  • Operações de arquivo: os SaveAsync(), DeleteAsync(), e NoteFileExists() todos os métodos delegam para o serviço de arquivo injetado, mantendo o modelo focado em coordenar operações em vez de implementar detalhes de I/O de arquivo.

Essa abordagem elimina a necessidade de o modelo usar o padrão do localizador de serviço (acessando App.Services diretamente), o que melhora a capacidade de teste e deixa as dependências claras.

Atualizar o modelo do AllNotes

Atualize a AllNotes classe para carregar anotações do armazenamento usando o serviço de arquivo:

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Windows.Storage;
using WinUINotes.Services;

namespace WinUINotes.Models;

public class AllNotes
{
        private IFileService fileService;
        public ObservableCollection<Note> Notes { get; set; } = [];

        public AllNotes(IFileService fileService)
        {
            this.fileService = fileService;
        }

        public async Task LoadNotes()
        {
            Notes.Clear();
            await GetFilesInFolderAsync(fileService.GetLocalFolder());
        }

        private async Task GetFilesInFolderAsync(IStorageFolder folder)
        {
            // Each StorageItem can be either a folder or a file.
            IReadOnlyList<IStorageItem> storageItems =
                                        await fileService.GetStorageItemsAsync(folder);
            foreach (IStorageItem item in storageItems)
            {
                if (item.IsOfType(StorageItemTypes.Folder))
                {
                    // Recursively get items from subfolders.
                    await GetFilesInFolderAsync((IStorageFolder)item);
                }
                else if (item.IsOfType(StorageItemTypes.File))
                {
                    IStorageFile file = (IStorageFile)item;
                    Note note = new(fileService)
                    {
                        Filename = file.Name,
                        Text = await fileService.GetTextFromFileAsync(file),
                        Date = file.DateCreated.DateTime
                    };
                    Notes.Add(note);
                }
            }
        }
}

O modelo AllNotes recebe o serviço de arquivo através de injeção de construtor, assim como o modelo Note. Como essa classe está no WinUINotes.Bus projeto, ela não pode acessar App.Current.Services do WinUINotes projeto (devido a restrições de referência do projeto).

O LoadNotes() método chama o método privado GetFilesInFolderAsync() para enumerar recursivamente todos os arquivos na pasta de armazenamento local e suas subpastas. Para cada item de armazenamento:

  1. Se for uma pasta, o método chama a si mesmo recursivamente para processar o conteúdo da pasta
  2. Se for um arquivo, ele criará uma nova Note instância com o serviço de arquivo injetado
  3. A anotação Filename é definida como o nome do arquivo
  4. A anotação Text é preenchida lendo o conteúdo do arquivo usando GetTextFromFileAsync()
  5. A anotação Date está definida como a data de criação do arquivo
  6. A anotação é adicionada à Notes coleção observável

Essa abordagem garante que todas as anotações carregadas do armazenamento tenham acesso ao serviço de arquivos de que precisam para operações futuras de salvamento e exclusão.

Atualizar ViewModels para usar o serviço de arquivo

Com os modelos agora usando o serviço de arquivo, você precisa atualizar os ViewModels. No entanto, como os modelos lidam diretamente com as operações de arquivo, os ViewModels se concentram principalmente na orquestração dos modelos e no gerenciamento de propriedades observáveis.

Atualizar AllNotesViewModel

Atualize o AllNotesViewModel para trabalhar com o modelo atualizado AllNotes

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using WinUINotes.Models;
using WinUINotes.Services;

namespace WinUINotes.ViewModels
{
    public partial class AllNotesViewModel : ObservableObject
    {
        private readonly AllNotes allNotes;

        [ObservableProperty]
        private ObservableCollection<Note> notes;

        public AllNotesViewModel(IFileService fileService)
        {
            allNotes = new AllNotes(fileService);
            notes = new ObservableCollection<Note>();
        }

        [RelayCommand]
        public async Task LoadAsync()
        {
            await allNotes.LoadNotes();
            Notes.Clear();
            foreach (var note in allNotes.Notes)
            {
                Notes.Add(note);
            }
        }
    }
}

O que mudou desde a Etapa 2?

A alteração principal é a adição do parâmetro IFileService ao construtor. Na Etapa 2, o ViewModel instanciou AllNotes com um construtor sem parâmetros (allNotes = new AllNotes()). Agora que o modelo AllNotes exige que o serviço de arquivo execute suas operações, o ViewModel recebe IFileService por meio da injeção do construtor e passa IFileService para o modelo.

Essa alteração mantém o fluxo de dependência adequado – o serviço de arquivo é injetado no nível superior (ViewModel) e flui para baixo para o modelo. O ViewModel continua focado em coordenar o processo de carregamento e manter a coleção observável Notes sincronizada com os dados do modelo, sem precisar saber os detalhes de implementação de como os arquivos são carregados.

Atualizar NoteViewModel

Atualize o NoteViewModel para injetar o serviço de arquivos e use o sistema de mensagens do Kit de Ferramentas MVVM:

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using System;
using System.Threading.Tasks;
using WinUINotes.Models;
using WinUINotes.Services;

namespace WinUINotes.ViewModels
{
    public partial class NoteViewModel : ObservableObject
    {
        private Note note;
        private IFileService fileService;

        [ObservableProperty]
        [NotifyCanExecuteChangedFor(nameof(SaveCommand))]
        [NotifyCanExecuteChangedFor(nameof(DeleteCommand))]
        private string filename = string.Empty;

        [ObservableProperty]
        [NotifyCanExecuteChangedFor(nameof(SaveCommand))]
        private string text = string.Empty;

        [ObservableProperty]
        private DateTime date = DateTime.Now;

        public NoteViewModel(IFileService fileService)
        {
            this.fileService = fileService;
            this.note = new Note(fileService);
            this.Filename = note.Filename;
        }

        public void InitializeForExistingNote(Note note)
        {
            this.note = note;
            this.Filename = note.Filename;
            this.Text = note.Text;
            this.Date = note.Date;
        }

        [RelayCommand(CanExecute = nameof(CanSave))]
        private async Task Save()
        {
            note.Filename = this.Filename;
            note.Text = this.Text;
            note.Date = this.Date;
            await note.SaveAsync();

            // Check if the DeleteCommand can now execute
            // (it can if the file now exists)
            DeleteCommand.NotifyCanExecuteChanged();
        }

        private bool CanSave()
        {
            return note is not null
                && !string.IsNullOrWhiteSpace(this.Text)
                && !string.IsNullOrWhiteSpace(this.Filename);
        }

        [RelayCommand(CanExecute = nameof(CanDelete))]
        private async Task Delete()
        {
            await note.DeleteAsync();
            note = new Note(fileService);
            // Send a message from some other module
            WeakReferenceMessenger.Default.Send(new NoteDeletedMessage(note));
        }

        private bool CanDelete()
        {
            // Note: This is to illustrate how commands can be
            // enabled or disabled.
            // In a real application, you shouldn't perform
            // file operations in your CanExecute logic.
            return note is not null
                && !string.IsNullOrWhiteSpace(this.Filename)
                && this.note.NoteFileExists();
        }
    }
}

O que mudou desde a Etapa 2?

Várias alterações importantes dão suporte à injeção de dependência e à comunicação entre ViewModel:

  1. Injeção do serviço de arquivos: o construtor agora aceita IFileService como um parâmetro e o armazena em um campo. Esse serviço é passado para o Note modelo ao criar novas instâncias, garantindo que todas as anotações possam executar operações de arquivo.

  2. WeakReferenceMessenger: o Delete() método agora usa o Kit de Ferramentas MVVM WeakReferenceMessenger.Default.Send() para enviar um NoteDeletedMessage após a exclusão de uma anotação. Essa abordagem permite o acoplamento flexível entre ViewModels – outras partes do aplicativo (como NotePage) podem escutar essa mensagem e responder adequadamente (por exemplo, navegando de volta para a lista de anotações, que foi atualizada) sem a NoteViewModel necessidade de uma referência direta a elas.

O WeakReferenceMessenger é um recurso fundamental do Kit de Ferramentas MVVM que impede vazamentos de memória usando referências fracas. Os componentes podem assinar mensagens sem criar referências fortes que impeçam a coleta de lixo.

Saiba mais nos documentos:

Criar a classe NoteDeletedMessage

A WeakReferenceMessenger precisa de uma classe de mensagem para enviar entre componentes. Crie uma nova classe para representar o evento de exclusão de notas:

  1. No projeto WinUINotes.Bus , adicione um novo arquivo NoteDeletedMessage.csde classe:

    using CommunityToolkit.Mvvm.Messaging.Messages;
    using WinUINotes.Models;
    
    namespace WinUINotes
    {
        public class NoteDeletedMessage : ValueChangedMessage<Note>
        {
            public NoteDeletedMessage(Note note) : base(note)
            {
            }
        }
    }
    

Essa classe de mensagem herda de ValueChangedMessage<Note>, que é um tipo de mensagem especializado fornecido pelo Kit de Ferramentas MVVM para carregar notificações de alteração de valor. O construtor aceita um Note e o passa para a classe base, tornando-o disponível para destinatários de mensagens por meio da Value propriedade. Quando NoteViewModel envia essa mensagem, qualquer componente que se inscreve para NoteDeletedMessage a recebe e pode acessar a nota excluída por meio da propriedade Value.

Como funciona o sistema de mensagens no Kit de Ferramentas do MVVM:

  1. Remetente: O método NoteViewModel.Delete() envia a mensagem usando WeakReferenceMessenger.Default.Send(new NoteDeletedMessage(note)).
  2. Receptor: páginas (como NotePage) podem se registrar para receber mensagens implementando IRecipient<NoteDeletedMessage> e registrando-se com o mensageiro. Quando a mensagem é recebida, a página pode navegar de volta para a lista de anotações.
  3. Acoplamento solto: o remetente não precisa saber quem (se alguém) está ouvindo. O receptor não precisa de uma referência direta ao remetente. Essa configuração mantém seus componentes independentes e testáveis.

A abordagem de referência fraca significa que, se um componente for coletado como lixo, sua assinatura de mensagens será automaticamente limpa, sem causar vazamentos de memória.

Atualize páginas para usar a injeção de dependência

Atualize os construtores de página para receber os ViewModels por meio da DI.

Atualizar AllNotesPage.xaml.cs

using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
using WinUINotes.ViewModels;

namespace WinUINotes.Views
{
    public sealed partial class AllNotesPage : Page
    {
        private AllNotesViewModel? viewModel;

        public AllNotesPage()
        {
            this.InitializeComponent();
            viewModel = App.Current.Services.GetService<AllNotesViewModel>();
        }

        private void NewNoteButton_Click(object sender, RoutedEventArgs e)
        {
            Frame.Navigate(typeof(NotePage));
        }

        private void ItemsView_ItemInvoked(ItemsView sender, ItemsViewItemInvokedEventArgs args)
        {
            Frame.Navigate(typeof(NotePage), args.InvokedItem);
        }

        protected override async void OnNavigatedTo(NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            if (viewModel is not null)
            {
                await viewModel.LoadAsync();
            }
        }
    }
}

O que mudou desde a Etapa 2?

O aplicativo agora obtém o AllNotesViewModel do contêiner de injeção de dependência usando App.Current.Services.GetService<AllNotesViewModel>() em vez de criá-lo diretamente com new AllNotesViewModel(). Essa abordagem tem vários benefícios:

  1. Resolução automática de dependência: o contêiner de DI fornece automaticamente a IFileService dependência necessária que AllNotesViewModel requer em seu construtor.
  2. Gerenciamento do ciclo de vida: o contêiner de DI gerencia o ciclo de vida do ViewModel de acordo com a forma como ele foi registrado (como transitório nesse caso, fornecendo uma nova instância).
  3. Testabilidade: esse padrão facilita a troca de implementações ou dependências simuladas em testes.
  4. Manutenção: se as dependências do ViewModel forem alteradas no futuro, você só precisará atualizar a configuração de DI, não todos os locais onde o ViewModel é criado.

O restante do código permanece o mesmo. O OnNavigatedTo() método ainda chama LoadAsync() para atualizar a lista de anotações quando o usuário navega até esta página.

Atualizar NotePage.xaml.cs

using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
using WinUINotes.Models;
using WinUINotes.ViewModels;

namespace WinUINotes.Views
{
    public sealed partial class NotePage : Page
    {
        private NoteViewModel? noteVm;

        public NotePage()
        {
            this.InitializeComponent();
        }

        public void RegisterForDeleteMessages()
        {
            WeakReferenceMessenger.Default.Register<NoteDeletedMessage>(this, (r, m) =>
            {
                if (Frame.CanGoBack)
                {
                    Frame.GoBack();
                }
            });
        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);
            noteVm = App.Current.Services.GetService<NoteViewModel>();
            RegisterForDeleteMessages();

            if (e.Parameter is Note note && noteVm is not null)
            {
                noteVm.InitializeForExistingNote(note);
            }
        }
    }
}

O que mudou desde a Etapa 2?

Várias alterações importantes integram recursos de injeção de dependência e mensagens:

  1. ViewModel do contêiner de DI: NoteViewModel agora é recuperado do contêiner de injeção de dependência usando App.Current.Services.GetService<NoteViewModel>() no método OnNavigatedTo() em vez de ser instanciado diretamente. Essa abordagem garante que o ViewModel receba automaticamente sua dependência necessária IFileService .
  2. Registro de mensagem: O novo RegisterForDeleteMessages() método assina NoteDeletedMessage usando o WeakReferenceMessenger. Quando uma anotação é excluída (do NoteViewModel.Delete() método), essa página recebe a mensagem e navega de volta para a lista de anotações usando Frame.GoBack().
  3. Padrão de mensagens: esse padrão demonstra o acoplamento flexível habilitado pelo sistema de mensagens do Kit de Ferramentas MVVM. O NoteViewModel não precisa saber sobre a navegação ou a estrutura da página – ele simplesmente envia uma mensagem quando uma anotação é excluída e a página manipula a resposta de navegação de forma independente.
  4. Tempo de ciclo de vida: o ViewModel é instanciado e o registro de mensagem ocorre em OnNavigatedTo(), garantindo que tudo seja inicializado corretamente quando a página se tornar ativa.

Esse padrão separa as preocupações efetivamente: o ViewModel se concentra na lógica de negócios e nas operações de dados, enquanto a página lida com preocupações específicas da interface do usuário, como navegação.