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
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:
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!));
}
}
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
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:
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>();
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.