Injeção de Dependência no .NET MAUI: Guia Completo com Lifetimes, Registro de Serviços e Boas Práticas em 2026

Guia prático de injeção de dependência no .NET MAUI 2026: lifetimes, registro em MauiProgram.cs, integração com MVVM Toolkit, testes e as armadilhas mais comuns em produção.

Injeção de Dependência .NET MAUI: Guia 2026

Atualizado: 1 de setembro de 2026

A injeção de dependência (DI) no .NET MAUI é um padrão de design nativo, fornecido pelo contêiner Microsoft.Extensions.DependencyInjection, que registra serviços no MauiProgram.cs e os entrega automaticamente via construtor a Views, ViewModels e handlers. No time em que lidero, adotar DI de forma disciplinada desde o primeiro commit foi o que permitiu escalar de um app de 20 telas para 120 sem que a base virasse spaghetti. Honestamente, sem isso, teria virado retrabalho puro. Este guia cobre lifetimes (Singleton, Scoped, Transient), registro de serviços de plataforma, integração com o MVVM Toolkit, testes e as armadilhas que ainda derrubam apps em produção em 2026.

  • O .NET MAUI usa Microsoft.Extensions.DependencyInjection nativamente através do MauiAppBuilder.Services no MauiProgram.cs. Não é opcional, é a espinha dorsal do app.
  • Existem três lifetimes: Singleton (uma instância no processo), Scoped (uma por escopo, raramente útil em MAUI) e Transient (nova a cada resolução). ViewModels de páginas quase sempre devem ser Transient.
  • Injeção em construtor é o caminho recomendado. Handler.MauiContext.Services.GetService<T>() deve ser usado apenas como escape hatch em código de infraestrutura.
  • Registrar interfaces de serviços de plataforma (armazenamento seguro, sensores, permissões) permite substituir implementações por fakes nos testes unitários.
  • Contêineres de terceiros (Autofac, DryIoc) ainda podem ser conectados via ConfigureContainer, mas em 2026 raramente valem a complexidade extra.

O que é injeção de dependência no .NET MAUI?

Injeção de dependência é um padrão em que um objeto recebe suas dependências (serviços, repositórios, clientes HTTP) de fora, tipicamente pelo construtor, em vez de instanciá-las diretamente. No .NET MAUI, isso não é uma biblioteca opcional. O próprio MauiAppBuilder expõe um IServiceCollection em builder.Services, o mesmo contêiner que ASP.NET Core, Blazor e Worker Services usam. Ou seja, você já vem com o contêiner oficial da Microsoft ligado, e cada Page, ContentView, handler ou ViewModel pode ser resolvido a partir dele.

Na prática, isso muda como estruturamos código. Em vez de uma ViewModel que faz new HttpClient() e new SecureStorageService() por conta própria (o que amarra teste e reuso), a ViewModel declara public LoginViewModel(IAuthApi api, ISecureStorage storage) e o contêiner entrega essas dependências. Nos apps que enviei para a App Store e Play Store, essa disciplina é o que separa código que sobrevive a três refatorações grandes de código que precisa ser reescrito. Se você ainda constrói ViewModels manualmente com new, você está pagando juros de dívida técnica todo sprint.

Como registrar serviços no .NET MAUI

Todo registro acontece dentro de MauiProgram.CreateMauiApp(), no arquivo MauiProgram.cs da raiz do projeto. O padrão que uso em times de mais de 5 desenvolvedores é dividir o registro em métodos de extensão por camada, para que o arquivo principal continue legível mesmo com dezenas de serviços.

// MauiProgram.cs
public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
            });

        builder.Services
            .AddInfrastructure()   // HttpClient, SQLite, cache
            .AddPlatformServices() // ISecureStorage, IConnectivity
            .AddFeatures();        // ViewModels e Pages por feature

#if DEBUG
        builder.Logging.AddDebug();
#endif
        return builder.Build();
    }
}

// InfrastructureRegistration.cs
public static class InfrastructureRegistration
{
    public static IServiceCollection AddInfrastructure(this IServiceCollection s)
    {
        s.AddHttpClient<IAuthApi, AuthApi>(c =>
            c.BaseAddress = new Uri("https://api.example.com/"));
        s.AddSingleton<IAppDatabase, SqliteAppDatabase>();
        return s;
    }
}

// FeatureRegistration.cs
public static class FeatureRegistration
{
    public static IServiceCollection AddFeatures(this IServiceCollection s)
    {
        s.AddTransient<LoginViewModel>();
        s.AddTransient<LoginPage>();
        s.AddTransient<OrderListViewModel>();
        s.AddTransient<OrderListPage>();
        return s;
    }
}

