つばろぐ

福岡のエンジニアによる技術的な備忘録です。

ASP.NET CoreのMiddlewareで依存関係を解決する方法

ASP.NET Core 2.xの内容です。1.xについては未検証。

ASP.NET Coreでは依存関係(Dependency Injection / DI)の取扱いがすごく簡単になっています。
なのでMiddlewareでもDIを使いたい場面がでてきます。例えばロガーとか。

Middlewareについての説明は省略しますので、公式ドキュメントを参照してください。

docs.microsoft.com

HTTPリクエスト/レスポンスの流れのなかに処理を差し込むパイプラインが用意されているイメージです。

pipeline

Middlewareでの依存関係の解決方法

ざっくりいうと .NET Coreでの依存関係解決は「コンストラクタ引数に使用したいオブジェクトを指定する」という方法になります。
ControllerやService、Repositoryなんかを使う方はイメージしやすいかと思います。
Middlewareも例に漏れず同じ方法になります。

この記事で詳細な説明をしますがサンプルコードが見たい方はこちらをどうぞ。

github.com

こんなMiddlewareが登録されていたとする

Startup.cs (該当箇所のみ記載)

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.UseMiddleware<MyMiddleware>();
    }
}

Startup.cs でMiddlewareを登録する際は、特にDIコンテナー内のオブジェクトは指定しません。

素の状態のMiddleware

MyMiddleware.cs
nextという変数はMiddlewareのルールとして必要なオブジェクトになります。

public class MyMiddleware
{
    private readonly RequestDelegate next;

    public MyMiddleware(RequestDelegate next)
    {
        this.next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        await next(context);
    }
}

コンストラクタ引数で依存関係を解決する

MyMiddleware.cs にロガーを取り出す処理を実装してみます。
Middlewareのコンストラクタ引数にロガーを指定するだけで済みます。
取り出したロガーは readonly なフィールド変数として保管しておくと他のメソッドで利用することができます。

public class MyMiddleware
{
    private readonly RequestDelegate next;
    private readonly ILogger<MyMiddleware> logger;

    public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger)
    {
        this.next = next;
        this.logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        logger.LogInformation($"QueryString: {context.Request.QueryString}");
        await next(context);
    }
}

要求ごとに依存関係を解決する

Startup.cs で何かしらのオブジェクトをDIに登録する際に、有効期間を1要求ごと( AddScoped )にしている場合、Middlewareではコンストラクタ引数で依存関係を解決するのではなく、要求ごとに依存関係を解決する必要があります。

docs.microsoft.com

ミドルウェアでスコープ サービスを使用している場合、サービスを Invoke または InvokeAsync メソッドに追加します。 コンストラクターを使用して挿入すると、サービスがシングルトンのように動作するよう強制されるので、コンストラクターを使用した挿入は行わないでください。 詳細については、「ASP.NET Core のミドルウェア」を参照してください。

Startup.cs の例 (該当箇所のみ記載)

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<IMyService, MyService>();
    }
}

MyMiddleware.cs

「要求ごとに依存関係を解決する」とはどういうことかというと、ミドルウェアパイプライン上で呼び出されるごとということなので、 InvokeAsync メソッドに引数を追加することで依存関係を解決します。

public class MyMiddleware
{
    private readonly RequestDelegate next;

    public MyMiddleware(RequestDelegate next)
    {
        this.next = next;
    }

    public async Task InvokeAsync(HttpContext context, IMyService myService)
    {
        string message = myService.Say("tsubakimoto");
        await next(context);
    }
}