Partilhar via


Injeção de dependência no ASP.NET Core

Observação

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 9 deste artigo.

Advertência

Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.

Importante

Estas informações referem-se a um produto de pré-lançamento que pode ser substancialmente modificado antes de ser lançado comercialmente. A Microsoft não oferece garantias, expressas ou implícitas, em relação às informações fornecidas aqui.

Para a versão atual, consulte a versão .NET 9 deste artigo.

Por Kirk Larkin, Steve Smithe Brandon Dahler

O ASP.NET Core suporta o padrão de design de software de injeção de dependência (DI), que é uma técnica para alcançar de Inversão de Controle (IoC) entre classes e suas dependências.

Para obter Blazor orientações sobre DI, que acrescentam ou substituem as orientações neste artigo, consulte injeção de dependência do ASP.NET Core Blazor.

Para obter informações específicas para a injeção de dependência em controladores MVC, consulte Injeção de dependência em controladores no ASP.NET Core.

Para obter informações sobre como usar a injeção de dependência em aplicativos que não sejam aplicativos Web, consulte injeção de dependência no .NET.

Para obter informações sobre a injeção de opções por dependência, consulte padrão Opções no ASP.NET Core.

Este artigo fornece informações sobre a injeção de dependência no ASP.NET Core. A documentação principal sobre o uso da injeção de dependência está contida em Injeção de Dependência no .NET.

Exibir ou baixar código de exemplo (como descarregar)

Visão geral da injeção de dependência

Um de dependência é um objeto do qual outro objeto depende. Examine a seguinte classe MyDependency com um método WriteMessage do qual outras classes dependem:

public class MyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
    }
}

Uma classe pode criar uma instância da classe MyDependency para fazer uso de seu método WriteMessage. No exemplo a seguir, a classe MyDependency é uma dependência da classe IndexModel:


public class IndexModel : PageModel
{
    private readonly MyDependency _dependency = new MyDependency();

    public void OnGet()
    {
        _dependency.WriteMessage("IndexModel.OnGet");
    }
}

A classe cria e depende diretamente da classe MyDependency. As dependências de código, como no exemplo anterior, são problemáticas e devem ser evitadas pelos seguintes motivos:

  • Para substituir MyDependency por uma implementação diferente, a classe IndexModel deve ser modificada.
  • Se MyDependency tiver dependências, elas também deverão ser configuradas pela classe IndexModel. Num projeto grande com várias classes dependentes de MyDependency, o código de configuração fica espalhado pelo aplicativo.
  • Esta implementação é difícil de testar por unidade.

A injeção de dependência resolve esses problemas através de:

  • O uso de uma interface ou classe base para abstrair a implementação de dependência.
  • Registro da dependência em um contêiner de serviço. ASP.NET Core fornece um contêiner de serviço integrado, IServiceProvider. Normalmente, os serviços são registrados no arquivo Program.cs do aplicativo.
  • Injeção do serviço no construtor da classe onde ele é usado. A estrutura assume a responsabilidade de criar uma instância da dependência e eliminá-la quando ela não for mais necessária.

No aplicativo de exemplo , a interface IMyDependency define o método WriteMessage:

public interface IMyDependency
{
    void WriteMessage(string message);
}

Esta interface é implementada por um tipo concreto, MyDependency:

public class MyDependency : IMyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
    }
}

A aplicação de exemplo regista o serviço IMyDependency com o tipo específico MyDependency. O método AddScoped registra o serviço com um tempo de vida definido, o tempo de vida de uma única solicitação. Tempos de vida do serviço são descritos mais adiante neste artigo.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

No aplicativo de exemplo, o serviço de IMyDependency é solicitado e usado para chamar o método WriteMessage:

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

    public Index2Model(IMyDependency myDependency)
    {
        _myDependency = myDependency;            
    }

    public void OnGet()
    {
        _myDependency.WriteMessage("Index2Model.OnGet");
    }
}

Usando o padrão DI, o controlador ou Razor Página:

  • Não usa o tipo concreto MyDependency, apenas a interface IMyDependency que implementa. Isso facilita a alteração da implementação sem modificar o controlador ou a Página Razor.
  • Não cria uma instância de MyDependency, ela é criada pelo contêiner DI.

A implementação da interface IMyDependency pode ser melhorada usando a API de log interna:

public class MyDependency2 : IMyDependency
{
    private readonly ILogger<MyDependency2> _logger;

    public MyDependency2(ILogger<MyDependency2> logger)
    {
        _logger = logger;
    }

    public void WriteMessage(string message)
    {
        _logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
    }
}

A atualização do Program.cs regista a nova implementação do IMyDependency.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency2>();

var app = builder.Build();

MyDependency2 depende de ILogger<TCategoryName>, que ele solicita no construtor. ILogger<TCategoryName> é um serviço fornecido pela estrutura .

Não é incomum usar a injeção de dependência de forma encadeada. Cada dependência solicitada, por sua vez, solicita suas próprias dependências. O contêiner resolve as dependências no gráfico e retorna o serviço totalmente resolvido. O conjunto coletivo de dependências que devem ser resolvidas é normalmente referido como uma árvore de dependência , gráfico de dependênciaou gráfico de objeto.

O contentor resolve ILogger<TCategoryName> tirando partido de tipos abertos (genéricos), eliminando a necessidade de registar todos os tipos construídos (genéricos).

Na terminologia de injeção de dependência, um serviço:

  • Normalmente, é um objeto que fornece um serviço para outros objetos, como o serviço IMyDependency.
  • Não está relacionado a um serviço Web, embora o serviço possa usar um serviço Web.

A estrutura fornece um sistema de registo robusto . As implementações IMyDependency mostradas nos exemplos anteriores foram escritas para demonstrar DI básica, não para implementar o registro em log. A maioria dos aplicativos não precisa escrever loggers. O código a seguir demonstra o uso do log padrão, que não requer nenhum serviço a ser registrado:

public class AboutModel : PageModel
{
    private readonly ILogger _logger;

    public AboutModel(ILogger<AboutModel> logger)
    {
        _logger = logger;
    }
    
    public string Message { get; set; } = string.Empty;

    public void OnGet()
    {
        Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
        _logger.LogInformation(Message);
    }
}

O código anterior funciona corretamente sem alterar nada no Program.cs, porque o registo é fornecido pela plataforma.

Registrar grupos de serviços com métodos de extensão

A estrutura ASP.NET Core usa uma convenção para registrar um grupo de serviços relacionados. A convenção é usar um único método de extensão Add{GROUP_NAME} para registar todos os serviços exigidos por uma função do framework. Por exemplo, o método de extensão AddControllers registra os serviços necessários para controladores MVC.

O código a seguir é gerado pelo modelo Razor Pages usando contas de usuário individuais e mostra como adicionar serviços adicionais ao contêiner usando os métodos de extensão AddDbContext e AddDefaultIdentity:

using DependencyInjectionSample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

var app = builder.Build();

Considere o seguinte que registra serviços e configura opções:

using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
    builder.Configuration.GetSection(ColorOptions.Color));

builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();

var app = builder.Build();

Grupos relacionados de registros podem ser movidos para um método de extensão para registrar serviços. Por exemplo, os serviços de configuração são adicionados à seguinte classe:

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class MyConfigServiceCollectionExtensions
    {
        public static IServiceCollection AddConfig(
             this IServiceCollection services, IConfiguration config)
        {
            services.Configure<PositionOptions>(
                config.GetSection(PositionOptions.Position));
            services.Configure<ColorOptions>(
                config.GetSection(ColorOptions.Color));

            return services;
        }

        public static IServiceCollection AddMyDependencyGroup(
             this IServiceCollection services)
        {
            services.AddScoped<IMyDependency, MyDependency>();
            services.AddScoped<IMyDependency2, MyDependency2>();

            return services;
        }
    }
}

Os restantes serviços estão registados numa classe semelhante. O código a seguir usa os novos métodos de extensão para registrar os serviços:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddConfig(builder.Configuration)
    .AddMyDependencyGroup();

builder.Services.AddRazorPages();

var app = builder.Build();

Nota: Cada método de extensão services.Add{GROUP_NAME} adiciona e potencialmente configura serviços. Por exemplo, AddControllersWithViews adiciona os serviços que os controladores MVC com exibições exigem e AddRazorPages adiciona os serviços Razor o Pages requer.

Vida útil do serviço

Consulte duração de vida do serviço na injeção de dependência no .NET

Para usar serviços com escopo no middleware, use uma das seguintes abordagens:

  • Injete o serviço no método Invoke ou InvokeAsync do middleware. Usar de injeção do construtor gera uma exceção de tempo de execução porque força o serviço com escopo a se comportar como um singleton. O exemplo na seção Ciclo de vida e opções de registro demonstra a abordagem InvokeAsync.
  • Use middleware com base em fábrica. O middleware registrado usando essa abordagem é ativado por solicitação do cliente (conexão), o que permite que serviços com escopo sejam injetados no construtor do middleware.

Para obter mais informações, consulte Write custom ASP.NET Core middleware.

Métodos de registo de serviços

Consulte Métodos de registro de serviço em injeção de dependência no .NET

É comum usar várias implementações quando tipos simulados para testar.

Registrar um serviço com apenas um tipo de implementação é equivalente a registrar esse serviço com a mesma implementação e tipo de serviço. É por isso que várias implementações de um serviço não podem ser registradas usando os métodos que não usam um tipo de serviço explícito. Esses métodos podem registrar várias instâncias de um serviço, mas todas elas têm o mesmo tipo de implementação .

Qualquer um desses métodos de registro de serviço pode ser usado para registrar várias instâncias de serviço do mesmo tipo de serviço. No exemplo a seguir, AddSingleton é chamado duas vezes com IMyDependency como o tipo de serviço. A segunda chamada para AddSingleton substitui a anterior quando resolvida como IMyDependency e acumula-se com a anterior quando vários serviços são resolvidos via IEnumerable<IMyDependency>. Os serviços aparecem na ordem em que foram registrados quando resolvidos via IEnumerable<{SERVICE}>.

services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();

public class MyService
{
    public MyService(IMyDependency myDependency, 
       IEnumerable<IMyDependency> myDependencies)
    {
        Trace.Assert(myDependency is DifferentDependency);

        var dependencyArray = myDependencies.ToArray();
        Trace.Assert(dependencyArray[0] is MyDependency);
        Trace.Assert(dependencyArray[1] is DifferentDependency);
    }
}

Serviços chaveados

