ASP.NET Core での依存関係の挿入

Note

これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

警告

このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、「.NET および .NET Core サポート ポリシー」を参照してください。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

重要

この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。

現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

作成者: Kirk LarkinSteve SmithBrandon Dahler

ASP.NET Core では依存関係の挿入 (DI) ソフトウェア設計パターンがサポートされています。これは、クラスとその依存関係の間で制御の反転 (IoC) を実現するための手法です。

MVC コントローラー内の依存依存関係の挿入に固有の詳細については、「ASP.NET Core でのコントローラーへの依存関係の挿入」を参照してください。

Web アプリ以外のアプリケーションで依存関係の挿入を使用する方法の詳細については、「.NET での依存関係の挿入」を参照してください。

オプションとしての依存関係の挿入の詳細については、「ASP.NET Core のオプション パターン」を参照してください。

このトピックでは、ASP.NET Core での依存関係の挿入について説明します。 依存関係の挿入の使用に関する主なドキュメントは、「.NET での依存関係の挿入」に含まれています。

この記事のガイダンスに追加されるまたは優先する Blazor DI のガイダンスについては、「ASP.NET Core Blazor 依存関係の挿入」をご覧ください。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

依存関係の挿入の概要

"依存関係" とは、他のオブジェクトが依存するオブジェクトのことです。 他のクラスが依存している、次の WriteMessage メソッドを備えた MyDependency クラスを調べます。

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

クラスは、MyDependency クラスのインスタンスを作成して、その WriteMessage メソッドを使用することができます。 次の例で、MyDependency クラスは IndexModel クラスの依存関係です。


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

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

このクラスは MyDependency クラスを作成し、これに直接依存しています。 コードの依存関係には、前の例のような問題が含まれ、次の理由から回避する必要があります。

  • MyDependency を別の実装で置き換えるには、IndexModel クラスを変更する必要があります。
  • MyDependency が依存関係を含んでいる場合、これらは IndexModel クラスによって構成する必要があります。 複数のクラスが MyDependency に依存している大規模なプロジェクトでは、構成コードがアプリ全体に分散するようになります。
  • このような実装では、単体テストを行うことが困難です。

依存関係の挿入は、次の方法によってこれらの問題に対応します。

  • 依存関係の実装を抽象化するための、インターフェイスまたは基底クラスの使用。
  • サービス コンテナー内の依存関係の登録。 ASP.NET Core には、組み込みのサービス コンテナー IServiceProvider が用意されています。 サービスは、通常、アプリの Program.cs ファイルに登録されています。
  • サービスを使用するクラスのコンストラクターへの、サービスの "挿入"。 依存関係のインスタンスの作成、およびインスタンスが不要になったときの廃棄の役割を、フレームワークが担当します。

サンプル アプリでは、IMyDependency インターフェイスは、WriteMessage メソッドを定義します。

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

このインターフェイスは、具象型 MyDependency によって実装されます。

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

サンプル アプリにおいて、IMyDependency サービスは具象型 MyDependency を使用して登録されます。 AddScoped メソッドによって、サービスは、1 つの要求の有効期間であるスコープ付き有効期間で登録されます。 サービスの有効期間については、このトピックの後半で説明します。

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

サンプル アプリでは、IMyDependency サービスが要求され、WriteMessage メソッドを呼び出すために使用されます。

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

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

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

DI パターンを使用すると、コントローラーまたは Razor ページは次のようになります。

  • 具象型 MyDependency は使用されず、実装される IMyDependency インターフェイスのみが使用されます。 これにより、実装はコントローラーまたは Razor ページを変更することなく、簡単に変更できるようになります。
  • MyDependency のインスタンスは作成されず、DI コンテナーによって作成されます。

組み込みのログ API を使用すると、IMyDependency インターフェイスの実装を向上させることができます。

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}");
    }
}

更新された Program.cs により、新しい 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 は、コンストラクターで要求される ILogger<TCategoryName> によって異なります。 ILogger<TCategoryName> は、フレームワークで提供されるサービスです。

依存関係の挿入をチェーン形式で使用することはよくあります。 次に、要求されたそれぞれの依存関係が、それ自身の依存関係を要求します。 コンテナーによってグラフ内の依存関係が解決され、完全に解決されたサービスが返されます。 解決する必要がある依存関係の集合的なセットは、通常、"依存関係ツリー"、"依存関係グラフ"、または "オブジェクト グラフ" と呼ばれます。

コンテナーでは、(ジェネリック) オープン型を活用し、すべての (ジェネリック) 構築型を登録する必要をなくすことで、ILogger<TCategoryName> を解決します。

依存関係の挿入に関する用語では、サービスは次のようになります。

  • 通常、他のオブジェクト (IMyDependency サービスなど) にサービスを提供するオブジェクトです。
  • Web サービスを使用する場合はありますが、サービスは Web サービスに関連付けられていません。

フレームワークは、堅牢な ログ システムを備えています。 前の例に示されている IMyDependency の実装は、ログを実装するのではなく、基本的な DI を実演するために記述されています。 ほとんどのアプリでは、ロガーを記述する必要はありません。 次のコードでは、既定のログを使用する方法が示されます。この場合、サービスを登録する必要はありません。

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);
    }
}

前のコードを使用すると、ログ がフレームワークによって提供されるため、Program.cs を更新する必要はありません。

拡張メソッドを使用したサービスのグループを登録する

ASP.NET Core フレームワークは、関連するサービスのグループを登録するための規則を使用します。 規則は、単一の Add{GROUP_NAME} 拡張メソッドを使用して、フレームワーク機能に必要なすべてのサービスを登録するというものです。 たとえば、AddControllers 拡張メソッドには、MVC コントローラーに必要なサービスが登録されます。

次のコードは、個々のユーザー アカウントを使用して Razor ページ テンプレートに基づいて生成されており、拡張メソッド AddDbContext および 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();

サービスを登録し、オプションを構成する以下について考えてみましょう。

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();

関連する登録グループは、サービスを登録するための拡張メソッドに移動できます。 たとえば、構成サービスは次のクラスに追加されます。

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;
        }
    }
}

残りのサービスは、同様のクラスに登録されます。 次のコードでは、新しい拡張メソッドを使用してサービスを登録します。

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

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

builder.Services.AddRazorPages();

var app = builder.Build();

注:services.Add{GROUP_NAME} 拡張メソッドは、サービスを追加、場合によっては構成します。 たとえば、AddControllersWithViews ではビューを持つ MVC コントローラーで必要なサービスが追加され、AddRazorPages では Razor Pages で必要なサービスが追加されます。

サービスの有効期間

.NET での依存関係の挿入」の「サービスの有効期間」を参照してください

ミドルウェアでスコープ付きサービスを使用するには、次のいずれかの方法を使用します。

  • ミドルウェアの Invoke または InvokeAsync メソッドにサービスを挿入します。 コンストラクターの挿入を使用すると、スコープ付きサービスがシングルトンのように動作するように強制されるため、実行時の例外がスローされます。 「有効期間と登録のオプション」セクションにあるサンプルは、InvokeAsync による方法を示しています。
  • ファクトリ ベースのミドルウェアを使用します。 この方法を使用して登録されたミドルウェアは、クライアント要求 (接続) ごとにアクティブ化されます。これにより、スコープ付きサービスをミドルウェアのコンストラクターに挿入できるようになります。

詳細については、「カスタム ASP.NET Core ミドルウェアを記述する」を参照してください。

サービス登録メソッド

.NET での依存関係の挿入」の「サービス登録メソッド」を参照してください

テスト用に型のモックを作成する場合に、複数の実装を使用することは一般的です。

実装型のみでサービスを登録することは、同じ実装とサービスの型でそのサービスを登録することと同じです。 明示的なサービス型を使用しないメソッドを使用してサービスの複数の実装を登録できないのは、このためです。 これらのメソッドでは、サービスの複数の "インスタンス" を登録できますが、すべて同じ "実装" 型になります。

上記のサービス登録メソッドのずれかを使用して、同じサービス型の複数のサービス インスタンスを登録できます。 次の例では、IMyDependency をサービス型として使用して、AddSingleton を 2 回呼び出します。 2 回目の AddSingleton の呼び出しにより、IMyDependency として解決された場合は前のものがオーバーライドされ、IEnumerable<IMyDependency> を介して複数のサービスが解決された場合は前のものに追加されます。 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);
    }
}

キー付きサービス

