ASP.NET Core – Abhängigkeit fügt einen Hintergrunddienst in die Controller ein

ASP.NET Core – Abhängigkeit fügt einen Hintergrunddienst in die Controller ein

Nehmen wir an, Sie haben einen Hintergrunddienst namens DatabaseLoggerService. Es wird als gehosteter Hintergrunddienst ausgeführt und protokolliert Nachrichten in der Datenbank. Es hat die folgende Definition:

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

Sie möchten, dass Ihre Controller dies für die Protokollierung verwenden. Sie müssen nichts über die konkrete DatabaseLoggerService-Klasse wissen, und sie müssen nicht wissen, dass sie tatsächlich einen Hintergrunddienst verwenden. Daher möchten Sie, dass sie von ILoggerService abhängig sind.

Zuerst injizieren Sie den Konstruktor ILoggerService wie folgt in Ihre Controller:

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

Dann müssen Sie in Startup.ConfigureServices() DatabaseLoggerService sowohl als ILoggerService als auch als HostedService registrieren, wie folgt:

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)

Hinweis:AddHostedService() erwartet einen IHostedService, daher können Sie serviceProvider.GetService() nicht verwenden. Aus diesem Grund müssen Sie die DatabaseLoggerService-Instanz tatsächlich erstellen und wie folgt registrieren.

Wenn eine Anforderung im Controller eingeht, injiziert das Framework das DatabaseLoggerService-Singleton in den Controller, da es als ILoggerService registriert wurde. Der Verantwortliche kann dann die Logging-Funktionalität nutzen, ohne etwas über die konkrete Implementierung oder die Tatsache, dass es sich um einen Hintergrunddienst handelt, wissen zu müssen.

Wenn für Ihren Hintergrunddienst Abhängigkeiten aufgelöst werden müssen

Der oben gezeigte Ansatz ist in Ordnung, wenn der Hintergrunddienst keine Abhängigkeiten hat, die er mithilfe von ServiceProvider auflösen muss. Nehmen wir jedoch an, Ihr Hintergrunddienst hängt von IHostApplicationLifetime ab. Sie können den folgenden Ansatz verwenden, um den Hintergrunddienst sowohl als Singleton- als auch als gehosteten Dienst zu registrieren und gleichzeitig seine Abhängigkeiten aufzulösen:

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)

Der Unterschied zwischen diesem und dem am Anfang des Artikels gezeigten Ansatz besteht darin, dass AddHostedService() ILoggerService auflöst und in einen DatabaseLoggerService umwandelt. Das ist etwas unsicher, aber wir wissen mit Sicherheit, dass der von uns registrierte ILoggerService definitiv ein DatabaseLoggerService ist, also ist es in Ordnung.

Warum nicht IHostedService oder die konkrete Klasse einfügen?

Sie haben wahrscheinlich schon das Sprichwort gehört:„Code gegen Schnittstellen, nicht gegen Implementierungen.

Muss der Controller wissen, dass er einen Hintergrunddienst verwendet, der sich bei der Datenbank anmeldet? In den meisten Fällen nein. Es muss nur wissen, dass es einen Logger verwendet. Mit anderen Worten, der Controller sollte für den ILoggerService kodieren, nicht für die konkrete DatabaseLoggerService-Klasse.

Wenn Sie die konkrete Hintergrunddienstklasse übergeben, hätte der Controller Zugriff auf die Hintergrunddienstmethoden, z. B. StopAsync(). In den meisten Fällen wäre dies unerwünscht:

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

Darüber hinaus erleichtert das Einfügen von Schnittstellen den Komponententest des Codes, da Sie die Schnittstelle nachahmen.


No