O termo serviços com chave refere-se a um mecanismo para registrar e recuperar serviços de injeção de dependência (DI) usando chaves. Um serviço é associado a uma chave chamando AddKeyedSingleton (ou AddKeyedScoped ou AddKeyedTransient) para registrá-lo. Acesse um serviço registrado especificando a chave com o atributo [FromKeyedServices]. O código a seguir mostra como usar serviços com chave:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
builder.Services.AddControllers();

var app = builder.Build();

app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) =>
                                                               smallCache.Get("date"));

app.MapControllers();

app.Run();

public interface ICache
{
    object Get(string key);
}
public class BigCache : ICache
{
    public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache
{
    public object Get(string key) => $"Resolving {key} from small cache.";
}

[ApiController]
[Route("/cache")]
public class CustomServicesApiController : Controller
{
    [HttpGet("big-cache")]
    public ActionResult<object> GetOk([FromKeyedServices("big")] ICache cache)
    {
        return cache.Get("data-mvc");
    }
}

public class MyHub : Hub
{
    public void Method([FromKeyedServices("small")] ICache cache)
    {
        Console.WriteLine(cache.Get("signalr"));
    }
}

Serviços chaveados em Middleware

O middleware suporta serviços chaveados no construtor e no método Invoke/InvokeAsync:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<MySingletonClass>("test");
builder.Services.AddKeyedScoped<MyScopedClass>("test2");

var app = builder.Build();
app.UseMiddleware<MyMiddleware>();
app.Run();

internal class MyMiddleware
{
    private readonly RequestDelegate _next;

    public MyMiddleware(RequestDelegate next,
        [FromKeyedServices("test")] MySingletonClass service)
    {
        _next = next;
    }

    public Task Invoke(HttpContext context,
        [FromKeyedServices("test2")]
            MyScopedClass scopedService) => _next(context);
}

Para obter mais informações sobre como criar middleware, consulte Write custom ASP.NET Core middleware

Comportamento de injeção do construtor

Consulte comportamento de injeção do construtor em injeção de dependência no .NET

Contextos do Entity Framework

Por padrão, os contextos do Entity Framework são adicionados ao contêiner de serviço usando o tempo de vida com escopo de porque as operações de base de dados das aplicações web normalmente têm como escopo o pedido do cliente. Para utilizar uma vida útil diferente, especifique-a utilizando uma sobrecarga de AddDbContext. Os serviços de um determinado tempo de vida não devem usar um contexto de banco de dados com um tempo de vida mais curto do que o tempo de vida do serviço.

Opções de subscrição vitalícia e registo

Para demonstrar a diferença entre os tempos de vida do serviço e suas opções de registro, considere as seguintes interfaces que representam uma tarefa como uma operação com um identificador, OperationId. Dependendo de como o tempo de vida do serviço de uma operação é configurado para as seguintes interfaces, o contêiner fornece instâncias iguais ou diferentes do serviço quando solicitado por uma classe:

public interface IOperation
{
    string OperationId { get; }
}

public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }

A classe Operation a seguir implementa todas as interfaces anteriores. O construtor Operation gera um GUID e armazena os últimos 4 caracteres na propriedade OperationId.

public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
    public Operation()
    {
        OperationId = Guid.NewGuid().ToString()[^4..];
    }

    public string OperationId { get; }
}

O código a seguir cria vários registos da classe Operation de acordo com os ciclos de vida nomeados:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseMyMiddleware();
app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

O aplicativo de exemplo demonstra o tempo de vida do objeto dentro e entre as solicitações. O IndexModel e o middleware solicitam cada tipo de IOperation e registam as OperationId para cada uma:

public class IndexModel : PageModel
{
    private readonly ILogger _logger;
    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;
    private readonly IOperationScoped _scopedOperation;

    public IndexModel(ILogger<IndexModel> logger,
                      IOperationTransient transientOperation,
                      IOperationScoped scopedOperation,
                      IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _scopedOperation    = scopedOperation;
        _singletonOperation = singletonOperation;
    }

    public void  OnGet()
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + _scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
    }
}

Semelhante ao IndexModel, o middleware resolve os mesmos serviços:

public class MyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    private readonly IOperationSingleton _singletonOperation;

    public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
        IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _singletonOperation = singletonOperation;
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context,
        IOperationTransient transientOperation, IOperationScoped scopedOperation)
    {
        _logger.LogInformation("Transient: " + transientOperation.OperationId);
        _logger.LogInformation("Scoped: " + scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

        await _next(context);
    }
}

public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyMiddleware>();
    }
}

Serviços definidos por escopo e transitórios devem ser resolvidos no método InvokeAsync.

public async Task InvokeAsync(HttpContext context,
    IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
    _logger.LogInformation("Transient: " + transientOperation.OperationId);
    _logger.LogInformation("Scoped: " + scopedOperation.OperationId);
    _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

    await _next(context);
}

A saída do logger mostra:

  • Objetos transitórios são sempre diferentes. O valor OperationId transitório é diferente no IndexModel e no middleware.
  • Os objetos de com escopo são os mesmos para uma determinada solicitação, mas diferem em cada nova solicitação.
  • objetos Singleton são os mesmos para todas as requisições.

Para reduzir a saída de log, defina "Logging:LogLevel:Microsoft:Error" no arquivo appsettings.Development.json:

{
  "MyKey": "MyKey from appsettings.Developement.json",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "System": "Debug",
      "Microsoft": "Error"
    }
  }
}

Resolver um serviço na inicialização do aplicativo

O código a seguir mostra como resolver um serviço com escopo por uma duração limitada quando o aplicativo é iniciado:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

using (var serviceScope = app.Services.CreateScope())
{
    var services = serviceScope.ServiceProvider;

    var myDependency = services.GetRequiredService<IMyDependency>();
    myDependency.WriteMessage("Call services from main");
}

app.MapGet("/", () => "Hello World!");

app.Run();

Validação do âmbito

Consulte comportamento de injeção do construtor em injeção de dependência no .NET

Para obter mais informações, consulte Validação de escopo.

Solicitar Serviços

Os serviços e suas dependências dentro de uma solicitação ASP.NET Core são expostos por meio HttpContext.RequestServices.

A estrutura cria um escopo por solicitação e RequestServices expõe o provedor de serviços com escopo. Todos os serviços com escopo são válidos enquanto a solicitação estiver ativa.

Observação

Prefira solicitar dependências como parâmetros do construtor, em vez de resolver serviços a partir de RequestServices. Solicitar dependências como parâmetros do construtor produz classes que são mais fáceis de testar.

Serviços de design para injeção de dependência

Ao projetar serviços para injeção de dependência:

  • Evite classes e membros estáticos e com estado. Evite criar um estado global ao projetar aplicativos que usem serviços singleton.
  • Evite a instanciação direta de classes dependentes dentro dos serviços. A instanciação direta acopla o código a uma implementação específica.
  • Torne os serviços pequenos, bem fatorados e facilmente testados.

Se uma classe tiver muitas dependências injetadas, isso pode ser um sinal de que a classe tem muitas responsabilidades e viola o Princípio de Responsabilidade Única (SRP). Tente refatorar a classe transferindo algumas de suas responsabilidades para novas classes. Lembre-se de que as classes de modelo de página Razor Pages e as classes de controlador MVC devem se concentrar nas preocupações da interface do usuário.

Eliminação de serviços

O contêiner chama Dispose para os tipos de IDisposable que ele cria. Os serviços resolvidos a partir do contêiner nunca devem ser descartados pelo desenvolvedor. Se um tipo ou fábrica estiver registado como singleton, o contêiner descarta o singleton automaticamente.

No exemplo a seguir, os serviços são criados pelo contêiner de serviço e descartados automaticamente:

public class Service1 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service1: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service1.Dispose");
        _disposed = true;
    }
}

public class Service2 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service2: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service2.Dispose");
        _disposed = true;
    }
}

public interface IService3
{
    public void Write(string message);
}

public class Service3 : IService3, IDisposable
{
    private bool _disposed;

    public Service3(string myKey)
    {
        MyKey = myKey;
    }

    public string MyKey { get; }

    public void Write(string message)
    {
        Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service3.Dispose");
        _disposed = true;
    }
}
using DIsample2.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();

var myKey = builder.Configuration["MyKey"];
builder.Services.AddSingleton<IService3>(sp => new Service3(myKey));

var app = builder.Build();
public class IndexModel : PageModel
{
    private readonly Service1 _service1;
    private readonly Service2 _service2;
    private readonly IService3 _service3;

    public IndexModel(Service1 service1, Service2 service2, IService3 service3)
    {
        _service1 = service1;
        _service2 = service2;
        _service3 = service3;
    }

    public void OnGet()
    {
        _service1.Write("IndexModel.OnGet");
        _service2.Write("IndexModel.OnGet");
        _service3.Write("IndexModel.OnGet");
    }
}

O console de depuração mostra a seguinte saída após cada atualização da página Índice:

Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Developement.json
Service1.Dispose

Serviços não criados pelo contêiner de serviço

Considere o seguinte código:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());

No código anterior:

  • As instâncias de serviço não são criadas pelo contêiner de serviço.
  • A estrutura não descarta os serviços automaticamente.
  • O desenvolvedor é responsável por gerir os serviços.

Orientação IDisposable para instâncias transitórias e compartilhadas

Consulte orientação IDisposable para de instância transitória e compartilhada em injeção de dependência no .NET

Substituição de contentor de serviço padrão

Consulte de substituição de contêiner de serviço padrão em injeção de dependência no .NET

Recomendações

Consulte as recomendações em .NET injeção de dependência

  • Evite usar o padrão de localizador de serviço . Por exemplo, não invoque GetService para obter uma instância de serviço quando puder usar DI:

    Incorreto:

    Código incorreto

    Correto:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • Outra variação do localizador de serviços a ser evitada é injetar uma fábrica que resolve dependências em tempo de execução. Ambas as práticas misturam as estratégias de Inversão de Controlo.

  • Evite o acesso estático a HttpContext (por exemplo, IHttpContextAccessor.HttpContext).

A DI é uma alternativa aos padrões de acesso a objetos estáticos/globais. Talvez você não consiga perceber os benefícios da DI se misturá-la com o acesso a objetos estáticos.

Orchard Core é um framework de aplicações para a construção de aplicações modulares e multi-inquilino no ASP.NET Core. Para obter mais informações, consulte a documentação do Orchard Core.

Consulte os cases do Orchard Core para ver como criar aplicações modulares e multi-inquilinos utilizando apenas o Orchard Core Framework, sem recorrer a nenhum dos seus recursos específicos do CMS.