キー付きサービスは、キーを使用して依存関係の挿入 (DI) サービスを登録および取得する仕組みを提供します。 サービスは、AddKeyedSingleton (または AddKeyedScopedAddKeyedTransient) を呼び出して登録することによって関連付けられます。 [FromKeyedServices] 属性でキーを指定して、登録済みサービスにアクセスします。 次のコードは、キー付きサービスの使用方法を示しています。

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"));
    }
}

ミドルウェアのキー付きサービス

ミドルウェアは、コンストラクターと 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);
}

ミドルウェア作成の詳細については、「カスタム ASP.NET Core ミドルウェアを記述する」をご覧ください。

コンストラクターの挿入の動作

.NET での依存関係の挿入」の「コンストラクターの挿入の動作」を参照してください

Entity Framework コンテキスト

既定の Entity Framework コンテキストは、スコープ付き有効期間を使用してサービス コンテナーに追加されます。これは、Web アプリ データベース操作は通常、そのスコープがクライアント要求に設定されるためです。 異なる有効期間を使用するには、AddDbContext のオーバーロードを使用して有効期間を指定します。 有効期間が与えられたサービスの場合、そのサービスより有効期間が短いデータベース コンテキストを使用できません。

有効期間と登録のオプション

サービスの有効期間とその登録のオプションの違いを示すために、タスクを識別子 OperationId を備えた操作として表す、次のインターフェイスについて考えます。 次のインターフェイスに対して操作のサービスの有効期間がどのように構成されているかに応じて、コンテナーからは、クラスによって要求されたときに、サービスの同じインスタンスか別のインスタンスが提供されます。

public interface IOperation
{
    string OperationId { get; }
}

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

次の Operation クラスでは、上記のすべてのインターフェイスが実装されます。 Operation コンストラクターによって GUID が生成され、OperationId プロパティの最後の 4 文字が格納されます。

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

    public string OperationId { get; }
}

次のコードでは、指定された有効期間に従って、Operation クラスの複数の登録を作成します。

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();

サンプル アプリでは、それぞれの要求内、およびそれぞれの要求間におけるオブジェクトの有効期間が示されます。 IndexModel とミドルウェアでは、すべての種類の IOperation 型が要求され、それぞれの OperationId がログに記録されます。

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);
    }
}

ミドルウェアは IndexModel に類似していて、同じサービスを解決します。

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>();
    }
}

スコープ付きの一時的なサービスは、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);
}

ロガーの出力は次のようになります。

  • "一時的な" オブジェクトは常に異なります。 一時的な OperationId 値は、IndexModel とミドルウェアでは異なります。
  • "スコープ付きの" オブジェクトは、特定の要求内では同じですが、それぞれの新しい要求間では異なります。
  • "シングルトン" オブジェクトは、すべての要求において同じです。

ログ出力を減らすには、appsettings.Development.json ファイル内で "Logging:LogLevel:Microsoft:Error" を設定します。

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

アプリの起動時にサービスを解決する

次のコードは、アプリの開始時に限られた期間で、スコープ指定されたサービスを解決する方法を示しています。

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();

スコープの検証

.NET での依存関係の挿入」の「コンストラクターの挿入の動作」を参照してください

詳しくは、「スコープの検証」をご覧ください。

要求サービス

ASP.NET Core 要求内のサービスとその依存関係は、HttpContext.RequestServices によって公開されます。

フレームワークでは要求ごとにスコープが作成され、RequestServices によってスコープ付きサービス プロバイダーが公開されます。 すべてのスコープ サービスは、要求がアクティブである限り有効です。

Note

RequestServices からサービスを解決するよりも、コンストラクターのパラメーターとして依存関係を要求するようにします。 コンストラクターのパラメーターとして依存関係を要求すると、テストしやすいクラスが生成されます。

依存関係の挿入のためのサービスの設計

依存関係の挿入のためのサービスの設計時には:

  • ステートフル、静的クラス、およびメンバーは避けてください。 代わりにシングルトン サービスを使用するようにアプリを設計し、グローバルな状態を作成しないようにします。
  • サービス内部で依存関係のあるクラスを直接インスタンス化することを回避します。 直接のインスタンス化は、コードの固有の実装につながります。
  • サービスを、小さく、十分に要素に分割された、テストしやすいものにします。

クラスに含まれる挿入される依存関係が多すぎる場合、それは、クラスの責任が多すぎて、単一責任の原則 (SRP) に違反しているサインである可能性があります。 責任の一部を新しいクラスに移動することにより、クラスのリファクタリングを試みます。 Razor Pages のページ モデル クラスと MVC コントローラー クラスは、UI の問題に集中する必要があることに留意します。

サービスの破棄

コンテナーは、作成する IDisposable 型の Dispose を呼び出します。 コンテナーから解決されたサービスが、開発者によって破棄されることはありません。 型またはファクトリがシングルトンとして登録されている場合、コンテナーによってシングルトンが自動的に破棄されます。

次の例では、サービスがサービス コンテナーによって作成され、自動的に破棄されます: 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");
    }
}

デバッグ コンソールでは、インデックス ページを更新するたびに次の出力が表示されます。

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

サービス コンテナーによって作成されていないサービス

次のコードがあるとします。

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

上のコードでは以下の操作が行われます。

  • サービス インスタンスは、サービス コンテナーによって作成されるわけではありません。
  • フレームワークでは、サービスが自動的に破棄されることはありません。
  • サービスを破棄する責任は開発者にあります。

一時的なインスタンスと共有インスタンスのための IDisposable ガイダンス

.NET での依存関係の挿入」の「一時的なインスタンスと共有インスタンスのための IDisposable ガイダンス」を参照してください

既定のサービス コンテナーの置換

.NET での依存関係の挿入」の「既定のサービス コンテナーの置換」を参照してください

Recommendations

.NET での依存関係の挿入」の「推奨事項」を参照してください

  • サービス ロケーター パターンの使用は避けてください。 たとえば、サービス インスタンスを取得する場合、DI を使用できるときに、GetService を呼び出さないでください。

    正しくない:

    正しくないコード

    正しい:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • 回避すべき別のサービス ロケーターのバリエーションは、実行時に依存関係を解決するファクトリを挿入することです。 この両方のプラクティスによって、複数の制御の反転方式が組み合わせられます。

  • HttpContext への静的なアクセスを回避します (たとえば IHttpContextAccessor.HttpContext)。

DI は静的/グローバル オブジェクト アクセス パターンの代替機能です。 静的オブジェクト アクセスと併用した場合、DI のメリットを実現することはできません。

Orchard Core は、ASP.NET Core でモジュール型のマルチテナント アプリケーションを構築するためのアプリケーション フレームワークです。 詳細については、Orchard Core のドキュメントを参照してください。

CMS 固有の機能を使用せずに Orchard Core Framework のみを使用してモジュール型のマルチテナント アプリを構築する方法の例については、Orchard Core のサンプルを参照してください。

フレームワークが提供するサービス

Program.cs により、Entity Framework Core や ASP.NET Core MVC といったプラットフォーム機能など、アプリで使用されるサービスが登録されます。 最初に、Program.cs に提供される IServiceCollection には、フレームワークによって定義されたサービスがあります (ホストの構成方法によって異なります)。 ASP.NET Core テンプレートに基づくアプリでは、フレームワークによって 250 を超えるサービスが登録されます。

次の表に、フレームワークによって登録されるサービスのごく一部を示します。

サービスの種類 有効期間
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory 一時的
IHostApplicationLifetime シングルトン
IWebHostEnvironment シングルトン
Microsoft.AspNetCore.Hosting.IStartup シングルトン
Microsoft.AspNetCore.Hosting.IStartupFilter 一時的
Microsoft.AspNetCore.Hosting.Server.IServer シングルトン
Microsoft.AspNetCore.Http.IHttpContextFactory 一時的
Microsoft.Extensions.Logging.ILogger<TCategoryName> シングルトン
Microsoft.Extensions.Logging.ILoggerFactory シングルトン
Microsoft.Extensions.ObjectPool.ObjectPoolProvider シングルトン
Microsoft.Extensions.Options.IConfigureOptions<TOptions> 一時的
Microsoft.Extensions.Options.IOptions<TOptions> シングルトン
System.Diagnostics.DiagnosticSource シングルトン
System.Diagnostics.DiagnosticListener シングルトン

その他の技術情報

作成者: Kirk LarkinSteve SmithBrandon Dahler

