services.AddSingleton<SampleInterface, SampleClass>()
allows you to register different implementations for the same interface without modifying the rest of your code.
Change implementations with minimal effort
Suppose you have an ILogger
interface and implementation that log eg to the browser's console or send the log entry to different services eg ConsoleLogger
, MyServiceLogger
or PrometheusLogger
. If you registered only the implementation, with eg services.AddSingleton<ConsoleLogger>()
you'd have to change all of your classes each time you changed a logger implementation.
You'd have to go to each page and change
@inject ConsoleLogger logger;
to
@inject MyServiceLogger logger;
Forget about specifying the logger at runtime too. You'd have to deploy the application each time you wanted to use a new logging service.
By registering the interface and a specific implementation, all of your classes can keep using ILogger<T>
and never know that the implementation has changed.
Implementation selection at runtime
You could even change the implementation at runtime, based on environment variables, configuration, or any other logic you want, eg :
if (app.IsDevelopment)
{
services.AddSingleton<ILogger,ConsoleLogger>();
}
else
{
services.AddSingleton<ILogger,MyServiceLogger>();
}
Unit Testing
In unit tests you could use a null logger - in fact the Logging middleware has a NullLogger class just for this reason, in the core Abstractions package.
Or you could wrap your test framework's output methods into an ILogger
implementation and use that, without modifying the code. xUnit for example uses the ITestOutputHelper interface for this. You could create an XUnitlogger
that forwards calls to this interface:
public class XUnitLogger:ILogger
{
private readonly ITestOutputHelper _output;
public XUnitLogger(ITestOutputHelper output)
{
_output=output;
}
...
void Log(...)
{
_output.WriteLine(...);
}
}