I have an application and pass some dependency injection things (logger for example):
ILog _logger;
_logger = new NLogService.NLogService();
if (serviceType == typeof(OCRAPIController))
return new OCRAPIController(_logger);
Domain.ILog _logger;
public OCRAPIController(Domain.ILog logger)
{
if (logger == null)
throw new ArgumentNullException(nameof(logger));
_logger = logger;
}
and then use it:
_logger.AddLog(...);
it works and works fine. Problem is with attributes. I.e. I have the following attribute:
public class ReportAPIAccessAttribute : ActionFilterAttribute
{
public override async Task OnActionExecutingAsync(HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken)
{
and I want to log something. Of course, I can't pass ILog as parameter for constructor. I can create static method as wrapper around ILog and call it or even create NLogService.NLogService() directly in attribute. But I don't like both approaches because I want to have only one place in application, where I call NLogService directly. I can create ServiceLocator for attribute, but then other developers (or even I when I forget) can call ServiceLocator in places, where DI parameter should be called...
How to solve this problem correctly?
Related
I am trying to replace dryIOC container with autofac container for some reason but I don't know the exact syntax how could I resolve ILogger uisng autofac
Here is how I register ILogger in DryIoc
public void Load(IContainer container)
{
container.Register<ILoggerFactory, Log4NetLoggerFactory>();
ConfigureLogger(container);
}
[Localizable(false)]
private void ConfigureLogger(IContainer container)
{
container.Register<ILogger>(
made: Made.Of(
r => typeof(ILoggerFactory).GetMethod(nameof(ILoggerFactory.Create), new[] { typeof(Type) }),
factoryInfo: ServiceInfo.Of<ILoggerFactory>(),
parameters: Parameters.Of.Name("type", r => r.Parent?.ImplementationType ?? typeof(Startup))),
reuse: Reuse.Transient);
}
I have tried something like this, but it does not work with Autofac
container.Register<ILoggerFactory, Log4NetLoggerFactory>();
If you try to resolve an instance of Microsoft.Extensions.Logging.ILogger then you have two options:
Option #1 - Resolve a ILogger<TCategoryName>.
public class Something
{
private readonly ILogger logger;
public Something(ILogger<Something> logger)
{
this.logger = logger;
}
}
Option #2 - Resolve an ILogger instead of ILogger<TCategoryName> automatically.
My nuget package does exactly this using Autofac:
EgonsoftHU.Extensions.Logging.Autofac
It contains an Autofac module. Register it then magic will happen.
public class Something
{
private readonly ILogger logger;
public Something(ILogger logger)
{
this.logger = logger; // It will be an instance of ILogger<Something>.
}
}
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?
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 logger
AdController 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.
I'm developing a middleware which I would like to have an optional dependency on a internal logging library. In another words, if MyLoggingService is registered, great!, else, life goes on and ill log to console.
But by declaring public async Task Invoke(HttpContext httpContext, MyLoggingService logger), I get a runtime error saying that it was not registred. I tried setting a default value to null but that didn't work. Also, because its a middleware, I can't overload the Invoke method.
Is there a solution other than requesting the service collection and resolving the dependency myself?
The answer is incredibly simple:
public async Task Invoke(HttpContext httpContext, MyLoggingService logger = null)
Instead of making dependencies optional, consider:
Programming to an abstraction, e.g. IMyLoggingService
Register a Null Object implementation
For instance:
public class CustomMiddleware1 : IMiddleware
{
private readonly IMyLoggingService logger;
public CustomMiddleware1(IMyLoggingService logger) => this.logger = logger;
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
this.logger.Log("Before");
await next(context);
this.logger.Log("After");
}
}
Null Object implementation:
public sealed class NullMyLoggingService : IMyLoggingService
{
public void Log(LogEntry e) { }
}
Registrations:
services.AddSingleton<IMyLoggingService>(new NullMyLoggingService());
app.Use<CustomMiddleware1>();
The call to AddSingleton<IMyLoggingService>(new NullMyLoggingService()) ensures a registration for IMyLoggingService always exists. This prevents complexity in consumers, who would otherwise have to add conditional logic for the case that the logger isn't present.
This null implementation can be replaced by simply adding a second IMyLoggingService after the first:
services.AddScoped<IMyLoggingService, DbMyLoggingService>();
app.Use<CustomMiddleware1>();
Consider we have multiple services, each of them uses ILoggerFactory interface creating own instance of logger next way:
public class Foo
{
private readonly ILogger _logger;
public Foo(ILoggerFactory logger)
{
_logger = loggerFactory.CreateForContext(this.GetType());
}
}
Also consider that we use Windsor Castle for dependency injection, so we have a registration like that:
container.Register(Component
.For<ILoggerFactory>()
.LifestyleSingleton()
.Instance(new LoggerFactory());
Of course, we dont want to repeat CreateForContext() in every class.
So, I'm trying to write something like that:
container.Register(Component
.For<ILogger>()
.LifestyleTransient()
.UsingFactoryMethod((kernel,model,context) =>
kernel.Resolve<ILoggerFactory>().CreateForContext(????));
+
public class Foo
{
private readonly ILogger _logger;
public Foo(ILogger logger)
{
_logger = logger;
}
}
But there is a problem - I can't find how to get type of dependency owner.
Is there any way to get it or another way to do same things?
Update
I implemented ISubDependencyResolver, but it doesn't work with late bound components. So, if you use a lot registrations via UsingFactoryMethod for example, it isn't a great solution.
Given several types of class constructor injection, i.e.;
public class DataService :IDataService
{
public DataService(ILogger logger) { ... }
}
and,
public class Logger
{
public Logger(IDataService service) { ... }
}
or, should I do this instead;
public class DataService : IDataService, ILogger, IDisposable
{
public DataService() { ... }
}
However, I actually don't like to do this on every repository classes or other classes that needs data services and logging at the same time;
public class SomeRepository : IRepostiory
{
public SomeRepository (IDataService service, ILogger logger) { ... }
}
I'm fine with this model;
public interface IRepository : ILogger { ... }
or,
public interface IDataService : ILogger { ... }
Which one is preferred as best practice design? Also, how do we determine if we would like to log the data service process, and at the same time we also would like to log other components that injected ILogger services or using repository?
This is a tongue in cheek answer, but there is no "right" way to implement ILogger. You should probably use the existing framework to construct an ILogger, or hand it over to NLog, log4net, or Serilog. You shouldn't directly implement ILogger unless you plan to build your own logging framework. You should use the logging libary's extension methods to wireup the ILogger Here is an article about this.
Here is a minimal example of how to get console logging up and running:
var hostBuilder = Host.CreateDefaultBuilder().
ConfigureLogging((builderContext, loggingBuilder) =>
{
loggingBuilder.AddConsole((options) =>
{
//This displays arguments from the scope
options.IncludeScopes = true;
});
});
var host = hostBuilder.Build();
var logger = host.Services.GetRequiredService<ILogger<LogTest>>();
//This specifies that every time a log message is logged, the correlation id will be logged as part of it
using (logger.BeginScope("Correlation ID: {correlationID}", 123))
{
logger.LogInformation("Test");
logger.LogInformation("Test2");
}
I'll like to add that the BeginScope method could take any kind of object, not only string. You may pass a more complex object that could represent better the context/scope, like User Id, HostName, CorrelationId, Tennant Id ...
In my case where a created an external Api Logger service, and thus create my own ILogger where BeginScope with those values make more sense.
public IDisposable BeginScope<TState>(TState state)
{
_scopeContextManager = state as ScopeContextManager;
return default!;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
Exception exception, Func<TState, Exception, string> formatter)
{
// use _scopeContextManager
}
I struggled for a long time before figuring this out.