ASP.NET Core では依存関係の挿入 (DI) ソフトウェア設計パターンがサポートされています。これは、クラスとその依存関係の間で制御の反転 (IoC) を実現するための手法です。

MVC コントローラー内の依存依存関係の挿入に固有の詳細については、「ASP.NET Core でのコントローラーへの依存関係の挿入」を参照してください。

Web アプリ以外のアプリケーションで依存関係の挿入を使用する方法の詳細については、「.NET での依存関係の挿入」を参照してください。

オプションとしての依存関係の挿入の詳細については、「ASP.NET Core のオプション パターン」を参照してください。

このトピックでは、ASP.NET Core での依存関係の挿入について説明します。 依存関係の挿入の使用に関する主なドキュメントは、「.NET での依存関係の挿入」に含まれています。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

依存関係の挿入の概要

"依存関係" とは、他のオブジェクトが依存するオブジェクトのことです。 他のクラスが依存している、次の WriteMessage メソッドを備えた MyDependency クラスを調べます。

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

クラスは、MyDependency クラスのインスタンスを作成して、その WriteMessage メソッドを使用することができます。 次の例で、MyDependency クラスは IndexModel クラスの依存関係です。


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

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

このクラスは MyDependency クラスを作成し、これに直接依存しています。 コードの依存関係には、前の例のような問題が含まれ、次の理由から回避する必要があります。

  • MyDependency を別の実装で置き換えるには、IndexModel クラスを変更する必要があります。
  • MyDependency が依存関係を含んでいる場合、これらは IndexModel クラスによって構成する必要があります。 複数のクラスが MyDependency に依存している大規模なプロジェクトでは、構成コードがアプリ全体に分散するようになります。
  • このような実装では、単体テストを行うことが困難です。

依存関係の挿入は、次の方法によってこれらの問題に対応します。

  • 依存関係の実装を抽象化するための、インターフェイスまたは基底クラスの使用。
  • サービス コンテナー内の依存関係の登録。 ASP.NET Core には、組み込みのサービス コンテナー IServiceProvider が用意されています。 サービスは、通常、アプリの Program.cs ファイルに登録されています。
  • サービスを使用するクラスのコンストラクターへの、サービスの "挿入"。 依存関係のインスタンスの作成、およびインスタンスが不要になったときの廃棄の役割を、フレームワークが担当します。

サンプル アプリでは、IMyDependency インターフェイスは、WriteMessage メソッドを定義します。

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

このインターフェイスは、具象型 MyDependency によって実装されます。

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

サンプル アプリにおいて、IMyDependency サービスは具象型 MyDependency を使用して登録されます。 AddScoped メソッドによって、サービスは、1 つの要求の有効期間であるスコープ付き有効期間で登録されます。 サービスの有効期間については、このトピックの後半で説明します。

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

サンプル アプリでは、IMyDependency サービスが要求され、WriteMessage メソッドを呼び出すために使用されます。

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

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

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

DI パターンを使用すると、コントローラーまたは Razor ページは次のようになります。

  • 具象型 MyDependency は使用されず、実装される IMyDependency インターフェイスのみが使用されます。 これにより、実装はコントローラーまたは Razor ページを変更することなく、簡単に変更できるようになります。
  • MyDependency のインスタンスは作成されず、DI コンテナーによって作成されます。

組み込みのログ API を使用すると、IMyDependency インターフェイスの実装を向上させることができます。

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}");
    }
}

更新された Program.cs により、新しい 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 は、コンストラクターで要求される ILogger<TCategoryName> によって異なります。 ILogger<TCategoryName> は、フレームワークで提供されるサービスです。

依存関係の挿入をチェーン形式で使用することはよくあります。 次に、要求されたそれぞれの依存関係が、それ自身の依存関係を要求します。 コンテナーによってグラフ内の依存関係が解決され、完全に解決されたサービスが返されます。 解決する必要がある依存関係の集合的なセットは、通常、"依存関係ツリー"、"依存関係グラフ"、または "オブジェクト グラフ" と呼ばれます。

コンテナーでは、(ジェネリック) オープン型を活用し、すべての (ジェネリック) 構築型を登録する必要をなくすことで、ILogger<TCategoryName> を解決します。

依存関係の挿入に関する用語では、サービスは次のようになります。

  • 通常、他のオブジェクト (IMyDependency サービスなど) にサービスを提供するオブジェクトです。
  • Web サービスを使用する場合はありますが、サービスは Web サービスに関連付けられていません。

フレームワークは、堅牢な ログ システムを備えています。 前の例に示されている IMyDependency の実装は、ログを実装するのではなく、基本的な DI を実演するために記述されています。 ほとんどのアプリでは、ロガーを記述する必要はありません。 次のコードでは、既定のログを使用する方法が示されます。この場合、サービスを登録する必要はありません。

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);
    }
}

前のコードを使用すると、ログ がフレームワークによって提供されるため、Program.cs を更新する必要はありません。

拡張メソッドを使用したサービスのグループを登録する

ASP.NET Core フレームワークは、関連するサービスのグループを登録するための規則を使用します。 規則は、単一の Add{GROUP_NAME} 拡張メソッドを使用して、フレームワーク機能に必要なすべてのサービスを登録するというものです。 たとえば、AddControllers 拡張メソッドには、MVC コントローラーに必要なサービスが登録されます。

次のコードは、個々のユーザー アカウントを使用して Razor ページ テンプレートに基づいて生成されており、拡張メソッド AddDbContext および 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();

サービスを登録し、オプションを構成する以下について考えてみましょう。

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();

関連する登録グループは、サービスを登録するための拡張メソッドに移動できます。 たとえば、構成サービスは次のクラスに追加されます。

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;
        }
    }
}

残りのサービスは、同様のクラスに登録されます。 次のコードでは、新しい拡張メソッドを使用してサービスを登録します。

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

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

builder.Services.AddRazorPages();

var app = builder.Build();

注:services.Add{GROUP_NAME} 拡張メソッドは、サービスを追加、場合によっては構成します。 たとえば、AddControllersWithViews ではビューを持つ MVC コントローラーで必要なサービスが追加され、AddRazorPages では Razor Pages で必要なサービスが追加されます。

サービスの有効期間

.NET での依存関係の挿入」の「サービスの有効期間」を参照してください

ミドルウェアでスコープ付きサービスを使用するには、次のいずれかの方法を使用します。

  • ミドルウェアの Invoke または InvokeAsync メソッドにサービスを挿入します。 コンストラクターの挿入を使用すると、スコープ付きサービスがシングルトンのように動作するように強制されるため、実行時の例外がスローされます。 「有効期間と登録のオプション」セクションにあるサンプルは、InvokeAsync による方法を示しています。
  • ファクトリ ベースのミドルウェアを使用します。 この方法を使用して登録されたミドルウェアは、クライアント要求 (接続) ごとにアクティブ化されます。これにより、スコープ付きサービスをミドルウェアのコンストラクターに挿入できるようになります。

詳細については、「カスタム ASP.NET Core ミドルウェアを記述する」を参照してください。

サービス登録メソッド

.NET での依存関係の挿入」の「サービス登録メソッド」を参照してください

テスト用に型のモックを作成する場合に、複数の実装を使用することは一般的です。

実装型のみでサービスを登録することは、同じ実装とサービスの型でそのサービスを登録することと同じです。 明示的なサービス型を使用しないメソッドを使用してサービスの複数の実装を登録できないのは、このためです。 これらのメソッドでは、サービスの複数の "インスタンス" を登録できますが、すべて同じ "実装" 型になります。

上記のサービス登録メソッドのずれかを使用して、同じサービス型の複数のサービス インスタンスを登録できます。 次の例では、IMyDependency をサービス型として使用して、AddSingleton を 2 回呼び出します。 2 回目の AddSingleton の呼び出しにより、IMyDependency として解決された場合は前のものがオーバーライドされ、IEnumerable<IMyDependency> を介して複数のサービスが解決された場合は前のものに追加されます。 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);
    }
}

キー付きサービス

キー付きサービスは、キーを使用して依存関係の挿入 (DI) サービスを登録および取得する仕組みを提供します。 サービスは、AddKeyedSingleton (または AddKeyedScopedAddKeyedTransient) を呼び出して登録することによって関連付けられます。 [FromKeyedServices] 属性でキーを指定して、登録済みサービスにアクセスします。 次のコードは、キー付きサービスの使用方法を示しています。

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"));
    }
}

コンストラクターの挿入の動作

