Using Lazy<T, TMetadata> In Dependency Injection

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.
  1. var builder = new ContainerBuilder();  
  2.   
  3. // registering components  
  4. builder.RegisterType<Program>();  
  5. builder.RegisterType<LogWriter>();  
  6. builder.RegisterType<TextLogger>()  
  7.    .As<ILogger>()  
  8.    .WithMetadata<LoggerMetadata>(config => config.For(lm => lm.LoggerName, "text"));  
  9.   
  10. builder.RegisterType<DbLogger>()  
  11.    .As<ILogger>()  
  12.    .WithMetadata<LoggerMetadata>(config => config.For(lm => lm.LoggerName, "db"));  
  13.   
  14.   
  15. // resolving the Program class  
  16. using (var container = builder.Build())  
  17. {  
  18.     container.Resolve<Program>().Start();  
  19. }  
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.
  1. public class LogWriter  
  2. {  
  3.     private readonly IEnumerable<Lazy<ILogger, LoggerMetadata>> _loggers;  
  4.     public LogWriter(IEnumerable<Lazy<ILogger, LoggerMetadata>> loggers)  
  5.     {  
  6.         _loggers = loggers;  
  7.     }  
  8.   
  9.     public void Write(string useLogger, string message)  
  10.     {  
  11.         var logger = _loggers.First(l => l.Metadata.LoggerName == useLogger);  
  12.         logger.Value.Log(message);  
  13.     }  
  14. }  

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