Serviços fornecidos pelo framework

Program.cs registra serviços que o aplicativo usa, incluindo recursos da plataforma, como Entity Framework Core e ASP.NET Core MVC. Inicialmente, o IServiceCollection fornecido ao Program.cs tem serviços definidos pelo framework, dependendo de como o host foi configurado. Para aplicativos baseados nos modelos ASP.NET Core, a estrutura registra mais de 250 serviços.

A tabela a seguir lista uma pequena amostra desses serviços registrados na estrutura:

Tipo de Serviço Tempo de vida
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Transitório
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Transitório
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Transitório
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Transitório
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Recursos adicionais

Por Kirk Larkin, Steve Smithe Brandon Dahler

O ASP.NET Core suporta o padrão de design de software de injeção de dependência (DI), que é uma técnica para alcançar de Inversão de Controle (IoC) entre classes e suas dependências.

Para obter mais informações específicas sobre a injeção de dependência em controladores MVC, consulte Injeção de dependência em controladores no ASP.NET Core.

Para obter informações sobre como usar a injeção de dependência em aplicativos que não sejam aplicativos Web, consulte injeção de dependência no .NET.

Para obter mais informações sobre a injeção de opções por dependência, consulte padrão Opções no ASP.NET Core.

Este tópico fornece informações sobre a injeção de dependência no ASP.NET Core. A documentação principal sobre o uso da injeção de dependência está contida em Injeção de Dependência no .NET.

Exibir ou baixar código de exemplo (como descarregar)

Visão geral da injeção de dependência

Um de dependência é um objeto do qual outro objeto depende. Examine a seguinte classe MyDependency com um método WriteMessage do qual outras classes dependem:

public class MyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
    }
}

Uma classe pode criar uma instância da classe MyDependency para fazer uso de seu método WriteMessage. No exemplo a seguir, a classe MyDependency é uma dependência da classe IndexModel:


public class IndexModel : PageModel
{
    private readonly MyDependency _dependency = new MyDependency();

    public void OnGet()
    {
        _dependency.WriteMessage("IndexModel.OnGet");
    }
}

A classe cria e depende diretamente da classe MyDependency. As dependências de código, como no exemplo anterior, são problemáticas e devem ser evitadas pelos seguintes motivos:

  • Para substituir MyDependency por uma implementação diferente, a classe IndexModel deve ser modificada.
  • Se MyDependency tiver dependências, elas também deverão ser configuradas pela classe IndexModel. Num projeto grande com várias classes dependentes de MyDependency, o código de configuração fica espalhado pelo aplicativo.
  • Esta implementação é difícil de testar por unidade.

A injeção de dependência resolve esses problemas através de:

  • O uso de uma interface ou classe base para abstrair a implementação de dependência.
  • Registro da dependência em um contêiner de serviço. ASP.NET Core fornece um contêiner de serviço integrado, IServiceProvider. Normalmente, os serviços são registrados no arquivo Program.cs do aplicativo.
  • Injeção do serviço no construtor da classe onde ele é usado. A estrutura assume a responsabilidade de criar uma instância da dependência e eliminá-la quando ela não for mais necessária.

No aplicativo de exemplo , a interface IMyDependency define o método WriteMessage:

public interface IMyDependency
{
    void WriteMessage(string message);
}

Esta interface é implementada por um tipo concreto, MyDependency:

public class MyDependency : IMyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
    }
}

A aplicação de exemplo regista o serviço IMyDependency com o tipo específico MyDependency. O método AddScoped registra o serviço com um tempo de vida definido, o tempo de vida de uma única solicitação. Tempos de vida do serviço são descritos mais adiante neste tópico.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

No aplicativo de exemplo, o serviço de IMyDependency é solicitado e usado para chamar o método WriteMessage:

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

    public Index2Model(IMyDependency myDependency)
    {
        _myDependency = myDependency;            
    }

    public void OnGet()
    {
        _myDependency.WriteMessage("Index2Model.OnGet");
    }
}

Usando o padrão DI, o controlador ou Razor Página:

  • Não usa o tipo concreto MyDependency, apenas a interface IMyDependency que implementa. Isso facilita a alteração da implementação sem modificar o controlador ou a Página Razor.
  • Não cria uma instância de MyDependency, ela é criada pelo contêiner DI.

A implementação da interface IMyDependency pode ser melhorada usando a API de log interna:

public class MyDependency2 : IMyDependency
{
    private readonly ILogger<MyDependency2> _logger;

    public MyDependency2(ILogger<MyDependency2> logger)
    {
        _logger = logger;
    }

    public void WriteMessage(string message)
    {
        _logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
    }
}

A atualização do Program.cs regista a nova implementação do IMyDependency.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency2>();

var app = builder.Build();

MyDependency2 depende de ILogger<TCategoryName>, que ele solicita no construtor. ILogger<TCategoryName> é um serviço fornecido pela estrutura .

Não é incomum usar a injeção de dependência de forma encadeada. Cada dependência solicitada, por sua vez, solicita suas próprias dependências. O contêiner resolve as dependências no gráfico e retorna o serviço totalmente resolvido. O conjunto coletivo de dependências que devem ser resolvidas é normalmente referido como uma árvore de dependência , gráfico de dependênciaou gráfico de objeto.

O contentor resolve ILogger<TCategoryName> tirando partido de tipos abertos (genéricos), eliminando a necessidade de registar todos os tipos construídos (genéricos).

Na terminologia de injeção de dependência, um serviço:

  • Normalmente, é um objeto que fornece um serviço para outros objetos, como o serviço IMyDependency.
  • Não está relacionado a um serviço Web, embora o serviço possa usar um serviço Web.

A estrutura fornece um sistema de registo robusto . As implementações IMyDependency mostradas nos exemplos anteriores foram escritas para demonstrar DI básica, não para implementar o registro em log. A maioria dos aplicativos não precisa escrever loggers. O código a seguir demonstra o uso do log padrão, que não requer nenhum serviço a ser registrado:

public class AboutModel : PageModel
{
    private readonly ILogger _logger;

    public AboutModel(ILogger<AboutModel> logger)
    {
        _logger = logger;
    }
    
    public string Message { get; set; } = string.Empty;

    public void OnGet()
    {
        Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
        _logger.LogInformation(Message);
    }
}

Usando o código anterior, não há necessidade de atualizar Program.cs, porque de log é fornecido pela estrutura.

Registrar grupos de serviços com métodos de extensão

A estrutura ASP.NET Core usa uma convenção para registrar um grupo de serviços relacionados. A convenção é usar um único método de extensão Add{GROUP_NAME} para registar todos os serviços exigidos por uma função do framework. Por exemplo, o método de extensão AddControllers registra os serviços necessários para controladores MVC.

O código a seguir é gerado pelo modelo Razor Pages usando contas de usuário individuais e mostra como adicionar serviços adicionais ao contêiner usando os métodos de extensão AddDbContext e AddDefaultIdentity:

using DependencyInjectionSample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

var app = builder.Build();

Considere o seguinte que registra serviços e configura opções:

using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
    builder.Configuration.GetSection(ColorOptions.Color));

builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();

var app = builder.Build();

Grupos relacionados de registros podem ser movidos para um método de extensão para registrar serviços. Por exemplo, os serviços de configuração são adicionados à seguinte classe:

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class MyConfigServiceCollectionExtensions
    {
        public static IServiceCollection AddConfig(
             this IServiceCollection services, IConfiguration config)
        {
            services.Configure<PositionOptions>(
                config.GetSection(PositionOptions.Position));
            services.Configure<ColorOptions>(
                config.GetSection(ColorOptions.Color));

            return services;
        }

        public static IServiceCollection AddMyDependencyGroup(
             this IServiceCollection services)
        {
            services.AddScoped<IMyDependency, MyDependency>();
            services.AddScoped<IMyDependency2, MyDependency2>();

            return services;
        }
    }
}

Os restantes serviços estão registados numa classe semelhante. O código a seguir usa os novos métodos de extensão para registrar os serviços:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddConfig(builder.Configuration)
    .AddMyDependencyGroup();

builder.Services.AddRazorPages();

var app = builder.Build();

Nota: Cada método de extensão services.Add{GROUP_NAME} adiciona e potencialmente configura serviços. Por exemplo, AddControllersWithViews adiciona os serviços que os controladores MVC com exibições exigem e AddRazorPages adiciona os serviços Razor o Pages requer.

Vida útil do serviço

Consulte duração de vida do serviço na injeção de dependência no .NET

Para usar serviços com escopo no middleware, use uma das seguintes abordagens:

  • Injete o serviço no método Invoke ou InvokeAsync do middleware. Usar de injeção do construtor gera uma exceção de tempo de execução porque força o serviço com escopo a se comportar como um singleton. O exemplo na seção Ciclo de vida e opções de registro demonstra a abordagem InvokeAsync.
  • Use middleware com base em fábrica. O middleware registrado usando essa abordagem é ativado por solicitação do cliente (conexão), o que permite que serviços com escopo sejam injetados no construtor do middleware.

Para obter mais informações, consulte Write custom ASP.NET Core middleware.

Métodos de registo de serviços

Consulte Métodos de registro de serviço em injeção de dependência no .NET

É comum usar várias implementações quando tipos simulados para testar.

Registrar um serviço com apenas um tipo de implementação é equivalente a registrar esse serviço com a mesma implementação e tipo de serviço. É por isso que várias implementações de um serviço não podem ser registradas usando os métodos que não aceitam um tipo de serviço explícito. Esses métodos podem registrar várias instâncias de um serviço, mas todas elas terão o mesmo tipo de implementação .

Qualquer um dos métodos de registro de serviço acima pode ser usado para registrar várias instâncias de serviço do mesmo tipo de serviço. No exemplo a seguir, AddSingleton é chamado duas vezes com IMyDependency como o tipo de serviço. A segunda chamada para AddSingleton substitui a anterior quando resolvida como IMyDependency e acumula-se com a anterior quando vários serviços são resolvidos via IEnumerable<IMyDependency>. Os serviços aparecem na ordem em que foram registrados quando resolvidos via IEnumerable<{SERVICE}>.

services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();

public class MyService
{
    public MyService(IMyDependency myDependency, 
       IEnumerable<IMyDependency> myDependencies)
    {
        Trace.Assert(myDependency is DifferentDependency);

        var dependencyArray = myDependencies.ToArray();
        Trace.Assert(dependencyArray[0] is MyDependency);
        Trace.Assert(dependencyArray[1] is DifferentDependency);
    }
}