.NET での依存関係の挿入」の「コンストラクターの挿入の動作」を参照してください

Entity Framework コンテキスト

既定の Entity Framework コンテキストは、スコープ付き有効期間を使用してサービス コンテナーに追加されます。これは、Web アプリ データベース操作は通常、そのスコープがクライアント要求に設定されるためです。 異なる有効期間を使用するには、AddDbContext のオーバーロードを使用して有効期間を指定します。 有効期間が与えられたサービスの場合、そのサービスより有効期間が短いデータベース コンテキストを使用できません。

有効期間と登録のオプション

サービスの有効期間とその登録のオプションの違いを示すために、タスクを識別子 OperationId を備えた操作として表す、次のインターフェイスについて考えます。 次のインターフェイスに対して操作のサービスの有効期間がどのように構成されているかに応じて、コンテナーからは、クラスによって要求されたときに、サービスの同じインスタンスか別のインスタンスが提供されます。

public interface IOperation
{
    string OperationId { get; }
}

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

次の Operation クラスでは、上記のすべてのインターフェイスが実装されます。 Operation コンストラクターによって GUID が生成され、OperationId プロパティの最後の 4 文字が格納されます。

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

    public string OperationId { get; }
}

次のコードでは、指定された有効期間に従って、Operation クラスの複数の登録を作成します。

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();

サンプル アプリでは、それぞれの要求内、およびそれぞれの要求間におけるオブジェクトの有効期間が示されます。 IndexModel とミドルウェアでは、すべての種類の IOperation 型が要求され、それぞれの OperationId がログに記録されます。

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);
    }
}

ミドルウェアは IndexModel に類似していて、同じサービスを解決します。

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>();
    }
}

スコープ付きの一時的なサービスは、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);
}

ロガーの出力は次のようになります。

  • "一時的な" オブジェクトは常に異なります。 一時的な OperationId 値は、IndexModel とミドルウェアでは異なります。
  • "スコープ付きの" オブジェクトは、特定の要求内では同じですが、それぞれの新しい要求間では異なります。
  • "シングルトン" オブジェクトは、すべての要求において同じです。

ログ出力を減らすには、appsettings.Development.json ファイル内で "Logging:LogLevel:Microsoft:Error" を設定します。

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

アプリの起動時にサービスを解決する

次のコードは、アプリの開始時に限られた期間で、スコープ指定されたサービスを解決する方法を示しています。

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();

スコープの検証

.NET での依存関係の挿入」の「コンストラクターの挿入の動作」を参照してください

詳しくは、「スコープの検証」をご覧ください。

要求サービス

ASP.NET Core 要求内のサービスとその依存関係は、HttpContext.RequestServices によって公開されます。

フレームワークでは要求ごとにスコープが作成され、RequestServices によってスコープ付きサービス プロバイダーが公開されます。 すべてのスコープ サービスは、要求がアクティブである限り有効です。

Note

RequestServices からサービスを解決するよりも、コンストラクターのパラメーターとして依存関係を要求するようにします。 コンストラクターのパラメーターとして依存関係を要求すると、テストしやすいクラスが生成されます。

依存関係の挿入のためのサービスの設計

依存関係の挿入のためのサービスの設計時には:

  • ステートフル、静的クラス、およびメンバーは避けてください。 代わりにシングルトン サービスを使用するようにアプリを設計し、グローバルな状態を作成しないようにします。
  • サービス内部で依存関係のあるクラスを直接インスタンス化することを回避します。 直接のインスタンス化は、コードの固有の実装につながります。
  • サービスを、小さく、十分に要素に分割された、テストしやすいものにします。

クラスに含まれる挿入される依存関係が多すぎる場合、それは、クラスの責任が多すぎて、単一責任の原則 (SRP) に違反しているサインである可能性があります。 責任の一部を新しいクラスに移動することにより、クラスのリファクタリングを試みます。 Razor Pages のページ モデル クラスと MVC コントローラー クラスは、UI の問題に集中する必要があることに留意します。

サービスの破棄

コンテナーは、作成する IDisposable 型の Dispose を呼び出します。 コンテナーから解決されたサービスが、開発者によって破棄されることはありません。 型またはファクトリがシングルトンとして登録されている場合、コンテナーによってシングルトンが自動的に破棄されます。

次の例では、サービスがサービス コンテナーによって作成され、自動的に破棄されます: 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");
    }
}

デバッグ コンソールでは、インデックス ページを更新するたびに次の出力が表示されます。

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

サービス コンテナーによって作成されていないサービス

次のコードがあるとします。

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

上のコードでは以下の操作が行われます。

  • サービス インスタンスは、サービス コンテナーによって作成されるわけではありません。
  • フレームワークでは、サービスが自動的に破棄されることはありません。
  • サービスを破棄する責任は開発者にあります。

一時的なインスタンスと共有インスタンスのための IDisposable ガイダンス

.NET での依存関係の挿入」の「一時的なインスタンスと共有インスタンスのための IDisposable ガイダンス」を参照してください

既定のサービス コンテナーの置換

.NET での依存関係の挿入」の「既定のサービス コンテナーの置換」を参照してください

Recommendations

.NET での依存関係の挿入」の「推奨事項」を参照してください

  • サービス ロケーター パターンの使用は避けてください。 たとえば、サービス インスタンスを取得する場合、DI を使用できるときに、GetService を呼び出さないでください。

    正しくない:

    正しくないコード

    正しい:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • 回避すべき別のサービス ロケーターのバリエーションは、実行時に依存関係を解決するファクトリを挿入することです。 この両方のプラクティスによって、複数の制御の反転方式が組み合わせられます。

  • HttpContext への静的なアクセスを回避します (たとえば IHttpContextAccessor.HttpContext)。

DI は静的/グローバル オブジェクト アクセス パターンの代替機能です。 静的オブジェクト アクセスと併用した場合、DI のメリットを実現することはできません。

Orchard Core は、ASP.NET Core でモジュール型のマルチテナント アプリケーションを構築するためのアプリケーション フレームワークです。 詳細については、Orchard Core のドキュメントを参照してください。

CMS 固有の機能を使用せずに Orchard Core Framework のみを使用してモジュール型のマルチテナント アプリを構築する方法の例については、Orchard Core のサンプルを参照してください。

フレームワークが提供するサービス

Program.cs により、Entity Framework Core や ASP.NET Core MVC といったプラットフォーム機能など、アプリで使用されるサービスが登録されます。 最初に、Program.cs に提供される IServiceCollection には、フレームワークによって定義されたサービスがあります (ホストの構成方法によって異なります)。 ASP.NET Core テンプレートに基づくアプリでは、フレームワークによって 250 を超えるサービスが登録されます。

次の表に、フレームワークによって登録されるサービスのごく一部を示します。

サービスの種類 有効期間
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory 一時的
IHostApplicationLifetime シングルトン
IWebHostEnvironment シングルトン
Microsoft.AspNetCore.Hosting.IStartup シングルトン
Microsoft.AspNetCore.Hosting.IStartupFilter 一時的
Microsoft.AspNetCore.Hosting.Server.IServer シングルトン
Microsoft.AspNetCore.Http.IHttpContextFactory 一時的
Microsoft.Extensions.Logging.ILogger<TCategoryName> シングルトン
Microsoft.Extensions.Logging.ILoggerFactory シングルトン
Microsoft.Extensions.ObjectPool.ObjectPoolProvider シングルトン
Microsoft.Extensions.Options.IConfigureOptions<TOptions> 一時的
Microsoft.Extensions.Options.IOptions<TOptions> シングルトン
System.Diagnostics.DiagnosticSource シングルトン
System.Diagnostics.DiagnosticListener シングルトン

その他の技術情報

作成者: Kirk LarkinSteve SmithBrandon Dahler

ASP.NET Core では依存関係の挿入 (DI) ソフトウェア設計パターンがサポートされています。これは、クラスとその依存関係の間で制御の反転 (IoC) を実現するための手法です。

MVC コントローラー内の依存依存関係の挿入に固有の詳細については、「ASP.NET Core でのコントローラーへの依存関係の挿入」を参照してください。

Web アプリ以外のアプリケーションで依存関係の挿入を使用する方法の詳細については、「.NET での依存関係の挿入」を参照してください。

オプションとしての依存関係の挿入の詳細については、「ASP.NET Core のオプション パターン」を参照してください。

このトピックでは、ASP.NET Core での依存関係の挿入について説明します。 依存関係の挿入の使用に関する主なドキュメントは、「.NET での依存関係の挿入」に含まれています。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