Registrar as próprias Pages no contêiner permite que a navegação Shell as resolva com injeção completa. Consulte a documentação oficial de DI no .NET MAUI para a lista completa de tipos que o contêiner sabe instanciar automaticamente.

Singleton, Scoped e Transient: qual lifetime escolher

Escolher o lifetime errado é o bug mais comum que vejo em code reviews. A regra que aplico no meu time:

LifetimeInstânciasUse paraEvite para
SingletonUma por appConfiguração, cache em memória, cliente HTTP, banco SQLite, serviços sem estadoViewModels de páginas, qualquer objeto que segure estado por tela
ScopedUma por escopoCasos avançados com IServiceScopeFactory, raro em MAUI puroUso geral; comporta-se como Singleton se você não criar escopos
TransientNova a cada resoluçãoViewModels de página, comandos, validadores, factories levesObjetos caros de construir (HttpClient sem factory, DbContext)

O caso clássico de bug: registrar LoginViewModel como Singleton. Na primeira navegação parece funcionar. Na segunda, os campos vêm preenchidos do usuário anterior, porque a mesma instância é reutilizada. Já perdi uma tarde debugando isso em um app de logística, e o fix era literalmente trocar AddSingleton por AddTransient. Sempre registre ViewModels de página como Transient, a menos que você tenha uma razão explícita para preservar estado entre navegações, e mesmo aí, prefira um serviço Singleton separado que a ViewModel Transient consulta.

Como injetar dependências em ViewModels e Views

Com serviços e páginas registrados, o restante é mecânico. Uma ContentPage declara a ViewModel no construtor, e o contêiner do MAUI resolve tudo em cascata quando a página é navegada via Shell.

// LoginPage.xaml.cs
public partial class LoginPage : ContentPage
{
    public LoginPage(LoginViewModel viewModel)
    {
        InitializeComponent();
        BindingContext = viewModel;
    }
}

// LoginViewModel.cs (usando MVVM Toolkit)
public partial class LoginViewModel : ObservableObject
{
    private readonly IAuthApi _api;
    private readonly ISecureStorage _storage;

    public LoginViewModel(IAuthApi api, ISecureStorage storage)
    {
        _api = api;
        _storage = storage;
    }

    [ObservableProperty] private string email = string.Empty;
    [ObservableProperty] private string password = string.Empty;

    [RelayCommand]
    private async Task SignInAsync()
    {
        var token = await _api.LoginAsync(Email, Password);
        await _storage.SetAsync("auth_token", token.Value);
        await Shell.Current.GoToAsync("//home");
    }
}

Para navegar entre páginas de forma type-safe, registre rotas com Routing.RegisterRoute("orders/detail", typeof(OrderDetailPage)) e chame await Shell.Current.GoToAsync("orders/detail"). Se você quiser um approach mais aprofundado sobre navegação, cobrimos o assunto no nosso guia de Shell Navigation no .NET MAUI. E se você ainda está aprendendo o MVVM Toolkit, o guia de MVVM com CommunityToolkit mostra como source generators reduzem o boilerplate combinado com DI.

Serviços de plataforma com interfaces

APIs específicas de Android e iOS (armazenamento seguro, geolocalização, sensores, notificações) devem ser expostas por trás de interfaces. Isso serve a dois propósitos: substituir a implementação em testes, e permitir que uma implementação falsa (por exemplo, um InMemorySecureStorage) rode no MAUI running no Windows durante o desenvolvimento.

public interface ISecureStorageService
{
    Task<string?> GetAsync(string key);
    Task SetAsync(string key, string value);
    void Remove(string key);
}

public sealed class MauiSecureStorageService : ISecureStorageService
{
    public Task<string?> GetAsync(string key) => SecureStorage.Default.GetAsync(key);
    public Task SetAsync(string key, string value) => SecureStorage.Default.SetAsync(key, value);
    public void Remove(string key) => SecureStorage.Default.Remove(key);
}

// Registro
builder.Services.AddSingleton<ISecureStorageService, MauiSecureStorageService>();

Em apps que precisam de comportamento distinto por plataforma, use #if ANDROID / #if IOS dentro do registro para vincular implementações diferentes à mesma interface. Isso mantém o código de features 100% agnóstico de plataforma. As ViewModels dependem apenas de ISecureStorageService, e o contêiner injeta a implementação correta em tempo de compilação por target framework.

Como testes se beneficiam de DI bem feita

O maior retorno de investir em DI aparece no dia em que você tenta escrever um teste. Uma LoginViewModel que depende de interfaces pode ser instanciada em um teste unitário sem inicializar o MAUI, sem contexto Android, sem simulador iOS. Apenas C# puro. Isso reduz o tempo de suite de minutos para segundos.

