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