Overview
I'm trying to port a number of projects based on the .NET Framework to .NET Core. This involves porting a number of class libraries as well as top-level console/web applications that consume these libraries.
Part of my requirements is that my top-level applications should support a plugin-based system where I can easily swap out functionality by referencing different subsets of these class libraries. I've used MEF to do this. As an example, one of my ASP.NET Core web applications involves communicating with devices through an ICommunicationService
, and I have different Nuget packages that export different implementations of this service:
[Export(typeof(ICommunicationService))]
[Shared]
public sealed class UsbCommunicationService : ICommunicationService
{
}
Redesigning Class Libraries
At the moment, these class libraries reference Common.Logging
and instantiate loggers as read-only static fields:
[Export(typeof(ICommunicationService))]
[Shared]
public sealed class UsbCommunicationService : ICommunicationService
{
...
private static readonly ILog Log = LogManager.GetLogger<UsbCommunicationService>();
....
}
I used Log4Net
within my top-level applications and facilitated logging from within my class libraries by referencing the Common.Logging.Log4Net
adapter.
However, I know that ASP.NET Core relies on Microsoft's new logging abstraction framework Microsoft.Extensions.Logging
and that ASP.NET Core applications should be designed to support logging via constructor dependency injection of loggers, like this:
public class HomeController : Controller
{
private ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
_logger.LogInformation("Index action requested at {requestTime}", DateTime.Now);
return View();
}
}
I'm not entirely sure which combination of logging frameworks to use within my new .NET Core libraries and applications (see this related post), but I'm leaning towards switching from using Common.Logging
to Microsoft.Extensions.Logging
within my class libraries. In that case, I'm wondering how I should handle instantiation of loggers. Would something like this be appropriate?
using Microsoft.Extensions.Logging;
...
[ImportingConstructor]
public UsbCommunicationService(
[Import] IUsbMessageEncoder encoder,
[Import] IUsbMessageDecoder decoder,
[Import] ILogger<UsbCommunicationService> logger /* Add this new import */)
{
...
}
In other words, should I switch all my class libraries that require logging to having those loggers injected during construction?
Consuming Class Libraries
Based on this answer to a similar question, I feel like the approach detailed above is along the right lines. However, I'm not sure how I would consume and properly instantiate services defined within class libraries within, say, an ASP.NET Core application.
ASP.NET Core uses its own dependency injection service which is completely separate to MEF. I can't simply write something like services.AddSingleton<ICommunicationService, UsbCommunicationService>();
within my web application for two reasons:
The idea is to support a plugin-based system where plugins are discovered dynamically and therefore can't be hard-referenced by the "core" application itself.
The other dependencies for
UsbCommunicationService
-IUsbMessageEncoder
andIUsbMessageDecoder
- are not known by ASP.NET Core's service injector and wouldn't be resolved.
Likewise, I can't use MEF to get an instance of UsbCommunicationService
either as it wouldn't be able to resolve the reference to ILogger<UsbCommunicationService>
.
Summary
In short, I'm trying to find solutions for the following:
- Facilitating logging within .NET Core libraries with maximum flexibility for logging providers.
- Allowing loggers to be supplied to these class libraries using dependency injection.
- Allowing top-level ASP.NET Core or .NET Core console applications to dynamically discover and load these .NET Core libraries at run-time and provide them all with loggers or logger factories so that the top-level application and all loaded plugins use a common logging provider (e.g. Serilog, NLog, Log4Net, etc.).
- For instance, if I wanted to use Log4Net's
ColoredConsoleAppender
, I should see all ASP.NET logs and class library logs appearing within the same console.
- For instance, if I wanted to use Log4Net's
NullLogger
(from Microsoft...Abstractions) or a mock logger injected into the SUT within test code. By the way, fantastic work in Guardians of the Galaxy. – Pavyer