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

Por Kirk Ltda, Steve Smithe James Da ltda

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

Para obter mais informações específicas sobre injeção de dependência em controladores de 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 diferentes de aplicativos Web, consulte Injeção de dependência no .NET.

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

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

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

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

Uma dependência é um objeto de que outro objeto depende. Examine a classe MyDependency a seguir com um método de que outras classes WriteMessage dependem:

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

Uma classe pode criar uma instância da MyDependency classe para usar seu método WriteMessage . No exemplo a seguir, MyDependency a classe é 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 MyDependency classe . As dependências de código, como no exemplo anterior, são problemáticas e devem ser evitadas pelos seguintes motivos:

  • Para substituir MyDependency por uma implementação diferente, a classe deve IndexModel ser modificada.
  • Se MyDependency tiver dependências, elas também deverão ser configuradas pela IndexModel classe . Em um projeto grande com várias classes dependendo da MyDependency, o código de configuração fica pulverizado por todo o aplicativo.
  • É difícil testar a unidade dessa implementação.

Injeção de dependência trata desses problemas da seguinte maneira:

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

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

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

Essa 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 IMyDependency serviço com o tipo concreto MyDependency . O AddScoped método registra o serviço com um tempo de vida com escopo, o tempo de vida de uma única solicitação. Descreveremos posteriormente neste tópico os tempos de vida do serviço.

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 IMyDependency serviço é 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 Page:

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

A implementação da IMyDependency interface pode ser aprimorada usando a API de registro em log integrado:

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 Program.cs atualizado registra a nova IMyDependency implementação:

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 uma maneira encadeada. Por sua vez, cada dependência solicitada solicita suas próprias dependências. O contêiner resolve as dependências no grafo e retorna o serviço totalmente resolvido. O conjunto de dependências que precisa ser resolvido normalmente é chamado de árvore de dependência, grafo de dependência ou grafo de objeto.

O contêiner é resolvido aproveitando os tipos abertos (genéricos), eliminando a necessidade de registrar todos os ILogger<TCategoryName> 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 IMyDependency serviço .
  • 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 registro em log robusto. As IMyDependency implementações mostradas nos exemplos anteriores foram escritas para demonstrar a DI básica, não para implementar o registro em log. A maioria dos aplicativos não precisa escrever os loggers. O código a seguir demonstra o uso do log padrão, que não exige que nenhum serviço seja 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 é necessário atualizar Program.cs, pois o registro em log é fornecido pela estrutura .

Serviços injetados em Program.c

Qualquer serviço registrado com o contêiner de DI pode ser resolvido app.Services em Program.cs:

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

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

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

O código a seguir é gerado pelo modelo Pages usando contas de usuário individuais e mostra como adicionar serviços adicionais ao contêiner usando os métodos Razor 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 os serviços e configura as 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;
        }
    }
}

Os serviços restantes são registrados em uma 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();

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

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

Tempos de vida do serviço

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

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

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

Para obter mais informações, consulte Escrever middleware do ASP.NET Core personalizado.

Métodos de registro do serviço

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

É comum usar várias implementações ao simular tipos para testaro .

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 todos eles 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 como o tipo de IMyDependency serviço. A segunda chamada para substitui a anterior quando resolvida como e adiciona à anterior quando AddSingleton IMyDependency vários serviços são resolvidos por meio de IEnumerable<IMyDependency> . Os serviços aparecem na ordem em que foram registrados quando resolvidos por meio de 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 da injeção de construtor

Consulte Comportamento de injeção de construtor na injeção de dependência no .NET

Contextos de Entity Framework

Por padrão, Entity Framework contextos são adicionados ao contêiner de serviço usando o tempo de vida com escopo, pois as operações de banco de dados do aplicativo Web normalmente têm escopo para a solicitação do cliente. Para usar um tempo de vida diferente, especifique o tempo de vida usando uma AddDbContext sobrecarga. 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 tempo de vida e de registro

Para demonstrar a diferença entre os tempos de vida do serviço e suas opções de registro, considere as interfaces a seguir 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 as mesmas instâncias ou instâncias 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 Operation construtor 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 Operation classe de acordo com os tempo 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();