Serviços chaveados

Serviços identificados por chaves refere-se a um mecanismo para registar e recuperar serviços de injeção de dependência (DI) usando chaves. Um serviço é associado a uma chave chamando AddKeyedSingleton (ou AddKeyedScoped ou AddKeyedTransient) para registrá-lo. Acesse um serviço registrado especificando a chave com o atributo [FromKeyedServices]. O código a seguir mostra como usar serviços com chave:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
builder.Services.AddControllers();

var app = builder.Build();

app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) =>
                                                               smallCache.Get("date"));

app.MapControllers();

app.Run();

public interface ICache
{
    object Get(string key);
}
public class BigCache : ICache
{
    public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache
{
    public object Get(string key) => $"Resolving {key} from small cache.";
}

[ApiController]
[Route("/cache")]
public class CustomServicesApiController : Controller
{
    [HttpGet("big-cache")]
    public ActionResult<object> GetOk([FromKeyedServices("big")] ICache cache)
    {
        return cache.Get("data-mvc");
    }
}

public class MyHub : Hub
{
    public void Method([FromKeyedServices("small")] ICache cache)
    {
        Console.WriteLine(cache.Get("signalr"));
    }
}

Comportamento de injeção do construtor

Consulte comportamento de injeção do construtor em injeção de dependência no .NET

Contextos do Entity Framework

Por padrão, os contextos do Entity Framework são adicionados ao contêiner de serviço usando o tempo de vida com escopo de porque as operações de base de dados das aplicações web normalmente têm como escopo o pedido do cliente. Para utilizar uma vida útil diferente, especifique-a utilizando uma sobrecarga de AddDbContext. Os serviços de um determinado tempo de vida não devem usar um contexto de banco de dados com um tempo de vida mais curto do que o tempo de vida do serviço.

Opções de subscrição vitalícia e registo

Para demonstrar a diferença entre os tempos de vida do serviço e suas opções de registro, considere as seguintes interfaces que representam uma tarefa como uma operação com um identificador, OperationId. Dependendo de como o tempo de vida do serviço de uma operação é configurado para as seguintes interfaces, o contêiner fornece instâncias iguais ou diferentes do serviço quando solicitado por uma classe:

public interface IOperation
{
    string OperationId { get; }
}

public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }

A classe Operation a seguir implementa todas as interfaces anteriores. O construtor Operation gera um GUID e armazena os últimos 4 caracteres na propriedade OperationId.

public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
    public Operation()
    {
        OperationId = Guid.NewGuid().ToString()[^4..];
    }

    public string OperationId { get; }
}

O código a seguir cria vários registos da classe Operation de acordo com os ciclos de vida nomeados:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseMyMiddleware();
app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

O aplicativo de exemplo demonstra o tempo de vida do objeto dentro e entre as solicitações. O IndexModel e o middleware solicitam cada tipo de IOperation e registam as OperationId para cada uma:

public class IndexModel : PageModel
{
    private readonly ILogger _logger;
    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;
    private readonly IOperationScoped _scopedOperation;

    public IndexModel(ILogger<IndexModel> logger,
                      IOperationTransient transientOperation,
                      IOperationScoped scopedOperation,
                      IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _scopedOperation    = scopedOperation;
        _singletonOperation = singletonOperation;
    }

    public void  OnGet()
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + _scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
    }
}

Semelhante ao IndexModel, o middleware resolve os mesmos serviços:

public class MyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    private readonly IOperationSingleton _singletonOperation;

    public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
        IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _singletonOperation = singletonOperation;
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context,
        IOperationTransient transientOperation, IOperationScoped scopedOperation)
    {
        _logger.LogInformation("Transient: " + transientOperation.OperationId);
        _logger.LogInformation("Scoped: " + scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

        await _next(context);
    }
}

public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyMiddleware>();
    }
}

Serviços definidos por escopo e transitórios devem ser resolvidos no método InvokeAsync.

public async Task InvokeAsync(HttpContext context,
    IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
    _logger.LogInformation("Transient: " + transientOperation.OperationId);
    _logger.LogInformation("Scoped: " + scopedOperation.OperationId);
    _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

    await _next(context);
}

A saída do logger mostra:

  • Objetos transitórios são sempre diferentes. O valor OperationId transitório é diferente no IndexModel e no middleware.
  • Os objetos de com escopo são os mesmos para uma determinada solicitação, mas diferem em cada nova solicitação.
  • objetos Singleton são os mesmos para todas as requisições.

Para reduzir a saída de log, defina "Logging:LogLevel:Microsoft:Error" no arquivo appsettings.Development.json:

{
  "MyKey": "MyKey from appsettings.Developement.json",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "System": "Debug",
      "Microsoft": "Error"
    }
  }
}

Resolver um serviço no arranque da aplicação

O código a seguir mostra como resolver um serviço com escopo por uma duração limitada quando o aplicativo é iniciado:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

using (var serviceScope = app.Services.CreateScope())
{
    var services = serviceScope.ServiceProvider;

    var myDependency = services.GetRequiredService<IMyDependency>();
    myDependency.WriteMessage("Call services from main");
}

app.MapGet("/", () => "Hello World!");

app.Run();

Validação do âmbito

Consulte comportamento de injeção do construtor em injeção de dependência no .NET

Para obter mais informações, consulte Validação de escopo.

Solicitar Serviços

Os serviços e suas dependências dentro de uma solicitação ASP.NET Core são expostos por meio HttpContext.RequestServices.

A estrutura cria um escopo por solicitação e RequestServices expõe o provedor de serviços com escopo. Todos os serviços com escopo são válidos enquanto a solicitação estiver ativa.

Observação

Prefira solicitar dependências como parâmetros do construtor, em vez de resolver serviços a partir de RequestServices. Solicitar dependências como parâmetros do construtor produz classes que são mais fáceis de testar.

Serviços de design para injeção de dependência

Ao projetar serviços para injeção de dependência:

  • Evite classes e membros estáticos e com estado. Evite criar um estado global ao projetar aplicativos que usem serviços singleton.
  • Evite a instanciação direta de classes dependentes dentro dos serviços. A instanciação direta acopla o código a uma implementação específica.
  • Torne os serviços pequenos, bem fatorados e facilmente testados.

Se uma classe tiver muitas dependências injetadas, isso pode ser um sinal de que a classe tem muitas responsabilidades e viola o Princípio de Responsabilidade Única (SRP). Tente refatorar a classe transferindo algumas de suas responsabilidades para novas classes. Lembre-se de que as classes de modelo de página Razor Pages e as classes de controlador MVC devem se concentrar nas preocupações da interface do usuário.

Eliminação de serviços

O contêiner chama Dispose para os tipos de IDisposable que ele cria. Os serviços resolvidos a partir do contêiner nunca devem ser descartados pelo desenvolvedor. Se um tipo ou fábrica estiver registado como singleton, o contêiner descarta o singleton automaticamente.

No exemplo a seguir, os serviços são criados pelo contenedor de serviços e eliminados automaticamente: dependency-injection\samples\6.x\DIsample2\DIsample2\Services\Service1.cs

public class Service1 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service1: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service1.Dispose");
        _disposed = true;
    }
}

public class Service2 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service2: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service2.Dispose");
        _disposed = true;
    }
}

public interface IService3
{
    public void Write(string message);
}

public class Service3 : IService3, IDisposable
{
    private bool _disposed;

    public Service3(string myKey)
    {
        MyKey = myKey;
    }

    public string MyKey { get; }

    public void Write(string message)
    {
        Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service3.Dispose");
        _disposed = true;
    }
}
using DIsample2.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();

var myKey = builder.Configuration["MyKey"];
builder.Services.AddSingleton<IService3>(sp => new Service3(myKey));

var app = builder.Build();
public class IndexModel : PageModel
{
    private readonly Service1 _service1;
    private readonly Service2 _service2;
    private readonly IService3 _service3;

    public IndexModel(Service1 service1, Service2 service2, IService3 service3)
    {
        _service1 = service1;
        _service2 = service2;
        _service3 = service3;
    }

    public void OnGet()
    {
        _service1.Write("IndexModel.OnGet");
        _service2.Write("IndexModel.OnGet");
        _service3.Write("IndexModel.OnGet");
    }
}

O console de depuração mostra a seguinte saída após cada atualização da página Índice:

Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Developement.json
Service1.Dispose

Serviços não criados pelo contêiner de serviço

Considere o seguinte código:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());

No código anterior:

  • As instâncias de serviço não são criadas pelo contêiner de serviço.
  • A estrutura não descarta os serviços automaticamente.
  • O desenvolvedor é responsável por gerir os serviços.

Orientação IDisposable para instâncias transitórias e compartilhadas

Consulte orientação IDisposable para de instância transitória e compartilhada em injeção de dependência no .NET

Substituição de contentor de serviço padrão

Consulte de substituição de contêiner de serviço padrão em injeção de dependência no .NET

Recomendações

Consulte as recomendações em .NET injeção de dependência

  • Evite usar o padrão de localizador de serviço . Por exemplo, não invoque GetService para obter uma instância de serviço quando puder usar DI:

    Incorreto:

    Código incorreto

    Correto:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • Outra variação do localizador de serviços a ser evitada é injetar uma fábrica que resolve dependências em tempo de execução. Ambas as práticas misturam as estratégias de Inversão de Controlo.

  • Evite o acesso estático a HttpContext (por exemplo, IHttpContextAccessor.HttpContext).

A DI é uma alternativa aos padrões de acesso a objetos estáticos/globais. Talvez você não consiga perceber os benefícios da DI se misturá-la com o acesso a objetos estáticos.

Orchard Core é uma estrutura de aplicativos para a criação de aplicativos modulares e multilocatários no ASP.NET Core. Para obter mais informações, consulte a documentação do Orchard Core.

Consulte os cases do Orchard Core para ver como criar aplicações modulares e multi-inquilinos utilizando apenas o Orchard Core Framework, sem recorrer a nenhum dos seus recursos específicos do CMS.

Serviços fornecidos pelo framework

Program.cs registra serviços que o aplicativo usa, incluindo recursos da plataforma, como Entity Framework Core e ASP.NET Core MVC. Inicialmente, o IServiceCollection fornecido ao Program.cs tem serviços definidos pelo framework, dependendo de como o host foi configurado. Para aplicativos baseados nos modelos ASP.NET Core, a estrutura registra mais de 250 serviços.

A tabela a seguir lista uma pequena amostra desses serviços registrados na estrutura:

Tipo de Serviço Tempo de vida
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Transitório
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Transitório
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Transitório
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Transitório
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Recursos adicionais

Por Kirk Larkin, Steve Smithe Brandon Dahler

O ASP.NET Core suporta o padrão de design de software de injeção de dependência (DI), que é uma técnica para alcançar de Inversão de Controle (IoC) entre classes e suas dependências.

Para obter mais informações específicas sobre a injeção de dependência em controladores MVC, consulte Injeção de dependência em controladores no ASP.NET Core.

Para obter informações sobre como usar a injeção de dependência em aplicativos que não sejam aplicativos Web, consulte injeção de dependência no .NET.

Para obter mais informações sobre a injeção de opções por dependência, consulte padrão Opções no ASP.NET Core.

Este tópico fornece informações sobre a injeção de dependência no ASP.NET Core. A documentação principal sobre o uso da injeção de dependência está contida em Injeção de Dependência no .NET.

Exibir ou baixar código de exemplo (como descarregar)

Visão geral da injeção de dependência

Um de dependência é um objeto do qual outro objeto depende. Examine a seguinte classe MyDependency com um método WriteMessage do qual outras classes dependem:

public class MyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
    }
}

Uma classe pode criar uma instância da classe MyDependency para fazer uso de seu método WriteMessage. No exemplo a seguir, a classe MyDependency é uma dependência da classe IndexModel:


public class IndexModel : PageModel
{
    private readonly MyDependency _dependency = new MyDependency();

    public void OnGet()
    {
        _dependency.WriteMessage("IndexModel.OnGet");
    }
}

A classe cria e depende diretamente da classe MyDependency. As dependências de código, como no exemplo anterior, são problemáticas e devem ser evitadas pelos seguintes motivos:

  • Para substituir MyDependency por uma implementação diferente, a classe IndexModel deve ser modificada.
  • Se MyDependency tiver dependências, elas também deverão ser configuradas pela classe IndexModel. Num projeto grande com várias classes dependentes de MyDependency, o código de configuração fica espalhado pelo aplicativo.
  • Esta implementação é difícil de testar por unidade.

A injeção de dependência resolve esses problemas através de:

  • O uso de uma interface ou classe base para abstrair a implementação de dependência.
  • Registro da dependência em um contêiner de serviço. ASP.NET Core fornece um contêiner de serviço integrado, IServiceProvider. Normalmente, os serviços são registrados no arquivo Program.cs do aplicativo.
  • Injeção do serviço no construtor da classe onde ele é usado. A estrutura assume a responsabilidade de criar uma instância da dependência e eliminá-la quando ela não for mais necessária.

No aplicativo de exemplo , a interface IMyDependency define o método WriteMessage:

public interface IMyDependency
{
    void WriteMessage(string message);
}

Esta interface é implementada por um tipo concreto, MyDependency:

public class MyDependency : IMyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
    }
}

A aplicação de exemplo regista o serviço IMyDependency com o tipo específico MyDependency. O método AddScoped registra o serviço com um tempo de vida definido, o tempo de vida de uma única solicitação. Tempos de vida do serviço são descritos mais adiante neste tópico.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

No aplicativo de exemplo, o serviço de IMyDependency é solicitado e usado para chamar o método WriteMessage:

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

    public Index2Model(IMyDependency myDependency)
    {
        _myDependency = myDependency;            
    }

    public void OnGet()
    {
        _myDependency.WriteMessage("Index2Model.OnGet");
    }
}

Usando o padrão DI, o controlador ou Razor Página:

  • Não usa o tipo concreto MyDependency, apenas a interface IMyDependency que implementa. Isso facilita a alteração da implementação sem modificar o controlador ou a Página Razor.
  • Não cria uma instância de MyDependency, ela é criada pelo contêiner DI.

A implementação da interface IMyDependency pode ser melhorada usando a API de log interna:

public class MyDependency2 : IMyDependency
{
    private readonly ILogger<MyDependency2> _logger;

    public MyDependency2(ILogger<MyDependency2> logger)
    {
        _logger = logger;
    }

    public void WriteMessage(string message)
    {
        _logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
    }
}

A atualização do Program.cs regista a nova implementação do IMyDependency.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency2>();

var app = builder.Build();

MyDependency2 depende de ILogger<TCategoryName>, que ele solicita no construtor. ILogger<TCategoryName> é um serviço fornecido pela estrutura .

Não é incomum usar a injeção de dependência de forma encadeada. Cada dependência solicitada, por sua vez, solicita suas próprias dependências. O contêiner resolve as dependências no gráfico e retorna o serviço totalmente resolvido. O conjunto coletivo de dependências que devem ser resolvidas é normalmente referido como uma árvore de dependência , gráfico de dependênciaou gráfico de objeto.

O contentor resolve ILogger<TCategoryName> tirando partido de tipos abertos (genéricos), eliminando a necessidade de registar todos os tipos construídos (genéricos).

Na terminologia de injeção de dependência, um serviço:

  • Normalmente, é um objeto que fornece um serviço para outros objetos, como o serviço IMyDependency.
  • Não está relacionado a um serviço Web, embora o serviço possa usar um serviço Web.

A estrutura fornece um sistema de registo robusto . As implementações IMyDependency mostradas nos exemplos anteriores foram escritas para demonstrar DI básica, não para implementar o registro em log. A maioria dos aplicativos não precisa escrever loggers. O código a seguir demonstra o uso do log padrão, que não requer nenhum serviço a ser registrado:

public class AboutModel : PageModel
{
    private readonly ILogger _logger;

    public AboutModel(ILogger<AboutModel> logger)
    {
        _logger = logger;
    }
    
    public string Message { get; set; } = string.Empty;

    public void OnGet()
    {
        Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
        _logger.LogInformation(Message);
    }
}

Usando o código anterior, não há necessidade de atualizar Program.cs, porque de log é fornecido pela estrutura.

Registrar grupos de serviços com métodos de extensão

A estrutura ASP.NET Core usa uma convenção para registrar um grupo de serviços relacionados. A convenção é usar um único método de extensão Add{GROUP_NAME} para registar todos os serviços exigidos por uma função do framework. Por exemplo, o método de extensão AddControllers registra os serviços necessários para controladores MVC.

O código a seguir é gerado pelo modelo Razor Pages usando contas de usuário individuais e mostra como adicionar serviços adicionais ao contêiner usando os métodos de extensão AddDbContext e AddDefaultIdentity:

using DependencyInjectionSample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

var app = builder.Build();

Considere o seguinte que registra serviços e configura opções:

using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
    builder.Configuration.GetSection(ColorOptions.Color));

builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();

var app = builder.Build();

Grupos relacionados de registros podem ser movidos para um método de extensão para registrar serviços. Por exemplo, os serviços de configuração são adicionados à seguinte classe:

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class MyConfigServiceCollectionExtensions
    {
        public static IServiceCollection AddConfig(
             this IServiceCollection services, IConfiguration config)
        {
            services.Configure<PositionOptions>(
                config.GetSection(PositionOptions.Position));
            services.Configure<ColorOptions>(
                config.GetSection(ColorOptions.Color));

            return services;
        }

        public static IServiceCollection AddMyDependencyGroup(
             this IServiceCollection services)
        {
            services.AddScoped<IMyDependency, MyDependency>();
            services.AddScoped<IMyDependency2, MyDependency2>();

            return services;
        }
    }
}

Os restantes serviços estão registados numa classe semelhante. O código a seguir usa os novos métodos de extensão para registrar os serviços:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddConfig(builder.Configuration)
    .AddMyDependencyGroup();

builder.Services.AddRazorPages();

var app = builder.Build();

Nota: Cada método de extensão services.Add{GROUP_NAME} adiciona e potencialmente configura serviços. Por exemplo, AddControllersWithViews adiciona os serviços que os controladores MVC com exibições exigem e AddRazorPages adiciona os serviços Razor o Pages requer.

Vida útil do serviço

Consulte duração de vida do serviço na injeção de dependência no .NET

Para usar serviços com escopo no middleware, use uma das seguintes abordagens:

  • Injete o serviço no método Invoke ou InvokeAsync do middleware. Usar de injeção do construtor gera uma exceção de tempo de execução porque força o serviço com escopo a se comportar como um singleton. O exemplo na seção Ciclo de vida e opções de registro demonstra a abordagem InvokeAsync.
  • Use middleware com base em fábrica. O middleware registrado usando essa abordagem é ativado por solicitação do cliente (conexão), o que permite que serviços com escopo sejam injetados no construtor do middleware.

Para obter mais informações, consulte Write custom ASP.NET Core middleware.

Métodos de registo de serviços

Consulte Métodos de registro de serviço em injeção de dependência no .NET

É comum usar várias implementações quando tipos simulados para testar.

Registrar um serviço com apenas um tipo de implementação é equivalente a registrar esse serviço com a mesma implementação e tipo de serviço. É por isso que várias implementações de um serviço não podem ser registradas usando os métodos que não aceitam um tipo de serviço explícito. Esses métodos podem registrar várias instâncias de um serviço, mas todas elas terão o mesmo tipo de implementação .

Qualquer um dos métodos de registro de serviço acima pode ser usado para registrar várias instâncias de serviço do mesmo tipo de serviço. No exemplo a seguir, AddSingleton é chamado duas vezes com IMyDependency como o tipo de serviço. A segunda chamada para AddSingleton substitui a anterior quando resolvida como IMyDependency e acumula-se com a anterior quando vários serviços são resolvidos via IEnumerable<IMyDependency>. Os serviços aparecem na ordem em que foram registrados quando resolvidos via IEnumerable<{SERVICE}>.

services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();

public class MyService
{
    public MyService(IMyDependency myDependency, 
       IEnumerable<IMyDependency> myDependencies)
    {
        Trace.Assert(myDependency is DifferentDependency);

        var dependencyArray = myDependencies.ToArray();
        Trace.Assert(dependencyArray[0] is MyDependency);
        Trace.Assert(dependencyArray[1] is DifferentDependency);
    }
}

Comportamento de injeção do construtor

Consulte comportamento de injeção do construtor em injeção de dependência no .NET

Contextos do Entity Framework

Por padrão, os contextos do Entity Framework são adicionados ao contêiner de serviço usando o tempo de vida com escopo de porque as operações de base de dados das aplicações web normalmente têm como escopo o pedido do cliente. Para utilizar uma vida útil diferente, especifique-a utilizando uma sobrecarga de AddDbContext. Os serviços de um determinado tempo de vida não devem usar um contexto de banco de dados com um tempo de vida mais curto do que o tempo de vida do serviço.

