Reputation: 2419
I have defined services in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
string connectionString = Configuration.GetConnectionString("DbConnection");
services.AddDbContext<AppDbContext>(options => options.UseSqlServer(connectionString));
services.AddControllersWithViews();
services.AddScoped<IAdsService, AdsService>();
services.AddScoped<ILogger, ConsoleLogger>();
services.AddScoped<ILogger, FileLogger>();
services.AddScoped<IAdsRepository, AdsRepository>();
}
This is my implementation of Logger:
public class ConsoleLogger: ILogger
{
public void Log(LogData data) => Console.WriteLine(data.ToString());
}
public class FileLogger : ILogger
{
private string Path = "logs";
public void Log(LogData data)
{
if (!Directory.Exists(Path))
{
DirectoryInfo di = Directory.CreateDirectory(Path);
}
File.AppendAllText(Path + "/logs.txt", data.ToString());
}
}
I want to use different loggers for different controllers:
e.g.
private readonly ILogger _logger;
private readonly IAdsService _adsService;
public AdController(IAdsService adsService, ILogger logger)
{
_adsService = adsService;
_logger = logger;
}
However it only takes FileLogger
- how to specify my controller to use ConsoleLogger
in Startup?
Upvotes: 0
Views: 3302
Reputation: 172646
What you are looking for is a feature called context-based injection, which is something that is not easily implemented with MS.DI. That's not to say it's impossible, but depending on your needs, it might require a lot of configuration.
One way to achieve this, though, it by configuring any component that requires an alternative logger explicitly using a lambda using the ActivatorUtilities
class. Here's an example:
private static void AddServices(IServiceCollection services)
{
// The default logger
services.AddScoped<ILogger, FileLogger>();
// Additional 'alternative' loggers
services.AddScoped<ConsoleLogger>();
// Configure a component that requires an alternative logger
services.AddTransient<AdController>(c =>
ActivatorUtilities.CreateInstance<AdController>(c,
c.GetRequiredService<ConsoleLogger>()));
}
In this example:
FileLogger
is registered as ILogger
allowing any 'normal' component that depends on ILogger
to get injected with FileLogger
.ConsoleLogger
is registered as itself, allowing it to be requested als alternative loggerAdController
is registered using the ActivatorUtilities
so that ActivatorUtilities
is responsible for creating a new AdController
where a resolved ConsoleLogger
is supplied to ActivatorUtilities
. This allows ActivatorUtilities
to supply ConsoleLogger
to the first constructor parameter that is assignable from ConsoleLogger
. This basically means that ConsoleLogger
is supplied to the ILogger
argument of AdController
.To test this code, try this:
public interface IAdsService { }
public class AdsService : IAdsService { }
public interface ILogger { }
public class ConsoleLogger : ILogger { }
public class FileLogger : ILogger { }
public class AdController
{
public AdController(IAdsService adsService, ILogger logger) => this.Logger = logger;
public ILogger Logger { get; }
}
class Program
{
static void Main(string[] args)
{
var services = new ServiceCollection();
services.AddTransient<IAdsService, AdsService>();
AddServices(services);
var provider = services.BuildServiceProvider();
using (var scope = provider.CreateScope())
{
var controller =
scope.ServiceProvider.GetRequiredService<AdController>();
Console.WriteLine(controller.Logger.GetType().Name);
}
}
}
There are several downsides to this approach, the most important being that this solution might not scale well. There is no good way to take a more convention-based approach where you say "use ConsoleLogger for any component that follows the following definition X". You must specify each component that uses an alternative logger explicitly.
If these limitations cause maintainability issues, try using a different DI Container that natively supports this feature.
Upvotes: 1