O aplicativo de exemplo demonstra os tempos de vida do objeto dentro e entre solicitações. O IndexModel e o middleware solicitam cada tipo IOperation de tipo e registram o para cada OperationId 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 InvokeAsync método :

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:

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

Para reduzir a saída de log, de definido "Logging:LogLevel:Microsoft:Error" nas appsettings. Arquivo Development.json:

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

Resolver um serviço na iniciar o 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 de escopo

Consulte Comportamento de injeção de construtor na injeção de dependência no .NET

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

Serviços de solicitação

Os serviços e suas dependências em uma solicitação ASP.NET Core são expostos por meio de 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 está ativa.

Observação

Prefira solicitar dependências como parâmetros de construtor em vez de resolver serviços do RequestServices . Solicitar dependências como parâmetros de construtor gera classes mais fáceis de testar.

Projetar serviços para injeção de dependência

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

  • Evite membros e classes estáticas com estado. Evite criar estado global criando aplicativos para usar serviços singleton.
  • Evite a instanciação direta das classes dependentes em serviços. A instanciação direta acopla o código a uma implementação específica.
  • Tornar os serviços pequenos, bem fatorados e facilmente testados.

Se uma classe tiver muitas dependências injetadas, pode ser um sinal de que a classe tem muitas responsabilidades e viola o SRP (Princípiode Responsabilidade Única). Tente refactorar a classe movendo algumas de suas responsabilidades para novas classes. Tenha em mente que as classes de modelo de página Pages e as classes de controlador Razor MVC devem se concentrar em preocupações com a interface do usuário.

Descarte de serviços

O contêiner chama Dispose para os tipos IDisposable criados por ele. Os serviços resolvidos do contêiner nunca devem ser descartados pelo desenvolvedor. Se um tipo ou fábrica for registrado como singleton, o contêiner descartará 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\Services\Service1.cs [!code-csharp]

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
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<Service1>();
builder.Services.AddSingleton<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.

Diretrizes IDisposable para instâncias transitórias e compartilhadas

Consulte IDisposable guidance for Transient and shared instance in Dependency injection in .NET (Diretrizes IDisposable para instância transitória e compartilhada em Injeção de dependência no .NET)

Substituição do contêiner de serviço padrão

Consulte Substituição de contêiner de serviço 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 do localizador de serviço. Por exemplo, não invoque GetService para obter uma instância de serviço quando for possível usar a DI:

    Incorreto:

    Código incorreto

    Correto:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • Outra variação de localizador de serviço a ser evitada é injetar um alocador que resolve as dependências em runtime. Essas duas 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 objeto estático/global. Talvez você não obtenha os benefícios da DI se combiná-lo com o acesso a objeto estático.

O Orchard Core é uma estrutura de aplicativos para a criação de aplicativos modulares multi tenant em ASP.NET Core. Para obter mais informações, consulte a Documentação do Orchard Core.

Consulte os exemplos do Orchard Core para ver exemplos de como criar aplicativos modulares e multi-locatários usando apenas o Orchard Core Framework sem qualquer um de seus recursos específicos do CMS.

Serviços fornecidos pela estrutura

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

A tabela a seguir lista um pequeno exemplo desses serviços registrados em estrutura:

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

Recursos adicionais

Por Kirk Ltd, Steve Smith, Scott A meioe James Da ltda

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

Para obter mais informações específicas sobre injeção de dependência em controladores de 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 diferentes de aplicativos Web, consulte Injeção de dependência no .NET.

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

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

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

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

Uma dependência é um objeto de que outro objeto depende. Examine a classe MyDependency a seguir com um método de que outras classes WriteMessage dependem:

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

Uma classe pode criar uma instância da MyDependency classe para usar seu método WriteMessage . No exemplo a seguir, MyDependency a classe é 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 MyDependency classe . 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 IndexModel classe deve ser modificada.
  • Se MyDependency tiver dependências, elas também deverão ser configuradas pela IndexModel classe. Em um projeto grande com várias classes dependendo da MyDependency, o código de configuração fica pulverizado por todo o aplicativo.
  • É difícil testar a unidade dessa implementação. O aplicativo deve usar uma simulação ou stub da classe MyDependency, o que não é possível com essa abordagem.