// LoginViewModelTests.cs (xUnit + NSubstitute)
[Fact]
public async Task SignIn_StoresToken_OnSuccess()
{
    var api = Substitute.For<IAuthApi>();
    var storage = Substitute.For<ISecureStorageService>();
    api.LoginAsync("[email protected]", "pw").Returns(new AuthToken("abc123"));

    var sut = new LoginViewModel(api, storage)
    {
        Email = "[email protected]",
        Password = "pw"
    };

    await sut.SignInCommand.ExecuteAsync(null);

    await storage.Received(1).SetAsync("auth_token", "abc123");
}

Se você quer aprofundar em estratégias de teste (unit, integração, UI com Appium), veja nosso guia completo de testes no .NET MAUI. A regra prática que uso: se você não consegue escrever um teste unitário para uma ViewModel sem instanciar o app inteiro, sua DI está errada.

Contêineres de terceiros: Autofac e DryIoc

O contêiner padrão da Microsoft cobre 95% das necessidades. Nos casos onde você precisa de recursos avançados (resolução por chave, decoradores automáticos, interceptação AOP), dá para trocar o contêiner via ConfigureContainer:

// Program.cs com Autofac
builder.ConfigureContainer(new AutofacServiceProviderFactory(cb =>
{
    cb.RegisterType<LoginViewModel>().AsSelf();
    cb.RegisterDecorator<LoggingAuthApi, IAuthApi>();
}));

Na minha experiência, esse tipo de complexidade raramente compensa em apps móveis. O tempo de startup importa muito no mobile, e o contêiner nativo é o mais leve. Só troque se você estiver migrando um app Xamarin que já usava Autofac pesado, e mesmo assim, use a migração como oportunidade para simplificar. Se você está vindo do Xamarin.Forms, o nosso guia de migração para .NET MAUI mostra o caminho mais seguro para trazer DI junto.

Armadilhas comuns e como evitá-las

Depois de revisar centenas de PRs em apps MAUI, essas são as cinco armadilhas que aparecem com mais frequência:

  1. Service Locator disfarçado. Chamadas como Handler.MauiContext.Services.GetService<IAuthApi>() espalhadas pelo código transformam DI em um anti-pattern. Restrinja essas chamadas à camada de infraestrutura.
  2. ViewModel Singleton. Como já mencionado, quase nunca é o que você quer. Se precisar de estado persistente, injete um serviço de estado Singleton na ViewModel Transient.
  3. HttpClient sem AddHttpClient. Registrar um HttpClient como Singleton "cru" causa DNS staleness. Use sempre services.AddHttpClient<T, TImpl>(), que aciona HttpClientFactory por baixo dos panos.
  4. Registro em App.xaml.cs. Toda configuração de DI vive em MauiProgram.cs. Registros dispersos quebram source generators e AOT.
  5. Ignorar IDisposable. O contêiner só descarta objetos que ele mesmo criou. Se você faz new SqliteConnection() dentro de uma ViewModel, ele nunca chama Dispose. Sempre registre e resolva.

Perguntas Frequentes

O .NET MAUI usa injeção de dependência nativamente?

Sim. Todo projeto .NET MAUI vem com Microsoft.Extensions.DependencyInjection integrado no MauiAppBuilder. Você registra serviços em builder.Services dentro de MauiProgram.cs e o contêiner os resolve em construtores de Views, ViewModels e handlers.

Qual a diferença entre Singleton, Scoped e Transient no MAUI?

Singleton cria uma única instância para toda a vida do app. Transient cria uma nova instância a cada resolução. Scoped cria uma por escopo, mas como o MAUI não abre escopos automaticamente, comporta-se como Singleton na prática. Use Singleton para serviços sem estado, Transient para ViewModels de páginas.

Como injetar dependências em uma View XAML no MAUI?

Declare as dependências no construtor da ContentPage, registre a página em builder.Services.AddTransient<MinhaPage>(), e navegue via Shell (Shell.Current.GoToAsync). O contêiner resolve tanto a Page quanto todas as dependências em cascata.

Posso usar Autofac ou DryIoc com .NET MAUI?

Sim, através de builder.ConfigureContainer(new AutofacServiceProviderFactory(...)). Na prática, o contêiner nativo cobre a grande maioria dos cenários com menos overhead de startup, então só troque se você precisar de recursos avançados como decoradores automáticos ou resolução por chave.

Por que minha ViewModel mantém dados antigos entre navegações?

Provavelmente ela está registrada como Singleton em vez de Transient. Registre com services.AddTransient<MinhaViewModel>() para garantir uma instância nova a cada navegação, e mova o estado compartilhado para um serviço Singleton separado que a ViewModel consulta.

Priya Sharma
Sobre o Autor Priya Sharma

Cross-platform engineering lead who's shipped apps to millions on both Play Store and App Store. Believes shared codebases shouldn't mean shared mediocrity.