Opções de subscrição vitalícia e registo

Para demonstrar a diferença entre os tempos de vida do serviço e suas opções de registro, considere as seguintes interfaces que representam uma tarefa como uma operação com um identificador, OperationId. Dependendo de como o tempo de vida do serviço de uma operação é configurado para as seguintes interfaces, o contêiner fornece instâncias iguais ou diferentes do serviço quando solicitado por uma classe:

public interface IOperation
{
    string OperationId { get; }
}

public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }

A classe Operation a seguir implementa todas as interfaces anteriores. O construtor Operation gera um GUID e armazena os últimos 4 caracteres na propriedade OperationId.

public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
    public Operation()
    {
        OperationId = Guid.NewGuid().ToString()[^4..];
    }

    public string OperationId { get; }
}

O código a seguir cria vários registos da classe Operation de acordo com os ciclos de vida nomeados:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseMyMiddleware();
app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

O aplicativo de exemplo demonstra o tempo de vida do objeto dentro e entre as solicitações. O IndexModel e o middleware solicitam cada tipo de IOperation e registam as OperationId para cada uma:

public class IndexModel : PageModel
{
    private readonly ILogger _logger;
    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;
    private readonly IOperationScoped _scopedOperation;

    public IndexModel(ILogger<IndexModel> logger,
                      IOperationTransient transientOperation,
                      IOperationScoped scopedOperation,
                      IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _scopedOperation    = scopedOperation;
        _singletonOperation = singletonOperation;
    }

    public void  OnGet()
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + _scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
    }
}

Semelhante ao IndexModel, o middleware resolve os mesmos serviços:

public class MyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    private readonly IOperationSingleton _singletonOperation;

    public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
        IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _singletonOperation = singletonOperation;
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context,
        IOperationTransient transientOperation, IOperationScoped scopedOperation)
    {
        _logger.LogInformation("Transient: " + transientOperation.OperationId);
        _logger.LogInformation("Scoped: " + scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

        await _next(context);
    }
}

public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyMiddleware>();
    }
}

Serviços definidos por escopo e transitórios devem ser resolvidos no método InvokeAsync.

public async Task InvokeAsync(HttpContext context,
    IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
    _logger.LogInformation("Transient: " + transientOperation.OperationId);
    _logger.LogInformation("Scoped: " + scopedOperation.OperationId);
    _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

    await _next(context);
}

A saída do logger mostra:

  • Objetos transitórios são sempre diferentes. O valor OperationId transitório é diferente no IndexModel e no middleware.
  • Os objetos de com escopo são os mesmos para uma determinada solicitação, mas diferem em cada nova solicitação.
  • objetos Singleton são os mesmos para todas as requisições.

Para reduzir a saída de log, defina "Logging:LogLevel:Microsoft:Error" no arquivo appsettings.Development.json:

{
  "MyKey": "MyKey from appsettings.Developement.json",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "System": "Debug",
      "Microsoft": "Error"
    }
  }
}

Resolver um serviço no arranque da aplicação

O código a seguir mostra como resolver um serviço com escopo por uma duração limitada quando o aplicativo é iniciado:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

using (var serviceScope = app.Services.CreateScope())
{
    var services = serviceScope.ServiceProvider;

    var myDependency = services.GetRequiredService<IMyDependency>();
    myDependency.WriteMessage("Call services from main");
}

app.MapGet("/", () => "Hello World!");

app.Run();

Validação do âmbito

Consulte comportamento de injeção do construtor em injeção de dependência no .NET

Para obter mais informações, consulte Validação de escopo.

Solicitar Serviços

Os serviços e suas dependências dentro de uma solicitação ASP.NET Core são expostos por meio HttpContext.RequestServices.

A estrutura cria um escopo por solicitação e RequestServices expõe o provedor de serviços com escopo. Todos os serviços com escopo são válidos enquanto a solicitação estiver ativa.

Observação

Prefira solicitar dependências como parâmetros do construtor, em vez de resolver serviços a partir de RequestServices. Solicitar dependências como parâmetros do construtor produz classes que são mais fáceis de testar.

Serviços de design para injeção de dependência

Ao projetar serviços para injeção de dependência:

  • Evite classes e membros estáticos e com estado. Evite criar um estado global ao projetar aplicativos que usem serviços singleton.
  • Evite a instanciação direta de classes dependentes dentro dos serviços. A instanciação direta acopla o código a uma implementação específica.
  • Torne os serviços pequenos, bem fatorados e facilmente testados.

Se uma classe tiver muitas dependências injetadas, isso pode ser um sinal de que a classe tem muitas responsabilidades e viola o Princípio de Responsabilidade Única (SRP). Tente refatorar a classe transferindo algumas de suas responsabilidades para novas classes. Lembre-se de que as classes de modelo de página Razor Pages e as classes de controlador MVC devem se concentrar nas preocupações da interface do usuário.

Eliminação de serviços

O contêiner chama Dispose para os tipos de IDisposable que ele cria. Os serviços resolvidos a partir do contêiner nunca devem ser descartados pelo desenvolvedor. Se um tipo ou fábrica estiver registado como singleton, o contêiner descarta o singleton automaticamente.

No exemplo a seguir, os serviços são criados pelo contenedor de serviços e eliminados automaticamente: dependency-injection\samples\6.x\DIsample2\DIsample2\Services\Service1.cs

public class Service1 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service1: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service1.Dispose");
        _disposed = true;
    }
}

public class Service2 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service2: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service2.Dispose");
        _disposed = true;
    }
}

public interface IService3
{
    public void Write(string message);
}

public class Service3 : IService3, IDisposable
{
    private bool _disposed;

    public Service3(string myKey)
    {
        MyKey = myKey;
    }

    public string MyKey { get; }

    public void Write(string message)
    {
        Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service3.Dispose");
        _disposed = true;
    }
}
using DIsample2.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();

var myKey = builder.Configuration["MyKey"];
builder.Services.AddSingleton<IService3>(sp => new Service3(myKey));

var app = builder.Build();
public class IndexModel : PageModel
{
    private readonly Service1 _service1;
    private readonly Service2 _service2;
    private readonly IService3 _service3;

    public IndexModel(Service1 service1, Service2 service2, IService3 service3)
    {
        _service1 = service1;
        _service2 = service2;
        _service3 = service3;
    }

    public void OnGet()
    {
        _service1.Write("IndexModel.OnGet");
        _service2.Write("IndexModel.OnGet");
        _service3.Write("IndexModel.OnGet");
    }
}

O console de depuração mostra a seguinte saída após cada atualização da página Índice:

Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Developement.json
Service1.Dispose

Serviços não criados pelo contêiner de serviço

Considere o seguinte código:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());

No código anterior:

  • As instâncias de serviço não são criadas pelo contêiner de serviço.
  • A estrutura não descarta os serviços automaticamente.
  • O desenvolvedor é responsável por gerir os serviços.

Orientação IDisposable para instâncias transitórias e compartilhadas

Consulte orientação IDisposable para de instância transitória e compartilhada em injeção de dependência no .NET

Substituição de contentor de serviço padrão

Consulte de substituição de contêiner de serviço padrão em injeção de dependência no .NET

Recomendações

Consulte as recomendações em .NET injeção de dependência

  • Evite usar o padrão de localizador de serviço . Por exemplo, não invoque GetService para obter uma instância de serviço quando puder usar DI:

    Incorreto:

    Código incorreto

    Correto:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • Outra variação do localizador de serviços a ser evitada é injetar uma fábrica que resolve dependências em tempo de execução. Ambas as práticas misturam as estratégias de Inversão de Controlo.

  • Evite o acesso estático a HttpContext (por exemplo, IHttpContextAccessor.HttpContext).

A DI é uma alternativa aos padrões de acesso a objetos estáticos/globais. Talvez você não consiga perceber os benefícios da DI se misturá-la com o acesso a objetos estáticos.

Orchard Core é uma estrutura de aplicativos para a criação de aplicativos modulares e multilocatários no ASP.NET Core. Para obter mais informações, consulte a documentação do Orchard Core.

Consulte os cases do Orchard Core para ver como criar aplicações modulares e multi-inquilinos utilizando apenas o Orchard Core Framework, sem recorrer a nenhum dos seus recursos específicos do CMS.

Serviços fornecidos pelo framework

Program.cs registra serviços que o aplicativo usa, incluindo recursos da plataforma, como Entity Framework Core e ASP.NET Core MVC. Inicialmente, o IServiceCollection fornecido ao Program.cs tem serviços definidos pelo framework, dependendo de como o host foi configurado. Para aplicativos baseados nos modelos ASP.NET Core, a estrutura registra mais de 250 serviços.

A tabela a seguir lista uma pequena amostra desses serviços registrados na estrutura:

Tipo de Serviço Tempo de vida
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Transitório
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Transitório
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Transitório
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Transitório
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Recursos adicionais

Por Kirk Larkin, Steve Smith, Scott Addiee Brandon Dahler

O ASP.NET Core suporta o padrão de design de software de injeção de dependência (DI), que é uma técnica para alcançar de Inversão de Controle (IoC) entre classes e suas dependências.

Para obter mais informações específicas sobre a injeção de dependência em controladores MVC, consulte Injeção de dependência em controladores no ASP.NET Core.

Para obter informações sobre como usar a injeção de dependência em aplicativos que não sejam aplicativos Web, consulte injeção de dependência no .NET.

Para obter mais informações sobre a injeção de opções por dependência, consulte padrão Opções no ASP.NET Core.

Este tópico fornece informações sobre a injeção de dependência no ASP.NET Core. A documentação principal sobre o uso da injeção de dependência está contida em Injeção de Dependência no .NET.

Exibir ou baixar código de exemplo (como descarregar)

Visão geral da injeção de dependência

Um de dependência é um objeto do qual outro objeto depende. Examine a seguinte classe MyDependency com um método WriteMessage do qual outras classes dependem:

public class MyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
    }
}

Uma classe pode criar uma instância da classe MyDependency para fazer uso de seu método WriteMessage. No exemplo a seguir, a classe MyDependency é uma dependência da classe IndexModel:

public class IndexModel : PageModel
{
    private readonly MyDependency _dependency = new MyDependency();

    public void OnGet()
    {
        _dependency.WriteMessage("IndexModel.OnGet created this message.");
    }
}

