ASP.NET Core – 依存性により、バックグラウンド サービスがコントローラーに挿入されます。

DatabaseLoggerService というバックグラウンド サービスがあるとします。これは、ホストされたバックグラウンド サービスとして実行され、メッセージをデータベースに記録します。次の定義があります:

public class DatabaseLoggerService : BackgroundService, ILoggerService
Code language: C# (cs)

コントローラーがこれをログに使用するようにします。具体的な DatabaseLoggerService クラスについて知る必要はなく、実際にバックグラウンド サービスを使用していることを知る必要もありません。したがって、ILoggerService に依存する必要があります。

最初に、コンストラクターは次のように ILoggerService をコントローラーに挿入します。

[Route("[controller]")]
[ApiController]
public class RecipesController : ControllerBase
{
	private readonly ILoggerService Logger;
	public RecipesController(ILoggerService logger)
	{
		Logger = logger;
	}
	
	//rest of controller
}
Code language: C# (cs)

次に、Startup.ConfigureServices() で、次のように DatabaseLoggerService を ILoggerService と HostedService の両方として登録する必要があります。

public class Startup
{
	//rest of class
	public void ConfigureServices(IServiceCollection services)
	{
		//rest of method
		
		var loggerService = new DatabaseLoggerService();

		services.AddSingleton<ILoggerService>(loggerService);
		services.AddHostedService(_ => loggerService);
	}
}
Code language: C# (cs)

注:AddHostedService() は IHostedService を想定しているため、serviceProvider.GetService() は使用できません。これが、実際に DatabaseLoggerService インスタンスを作成し、このように登録する必要がある理由です。

ILoggerService として登録されているため、要求がコントローラーに届くと、フレームワークは DatabaseLoggerService シングルトンをコントローラーに挿入します。コントローラーは、具体的な実装や、それがバックグラウンド サービスであるという事実について何も知らなくても、ログ機能を使用できます。

バックグラウンド サービスで依存関係を解決する必要がある場合

バックグラウンド サービスに、ServiceProvider を使用して解決する必要がある依存関係がない場合、上記のアプローチは問題ありません。ただし、バックグラウンド サービスが IHostApplicationLifetime に依存しているとします。次のアプローチを使用して、依存関係を解決しながら、バックグラウンド サービスをシングルトン サービスとホステッド サービスの両方として登録できます。

public class Startup
{
	//rest of class
	public void ConfigureServices(IServiceCollection services)
	{
		//rest of method

		services.AddSingleton<ILoggerService>(sp =>
		{
			var hostAppLifetime = sp.GetService<IHostApplicationLifetime>();
			return new DatabaseLoggerService(hostAppLifetime);
		});
		services.AddHostedService(sp => sp.GetService<ILoggerService>() as DatabaseLoggerService);
	   
	}
}
Code language: C# (cs)

このアプローチと記事の冒頭で示したアプローチとの違いは、AddHostedService() にあり、ILoggerService を解決し、それを DatabaseLoggerService にキャストします。これは少し安全ではありませんが、登録した ILoggerService が DatabaseLoggerService であることは確かなので、問題ありません。

IHostedService や具象クラスを注入してみませんか?

実装ではなくインターフェースに対するコード」という言葉を聞いたことがあるでしょう。 「

コントローラーは、データベースにログを記録しているバックグラウンド サービスを使用していることを認識する必要がありますか?ほとんどの場合、いいえ。ロガーを使用していることを知る必要があるだけです。つまり、コントローラーは、具体的な DatabaseLoggerService クラスではなく、ILoggerService に対してコーディングする必要があります。

具体的なバックグラウンド サービス クラスを渡すと、コントローラーは StopAsync() などのバックグラウンド サービス メソッドにアクセスできます。ほとんどの場合、これは望ましくありません:

[Route("[controller]")]
[ApiController]
public class RecipesController : ControllerBase
{
	private readonly DatabaseLoggerService Logger;
	public RecipesController(DatabaseLoggerService logger)
	{
		Logger = logger;
	}

	[HttpGet("{id}")]
	public async Task<IActionResult> Get(int id)
	{
		await Logger.StopAsync(HttpContext.RequestAborted);

		//rest of method
	}
}
Code language: C# (cs)

さらに、インターフェースをインジェクトすると、インターフェースをモックアウトするため、コードの単体テストが容易になります。


No