依存関係の挿入の概要

"依存関係" とは、他のオブジェクトが依存するオブジェクトのことです。 他のクラスが依存している、次の WriteMessage メソッドを備えた MyDependency クラスを調べます。

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

クラスは、MyDependency クラスのインスタンスを作成して、その WriteMessage メソッドを使用することができます。 次の例で、MyDependency クラスは IndexModel クラスの依存関係です。


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

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

このクラスは MyDependency クラスを作成し、これに直接依存しています。 コードの依存関係には、前の例のような問題が含まれ、次の理由から回避する必要があります。

  • MyDependency を別の実装で置き換えるには、IndexModel クラスを変更する必要があります。
  • MyDependency が依存関係を含んでいる場合、これらは IndexModel クラスによって構成する必要があります。 複数のクラスが MyDependency に依存している大規模なプロジェクトでは、構成コードがアプリ全体に分散するようになります。
  • このような実装では、単体テストを行うことが困難です。

依存関係の挿入は、次の方法によってこれらの問題に対応します。

  • 依存関係の実装を抽象化するための、インターフェイスまたは基底クラスの使用。
  • サービス コンテナー内の依存関係の登録。 ASP.NET Core には、組み込みのサービス コンテナー IServiceProvider が用意されています。 サービスは、通常、アプリの Program.cs ファイルに登録されています。
  • サービスを使用するクラスのコンストラクターへの、サービスの "挿入"。 依存関係のインスタンスの作成、およびインスタンスが不要になったときの廃棄の役割を、フレームワークが担当します。

サンプル アプリでは、IMyDependency インターフェイスは、WriteMessage メソッドを定義します。

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

このインターフェイスは、具象型 MyDependency によって実装されます。

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

サンプル アプリにおいて、IMyDependency サービスは具象型 MyDependency を使用して登録されます。 AddScoped メソッドによって、サービスは、1 つの要求の有効期間であるスコープ付き有効期間で登録されます。 サービスの有効期間については、このトピックの後半で説明します。

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

サンプル アプリでは、IMyDependency サービスが要求され、WriteMessage メソッドを呼び出すために使用されます。

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

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

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

DI パターンを使用すると、コントローラーまたは Razor ページは次のようになります。

  • 具象型 MyDependency は使用されず、実装される IMyDependency インターフェイスのみが使用されます。 これにより、実装はコントローラーまたは Razor ページを変更することなく、簡単に変更できるようになります。
  • MyDependency のインスタンスは作成されず、DI コンテナーによって作成されます。

組み込みのログ API を使用すると、IMyDependency インターフェイスの実装を向上させることができます。

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}");
    }
}

更新された Program.cs により、新しい 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 は、コンストラクターで要求される ILogger<TCategoryName> によって異なります。 ILogger<TCategoryName> は、フレームワークで提供されるサービスです。

依存関係の挿入をチェーン形式で使用することはよくあります。 次に、要求されたそれぞれの依存関係が、それ自身の依存関係を要求します。 コンテナーによってグラフ内の依存関係が解決され、完全に解決されたサービスが返されます。 解決する必要がある依存関係の集合的なセットは、通常、"依存関係ツリー"、"依存関係グラフ"、または "オブジェクト グラフ" と呼ばれます。

コンテナーでは、(ジェネリック) オープン型を活用し、すべての (ジェネリック) 構築型を登録する必要をなくすことで、ILogger<TCategoryName> を解決します。

依存関係の挿入に関する用語では、サービスは次のようになります。

  • 通常、他のオブジェクト (IMyDependency サービスなど) にサービスを提供するオブジェクトです。
  • Web サービスを使用する場合はありますが、サービスは Web サービスに関連付けられていません。

フレームワークは、堅牢な ログ システムを備えています。 前の例に示されている IMyDependency の実装は、ログを実装するのではなく、基本的な DI を実演するために記述されています。 ほとんどのアプリでは、ロガーを記述する必要はありません。 次のコードでは、既定のログを使用する方法が示されます。この場合、サービスを登録する必要はありません。

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);
    }
}

前のコードを使用すると、ログ がフレームワークによって提供されるため、Program.cs を更新する必要はありません。

拡張メソッドを使用したサービスのグループを登録する

ASP.NET Core フレームワークは、関連するサービスのグループを登録するための規則を使用します。 規則は、単一の Add{GROUP_NAME} 拡張メソッドを使用して、フレームワーク機能に必要なすべてのサービスを登録するというものです。 たとえば、AddControllers 拡張メソッドには、MVC コントローラーに必要なサービスが登録されます。

次のコードは、個々のユーザー アカウントを使用して Razor ページ テンプレートに基づいて生成されており、拡張メソッド AddDbContext および 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();

サービスを登録し、オプションを構成する以下について考えてみましょう。

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();

関連する登録グループは、サービスを登録するための拡張メソッドに移動できます。 たとえば、構成サービスは次のクラスに追加されます。

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;
        }
    }
}

残りのサービスは、同様のクラスに登録されます。 次のコードでは、新しい拡張メソッドを使用してサービスを登録します。

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

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

builder.Services.AddRazorPages();

var app = builder.Build();

注:services.Add{GROUP_NAME} 拡張メソッドは、サービスを追加、場合によっては構成します。 たとえば、AddControllersWithViews ではビューを持つ MVC コントローラーで必要なサービスが追加され、AddRazorPages では Razor Pages で必要なサービスが追加されます。

サービスの有効期間

.NET での依存関係の挿入」の「サービスの有効期間」を参照してください

ミドルウェアでスコープ付きサービスを使用するには、次のいずれかの方法を使用します。

  • ミドルウェアの Invoke または InvokeAsync メソッドにサービスを挿入します。 コンストラクターの挿入を使用すると、スコープ付きサービスがシングルトンのように動作するように強制されるため、実行時の例外がスローされます。 「有効期間と登録のオプション」セクションにあるサンプルは、InvokeAsync による方法を示しています。
  • ファクトリ ベースのミドルウェアを使用します。 この方法を使用して登録されたミドルウェアは、クライアント要求 (接続) ごとにアクティブ化されます。これにより、スコープ付きサービスをミドルウェアのコンストラクターに挿入できるようになります。

詳細については、「カスタム ASP.NET Core ミドルウェアを記述する」を参照してください。

サービス登録メソッド

.NET での依存関係の挿入」の「サービス登録メソッド」を参照してください

テスト用に型のモックを作成する場合に、複数の実装を使用することは一般的です。

実装型のみでサービスを登録することは、同じ実装とサービスの型でそのサービスを登録することと同じです。 明示的なサービス型を使用しないメソッドを使用してサービスの複数の実装を登録できないのは、このためです。 これらのメソッドでは、サービスの複数の "インスタンス" を登録できますが、すべて同じ "実装" 型になります。

上記のサービス登録メソッドのずれかを使用して、同じサービス型の複数のサービス インスタンスを登録できます。 次の例では、IMyDependency をサービス型として使用して、AddSingleton を 2 回呼び出します。 2 回目の AddSingleton の呼び出しにより、IMyDependency として解決された場合は前のものがオーバーライドされ、IEnumerable<IMyDependency> を介して複数のサービスが解決された場合は前のものに追加されます。 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);
    }
}

コンストラクターの挿入の動作

.NET での依存関係の挿入」の「コンストラクターの挿入の動作」を参照してください

Entity Framework コンテキスト

既定の Entity Framework コンテキストは、スコープ付き有効期間を使用してサービス コンテナーに追加されます。これは、Web アプリ データベース操作は通常、そのスコープがクライアント要求に設定されるためです。 異なる有効期間を使用するには、AddDbContext のオーバーロードを使用して有効期間を指定します。 有効期間が与えられたサービスの場合、そのサービスより有効期間が短いデータベース コンテキストを使用できません。

有効期間と登録のオプション

サービスの有効期間とその登録のオプションの違いを示すために、タスクを識別子 OperationId を備えた操作として表す、次のインターフェイスについて考えます。 次のインターフェイスに対して操作のサービスの有効期間がどのように構成されているかに応じて、コンテナーからは、クラスによって要求されたときに、サービスの同じインスタンスか別のインスタンスが提供されます。

public interface IOperation
{
    string OperationId { get; }
}

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

次の Operation クラスでは、上記のすべてのインターフェイスが実装されます。 Operation コンストラクターによって GUID が生成され、OperationId プロパティの最後の 4 文字が格納されます。

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

    public string OperationId { get; }
}