A classe cria e depende diretamente da classe MyDependency. As dependências de código, como no exemplo anterior, são problemáticas e devem ser evitadas pelos seguintes motivos:

  • Para substituir MyDependency por uma implementação diferente, a classe IndexModel deve ser modificada.
  • Se MyDependency tiver dependências, elas também deverão ser configuradas pela classe IndexModel. Num projeto grande com várias classes dependentes de MyDependency, o código de configuração fica espalhado pelo aplicativo.
  • Esta implementação é difícil de testar por unidade. O aplicativo deve usar uma classe MyDependency simulada ou stub, o que não é possível com essa abordagem.

A injeção de dependência resolve esses problemas através de:

  • O uso de uma interface ou classe base para abstrair a implementação de dependência.
  • Registro da dependência em um contêiner de serviço. ASP.NET Core fornece um contêiner de serviço integrado, IServiceProvider. Normalmente, os serviços são registrados no método Startup.ConfigureServices do aplicativo.
  • Injeção do serviço no construtor da classe onde ele é usado. A estrutura assume a responsabilidade de criar uma instância da dependência e eliminá-la quando ela não for mais necessária.

No aplicativo de exemplo , a interface IMyDependency define o método WriteMessage:

public interface IMyDependency
{
    void WriteMessage(string message);
}

Esta interface é implementada por um tipo concreto, MyDependency:

public class MyDependency : IMyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
    }
}

A aplicação de exemplo regista o serviço IMyDependency com o tipo específico MyDependency. O método AddScoped registra o serviço com um tempo de vida definido, o tempo de vida de uma única solicitação. Tempos de vida do serviço são descritos mais adiante neste tópico.

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IMyDependency, MyDependency>();

    services.AddRazorPages();
}

No aplicativo de exemplo, o serviço de IMyDependency é solicitado e usado para chamar o método WriteMessage:

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

    public Index2Model(IMyDependency myDependency)
    {
        _myDependency = myDependency;            
    }

    public void OnGet()
    {
        _myDependency.WriteMessage("Index2Model.OnGet");
    }
}

Usando o padrão DI, o controlador:

  • Não usa o tipo concreto MyDependency, apenas a interface IMyDependency que implementa. Isso facilita a alteração da implementação que o controlador usa sem modificá-lo.
  • Não cria uma instância de MyDependency, ela é criada pelo contêiner DI.

A implementação da interface IMyDependency pode ser melhorada usando a API de log interna:

public class MyDependency2 : IMyDependency
{
    private readonly ILogger<MyDependency2> _logger;

    public MyDependency2(ILogger<MyDependency2> logger)
    {
        _logger = logger;
    }

    public void WriteMessage(string message)
    {
        _logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
    }
}

O método ConfigureServices atualizado registra a nova implementação IMyDependency:

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IMyDependency, MyDependency2>();

    services.AddRazorPages();
}

MyDependency2 depende de ILogger<TCategoryName>, que ele solicita no construtor. ILogger<TCategoryName> é um serviço fornecido pela estrutura .

Não é incomum usar a injeção de dependência de forma encadeada. Cada dependência solicitada, por sua vez, solicita suas próprias dependências. O contêiner resolve as dependências no gráfico e retorna o serviço totalmente resolvido. O conjunto coletivo de dependências que devem ser resolvidas é normalmente referido como uma árvore de dependência , gráfico de dependênciaou gráfico de objeto.

O contentor resolve ILogger<TCategoryName> tirando partido de tipos abertos (genéricos), eliminando a necessidade de registar todos os tipos construídos (genéricos).

Na terminologia de injeção de dependência, um serviço:

  • Normalmente, é um objeto que fornece um serviço para outros objetos, como o serviço IMyDependency.
  • Não está relacionado a um serviço Web, embora o serviço possa usar um serviço Web.

A estrutura fornece um sistema de registo robusto . As implementações IMyDependency mostradas nos exemplos anteriores foram escritas para demonstrar DI básica, não para implementar o registro em log. A maioria dos aplicativos não precisa escrever loggers. O código a seguir demonstra o uso do log padrão, que não requer que nenhum serviço seja registrado no ConfigureServices:

public class AboutModel : PageModel
{
    private readonly ILogger _logger;

    public AboutModel(ILogger<AboutModel> logger)
    {
        _logger = logger;
    }
    
    public string Message { get; set; }

    public void OnGet()
    {
        Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
        _logger.LogInformation(Message);
    }
}

Usando o código anterior, não há necessidade de atualizar ConfigureServices, porque de log é fornecido pela estrutura.

Serviços injetados no Startup

Os serviços podem ser injetados no construtor Startup e no método Startup.Configure.

Somente os seguintes serviços podem ser injetados no construtor Startup ao usar o host genérico (IHostBuilder):

Qualquer serviço registado com o container DI pode ser injetado no método Startup.Configure:

public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
    ...
}

Para obter mais informações, consulte inicialização do aplicativo no ASP.NET Core e Configuração do Access no Startup.

Registrar grupos de serviços com métodos de extensão

A estrutura ASP.NET Core usa uma convenção para registrar um grupo de serviços relacionados. A convenção é usar um único método de extensão Add{GROUP_NAME} para registar todos os serviços exigidos por uma função do framework. Por exemplo, o método de extensão AddControllers registra os serviços necessários para controladores MVC.

O código a seguir é gerado pelo modelo Razor Pages usando contas de usuário individuais e mostra como adicionar serviços adicionais ao contêiner usando os métodos de extensão AddDbContext e AddDefaultIdentity:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    services.AddRazorPages();
}

Considere o seguinte método ConfigureServices, que registra serviços e configura opções:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<PositionOptions>(
        Configuration.GetSection(PositionOptions.Position));
    services.Configure<ColorOptions>(
        Configuration.GetSection(ColorOptions.Color));

    services.AddScoped<IMyDependency, MyDependency>();
    services.AddScoped<IMyDependency2, MyDependency2>();

    services.AddRazorPages();
}

Grupos relacionados de registros podem ser movidos para um método de extensão para registrar serviços. Por exemplo, os serviços de configuração são adicionados à seguinte classe:

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class MyConfigServiceCollectionExtensions
    {
        public static IServiceCollection AddConfig(
             this IServiceCollection services, IConfiguration config)
        {
            services.Configure<PositionOptions>(
                config.GetSection(PositionOptions.Position));
            services.Configure<ColorOptions>(
                config.GetSection(ColorOptions.Color));

            return services;
        }

        public static IServiceCollection AddMyDependencyGroup(
             this IServiceCollection services)
        {
            services.AddScoped<IMyDependency, MyDependency>();
            services.AddScoped<IMyDependency2, MyDependency2>();

            return services;
        }
    }
}

Os restantes serviços estão registados numa classe semelhante. O seguinte método ConfigureServices usa os novos métodos de extensão para registrar os serviços:

public void ConfigureServices(IServiceCollection services)
{
    services.AddConfig(Configuration)
            .AddMyDependencyGroup();

    services.AddRazorPages();
}

Nota: Cada método de extensão services.Add{GROUP_NAME} adiciona e potencialmente configura serviços. Por exemplo, AddControllersWithViews adiciona os serviços que os controladores MVC com exibições exigem e AddRazorPages adiciona os serviços Razor o Pages requer. Recomendamos que os aplicativos sigam a convenção de nomenclatura de criação de métodos de extensão no namespace Microsoft.Extensions.DependencyInjection. Criando métodos de extensão no namespace Microsoft.Extensions.DependencyInjection:

  • Encapsula grupos de registos de serviço.
  • Fornece acesso conveniente IntelliSense ao serviço.

Vida útil do serviço

Consulte duração de vida do serviço na injeção de dependência no .NET

Para usar serviços com escopo no middleware, use uma das seguintes abordagens:

  • Injete o serviço no método Invoke ou InvokeAsync do middleware. Usar de injeção do construtor gera uma exceção de tempo de execução porque força o serviço com escopo a se comportar como um singleton. O exemplo na seção Ciclo de vida e opções de registro demonstra a abordagem InvokeAsync.
  • Use middleware com base em fábrica. O middleware registado usando esta abordagem é ativado por pedido do cliente (conexão), o que permite a injeção de serviços com escopo no método InvokeAsync do middleware.

Para obter mais informações, consulte Write custom ASP.NET Core middleware.

Métodos de registo de serviços

Consulte Métodos de registro de serviço em injeção de dependência no .NET

É comum usar várias implementações quando tipos simulados para testar.

Registrar um serviço com apenas um tipo de implementação é equivalente a registrar esse serviço com a mesma implementação e tipo de serviço. É por isso que várias implementações de um serviço não podem ser registradas usando os métodos que não aceitam um tipo de serviço explícito. Esses métodos podem registrar várias instâncias de um serviço, mas todas elas terão o mesmo tipo de implementação .

Qualquer um dos métodos de registro de serviço acima pode ser usado para registrar várias instâncias de serviço do mesmo tipo de serviço. No exemplo a seguir, AddSingleton é chamado duas vezes com IMyDependency como o tipo de serviço. A segunda chamada para AddSingleton substitui a anterior quando resolvida como IMyDependency e acumula-se com a anterior quando vários serviços são resolvidos via IEnumerable<IMyDependency>. Os serviços aparecem na ordem em que foram registrados quando resolvidos via IEnumerable<{SERVICE}>.

services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();

public class MyService
{
    public MyService(IMyDependency myDependency, 
       IEnumerable<IMyDependency> myDependencies)
    {
        Trace.Assert(myDependency is DifferentDependency);

        var dependencyArray = myDependencies.ToArray();
        Trace.Assert(dependencyArray[0] is MyDependency);
        Trace.Assert(dependencyArray[1] is DifferentDependency);
    }
}

Comportamento de injeção do construtor

Consulte comportamento de injeção do construtor em injeção de dependência no .NET

Contextos do Entity Framework

Por padrão, os contextos do Entity Framework são adicionados ao contêiner de serviço usando o tempo de vida com escopo de porque as operações de base de dados das aplicações web normalmente têm como escopo o pedido do cliente. Para utilizar uma vida útil diferente, especifique-a utilizando uma sobrecarga de AddDbContext. Os serviços de um determinado tempo de vida não devem usar um contexto de banco de dados com um tempo de vida mais curto do que o tempo de vida do serviço.

Opções de subscrição vitalícia e registo

Para demonstrar a diferença entre os tempos de vida do serviço e suas opções de registro, considere as seguintes interfaces que representam uma tarefa como uma operação com um identificador, OperationId. Dependendo de como o tempo de vida do serviço de uma operação é configurado para as seguintes interfaces, o contêiner fornece instâncias iguais ou diferentes do serviço quando solicitado por uma classe:

public interface IOperation
{
    string OperationId { get; }
}

