Introduction
We know how to use a
Lazy<T> and
IEnumerable<T>. Recently, I came across a piece of code that uses these two in combination. However, it doesn't make sense if we simply inject a dependency of IEnumerable<Lazy<T>>. Therefore, we need some data to decide which one of the registered implementations shall be instantiated. This data, in general, is known as the
metadata. And so, we will now inject a dependency as IEnumerable<Lazy<T,TMetadata>>.
Lazy<T, TMetadata>
Lazy<T,TMetadata> is a type provided by
MEF to hold the indirect references to exports. Here, in addition to the exported object itself, you also get
export metadata, or information that describes the exported object. Each Lazy<T,TMetadata> contains a T object, representing an actual operation, and TMetadata object, representing its metadata.
Register & Resolve
Suppose, we have a service ILogger which has multiple implementations in the system. And, the class LoggerMetadata provides metadata for the service. Now, the code to register and resolve this service from the container looks like the following.
- var builder = new ContainerBuilder();
-
-
- builder.RegisterType<Program>();
- builder.RegisterType<LogWriter>();
- builder.RegisterType<TextLogger>()
- .As<ILogger>()
- .WithMetadata<LoggerMetadata>(config => config.For(lm => lm.LoggerName, "text"));
-
- builder.RegisterType<DbLogger>()
- .As<ILogger>()
- .WithMetadata<LoggerMetadata>(config => config.For(lm => lm.LoggerName, "db"));
-
-
-
- using (var container = builder.Build())
- {
- container.Resolve<Program>().Start();
- }
Using Metadata
Now, think of a user class, say LogWriter, which uses the service ILogger. The LogWriter accepts all the implementations of service registered with the container. Furthermore, it uses the metadata to decide which implementation should be instantiated. Kindly, refer the below code.
- public class LogWriter
- {
- private readonly IEnumerable<Lazy<ILogger, LoggerMetadata>> _loggers;
- public LogWriter(IEnumerable<Lazy<ILogger, LoggerMetadata>> loggers)
- {
- _loggers = loggers;
- }
-
- public void Write(string useLogger, string message)
- {
- var logger = _loggers.First(l => l.Metadata.LoggerName == useLogger);
- logger.Value.Log(message);
- }
- }
Summary
Of course, the above code can break with a NullReferenceException, but kindly ignore that for now. Not to mention, instead of passing the information of the logger that shall be used as a method parameter, there are surely better ways to do this. However, this example only intends to give you a gist of how we can use metadata to make runtime decisions and I hope it serves its purpose.
Related Articles