次のコードでは、指定された有効期間に従って、Operation クラスの複数の登録を作成します。

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();

サンプル アプリでは、それぞれの要求内、およびそれぞれの要求間におけるオブジェクトの有効期間が示されます。 IndexModel とミドルウェアでは、すべての種類の IOperation 型が要求され、それぞれの OperationId がログに記録されます。

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);
    }
}

ミドルウェアは IndexModel に類似していて、同じサービスを解決します。

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>();
    }
}

スコープ付きの一時的なサービスは、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);
}

ロガーの出力は次のようになります。

  • "一時的な" オブジェクトは常に異なります。 一時的な OperationId 値は、IndexModel とミドルウェアでは異なります。
  • "スコープ付きの" オブジェクトは、特定の要求内では同じですが、それぞれの新しい要求間では異なります。
  • "シングルトン" オブジェクトは、すべての要求において同じです。

ログ出力を減らすには、appsettings.Development.json ファイル内で "Logging:LogLevel:Microsoft:Error" を設定します。

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

アプリの起動時にサービスを解決する

次のコードは、アプリの開始時に限られた期間で、スコープ指定されたサービスを解決する方法を示しています。

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();

スコープの検証

.NET での依存関係の挿入」の「コンストラクターの挿入の動作」を参照してください

詳しくは、「スコープの検証」をご覧ください。

要求サービス

ASP.NET Core 要求内のサービスとその依存関係は、HttpContext.RequestServices によって公開されます。

フレームワークでは要求ごとにスコープが作成され、RequestServices によってスコープ付きサービス プロバイダーが公開されます。 すべてのスコープ サービスは、要求がアクティブである限り有効です。

Note

RequestServices からサービスを解決するよりも、コンストラクターのパラメーターとして依存関係を要求するようにします。 コンストラクターのパラメーターとして依存関係を要求すると、テストしやすいクラスが生成されます。

依存関係の挿入のためのサービスの設計

依存関係の挿入のためのサービスの設計時には:

  • ステートフル、静的クラス、およびメンバーは避けてください。 代わりにシングルトン サービスを使用するようにアプリを設計し、グローバルな状態を作成しないようにします。
  • サービス内部で依存関係のあるクラスを直接インスタンス化することを回避します。 直接のインスタンス化は、コードの固有の実装につながります。
  • サービスを、小さく、十分に要素に分割された、テストしやすいものにします。

クラスに含まれる挿入される依存関係が多すぎる場合、それは、クラスの責任が多すぎて、単一責任の原則 (SRP) に違反しているサインである可能性があります。 責任の一部を新しいクラスに移動することにより、クラスのリファクタリングを試みます。 Razor Pages のページ モデル クラスと MVC コントローラー クラスは、UI の問題に集中する必要があることに留意します。

サービスの破棄

コンテナーは、作成する IDisposable 型の Dispose を呼び出します。 コンテナーから解決されたサービスが、開発者によって破棄されることはありません。 型またはファクトリがシングルトンとして登録されている場合、コンテナーによってシングルトンが自動的に破棄されます。

次の例では、サービスがサービス コンテナーによって作成され、自動的に破棄されます: 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");
    }
}

デバッグ コンソールでは、インデックス ページを更新するたびに次の出力が表示されます。

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

サービス コンテナーによって作成されていないサービス

次のコードがあるとします。

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

上のコードでは以下の操作が行われます。

  • サービス インスタンスは、サービス コンテナーによって作成されるわけではありません。
  • フレームワークでは、サービスが自動的に破棄されることはありません。
  • サービスを破棄する責任は開発者にあります。

一時的なインスタンスと共有インスタンスのための IDisposable ガイダンス

.NET での依存関係の挿入」の「一時的なインスタンスと共有インスタンスのための IDisposable ガイダンス」を参照してください

既定のサービス コンテナーの置換

.NET での依存関係の挿入」の「既定のサービス コンテナーの置換」を参照してください

Recommendations

.NET での依存関係の挿入」の「推奨事項」を参照してください

  • サービス ロケーター パターンの使用は避けてください。 たとえば、サービス インスタンスを取得する場合、DI を使用できるときに、GetService を呼び出さないでください。

    正しくない:

    正しくないコード

    正しい:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • 回避すべき別のサービス ロケーターのバリエーションは、実行時に依存関係を解決するファクトリを挿入することです。 この両方のプラクティスによって、複数の制御の反転方式が組み合わせられます。

  • HttpContext への静的なアクセスを回避します (たとえば IHttpContextAccessor.HttpContext)。

DI は静的/グローバル オブジェクト アクセス パターンの代替機能です。 静的オブジェクト アクセスと併用した場合、DI のメリットを実現することはできません。

Orchard Core は、ASP.NET Core でモジュール型のマルチテナント アプリケーションを構築するためのアプリケーション フレームワークです。 詳細については、Orchard Core のドキュメントを参照してください。

CMS 固有の機能を使用せずに Orchard Core Framework のみを使用してモジュール型のマルチテナント アプリを構築する方法の例については、Orchard Core のサンプルを参照してください。

フレームワークが提供するサービス

Program.cs により、Entity Framework Core や ASP.NET Core MVC といったプラットフォーム機能など、アプリで使用されるサービスが登録されます。 最初に、Program.cs に提供される IServiceCollection には、フレームワークによって定義されたサービスがあります (ホストの構成方法によって異なります)。 ASP.NET Core テンプレートに基づくアプリでは、フレームワークによって 250 を超えるサービスが登録されます。

次の表に、フレームワークによって登録されるサービスのごく一部を示します。

サービスの種類 有効期間
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory 一時的
IHostApplicationLifetime シングルトン
IWebHostEnvironment シングルトン
Microsoft.AspNetCore.Hosting.IStartup シングルトン
Microsoft.AspNetCore.Hosting.IStartupFilter 一時的
Microsoft.AspNetCore.Hosting.Server.IServer シングルトン
Microsoft.AspNetCore.Http.IHttpContextFactory 一時的
Microsoft.Extensions.Logging.ILogger<TCategoryName> シングルトン
Microsoft.Extensions.Logging.ILoggerFactory シングルトン
Microsoft.Extensions.ObjectPool.ObjectPoolProvider シングルトン
Microsoft.Extensions.Options.IConfigureOptions<TOptions> 一時的
Microsoft.Extensions.Options.IOptions<TOptions> シングルトン
System.Diagnostics.DiagnosticSource シングルトン
System.Diagnostics.DiagnosticListener シングルトン

その他の技術情報

作成者: Kirk LarkinSteve SmithScott AddieBrandon Dahler

ASP.NET Core では依存関係の挿入 (DI) ソフトウェア設計パターンがサポートされています。これは、クラスとその依存関係の間で制御の反転 (IoC) を実現するための手法です。

MVC コントローラー内の依存依存関係の挿入に固有の詳細については、「ASP.NET Core でのコントローラーへの依存関係の挿入」を参照してください。

Web アプリ以外のアプリケーションで依存関係の挿入を使用する方法の詳細については、「.NET での依存関係の挿入」を参照してください。

オプションとしての依存関係の挿入の詳細については、「ASP.NET Core のオプション パターン」を参照してください。

このトピックでは、ASP.NET Core での依存関係の挿入について説明します。 依存関係の挿入の使用に関する主なドキュメントは、「.NET での依存関係の挿入」に含まれています。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

依存関係の挿入の概要

"依存関係" とは、他のオブジェクトが依存するオブジェクトのことです。 他のクラスが依存している、次の WriteMessage メソッドを備えた MyDependency クラスを調べます。

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

クラスは、MyDependency クラスのインスタンスを作成して、その WriteMessage メソッドを使用することができます。 次の例で、MyDependency クラスは IndexModel クラスの依存関係です。

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

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

このクラスは MyDependency クラスを作成し、これに直接依存しています。 コードの依存関係には、前の例のような問題が含まれ、次の理由から回避する必要があります。

  • MyDependency を別の実装で置き換えるには、IndexModel クラスを変更する必要があります。
  • MyDependency が依存関係を含んでいる場合、これらは IndexModel クラスによって構成する必要があります。 複数のクラスが MyDependency に依存している大規模なプロジェクトでは、構成コードがアプリ全体に分散するようになります。
  • このような実装では、単体テストを行うことが困難です。 アプリはモックまたはスタブの MyDependency クラスを使用する必要がありますが、この方法では不可能です。