Injeção de dependência trata desses problemas da seguinte maneira:

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

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

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

Essa 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 IMyDependency serviço com o tipo concreto MyDependency . O AddScoped método registra o serviço com um tempo de vida de escopo, o tempo de vida de uma única solicitação. Descreveremos posteriormente neste tópico os tempos de vida do serviço.

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

    services.AddRazorPages();
}

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

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

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

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

Usando o padrão DI, o controlador:

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

A implementação da IMyDependency interface pode ser aprimorada usando a API de registro em 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 ConfigureServices método atualizado registra a nova IMyDependency implementação:

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

    services.AddRazorPages();
}

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

Não é incomum usar a injeção de dependência de uma maneira encadeada. Por sua vez, cada dependência solicitada solicita suas próprias dependências. O contêiner resolve as dependências no grafo e retorna o serviço totalmente resolvido. O conjunto de dependências que precisa ser resolvido normalmente é chamado de árvore de dependência, grafo de dependência ou grafo de objeto.

O contêiner resolve aproveitando ILogger<TCategoryName> os tipos abertos (genéricos), eliminando a necessidade de registrar cada tipo construído (genérico).

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

  • Normalmente é um objeto que fornece um serviço para outros objetos, como o IMyDependency serviço.
  • O 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 registro em log robusto. As IMyDependency implementações mostradas nos exemplos anteriores foram escritas para demonstrar a di básica, não para implementar o registro em log. A maioria dos aplicativos não deve precisar gravar agentes. O código a seguir demonstra como usar o log padrão, que não exige 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 registro em log é fornecido pela estrutura.

Serviços injetados na inicialização

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

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

Qualquer serviço registrado com o contêiner DI pode ser injetado no Startup.Configure método:

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

Para obter mais informações, consulte Inicialização de aplicativo no ASP.NET Core e acessar a configuração na inicialização.

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

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

O código a seguir é gerado pelo Razor modelo de páginas 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 ConfigureServices método, que registra os serviços e configura as 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;
        }
    }
}

Os serviços restantes são registrados em uma classe semelhante. O método a seguir 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();
}

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

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

Tempos de vida do serviço

Confira tempos de vida de serviço na injeção de dependência no .net

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

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

Para obter mais informações, consulte Escrever middleware do ASP.NET Core personalizado.

Métodos de registro do serviço

Consulte métodos de registro de serviço na injeção de dependência no .net

É comum usar várias implementações ao simular tipos para teste.

O registro de um serviço com apenas um tipo de implementação é equivalente ao registro desse 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 todos 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 a adiciona à anterior quando vários serviços são resolvidos por meio de IEnumerable<IMyDependency> . Os serviços aparecem na ordem em que foram registrados quando resolvidos por meio de 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 da injeção de construtor

Consulte comportamento de injeção de Construtor na injeção de dependência no .net

Contextos de Entity Framework

Por padrão, os contextos de Entity Framework são adicionados ao contêiner de serviço usando o tempo de vida do escopo porque as operações de banco de dados do aplicativo Web normalmente são delimitadas à solicitação do cliente. Para usar um tempo de vida diferente, especifique o tempo de vida usando uma AddDbContext sobrecarga. Os serviços de um determinado tempo de vida não devem usar um contexto de banco de dados com um tempo de vida menor do que o tempo de vida do serviço.

Opções de tempo de vida e de registro

Para demonstrar a diferença entre tempos de vida de 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 está configurado para as seguintes interfaces, o contêiner fornecerá as mesmas instâncias do serviço ou as mesmas 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 a seguir Operation implementa todas as interfaces anteriores. O Operation Construtor gera um GUID e armazena os quatro últimos caracteres na OperationId Propriedade:

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

    public string OperationId { get; }
}

O Startup.ConfigureServices método cria vários registros da classe de Operation acordo com os tempos 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 tempos de vida de objeto dentro e entre solicitações. O IndexModel e o middleware solicitam cada tipo de IOperation tipo e registram o 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 InvokeAsync método:

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 agente mostra:

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

