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 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 é 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

ASP.NET Core suporta o padrão de desenho de software de injeção de dependência (DI), que é uma técnica para alcançar 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ências em aplicações além de aplicações Web, consulte injeção de dependências 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á incluída 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. Em um projeto grande com várias classes que dependem de MyDependency, o código de configuração fica espalhado pela aplicação.
  • 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}");
    }
}

O aplicativo de exemplo registra o serviço de IMyDependency com o tipo concreto 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 da Program.cs regista a nova implementação 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. é um serviçofornecido pela estrutura .

Não é incomum usar a injeção de dependência de forma encadeada. Cada dependência solicitada solicita, por sua vez, as 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 contêiner resolve ILogger<TCategoryName> ao tirar partido dos tipos abertos (genéricos) , eliminando a necessidade de registar todos os tipos genéricos construídos .

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 robusto registro sistema. 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 deveria precisar escrever registos. 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 com o sufixo Add{GROUP_NAME} para registar todos os serviços exigidos por uma funcionalidade 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 vistas exigem e AddRazorPages adiciona os serviços que Razor Pages requer.

Vida útil do serviço

Consulte ciclos de vida dos serviços na injeção de dependências no .NET

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

  • Injete o serviço no método Invoke ou InvokeAsync do middleware. Usar a injeção de construtor gera uma exceção em tempo de execução porque força o serviço com escopo a se comportar como um singleton. O exemplo na seção Tempo de vida útil 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

Veja métodos de registo de serviços em injeção de dependências em .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 sobrepõe-se à anterior quando esta é resolvida como IMyDependency e acumula-se à anterior quando múltiplos 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ços com um tempo de vida com escopo de usando o, porque as operações de base de dados das aplicações Web normalmente têm o pedido do cliente como escopo. Para usar um tempo de vida diferente, especifique o tempo de vida usando 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 seguinte código cria múltiplos 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 o OperationId de cada um:

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 com 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 solicitaçõ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 de escopo por um tempo limitado quando a aplicação é iniciada.

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 da injeção via construtor em injeção de dependências 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 escopados 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 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 com estado, estáticos. Evite criar um estado global ao desenhar aplicativos para usar 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 IDisposable que cria. Os serviços resolvidos a partir do contêiner nunca devem ser descartados pelo desenvolvedor. Se um tipo ou fábrica estiver registrado como um singleton, o contêiner descarta o singleton automaticamente.

No exemplo a seguir, os serviços são criados pelo contêiner de serviços e removidos 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 elimina 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 contenedor de serviço padrão

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

Recomendações

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

  • 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 é utilizar uma fábrica que resolve dependências em tempo de execução. Ambas estas práticas misturam estratégias de Inversão de Controle.

  • 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.

Veja os exemplos do Orchard Core para saber como criar aplicações modulares e multi-inquilino usando apenas o Orchard Core Framework sem utilizar quaisquer características específicas 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 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 aplicações que não sejam aplicações 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.

Visualizar ou descarregar 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. Em um grande projeto, onde várias classes dependem de MyDependency, o código de configuração torna-se disperso por toda a aplicação.
  • 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}");
    }
}

O aplicativo de exemplo registra o serviço de IMyDependency com o tipo concreto 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 e a página Razor:

  • 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 Program.cs atualizada regista a nova implementação 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 é solicitado no construtor. é um serviçofornecido pelo quadro .

É comum 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> ao tirar 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 robusto registro sistema. 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 registos de log. 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 o registo de é fornecido pela plataforma.

Registar grupos de serviços utilizando 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 método de extensão único de Add{GROUP_NAME} para registrar todos os serviços exigidos por uma funcionalidade 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 vistas exigem e AddRazorPages adiciona os serviços que as Páginas Razor requerem.

Vida útil do serviço

Consulte ciclo de vida do serviço na injeção de dependências 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. O uso da injeção do construtor gera uma exceção em tempo de execução porque força o serviço com escopo a comportar-se como um singleton. O exemplo na secção Ciclo de vida e opções de registo demonstra a abordagem InvokeAsync.
  • Use middleware de 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 registo de serviço em Injeção de Dependências 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 a mesma implementação do tipo.

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 é adicionada à anterior quando múltiplos serviços são resolvidos através de 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 container de serviços usando o com um tempo de vida definido porque as operações de banco de dados do aplicativo Web normalmente têm como escopo a solicitação do cliente. Para usar um tempo de vida diferente, especifique o tempo de vida usando 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 vitalícias e de 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 registros da classe Operation de acordo com os tempos 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 registram as OperationId para cada:

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 com 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 solicitaçõ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 o comportamento da injeção do construtor na 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 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 com estado e membros estáticos. Evite criar um estado global desenvolvendo aplicativos para usar serviços do tipo 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 IDisposable que 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 contenedor descarta o singleton automaticamente.

No exemplo a seguir, os serviços são criados pelo contêiner de serviço e descartados 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 descartar 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 do contentor de serviço padrão

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

Recomendações

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

  • 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 se misturam Inversão de Controle estratégias.

  • 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 exemplos do Orchard Core para saber como criar aplicações modulares e multilocatárias usando apenas o Orchard Core Framework sem 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 aplicações que não sejam 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.

Ver ou transferir código de exemplo (como transferir)

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 a depender de MyDependency, o código de configuração fica disperso pela aplicação.
  • 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 concreto 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 utiliza o tipo concreto MyDependency, apenas a interface IMyDependency que este 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 é solicitado 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> ao aproveitar os 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 das aplicações não deveria precisar escrever registos de eventos. 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 o registo é fornecido pelo framework.

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 de Add{GROUP_NAME} para registrar todos os serviços exigidos por um recurso de estrutura. 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 vistas requerem, e AddRazorPages adiciona os serviços que Razor 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.

A vida útil do serviço

Consulte ciclos de vida do serviço em injeção de dependências no .NET

Para usar serviços controlados no middleware, utilize uma das abordagens seguintes:

  • Injete o serviço no método Invoke ou InvokeAsync do middleware. Usar injeção de construtor gera uma exceção em tempo de execução porque força o serviço de escopo a se comportar como um singleton. O exemplo na secção Vida útil e opções de registo demonstra a abordagem InvokeAsync.
  • Use middleware baseado 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 contribui para 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 de tempo de vida com escopo de porque as operações de banco de dados do aplicativo Web normalmente têm como escopo a solicitação do cliente. Para usar um tempo de vida diferente, especifique o tempo de vida usando 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 vitalícias e de 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 guarda na propriedade OperationId os últimos 4 caracteres.

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 um:

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 com escopo são os mesmos para um determinado pedido, mas diferem em cada novo pedido.
  • objetos Singleton são os mesmos para todas as solicitaçõ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 ao invés 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 com estado interno e estáticos. Evite criar um estado global projetando aplicativos para usar 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 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 elimina 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 elimina os serviços automaticamente.
  • O desenvolvedor é responsável por descartar 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 contêiner de serviço padrão

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

Recomendações

Consulte Recomendações em injeção de dependência no .NET

  • 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 factory que resolve dependências em tempo de execução. Ambas as práticas se misturam Inversão de Controle estratégias.

  • 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 é um framework de aplicação para criar aplicações modulares e multitenant no ASP.NET Core. Para obter mais informações, consulte a Documentação do Orchard Core.

Consulte os exemplos do Orchard Core para ver como criar aplicações modulares e multilocatárias utilizando apenas a framework Orchard Core, sem recorrer a nenhuma das suas funcionalidades específicas de 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 pela estrutura 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