依存関係の挿入は、次の方法によってこれらの問題に対応します。

  • 依存関係の実装を抽象化するための、インターフェイスまたは基底クラスの使用。
  • サービス コンテナー内の依存関係の登録。 ASP.NET Core には、組み込みのサービス コンテナー IServiceProvider が用意されています。 サービスは通常、アプリの Startup.ConfigureServices メソッドに登録されています。
  • サービスを使用するクラスのコンストラクターへの、サービスの "挿入"。 依存関係のインスタンスの作成、およびインスタンスが不要になったときの廃棄の役割を、フレームワークが担当します。

サンプル アプリでは、IMyDependency インターフェイスは、WriteMessage メソッドを定義します。

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

このインターフェイスは、具象型 MyDependency によって実装されます。

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

サンプル アプリにおいて、IMyDependency サービスは具象型 MyDependency を使用して登録されます。 AddScoped メソッドによって、サービスは、1 つの要求の有効期間であるスコープ付き有効期間で登録されます。 サービスの有効期間については、このトピックの後半で説明します。

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

    services.AddRazorPages();
}

サンプル アプリでは、IMyDependency サービスが要求され、WriteMessage メソッドを呼び出すために使用されます。

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

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

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

DI パターンを使用すると、コントローラーは次のようになります。

  • 具象型 MyDependency は使用されず、実装される IMyDependency インターフェイスのみが使用されます。 これにより、コントローラーが使用する実装は、コントローラーを変更することなく、簡単に変更できるようになります。
  • MyDependency のインスタンスは作成されず、DI コンテナーによって作成されます。

組み込みのログ API を使用すると、IMyDependency インターフェイスの実装を向上させることができます。

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}");
    }
}

更新された ConfigureServices メソッドでは、新しい IMyDependency の実装が登録されます。

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

    services.AddRazorPages();
}

MyDependency2 は、コンストラクターで要求される ILogger<TCategoryName> によって異なります。 ILogger<TCategoryName> は、フレームワークで提供されるサービスです。

依存関係の挿入をチェーン形式で使用することはよくあります。 次に、要求されたそれぞれの依存関係が、それ自身の依存関係を要求します。 コンテナーによってグラフ内の依存関係が解決され、完全に解決されたサービスが返されます。 解決する必要がある依存関係の集合的なセットは、通常、"依存関係ツリー"、"依存関係グラフ"、または "オブジェクト グラフ" と呼ばれます。

コンテナーでは、(ジェネリック) オープン型を活用し、すべての (ジェネリック) 構築型を登録する必要をなくすことで、ILogger<TCategoryName> を解決します。

依存関係の挿入に関する用語では、サービスは次のようになります。

  • 通常、他のオブジェクト (IMyDependency サービスなど) にサービスを提供するオブジェクトです。
  • Web サービスを使用する場合はありますが、サービスは Web サービスに関連付けられていません。

フレームワークは、堅牢な ログ システムを備えています。 前の例に示されている IMyDependency の実装は、ログを実装するのではなく、基本的な DI を実演するために記述されています。 ほとんどのアプリでは、ロガーを記述する必要はありません。 次のコードでは、既定のログを使用する方法が示されます。この場合、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);
    }
}

前のコードを使用すると、ログ がフレームワークによって提供されるため、ConfigureServices を更新する必要はありません。

Startup に挿入されるサービス

サービスは、Startup コンストラクターと Startup.Configure メソッドに挿入できます。

汎用ホスト (IHostBuilder) を使用すると、次のサービスのみを Startup コンストラクターに挿入できます。

DI コンテナーに登録されているすべてのサービスを、Startup.Configure メソッドに挿入できます。

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

詳細については、「ASP.NET Core でのアプリケーションのスタートアップ」および起動時の構成へのアクセスに関するページを参照してください。

拡張メソッドを使用したサービスのグループを登録する

ASP.NET Core フレームワークは、関連するサービスのグループを登録するための規則を使用します。 規則は、単一の Add{GROUP_NAME} 拡張メソッドを使用して、フレームワーク機能に必要なすべてのサービスを登録するというものです。 たとえば、AddControllers 拡張メソッドには、MVC コントローラーに必要なサービスが登録されます。

次のコードは、個々のユーザー アカウントを使用して Razor ページ テンプレートに基づいて生成されており、拡張メソッド AddDbContext および 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();
}

サービスを登録し、オプションを構成する次の ConfigureServices メソッドについて考えてみましょう。

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();
}

関連する登録グループは、サービスを登録するための拡張メソッドに移動できます。 たとえば、構成サービスは次のクラスに追加されます。

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;
        }
    }
}

残りのサービスは、同様のクラスに登録されます。 次の ConfigureServices メソッドでは、新しい拡張メソッドを使用してサービスを登録します。

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

    services.AddRazorPages();
}

注:services.Add{GROUP_NAME} 拡張メソッドは、サービスを追加、場合によっては構成します。 たとえば、AddControllersWithViews ではビューを持つ MVC コントローラーで必要なサービスが追加され、AddRazorPages では Razor Pages で必要なサービスが追加されます。 アプリは、Microsoft.Extensions.DependencyInjection 名前空間で拡張メソッドを作成する場合の名前付け規則に従うようにすることをお勧めします。 Microsoft.Extensions.DependencyInjection 名前空間での拡張メソッドの作成:

  • サービス登録のグループをカプセル化します。
  • サービスへの便利な IntelliSense アクセスを提供します。

サービスの有効期間

.NET での依存関係の挿入」の「サービスの有効期間」を参照してください

ミドルウェアでスコープ付きサービスを使用するには、次のいずれかの方法を使用します。

  • ミドルウェアの Invoke または InvokeAsync メソッドにサービスを挿入します。 コンストラクターの挿入を使用すると、スコープ付きサービスがシングルトンのように動作するように強制されるため、実行時の例外がスローされます。 「有効期間と登録のオプション」セクションにあるサンプルは、InvokeAsync による方法を示しています。
  • ファクトリ ベースのミドルウェアを使用します。 この方法を使用して登録されたミドルウェアは、クライアント要求 (接続) ごとにアクティブ化されます。これにより、スコープ付きサービスをミドルウェアの InvokeAsync メソッドに挿入できるようになります。

詳細については、「カスタム ASP.NET Core ミドルウェアを記述する」を参照してください。

サービス登録メソッド

.NET での依存関係の挿入」の「サービス登録メソッド」を参照してください

テスト用に型のモックを作成する場合に、複数の実装を使用することは一般的です。

実装型のみでサービスを登録することは、同じ実装とサービスの型でそのサービスを登録することと同じです。 明示的なサービス型を使用しないメソッドを使用してサービスの複数の実装を登録できないのは、このためです。 これらのメソッドでは、サービスの複数の "インスタンス" を登録できますが、すべて同じ "実装" 型になります。

上記のサービス登録メソッドのずれかを使用して、同じサービス型の複数のサービス インスタンスを登録できます。 次の例では、IMyDependency をサービス型として使用して、AddSingleton を 2 回呼び出します。 2 回目の AddSingleton の呼び出しにより、IMyDependency として解決された場合は前のものがオーバーライドされ、IEnumerable<IMyDependency> を介して複数のサービスが解決された場合は前のものに追加されます。 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);
    }
}

コンストラクターの挿入の動作

.NET での依存関係の挿入」の「コンストラクターの挿入の動作」を参照してください

Entity Framework コンテキスト

既定の Entity Framework コンテキストは、スコープ付き有効期間を使用してサービス コンテナーに追加されます。これは、Web アプリ データベース操作は通常、そのスコープがクライアント要求に設定されるためです。 異なる有効期間を使用するには、AddDbContext のオーバーロードを使用して有効期間を指定します。 有効期間が与えられたサービスの場合、そのサービスより有効期間が短いデータベース コンテキストを使用できません。

有効期間と登録のオプション

サービスの有効期間とその登録のオプションの違いを示すために、タスクを識別子 OperationId を備えた操作として表す、次のインターフェイスについて考えます。 次のインターフェイスに対して操作のサービスの有効期間がどのように構成されているかに応じて、コンテナーからは、クラスによって要求されたときに、サービスの同じインスタンスか別のインスタンスが提供されます。

public interface IOperation
{
    string OperationId { get; }
}

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

次の Operation クラスでは、上記のすべてのインターフェイスが実装されます。 Operation コンストラクターによって GUID が生成され、OperationId プロパティの最後の 4 文字が格納されます。

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

    public string OperationId { get; }
}

