Módulo 6 · Aplicações Desktop — Capítulo 04

Navegação e Gerenciamento de Estado

Sem URLs nem roteamento HTTP — como uma aplicação desktop troca de tela mantendo o desacoplamento MVVM, e onde guardar estado que precisa sobreviver entre telas.

1. Navegação não é routing

No Módulo 3, navegar entre telas significava mudar a URL, resolvida por routing (capítulo 2) para um Controller/Action diferente. Numa aplicação desktop não existe URL — "navegar" significa abrir uma nova janela, ou trocar o conteúdo exibido dentro da janela atual. A forma de fazer isso sem quebrar o desacoplamento MVVM é através de um serviço de navegação injetado nos ViewModels.

2. Um serviço de navegação simples

C# INavegacaoService
public interface INavegacaoService
{
    void AbrirTelaDetalhesCliente(int clienteId);
    void FecharJanelaAtual();
}

public class NavegacaoService : INavegacaoService
{
    private readonly IServiceProvider _provedor;
    public NavegacaoService(IServiceProvider provedor) => _provedor = provedor;

    public void AbrirTelaDetalhesCliente(int clienteId)
    {
        var viewModel = _provedor.GetRequiredService<ClienteDetalhesViewModel>();
        viewModel.ClienteId = clienteId;

        var janela = new ClienteDetalhesView { DataContext = viewModel };
        janela.Show();
    }

    public void FecharJanelaAtual()
        => Application.Current.Windows.OfType<Window>().LastOrDefault(w => w.IsActive)?.Close();
}

Um ClienteListaViewModel aciona a navegação assim, sem nunca referenciar a classe ClienteDetalhesView diretamente:

C# ClienteListaViewModel.cs
public class ClienteListaViewModel : ViewModelBase
{
    private readonly INavegacaoService _navegacao;

    public ICommand AbrirDetalhesCommand { get; }

    public ClienteListaViewModel(INavegacaoService navegacao)
    {
        _navegacao = navegacao;
        AbrirDetalhesCommand = new RelayCommand(
            parametro => _navegacao.AbrirTelaDetalhesCliente((int)parametro!));
    }
}
Conectando com o Módulo 1

INavegacaoService é injetado por construtor, exatamente como IClienteRepository — o mesmo padrão de Dependency Injection do capítulo 12 do Módulo 1, agora resolvendo "abrir uma tela" em vez de "buscar um dado".

3. Registrando ViewModels e Views no container de DI

C# App.xaml.cs
public partial class App : Application
{
    private readonly IHost _host;

    public App()
    {
        _host = Host.CreateDefaultBuilder()
            .ConfigureServices((contexto, servicos) =>
            {
                servicos.AddSingleton<INavegacaoService, NavegacaoService>();
                servicos.AddScoped<IClienteRepository>(_ =>
                    new ClienteRepository(contexto.Configuration.GetConnectionString("Default")!));

                servicos.AddTransient<ClienteListaViewModel>();
                servicos.AddTransient<ClienteDetalhesViewModel>();
                servicos.AddTransient<MainWindow>();
            })
            .Build();
    }

    protected override void OnStartup(StartupEventArgs e)
    {
        var janelaPrincipal = _host.Services.GetRequiredService<MainWindow>();
        janelaPrincipal.Show();
        base.OnStartup(e);
    }
}

Sim — o mesmo container de Dependency Injection do ASP.NET Core (Módulo 3, capítulo 6) funciona identicamente numa aplicação WPF, através de Microsoft.Extensions.Hosting. A diferença é só quem inicia o host: em vez de app.Run() escutando requisições HTTP, é OnStartup abrindo a primeira janela.

4. Onde guardar estado entre telas

Diferente de uma API stateless (Módulo 4, capítulo 1), uma aplicação desktop pode perfeitamente manter estado em memória durante toda a sessão do usuário — não há "fim de requisição" apagando tudo. Um serviço Singleton (Módulo 3, capítulo 6) é o lugar natural para isso:

C# estado de sessão compartilhado
public class SessaoUsuario
{
    public int? ClienteIdLogado { get; set; }
    public string? NomeUsuario { get; set; }
}

// registrado como Singleton — vive durante toda a execução da aplicação
servicos.AddSingleton<SessaoUsuario>();
Atenção

Isso é exatamente o cenário oposto ao aviso do Módulo 3 (capítulo 6) sobre nunca registrar DbContext/repositórios como Singleton — aqui, o estado de sessão é seguro como Singleton (é intencional que viva para sempre e seja compartilhado), mas o acesso a dados continua precisando de escopo próprio a cada operação, como no Módulo 5 (Worker Service, capítulo 3).

5. Onde isso te leva

Com navegação e estado cobertos, o último capítulo antes do projeto prático trata de como conectar tudo isso às procedures do Módulo 2 de forma consistente com o resto da aplicação.

📌 Resumo do capítulo

  • Navegação desktop usa um serviço injetado (INavegacaoService), nunca uma referência direta de um ViewModel para uma classe View.
  • O mesmo container de DI do ASP.NET Core funciona em WPF via Microsoft.Extensions.Hosting.
  • Estado de sessão pode legitimamente ser Singleton numa app desktop — diferente do cuidado necessário com DbContext/repositórios.

✏️ Praticando

  1. Implemente INavegacaoService e use-o para abrir uma tela de detalhes a partir de um Command na lista de clientes.
  2. Registre todos os ViewModels e Views no container de DI em App.xaml.cs.
  3. Crie SessaoUsuario como Singleton e use-o para exibir o nome do usuário logado em todas as telas.