KENTEM TechBlog

建設業のDXを実現するKENTEMの技術ブログです。

ASP.NET Core の依存関係の挿入を使おう

ASP.NET Core の依存関係の挿入って知っていますか?依存関係の挿入を使う事で効率的なリソース管理を行ったり、テストコードが書きやすかったりします。今回は ASP.NET Core を使ったクラス管理について MVC パターンでまとめたいと思います。

シンプルに登録して使う

クラスを依存関係に登録します。この記事では登録するクラスの事をサービスクラスと呼称します。 サービスクラスを依存関係に登録し、 Controller クラスで使いたいサービスクラスを Controller クラスのコンストラクタの引数にしていすることでサービスクラスのインスタンスを受け取ることができます。

Controller クラス以外では Middleware クラスや他のサービスクラスでも同様にサービスクラスのインスタンスを受け取ることができます。

※ Middleware クラスの場合はコンストラクタで受け取るとエラーになる場合があるため、 InvokeAsync() メソッドの引数として受け取るのが安全です。

以下の様なサンプルのサービスクラスを実装します。

public class MyService
{
    public void Hoge() { }
}

このクラスを Program.cs で依存関係に登録します。

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<MyService>();
var app = builder.Build();

これで登録されたので Controller クラスのコンストラクタで受け取ります。

public class MyController : ControllerBase
{
    private readonly MyService _myService;

    public MyController(MyService myService)
    {
         _myService = myService;
    }

    [HttpGet]
    [Route("/my")]
    public string GetAsync()
    {
        _myService .Hoge();
        return "hoge";
    }
}

サービスの有効期間を使い分ける

サーバーのバックエンドは、たくさんのアクセスを処理します。そのため登録したサービスの有効期間を管理し、無駄なメモリ消費を減らしたりデータの混線などを防ぐ必要があります。

登録メソッド

サービスクラスを Program.cs で依存関係に登録する時に使用するメソッドです。サービスの有効期間に合わせて適切なメソッドを選択しましょう。

AddSingleton

初めてサービスクラスが要求された時に作成され、以降はどのユーザーのアクセスでも同じインスタンスが使いまわしされます。

AddScoped

ユーザーのアクセス毎に作成され、1回のアクセスの内では同じインスタンスが使いまわしされます。

AddTransient

サービスクラスが要求されるたびに作られインスタンスが使い回されることはありません。

インターフェイスを使ってダミーに差し替える

依存関係に登録されるサービスクラスはデータベースにアクセスしたり、他の API を呼んだりと様々な処理が実装されています。しかしテストコードを実行した時に他の API を呼んだりされると困ってしまいます。そこでインターフェイスを使って依存関係に登録する事でテストコードではダミーのサービスが呼ばれるように差し替えることができます。

運用コードの変更

まずはサービスインターフェイスを実装し、サービスクラスに継承させます。

public interface IMyService
{
    public void Hoge();
}
public class MyService : IMyService
{
    public void Hoge() { }
}

依存関係の登録と要求時にインターフェイスを使うように変更します。

var builder = WebApplication.CreateBuilder(args);
// インターフェイス, クラス
builder.Services.AddSingleton<IMyService, MyService>();
var app = builder.Build();
public class MyController : ControllerBase
{
    private readonly IMyService _myService;

    public MyController(IMyService myService)
    {
         _myService = myService;
    }

    [HttpGet]
    [Route("/my")]
    public string GetAsync()
    {
        _myService .Hoge();
        return "hoge";
    }
}

最近の Program.cs は名前空間が省略されていますが、この後の実装では困るので名前空間を明示的に記述します。Program.cs の末尾に以下のコードを追加してください。

namespace Hogespace
{
    public partial class Program { }
}

テストコードの実装

Visual Studio からテストプロジェクトを追加し、 以下の Nuget Package をインストールします。

  • Microsoft.AspNetCore.Mvc.Testing

差し替えるためのダミーサービスクラスを実装します。

public class DummyMyService : IMyService
{
    public void Hoge() { }
}

テストコードで仮想的に実行される Web アプリケーション用 Factory クラスを実装します。

public class TestWebApplicationFactory : WebApplicationFactory<Hogespace.Program>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.UseSetting("https_port", "5001");
        builder.UseEnvironment("Testing");
        builder.ConfigureAppConfiguration(config =>
        {
        });

        // ダミーサービスを登録する
        builder.Services.AddSingleton<IMyService, DummyMyService>();
    }
}

※ WebApplicationFactory の型パラメータ(<T>) に Program.cs のクラスが指定されている事を確認してください。

テストコードを実装します。

public class MyTest : IDisposable
{
    [Fact]
    public async Task Test1()
    {
        TestWebApplicationFactory factory = new();
        HttpClient client = factory.CreateClient();
        HttpResponseMessage response = await client.GetAsync("/my");
    }
}

こうすることで、Program.cs のコードよりも先に TestWebApplicationFactory クラスの ConfigureWebHost メソッドにより IMyService で要求されるクラスが登録されるため、 Controller クラスなどで要求された時に DummyMyService クラスのインスタンスが渡されます。

まとめ

依存関係の挿入を使いこなすことで適切なサーバー実装を行ったり、必要なテストコードを実装する事ができるようになります。 公式ドキュメントには更に詳しいことも書いてあるのでぜひ一度読んでみると良いと思います。

おわりに

KENTEMでは、様々な拠点でエンジニアを大募集しています! 建設×ITにご興味頂いた方は、是非下記のリンクからご応募ください。 recruit.kentem.jp career.kentem.jp