I'm using MEF (System.Composition from nuget) + Common.Logging and have classes that call ILog, e.g.,
[Export(typeof(ITranslator))]
public class ATranslator : BaseTranslator
{
[ImportingConstructor]
public ATranslator(ILog log)
{
...
}
The logger instance is available from Common.Logging via log = LogManager.GetLogger<ITranslator>(); but how do I plugin this into my composition container?
var container = new ContainerConfiguration()
.WithAssembly(typeof(ITranslator).Assembly)
.CreateContainer();
container.SatisfyImports(this);
so that I can
[ImportMany]
private IEnumerable<ITranslator> Translators { get; set; }
without
System.Composition.Hosting.CompositionFailedException : No export was
found for the contract 'ILog' -> required by import 'log' of part
'ATranslator' -> required by import 'item' of part 'ITranslator[]'
-> required by initial request for contract 'IEnumerable { IsImportMany = True }'
Maybe you can try to do the export to ILog as a property in other class?
public class LogExporter
{
[Export(typeof(ILog))]
public ILog Log
{
return LogManager.GetLogger<ITranslator>();
}
}
Alternatively you can try to do it using ComposeExportedValue Method with code instead of attributes:
container.ComposeExportedValue<ILog>(LogManager.GetLogger<ITranslator>());
Related
How to inject Settings created from IOptions value to call some class method?
I have MailAppSettings class and MailSender: IMailSender. Both are in different .NET projects.
Values into MailAppSettings are loaded using IOptions pattern in .NET Core. Values are loaded from appsettings.json configuration. For DI we use AutoFac, so it looks like this:
serviceCollection.AddOptions();
serviceCollection.Configure<MailAppSettings>(config.GetSection("MailAppSettings"));
And later then we access mailSettings like this:
public class CustomerSender:ICustomerSender
{
private readonly MailSettings _mailSettings;
private readonly IMailSender _mailSender;
public MyCustomerSender(IOptions<MailSettings> mailSettings, IMailSender mailSender)
{
_mailSettings = mailSettings.Value;
_mailSender = mailSender;
}
public SomeCustomMethod1() {
// use of mailsettings
var recipient = mailSettings.Recipient1;
var body = BuildSomeBody();
var subject = mailSettings.Subject1;
// mail sending
_mailSender.Send(recipient, body, subject);
}
mailSender is the class that sits in separate namespace and is called from different places in different projects.
public class MailSender: IMailSender
{
private readonly IMailSenderSettings _mailSenderSettings;
public MailSender(IMailSenderSettings confg) ///!!! here we need to inject those settings
{
_mailSenderSettings= confg;
}
...
Here, you see that mailSender needs IMailSenderSettings to be injected.
MailAppSettings implements this interface (but also contains additional properties).
My question is- how to inject MailAppSettings into IMailSenderSettings?
If I just register type like this:
containerBuilder.RegisterType<MailAppSettings>()
.As<IMailSenderSettings>()
.SingleInstance();
it won't work, values of injected IMailSenderSettings would be null.
IMailSenderSettings, as well as, MailAppSettings contains recipient, smtp host, password, user, but MailAppSettings also contains additional properties not needed for IMailSenderSettings
You will need to create IMailSenderSettings derived class that depends on MailSettings
public class MyMailSenderSettings: IMailSenderSettings {
private readonly MailSettings mailSettings;
public MyMailSenderSettings(IOptions<MailSettings> options) {
this.mailSettings = options.Value;
}
public string SomeProperty => mailSettings.SomeMatchingProperty;
//... other members mapped from settings
}
so that the desired members can be mapped in the composition root.
From there it is only a matter of registering the implementation so that the DI container can handle the rest.
//...
serviceCollection.AddOptions();
serviceCollection.Configure<MailAppSettings>(config.GetSection("MailAppSettings"));
serviceCollection.AddSingleton<IMailSenderSettings, MyMailSenderSettings>()
//...
In ASP.NET Core the default resolver will resolve Microsoft.Extensions.Logging.ILogger<MyClass> in the controller.
Suppose I create a fresh .NET Standard library that is called from the controller.
How do I pass a Microsoft.Extensions.Logging instance into it?
How do I create a new() instance of my class if ILogger<MyClass2> is required?
Can C# create a manual automapping for ILogger<T>, which I can pass into my library?
There are times when Dependency Injection isn't available - or when you're in a context where you shouldn't be using DI (such as a Unit Test, where you're meant to explicitly define each injected service), ditto some forms of Integration testing.
In those cases - if you don't care about logging (such as when prototyping or experimenting) then use the NullLogger, which is built-in to Microsoft.Extensions.Logging.Abstractions (which is the common NuGet package that all MEL-using projects must reference, so it's guaranteed to be available).
For example, if you have a service implementation that requires a non-null ILogger<T>:
public interface IEncabulatorService
{
void Foo();
}
public class TurboEncabulatorService : IEncabulatorService
{
private readonly ILogger log;
public TurboEncabulatorService( ILogger<TurboEncabulatorService> log )
{
this.log = log ?? throw new ArgumentNullException(nameof(log));
}
public void Foo()
{
this.log.LogInformation( "Foo was invoked." );
}
}
Then you can instantiate this with the NullLogger dummy logger (for example, in a Unit testing project) like so:
using Microsoft.Extensions.Logging; // This namespace must be imported because it uses extension-methods.
using Microsoft.Extensions.Logging.Abstractions;
[TestClass]
public class MyTests
{
[TestMethod]
public void TestEncabulator()
{
ILogger<TurboEncabulatorService> log = NullLoggerFactory.Instance.CreateLogger<TurboEncabulatorService>()
IEncabulatorService service = new TurboEncabulatorService( log );
}
}
If you do care about what is logged, then unfortunately you do need to implement your own ILoggerFactory (and log to some internal buffer), but you do not need to provide your own CreateLogger<T>() method to create strongly-typed ILogger<T> instances, as that's provided for-free in the MEL library as an extension method.
Regarding subclasses
You mentioned in your post "derived" classes - because if you have this...
public class BaseService
{
public BaseService( ILogger<BaseService> log )
{
// ...
}
}
public class DerivedService : BaseService
{
// ...?
}
...you might wonder if a DerivedService is supposed to accept ILogger<BaseService> or ILogger<DerivedService> as surely it needs an ILogger<BaseService> to pass down to BaseService, but then DerivedService would lose its strongly-typed category name.
...but this is not the case! If you look at the definition of ILogger<T> you'll see it's a covariant generic interface because it has <out T> rather than just <T>. This means that any variable, method or constructor that accepts an ILogger<Base> will also accept an ILogger<Derived>!
So you have this and it's perfectly legal:
public class BaseService
{
public BaseService( ILogger<BaseService> log )
{
// ...
}
}
public class DerivedService : BaseService
{
public DerivedService( ILogger<DerivedService> log )
: base( log ) // `ILogger<BaseService>` can accept an `ILogger<DerivedService>`!
{
}
}
And you can instantiate an instance of DerivedService or BaseService with a NullLogger as per my earlier example:
BaseService bs = new BaseService( NullLoggerFactory.Instance.CreateLogger<BaseService>() );
DerivedService ds = new DerivedService ( NullLoggerFactory.Instance.CreateLogger<DerivedService>() );
Factory helper:
If you find yourself needing to make a NullLogger (or your own ILogger implementation) frequently then you can use this factory-helper method:
public static TService CreateServiceWithLogger<TService>( Func<ILogger<TService>,TService> ctor )
{
ILogger<TService> log = NullLoggerFactory.Instance.CreateLogger<TService>();
return ctor( log );
}
C#'s type-inference rules will ensure you won't need to provide an explicit TService generic parameter type argument, like so:
TurboEncabulatorService service = CreateServiceWithNullLogger( log => new TurboEncabulatorService( log ) );
You should use Dependency Injection for this.
Your Controller leverages DI resources by referencing the interface in its constructor. For ILogger<Class> this would look this like:
public class MyAwesomeController : Controller
{
private readonly ILogger _logger;
public MyAwesomeController(ILogger<MyAwesomeController> logger)
{
// logger contains a refference to the DIed ILogger
// We assign it to _logger so we can reference it from other
// methods in the class
_logger = logger;
}
public IActionResult GetIndex()
{
// Log something
//_logger.LogInformation();
return View();
}
}
You can find a lot more detail in the documentation: Logging in ASP.NET Core
I'm creating a service that requires some config parameters and a logger. Here is the constructor for my service:
public StorageProvider(string directory, ILogger<StorageProvider> logger)
I just added the logger. I used to initalize it like this in my startup.cs:
services.AddSingleton<IStorageProvider>(
new StorageProvider(Configuration["TempStorage.Path"]));
The directory parameter comes from the config file, and the logger gets DI'ed. How do I setup my IStorageProvider?
You should do the following:
Wrap the configuration value TempStorage:Path into its own configuration class, e.g. StorageProviderSettings.
Let StorageProvider depend upon that new configuration class.
Register that configuration class as singleton into the ASP.NET configuration system.
Example:
public sealed class StorageProviderSettings
{
public readonly string TempStoragePath;
public StorageProviderSettings(string tempStoragePath)
{
if (string.IsNullOrWhiteSpace(tempStoragePath))
throw new ArgumentException(nameof(tempStoragePath));
this.TempStoragePath = tempStoragePath;
}
}
public sealed class StorageProvider : IStorageProvider
{
public StorageProvider(
StorageProviderSettings settings, ILogger<StorageProvider> logger)
{
// ...
}
}
// Registration
services.AddSingleton(new StorageProviderSettings(Configuration["TempStorage.Path"]));
services.AddSingleton<IStorageProvider, StorageProvider>();
Use the Options pattern as Tratcher suggests in a comment. Read more in the official docs on Configuration.
Basically you define a class to be hold the value you need:
public class StorageProviderOptions
{
public string TempStoragePath { get; set; }
}
Then in ConfigureServices you register the type:
services.Configure<StorageProviderOptions>();
In your code, you request IOptions<StorageProviderOptions> and set this to an instance of StorageProviderOptions:
public class SomeController
{
private readonly StorageProviderOptions _options;
public SomeController(IOptions<StorageProviderOptions> options)
{
_options = options.Value;
}
}
Finally, make sure you have an element in your configuration source that matches the TempStoragePath name. Alternately, you can register the option in ConfigureServices using code:
services.Configure<ServiceProviderOptions>(o => o.TempStoragePath = "temp");
I have a class that is instantiated using dependency injection of a logger type like so:
var _logger = Logger.GetLogger(LoggerType.MongoLogger);
var service = new MyService(_logger);
in my unit tests, I replace the logger to use:
var _logger = Logger.GetLogger(LoggerType.TextFileLogger);
Now, I want to use MEF to load MyService as a plugin I created the service like this:
[Export(typeof(IService))]
public class MyService: IService
{
private ILogger _logger;
public MyService(ILogger logger)
{
this._logger = logger;
}
public void DoServiceWork()
{
_logger.Log("Starting service work");
}
}
How do I make this thing work in the MEF framework ?
Edited:
Added more elaborate example, using a console app.
Bootstrap Class
This class creates the MEF container as well as initializes the aggregate catalog. You should also instantiate other exportable items, e.g. ILogger, which be used by other dependent classes in your program. Creating properties and marking them with Export allows these instances to be used throughout your program.
You should only instantiate this class once. In this example, we instantiate it in the main program block at the startup.
We have marked both Container and ILogger as exports as we want these instances to be available to other dependent classes.
Exporting IService
Marking your MySerivce class with Export(IService) allows it to be exportable in MEF. We use MEF to get an instance of it by calling Container.GetExportedValue<IService>();. Note: by default MEF will use singleton shared instance, i.e. object will be created once. If you want non-shared instances, you will have to mark classes with PartCreationPolicy(CreationPolicy.NonShared)
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition;
class Program
{
static void Main(string[] args)
{
var bootstrap = new Bootstrap();
var service = bootstrap.Container.GetExportedValue<IService>();
service.DoServiceWork();
}
}
public class Bootstrap
{
[Export]
public CompositionContainer Container { get; private set; }
[Export(typeof(ILogger))]
public ILogger Logger { get; private set; }
public Bootstrap()
{
//Create an aggregate catalog that will hold assembly references
var catalog = new AggregateCatalog();
//Adds this assembly.
//Exports defined in the classes and types within this assembly will now be composable
//Add to the catalogs if there are more assemblies where exports are defined.
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
//Create the CompositionContainer with the parts in the catalog
this.Container = new CompositionContainer(catalog);
this.Logger = Logger.GetLogger(LoggerType.MongoLogger);
this.Container.ComposeParts(this);
}
}
[Export(typeof(IService))]
public class MyService : IService
{
//adding pragma directive removes compiler warning of unassigned property/field
//as these will be assigned by MEF import
#pragma warning disable
[Import]
private ILogger _logger;
#pragma warning restore
public MyService()
{
//logger will be instantiated by MEF
}
public void DoServiceWork()
{
_logger.Log("Starting service work");
}
}
Approach
I am employing MEF to create a plugin-nable, if you will, application. My MEF host has an ILogger which exposes TraceMessage(string message). Class Logger implements ILogger and is decorated with an Export attribute so Logger looks like:
[Export(typeof (ILogger))]
public class Logger : ILogger
{ }
The idea being that the various plugins can be offered a central logger that they can write to. Thus, instantiation would be via [Import] attribue, for example:
[Export(typeof (ILogger))]
public class Logger : ILogger
{
private readonly IWindsorContainer _container;
public ICloudTrace CloudTrace
{
get { return _container.Resolve<ICloudTrace>(); }
}
public Logger()
{
_container = new WindsorContainer(new XmlInterpreter());
}
public void TraceMessage(string categoryName, string componentName, string message)
{
CloudTrace.TraceMessage(categoryName, componentName, message);
}
}
And subsequently a log message would be written via Logger.TraceMessage(string message).
Problem
However, this approach throws an InvalidOperationException in my host when its trying to resolve exports, with an error message Sequence contains no matching element.
Exports are resolved in ResolveType(string commandType) (where commandType is the command line parameter needed to execute relevant plugin). ResolveType() looks like:
public dynamic ResolveType(string commandType)
{
try
{
return this.Container.GetExports<ICommand, ICommandMetaData>()
.First(contract => contract.Metadata.CommandType.Equals(commandType, StringComparison.OrdinalIgnoreCase))
.Value;
}
catch (Exception e)
{
Console.WriteLine(e.message);
}
}
I should mention that each plugin has an Execute(Dictionary<string, string> parameters) which is the entry point for the plugin and the class containing this method is decorated with [Export(typeof(ICommand))] [ExportMetadata("CommandType","CommandLine Command string goes here")] attributes.
The problem was in the CompositionContainer construction. Currently, it just loads the plugin assembly specified in the command line instead of doing directory scan or loading currently executing assembly. This has been done for various reasons. So:
var assemblyCatalog = new AssemblyCatalog(Assembly.LoadFrom(assemblyFile: assemblyToLoad));
where assemblyToLoad is a string to the .dll file for the specific plugin. However, the logger is in the host so the host's assembly needs to be loaded. Thus:
var assemblyCatalog = new AggregateCatalog(new AssemblyCatalog(Assembly.GetExecutingAssembly()),
new AssemblyCatalog(Assembly.LoadFrom(assemblyFile: assemblyToLoad)));
fixes the issue.
Thanks to #Matthew Abbott for pointing this out