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 classeIndexModel
deve ser modificada. - Se
MyDependency
tiver dependências, elas também deverão ser configuradas pela classeIndexModel
. Num projeto grande com várias classes dependentes deMyDependency
, 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 interfaceIMyDependency
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
ouInvokeAsync
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 abordagemInvokeAsync
. - 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 noIndexModel
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:
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.
Padrões recomendados para multilocação em DI
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
- Injeção de dependência no ASP.NET Core Blazor
- Injeção de dependência em visualizações no ASP.NET Core
- Injeção de dependência em controladores no ASP.NET Core
- Injeção de dependência em manipuladores de requisitos no ASP.NET Core
- Padrões de Conferência NDC para o desenvolvimento de apps DI
- Inicialização do aplicativo no ASP.NET Core
- Ativação de middleware baseada em fábrica no ASP.NET Core
- Compreender noções básicas de injeção de dependência no .NET
- Diretrizes de injeção de dependência
- Tutorial: Usar a injeção de dependência no .NET
- Injeção de Dependência do .NET
- ASP.NET INJEÇÃO DE DEPENDÊNCIA CENTRAL: O QUE É O ISERVICECOLLECTION?
- Quatro maneiras de descartar IDisposables no ASP.NET Core
- Escrevendo código limpo no ASP.NET Core com injeção de dependência (MSDN)
- Princípio das Dependências Explícitas
- Inversão de contêineres de controle e o padrão de injeção de dependência (Martin Fowler)
- Como registrar um serviço com várias interfaces no ASP.NET Core DI
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 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 classeIndexModel
deve ser modificada. - Se
MyDependency
tiver dependências, elas também deverão ser configuradas pela classeIndexModel
. Em um projeto grande com várias classes que dependem deMyDependency
, 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 interfaceIMyDependency
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.
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
ouInvokeAsync
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 abordagemInvokeAsync
. - 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 noIndexModel
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:
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.
Padrões recomendados para multilocação em DI
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
- Injeção de dependência em visualizações no ASP.NET Core
- Injeção de dependência em controladores no ASP.NET Core
- Injeção de dependência em manipuladores de requisitos no ASP.NET Core
- Injeção de dependência no ASP.NET Core Blazor
- NDC Conference Patterns para de desenvolvimento de aplicações DI
- Inicialização do aplicativo no ASP.NET Core
- Ativação de middleware baseada em fábrica no ASP.NET Core
- Compreender noções básicas de injeção de dependência no .NET
- Diretrizes de injeção de dependência
- Tutorial: Usar a injeção de dependência no .NET
- Injeção de dependências do .NET
- INJEÇÃO DE DEPENDÊNCIA DO ASP.NET CORE: O QUE É O ISERVICECOLLECTION?
- Quatro maneiras de descartar IDisposables no ASP.NET Core
- Escrevendo código limpo no ASP.NET Core com injeção de dependência (MSDN)
- Princípio das Dependências Explícitas
- Inversão de contêineres de controle e o padrão de injeção de dependência (Martin Fowler)
- Como registrar um serviço com várias interfaces no ASP.NET Core DI
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 classeIndexModel
deve ser modificada. - Se
MyDependency
tiver dependências, elas também deverão ser configuradas pela classeIndexModel
. Em um grande projeto, onde várias classes dependem deMyDependency
, 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 interfaceIMyDependency
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.
É 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
ouInvokeAsync
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 abordagemInvokeAsync
. - 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
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 noIndexModel
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:
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.
Padrões recomendados para multilocação em DI
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
- Injeção de dependência em visualizações no ASP.NET Core
- Injeção de dependência em controladores no ASP.NET Core
- Injeção de dependência em manipuladores de requisitos no ASP.NET Core
- ASP.NET Core Blazor injeção de dependência
- Padrões da Conferência NDC para desenvolvimento de aplicações DI
- Inicialização do aplicativo no ASP.NET Core
- Ativação de middleware baseada em fábrica no ASP.NET Core
- Quatro maneiras de descartar IDisposables no ASP.NET Core
- Escrevendo código limpo no ASP.NET Core com injeção de dependência (MSDN)
- Princípio das Dependências Explícitas
- Inversão de contêineres de controle e o padrão de injeção de dependência (Martin Fowler)
- Como registrar um serviço com várias interfaces no ASP.NET Core DI
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 classeIndexModel
deve ser modificada. - Se
MyDependency
tiver dependências, elas também deverão ser configuradas pela classeIndexModel
. Num projeto grande com várias classes a depender deMyDependency
, 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 interfaceIMyDependency
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
ouInvokeAsync
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 abordagemInvokeAsync
. - 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
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 noIndexModel
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:
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
. ChamarBuildServiceProvider
normalmente acontece quando o desenvolvedor deseja resolver um serviço emConfigureServices
. Por exemplo, considere o caso em que oLoginPath
é carregado da configuração. Evite a seguinte abordagem: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.
Padrões recomendados para multilocação em DI
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
- Injeção de dependência em visualizações no ASP.NET Core
- Injeção de dependência em controladores no ASP.NET Core
- Injeção de dependência em manipuladores de requisitos no ASP.NET Core
- ASP.NET Core injeção de dependência Blazor
- NDC Conference Patterns para o desenvolvimento de aplicações DI
- Inicialização do aplicativo no ASP.NET Core
- Ativação de middleware baseada em fábrica no ASP.NET Core
- Quatro maneiras de descartar IDisposables no ASP.NET Core
- Escrevendo código limpo no ASP.NET Core com injeção de dependência (MSDN)
- Princípio das Dependências Explícitas
- Inversão de contêineres de controle e o padrão de injeção de dependência (Martin Fowler)
- Como registrar um serviço com várias interfaces no ASP.NET Core DI