Startup.ConfigureServices メソッドでは、指定された有効期間に従って、Operation クラスの複数の登録が作成されます。

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

    services.AddRazorPages();
}

サンプル アプリでは、それぞれの要求内、およびそれぞれの要求間におけるオブジェクトの有効期間が示されます。 IndexModel とミドルウェアでは、すべての種類の IOperation 型が要求され、それぞれの OperationId がログに記録されます。

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);
    }
}

ミドルウェアは IndexModel に類似していて、同じサービスを解決します。

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>();
    }
}

スコープ付きのサービスは 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);
}

ロガーの出力は次のようになります。

  • "一時的な" オブジェクトは常に異なります。 一時的な OperationId 値は、IndexModel とミドルウェアでは異なります。
  • "スコープ付きの" オブジェクトは、特定の要求内では同じですが、それぞれの新しい要求間では異なります。
  • "シングルトン" オブジェクトは、すべての要求において同じです。

ログ出力を減らすには、appsettings.Development.json ファイル内で "Logging:LogLevel:Microsoft:Error" を設定します。

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

main からサービスを呼び出す

IServiceScopeFactory.CreateScope を使用して IServiceScope を作成し、アプリのスコープ内のスコープ サービスを解決します。 この方法は、起動時に初期化タスクを実行するために、スコープ サービスにアクセスするのに便利です。

次の例は、スコープ付きの IMyDependency サービスにアクセスし、Program.Main でその WriteMessage メソッドを呼び出す方法を示します。

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>();
            });
}

スコープの検証

.NET での依存関係の挿入」の「コンストラクターの挿入の動作」を参照してください

詳しくは、「スコープの検証」をご覧ください。

要求サービス

ASP.NET Core 要求内のサービスとその依存関係は、HttpContext.RequestServices によって公開されます。

フレームワークでは要求ごとにスコープが作成され、RequestServices によってスコープ付きサービス プロバイダーが公開されます。 すべてのスコープ サービスは、要求がアクティブである限り有効です。

Note

RequestServices からサービスを解決するよりも、コンストラクターのパラメーターとして依存関係を要求するようにします。 コンストラクターのパラメーターとして依存関係を要求すると、テストしやすいクラスが生成されます。

依存関係の挿入のためのサービスの設計

依存関係の挿入のためのサービスの設計時には:

  • ステートフル、静的クラス、およびメンバーは避けてください。 代わりにシングルトン サービスを使用するようにアプリを設計し、グローバルな状態を作成しないようにします。
  • サービス内部で依存関係のあるクラスを直接インスタンス化することを回避します。 直接のインスタンス化は、コードの固有の実装につながります。
  • サービスを、小さく、十分に要素に分割された、テストしやすいものにします。

クラスに含まれる挿入される依存関係が多すぎる場合、それは、クラスの責任が多すぎて、単一責任の原則 (SRP) に違反しているサインである可能性があります。 責任の一部を新しいクラスに移動することにより、クラスのリファクタリングを試みます。 Razor Pages のページ モデル クラスと MVC コントローラー クラスは、UI の問題に集中する必要があることに留意します。

サービスの破棄

コンテナーは、作成する IDisposable 型の Dispose を呼び出します。 コンテナーから解決されたサービスが、開発者によって破棄されることはありません。 型またはファクトリがシングルトンとして登録されている場合、コンテナーによってシングルトンが自動的に破棄されます。

次の例では、サービスがサービス コンテナーによって作成され、自動的に破棄されます。

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");
    }
}

デバッグ コンソールでは、インデックス ページを更新するたびに次の出力が表示されます。

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

サービス コンテナーによって作成されていないサービス

次のコードがあるとします。

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

    services.AddRazorPages();
}

上のコードでは以下の操作が行われます。

  • サービス インスタンスは、サービス コンテナーによって作成されるわけではありません。
  • フレームワークでは、サービスが自動的に破棄されることはありません。
  • サービスを破棄する責任は開発者にあります。

一時的なインスタンスと共有インスタンスのための IDisposable ガイダンス

.NET での依存関係の挿入」の「一時的なインスタンスと共有インスタンスのための IDisposable ガイダンス」を参照してください

既定のサービス コンテナーの置換

.NET での依存関係の挿入」の「既定のサービス コンテナーの置換」を参照してください

Recommendations

.NET での依存関係の挿入」の「推奨事項」を参照してください

  • サービス ロケーター パターンの使用は避けてください。 たとえば、サービス インスタンスを取得する場合、DI を使用できるときに、GetService を呼び出さないでください。

    正しくない:

    正しくないコード

    正しい:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • 回避すべき別のサービス ロケーターのバリエーションは、実行時に依存関係を解決するファクトリを挿入することです。 この両方のプラクティスによって、複数の制御の反転方式が組み合わせられます。

  • HttpContext への静的なアクセスを回避します (たとえば IHttpContextAccessor.HttpContext)。

  • ConfigureServicesBuildServiceProvider を呼び出すことは避けてください。 BuildServiceProvider の呼び出しは、通常、開発者が ConfigureServices でサービスを解決する必要がある場合に発生します。 たとえば、構成から LoginPath が読み込まれる場合を考えてみます。 次の方法は避けてください。

    BuildServiceProvider を呼び出すコードが正しくありません

    上の図では、services.BuildServiceProvider の下の緑の波線を選択すると、次の ASP0000 警告が表示されます。

    アプリケーション コードから ASP0000 呼び出し 'BuildServiceProvider' を行うと、シングルトン サービスの追加のコピーが作成されます。 'Configure' のパラメーターとして依存関係挿入サービスなどの代替手段を検討してください。

    BuildServiceProvider を呼び出すと、2 つ目のコンテナーが作成されます。これを使用すれば、破損したシングルトンが作成され、複数のコンテナーにまたがるオブジェクト グラフへの参照を生じさせることができます。

    LoginPath を取得する正しい方法は、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();
    }
    
  • 破棄可能な一時的なサービスは、破棄のためにコンテナーによってキャプチャされます。 これにより、最上位のコンテナーから解決された場合、メモリ リークが発生する可能性があります。

  • スコープの検証を有効にすることで、アプリにスコープ付きサービスをキャプチャするシングルトンがないようにします。 詳しくは、「スコープの検証」をご覧ください。

どのような推奨事項であっても、それを無視する必要がある状況が発生する可能性があります。 例外はまれです。ほとんどがフレームワーク自体の内の特殊なケースです。

DI は静的/グローバル オブジェクト アクセス パターンの代替機能です。 静的オブジェクト アクセスと併用した場合、DI のメリットを実現することはできません。

Orchard Core は、ASP.NET Core でモジュール型のマルチテナント アプリケーションを構築するためのアプリケーション フレームワークです。 詳細については、Orchard Core のドキュメントを参照してください。

CMS 固有の機能を使用せずに Orchard Core Framework のみを使用してモジュール型のマルチテナント アプリを構築する方法の例については、Orchard Core のサンプルを参照してください。

フレームワークが提供するサービス

Startup.ConfigureServices メソッドでは、Entity Framework Core や ASP.NET Core MVC のようなプラットフォーム機能など、アプリが使うサービスが登録されます。 最初に、ConfigureServices に提供される IServiceCollection には、フレームワークによって定義されたサービスがあります (ホストの構成方法によって異なります)。 ASP.NET Core テンプレートに基づくアプリでは、フレームワークによって 250 を超えるサービスが登録されます。

次の表に、フレームワークによって登録されるサービスのごく一部を示します。

サービスの種類 有効期間
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory 一時的
IHostApplicationLifetime シングルトン
IWebHostEnvironment シングルトン
Microsoft.AspNetCore.Hosting.IStartup シングルトン
Microsoft.AspNetCore.Hosting.IStartupFilter 一時的
Microsoft.AspNetCore.Hosting.Server.IServer シングルトン
Microsoft.AspNetCore.Http.IHttpContextFactory 一時的
Microsoft.Extensions.Logging.ILogger<TCategoryName> シングルトン
Microsoft.Extensions.Logging.ILoggerFactory シングルトン
Microsoft.Extensions.ObjectPool.ObjectPoolProvider シングルトン
Microsoft.Extensions.Options.IConfigureOptions<TOptions> 一時的
Microsoft.Extensions.Options.IOptions<TOptions> シングルトン
System.Diagnostics.DiagnosticSource シングルトン
System.Diagnostics.DiagnosticListener シングルトン

その他の技術情報