public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }

A classe Operation a seguir implementa todas as interfaces anteriores. O construtor Operation gera um GUID e armazena os últimos 4 caracteres na propriedade OperationId.

public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
    public Operation()
    {
        OperationId = Guid.NewGuid().ToString()[^4..];
    }

    public string OperationId { get; }
}

O método Startup.ConfigureServices cria vários registos da classe Operation de acordo com os ciclos de vida nomeados.

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IOperationTransient, Operation>();
    services.AddScoped<IOperationScoped, Operation>();
    services.AddSingleton<IOperationSingleton, Operation>();

    services.AddRazorPages();
}

O aplicativo de exemplo demonstra o tempo de vida do objeto dentro e entre as solicitações. O IndexModel e o middleware solicitam cada tipo de IOperation e registam as OperationId para cada uma:

public class IndexModel : PageModel
{
    private readonly ILogger _logger;
    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;
    private readonly IOperationScoped _scopedOperation;

    public IndexModel(ILogger<IndexModel> logger,
                      IOperationTransient transientOperation,
                      IOperationScoped scopedOperation,
                      IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _scopedOperation    = scopedOperation;
        _singletonOperation = singletonOperation;
    }

    public void  OnGet()
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + _scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
    }
}

Semelhante ao IndexModel, o middleware resolve os mesmos serviços:

public class MyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;

    public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
        IOperationTransient transientOperation,
        IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _singletonOperation = singletonOperation;
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context,
        IOperationScoped scopedOperation)
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

        await _next(context);
    }
}

public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyMiddleware>();
    }
}

Os serviços com escopo devem ser resolvidos no método InvokeAsync:

public async Task InvokeAsync(HttpContext context,
    IOperationScoped scopedOperation)
{
    _logger.LogInformation("Transient: " + _transientOperation.OperationId);
    _logger.LogInformation("Scoped: "    + scopedOperation.OperationId);
    _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

    await _next(context);
}

A saída do logger mostra:

  • Objetos transitórios são sempre diferentes. O valor OperationId transitório é diferente no IndexModel e no middleware.
  • Os objetos de com escopo são os mesmos para uma determinada solicitação, mas diferem em cada nova solicitação.
  • objetos Singleton são os mesmos para todas as requisições.

Para reduzir a saída de log, defina "Logging:LogLevel:Microsoft:Error" no arquivo appsettings.Development.json:

{
  "MyKey": "MyKey from appsettings.Developement.json",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "System": "Debug",
      "Microsoft": "Error"
    }
  }
}

Ligue para os serviços a partir do principal

Crie um IServiceScope com IServiceScopeFactory.CreateScope para resolver um serviço de escopo dentro do âmbito da aplicação. Essa abordagem é útil para aceder a um serviço com escopo definido no arranque para executar tarefas de inicialização.

O exemplo a seguir mostra como aceder ao serviço com escopo IMyDependency e chamar o seu método WriteMessage em Program.Main:

public class Program
{
    public static void Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();

        using (var serviceScope = host.Services.CreateScope())
        {
            var services = serviceScope.ServiceProvider;

            try
            {
                var myDependency = services.GetRequiredService<IMyDependency>();
                myDependency.WriteMessage("Call services from main");
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occurred.");
            }
        }

        host.Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

Validação do âmbito

Consulte comportamento de injeção do construtor em injeção de dependência no .NET

Para obter mais informações, consulte Validação de escopo.

Solicitar Serviços

Os serviços e suas dependências dentro de uma solicitação ASP.NET Core são expostos por meio HttpContext.RequestServices.

A estrutura cria um escopo por solicitação e RequestServices expõe o provedor de serviços com escopo. Todos os serviços com escopo são válidos enquanto a solicitação estiver ativa.

Observação

Prefira solicitar dependências como parâmetros do construtor, em vez de resolver serviços a partir de RequestServices. Solicitar dependências como parâmetros do construtor produz classes que são mais fáceis de testar.

Serviços de design para injeção de dependência

Ao projetar serviços para injeção de dependência:

  • Evite classes e membros estáticos e com estado. Evite criar um estado global ao projetar aplicativos que usem serviços singleton.
  • Evite a instanciação direta de classes dependentes dentro dos serviços. A instanciação direta acopla o código a uma implementação específica.
  • Torne os serviços pequenos, bem fatorados e facilmente testados.

Se uma classe tiver muitas dependências injetadas, isso pode ser um sinal de que a classe tem muitas responsabilidades e viola o Princípio de Responsabilidade Única (SRP). Tente refatorar a classe transferindo algumas de suas responsabilidades para novas classes. Lembre-se de que as classes de modelo de página Razor Pages e as classes de controlador MVC devem se concentrar nas preocupações da interface do usuário.

Eliminação de serviços

O contêiner chama Dispose para os tipos de IDisposable que ele cria. Os serviços resolvidos a partir do contêiner nunca devem ser descartados pelo desenvolvedor. Se um tipo ou fábrica estiver registado como singleton, o contêiner descarta o singleton automaticamente.

No exemplo a seguir, os serviços são criados pelo contêiner de serviço e descartados automaticamente:

public class Service1 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service1: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service1.Dispose");
        _disposed = true;
    }
}

public class Service2 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service2: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service2.Dispose");
        _disposed = true;
    }
}

public interface IService3
{
    public void Write(string message);
}

public class Service3 : IService3, IDisposable
{
    private bool _disposed;

    public Service3(string myKey)
    {
        MyKey = myKey;
    }

    public string MyKey { get; }

    public void Write(string message)
    {
        Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service3.Dispose");
        _disposed = true;
    }
}
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<Service1>();
    services.AddSingleton<Service2>();
    
    var myKey = Configuration["MyKey"];
    services.AddSingleton<IService3>(sp => new Service3(myKey));

    services.AddRazorPages();
}
public class IndexModel : PageModel
{
    private readonly Service1 _service1;
    private readonly Service2 _service2;
    private readonly IService3 _service3;

    public IndexModel(Service1 service1, Service2 service2, IService3 service3)
    {
        _service1 = service1;
        _service2 = service2;
        _service3 = service3;
    }

    public void OnGet()
    {
        _service1.Write("IndexModel.OnGet");
        _service2.Write("IndexModel.OnGet");
        _service3.Write("IndexModel.OnGet");
    }
}

O console de depuração mostra a seguinte saída após cada atualização da página Índice:

Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = My Key from config
Service1.Dispose

Serviços não criados pelo contêiner de serviço

Considere o seguinte código:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton(new Service1());
    services.AddSingleton(new Service2());

    services.AddRazorPages();
}

No código anterior:

  • As instâncias de serviço não são criadas pelo contêiner de serviço.
  • A estrutura não descarta os serviços automaticamente.
  • O desenvolvedor é responsável por gerir os serviços.

Orientação IDisposable para instâncias transitórias e compartilhadas

Consulte orientação IDisposable para de instância transitória e compartilhada em injeção de dependência no .NET

Substituição de contentor de serviço padrão

Consulte de substituição de contêiner de serviço padrão em injeção de dependência no .NET

Recomendações

Consulte as recomendações em .NET injeção de dependência

  • Evite usar o padrão de localizador de serviço . Por exemplo, não invoque GetService para obter uma instância de serviço quando puder usar DI:

    Incorreto:

    Código incorreto

    Correto:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • Outra variação do localizador de serviços a ser evitada é injetar uma fábrica que resolve dependências em tempo de execução. Ambas as práticas misturam as estratégias de Inversão de Controlo.

  • Evite o acesso estático a HttpContext (por exemplo, IHttpContextAccessor.HttpContext).

  • Evite chamadas para BuildServiceProvider em ConfigureServices. Chamar BuildServiceProvider normalmente acontece quando o desenvolvedor deseja resolver um serviço em ConfigureServices. Por exemplo, considere o caso em que o LoginPath é carregado da configuração. Evite a seguinte abordagem:

    código incorreto ao chamar BuildServiceProvider

    Na imagem anterior, selecionar a linha ondulada verde sob services.BuildServiceProvider mostra o seguinte aviso ASP0000:

    ASP0000 Chamar 'BuildServiceProvider' a partir do código do aplicativo resulta na criação de uma cópia adicional dos serviços singleton. Considere alternativas como serviços de injeção de dependência como parâmetros para 'Configurar'.

    Chamar BuildServiceProvider cria um segundo contêiner, que pode criar singletons rasgados e causar referências a gráficos de objetos em vários contêineres.

    Uma maneira correta de obter LoginPath é usar o suporte interno do padrão de opções para DI:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddCookie();
    
        services.AddOptions<CookieAuthenticationOptions>(
                            CookieAuthenticationDefaults.AuthenticationScheme)
            .Configure<IMyService>((options, myService) =>
            {
                options.LoginPath = myService.GetLoginPath();
            });
    
        services.AddRazorPages();
    }
    
  • Os serviços transitórios descartáveis são capturados pelo contentor para descarte. Isso pode tornar-se um vazamento de memória caso seja resolvido a partir do container de nível superior.

  • Habilite a validação de escopo para garantir que o aplicativo não tenha singletons que capturem serviços com escopo. Para obter mais informações, consulte Validação de escopo.

Como todos os conjuntos de recomendações, você pode encontrar situações em que ignorar uma recomendação é necessário. As exceções são raras, na sua maioria casos especiais dentro do próprio quadro.

A DI é uma alternativa aos padrões de acesso a objetos estáticos/globais. Talvez você não consiga perceber os benefícios da DI se misturá-la com o acesso a objetos estáticos.

Orchard Core é uma estrutura de aplicativos para a criação de aplicativos modulares e multilocatários no ASP.NET Core. Para obter mais informações, consulte a documentação do Orchard Core.

Consulte os cases do Orchard Core para ver como criar aplicações modulares e multi-inquilinos utilizando apenas o Orchard Core Framework, sem recorrer a nenhum dos seus recursos específicos do CMS.

Serviços fornecidos pelo framework

O método Startup.ConfigureServices registra serviços que o aplicativo usa, incluindo recursos da plataforma, como Entity Framework Core e ASP.NET Core MVC. Inicialmente, o IServiceCollection fornecido ao ConfigureServices tem serviços definidos pelo framework, dependendo de como o host foi configurado. Para aplicativos baseados nos modelos ASP.NET Core, a estrutura registra mais de 250 serviços.

A tabela a seguir lista uma pequena amostra desses serviços registrados na estrutura:

Tipo de Serviço Tempo de vida
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Transitório
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Transitório
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Transitório
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Transitório
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Recursos adicionais