ASP.NET Core:la dipendenza inserisce un servizio in background nei controller

ASP.NET Core:la dipendenza inserisce un servizio in background nei controller

Supponiamo che tu abbia un servizio in background chiamato DatabaseLoggerService. Funziona come un servizio in background ospitato e registra i messaggi nel database. Ha la seguente definizione:

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

Vuoi che i tuoi controller lo utilizzino per la registrazione. Non hanno bisogno di conoscere la classe concreta DatabaseLoggerService e non hanno bisogno di sapere che stanno effettivamente utilizzando un servizio in background. Pertanto, vorrai che dipendano da ILoggerService.

Innanzitutto, il costruttore inietta ILoggerService nei controller, in questo modo:

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

Quindi, in Startup.ConfigureServices(), dovrai registrare DatabaseLoggerService sia come ILoggerService che come HostedService, in questo modo:

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)

Nota:AddHostedService() prevede un IHostedService, quindi non puoi utilizzare serviceProvider.GetService(). Questo è il motivo per cui devi effettivamente creare l'istanza DatabaseLoggerService e registrarla in questo modo.

Quando una richiesta arriva nel controller, il framework inietterà il singleton DatabaseLoggerService nel controller, perché è stato registrato come ILoggerService. Il controller può quindi utilizzare la funzionalità di registrazione senza dover sapere nulla sull'implementazione concreta o sul fatto che si tratti di un servizio in background.

Se il tuo servizio in background deve risolvere le dipendenze

L'approccio mostrato sopra va bene se il servizio in background non ha dipendenze da risolvere usando ServiceProvider. Tuttavia, supponiamo che il tuo servizio in background dipenda da IHostApplicationLifetime. Puoi utilizzare il seguente approccio per registrare il servizio in background sia come servizio singleton che come servizio ospitato risolvendone le dipendenze:

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)

La differenza con questo approccio e quello mostrato all'inizio dell'articolo è in AddHostedService() che risolve ILoggerService e lo trasmette a un DatabaseLoggerService. È un po' pericoloso, ma sappiamo per certo che ILoggerService che abbiamo registrato è sicuramente un DatabaseLoggerService, quindi va bene.

Perché non iniettare IHostedService o la classe concrete?

Probabilmente hai sentito il detto:"Codice contro interfacce, non implementazioni.

Il controller deve sapere che sta utilizzando un servizio in background che sta registrando nel database? Nella maggior parte dei casi, no. Ha solo bisogno di sapere che sta usando un logger. In altre parole, il controller deve codificare in base a ILoggerService, non alla classe concreta DatabaseLoggerService.

Se si passa la classe del servizio in background concreta, il controller avrà accesso ai metodi del servizio in background, come StopAsync(). Nella maggior parte dei casi, questo sarebbe indesiderabile:

[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)

Inoltre, l'iniezione di interfacce rende il codice più facile da testare in quanto si prende in giro l'interfaccia.


No