Para reduzir a saída de log, defina "log: LogLevel: Microsoft: Error" em appSettings. Arquivo Development. JSON :

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

Chamar os serviços desde o principal

Crie um IServiceScope com IServiceScopeFactory.CreateScope para resolver um serviço com escopo dentro do escopo do aplicativo. Essa abordagem é útil para acessar um serviço com escopo durante a inicialização para executar tarefas de inicialização.

O exemplo a seguir mostra como acessar o serviço com escopo IMyDependency e chamar seu WriteMessage método 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 de escopo

Consulte comportamento de injeção de Construtor na injeção de dependência no .net

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

Serviços de solicitação

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

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

Observação

Prefira a solicitação de dependências como parâmetros de Construtor sobre como resolver serviços RequestServices . A solicitação de dependências como parâmetros de construtor gera classes que são mais fáceis de testar.

Projetar serviços para injeção de dependência

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

  • Evite classes e membros com estado e estáticos. Evite criar o estado global criando aplicativos para usar os serviços singleton.
  • Evite a instanciação direta das classes dependentes em 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, pode ser um sinal de que a classe tem muitas responsabilidades e viola o princípio de responsabilidade única (SRP). Tente refatorar a classe movendo algumas de suas responsabilidades para novas classes. Tenha em mente que as classes Razor de modelo de página de páginas e as classes do controlador MVC devem se concentrar nas preocupações da interface do usuário.

Descarte de serviços

O contêiner chama Dispose para os tipos IDisposable criados por ele. Os serviços resolvidos do contêiner nunca devem ser descartados pelo desenvolvedor. Se um tipo ou fábrica for registrado como um singleton, o contêiner descartará 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 saída a seguir após cada atualização da página de índice:

Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet
Service1.Dispose

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

Considere o seguinte código:

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

    services.AddRazorPages();
}

No código anterior:

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

Diretrizes de IDisposable para instâncias transitórias e compartilhadas

Consulte as diretrizes de IDisposable para a instância transitória e compartilhada na injeção de dependência no .net

Substituição do contêiner de serviço padrão

Consulte substituição do contêiner de serviço 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 do localizador de serviço. Por exemplo, não invoque GetService para obter uma instância de serviço quando for possível usar a DI:

    Incorreto:

    Código incorreto

    Correto:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • Outra variação de localizador de serviço a ser evitada é injetar um alocador que resolve as dependências em runtime. Essas duas práticas misturam estratégias de inversão de controle.

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

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

    código inadequado chamando BuildServiceProvider

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

    ASP0000 chamar ' BuildServiceProvider ' do código do aplicativo resulta em uma cópia adicional dos serviços singleton que estão sendo criados. Considere alternativas como a injeção de dependência de serviços como parâmetros para ' Configurar '.

    BuildServiceProviderA chamada cria um segundo contêiner, que pode criar singletons rasgados e causar referências a grafos de objeto em vários contêineres.

    Uma maneira correta de LoginPath se obter é 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();
    }
    
  • Serviços inposados transitórios são capturados pelo contêiner para descarte. Isso pode transformar em um vazamento de memória se resolvido no contêiner de nível superior.

  • Habilite a validação de escopo para certificar-se de que o aplicativo não tem singletons que capturam serviços com escopo. Para obter mais informações, confira Validação de escopo.

Como todos os conjuntos de recomendações, talvez você encontre situações em que é necessário ignorar uma recomendação. As exceções são raras, principalmente os casos especiais dentro da própria estrutura.

A DI é uma alternativa aos padrões de acesso a objeto estático/global. Talvez você não obtenha os benefícios da DI se combiná-lo com o acesso a objeto estático.

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

Consulte os exemplos do Orchard Core para obter exemplos de como criar aplicativos modulares e multilocatários usando apenas a estrutura do Orchard Core sem qualquer um de seus recursos específicos do CMS.

Serviços fornecidos pela estrutura

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

A tabela a seguir lista uma pequena amostra desses serviços registrados no Framework:

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