Prism And Custom Enterprise Library Listeners

Ade was working on a personal project using Prism 1.0 and Enterprise Library 4.1.  He ran into a couple challenges I thought others might be interested in hearing about.  The first challenge he encountered was that the Unity IUnityContainer interface had changed in version 1.2 and he needed to modify the MockUnityContainer we had used for some of our tests.   You can find the resolution for this with Prism 1.0 on his blog.

The second challenge is a little trickier.  Before I get to the second challenge, let me explain how the bootstrapper works in Prism.  Prism has a base bootstrapper that configures Prism to work with the Unity container.  While the Prism library itself does not know anything about a specific container (it always accesses the container through an IContainerFacade and, in Prism 2.0 through the Common Service Locator), most applications will use a specific container and we have provided a Unity bootstrapper that connects Prism with Unity. 

In addition to a common service locator, Prism may log messages to a logging facade, ILoggerFacade.  The Unity bootstrapper, by default, logs messages to Console.Out.  This can easily be replaced during the bootstrap sequence by providing a different ILoggerFacade, say one that uses Enterprise Library logging:

    1: public partial class StockTraderRIBootstrapper : UnityBootstrapper   
    2: {   
    3:     private readonly EnterpriseLibraryLoggerAdapter _logger = 
    4:         new EnterpriseLibraryLoggerAdapter();   
    5:  
    6:     protected override ILoggerFacade LoggerFacade   
    7:     {   
    8:         get { return _logger; }   
    9:     }   
   10:     
   11:     ...  
   12: }

You can find an EnterpriseLibraryLoggerAdapter as part of the reference implementation with the Prism source. 

The challenge comes when you are using a custom trace listener for Enterprise Library that you need to connect to something in your application (say a window in your application) from your container. 

It turns out that, under the cover, Enterprise Library Logging creates a new container for your provider that is separate from the container in your application. While this is great for many situations, not so great if you want Enterprise Library to configure logging from within your container.

Sharing a container between Prism and Enterprise Library

You can get Enterprise Library to configure the container you use with Prism, but in the bootstrapper you will need to create the container yourself and configure it with some Enterprise Library extensions before handing it to Prism.  In your application-specific bootstrapper, you will need to create a custom run method for your bootstrapper that sets up the container before calling the base run method:

    1: public partial class StockTraderRIBootstrapper : UnityBootstrapper
    2: {
    3:     private IUnityContainer container;
    4:     
    5:     public void CustomRun()
    6:     {
    7:         container = new UnityContainer();
    8:         container.AddNewExtension<EnterpriseLibraryCoreExtension>();
    9:         container.AddNewExtension<LoggingBlockExtension>();
   10:         
   11:         container.RegisterType<ILoggerFacade, EnterpriseLibraryLoggerAdapter>();
   12:         container.RegisterType<IMessageModel, MessageModel>(new ContainerControlledLifetimeManager());
   13:         
   14:         // Force a resolve so static Service property gets injected
   15:         container.Resolve<MyCustomListener>();
   16:         
   17:         Run();
   18:     }
   19:     
   20:     protected override IUnityContainer CreateContainer()
   21:     {
   22:         return this.container;
   23:     }
   24:     
   25:     protected override ILoggerFacade LoggerFacade
   26:     {
   27:         get { return container.Resolve<ILoggerFacade>(); }
   28:     }
   29:     
   30:     protected override Microsoft.Practices.Composite.Modularity.IModuleCatalog GetModuleCatalog()
   31:     {
   32:         ...
   33:     }
   34:  
   35:     protected override DependencyObject CreateShell()
   36:     {        
   37:         ...
   38:     }
   39: }

The first few lines of CustomRun define the container and plug in the Enterprise Library extensions.  This enables the app.config to be consumed properly.

Lines 11-12 define the logger facade and shared MessageModel used by my display window and MyCustomListener.   The MessageModel maintains the list of message to be displayed and the custom listener populates this model.  One thing to note is that, to ensure the static reference to the model is set appropriately, we force a resolution of MyCustomListener (line 15).

Below are what MessageModel and MyCustomListener look like:

    1: public class MessageModel : IMessageModel
    2: {
    3:     ObservableCollection<string> messages = new ObservableCollection<string>();
    4:  
    5:     public MessageModel()
    6:     {
    7:     }
    8:  
    9:     public IList<string> Messages
   10:     {
   11:         get { return messages; }
   12:     }
   13: }

 

    1: [ConfigurationElementType(typeof(CustomTraceListenerData))]
    2: public class MyCustomListener : CustomTraceListener
    3: {
    4:     static IMessageModel model;
    5:     
    6:     public MyCustomListener()
    7:     {
    8:     }
    9:     
   10:     public MyCustomListener(IMessageModel model)
   11:     {
   12:         MyCustomListener.model = model;
   13:     }
   14:     
   15:     public override void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, object data)
   16:     {
   17:         if (data is LogEntry && this.Formatter != null)
   18:         {
   19:             this.WriteLine(this.Formatter.Format(data as LogEntry));
   20:         }
   21:         else
   22:         {
   23:             this.WriteLine(data.ToString());
   24:         }
   25:     }
   26:     
   27:     public override void Write(string message)
   28:     {
   29:         model.Messages.Add(message);
   30:     }
   31:     
   32:     public override void WriteLine(string message)
   33:     {
   34:         model.Messages.Add(message);
   35:     }
   36: }

Configuring logging this way also means we can no longer use the Enterprise Library Logger facade.  Instead we had to modify the adapter to use a LogWriter directly in our EnterpriseLibraryLoggerAdapter:

    1: public class EnterpriseLibraryLoggerAdapter : ILoggerFacade
    2: {
    3:     LogWriter writer;
    4:     
    5:     public EnterpriseLibraryLoggerAdapter(LogWriter writer)
    6:     {
    7:         this.writer = writer;
    8:     }
    9:       
   10:     public void Log(string message, Category category, Priority priority)
   11:     {
   12:         LogEntry entry = new LogEntry(message, category.ToString(), (int)priority, 1, TraceEventType.Information, "", null);
   13:         writer.Write(entry);
   14:         
   15:         // We can't use the facade since it would create a new container
   16:         //Logger.Write(message, category.ToString(), (int)priority);
   17:     }
   18: }

Hopefully this will help you in those situations that you need to use one container between Prism and Enterprise Library.