views:

85

answers:

1

Sorry, this is quite a special topic so this may not be of interest to many. :-(

However, I need to do the following thing:

  • I have an application that provides logging to some kind of console window (it's a WPF window, because of application requirements and because the application needs to look flashy even here - our special customer asked for that and talks about it every time we meet)

  • To provide thread - agnostic logging I created an interface / implementation pair "IGUIController" / "GUIController"

So far, so good. It's all fine.

However:

  • I need my own custom trace listener (I call it "GUITraceListener") which uses the IGUIController - interface to write specific log messages to that flashy WPF - window

So far, my solution is to have a hacked, old skool code smell singleton - style "Current" - property on my GUIController (yes, and I'm ashamed, and I DO know that this is terrible) which I invoke like this:

GUIController.Current.Log(message);

Obviously, this is a no - go.

So, the correct way to do that would be to inject the dependency (of course). However, when I do that, I get the complaint (runtime), that I don't provide a constructor with 0 (zero) arguments for my GUITraceListener - class.

As a matter of fact, I get that here:

EnterpriseLibraryContainer.ConfigureContainer(configurator, 
ConfigurationSourceFactory.Create());

And the complaint is:

ArgumentException was unhandled Unable to find appropriate 0 argument constructor for GUITraceListener

This is really bad. I mean, Unity is part of the Enterprise Library, why didn't those guys simply add the possibility of injecting my dependencies?

Clarification:

What I want to have in the end is:

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="guiController"></param>
    public GUITraceListener(IGUIController guiController)
    {
        // Store the dependencies
        this.m_GUIController = guiController;
    }
+1  A: 

Entlib 5 does have the ability to do this. I'm guessing you've got a [ConfigurationElementType(typeof(CustomTraceListenerData)] on your listener class, right?

Entlib 5 is designed to be container independent. As such, we can't rely on any kind of auto-wiring semantics because each container does it differently. So you need to tell Entlib which constructor to call, and what dependencies should be injected. This is done through your configuration data class.

I slapped together a quick example. Here's the trace listener - not much special:

[ConfigurationElementType(typeof(GuiTraceListenerData))]
public class GuiTraceListener : TraceListener
{
    private readonly ILogFormatter formatter;
    private readonly IGuiController guiController;

    public GuiTraceListener()
        : this(string.Empty, null, null)
    {
    }

    public GuiTraceListener(string name)
        : this(name, null, null)
    {
    }

    public GuiTraceListener(string name, ILogFormatter formatter, IGuiController guiController) : base(name)
    {
        this.formatter = formatter;
        this.guiController = guiController;
    }

    public override void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id,
        object data)
    {
        if ((Filter == null) || Filter.ShouldTrace(eventCache, source, eventType, id, null, null, data, null))
        {
            if (data is LogEntry)
            {
                if (formatter != null)
                {
                    Write(formatter.Format(data as LogEntry));
                }
                else
                {
                    base.TraceData(eventCache, source, eventType, id, data);
                }
            }
            else
            {
                base.TraceData(eventCache, source, eventType, id, data);
            }
        }
    }

    public override void Write(string message)
    {
        guiController.Log(message);
    }

    public override void WriteLine(string message)
    {
        guiController.Log(message);
    }
}

The interesting part is in the GuiTraceListenerData class:

public class GuiTraceListenerData : TraceListenerData
{
    private const string formatterNameProperty = "formatter";

    public GuiTraceListenerData()
        : this("unnamed", null, TraceOptions.None)
    {
    }

    public GuiTraceListenerData(string name)
        : this(name, null, TraceOptions.None)
    {
    }

    public GuiTraceListenerData(string name, string formatterName)
        : this(name, formatterName, TraceOptions.None)
    {
    }

    protected GuiTraceListenerData(string name, string formatterName, TraceOptions traceOutputOptions)
        : base(name, typeof (GuiTraceListener), traceOutputOptions, SourceLevels.All)
    {
        ListenerDataType = typeof (GuiTraceListenerData);
        Formatter = formatterName;
    }

    [ConfigurationProperty(formatterNameProperty, IsRequired = false)]
    [Reference(typeof(NameTypeConfigurationElementCollection<FormatterData, CustomFormatterData>), typeof(FormatterData))]
    public string Formatter
    {
        get { return (string) base[formatterNameProperty]; }
        set { base[formatterNameProperty] = value; }
    }

    protected override Expression<Func<TraceListener>> GetCreationExpression()
    {
        return () =>
            new GuiTraceListener(Name,
                Container.ResolvedIfNotNull<ILogFormatter>(Formatter),
                Container.Resolved<IGuiController>());
    }
}

In particular, look at that GetCreationExpression method. that's telling entlib that to create the object represented by this config new, call that constructor, and resolve the formatter (if one is specified) and the IGuiController.

Then, in my test app (I used Winforms just to be quick) I initialized my container and app like this:

static void Main()
{
    var container = new UnityContainer()
        .AddNewExtension<EnterpriseLibraryCoreExtension>()
        .RegisterType<IGuiController, MessageBoxGuiController>();

    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(container.Resolve<Form1>());
}

My Form1 class takes a LogWriter as a constructor parameter.

The nice thing about how Entlib 5 is built is that you get almost automatic config tool support when doing this - no need to write a separate config node. Your runtime config element is all you need - just copy the DLL into the same directory with the config tool and it'll just pick it up.

Anyway, from here on it just works.

Hope this helps. If you want more details, drop me a line and I can send you the entire working project.

-Chris

Chris Tavares
WOW, that was EXACTLY what I was looking for! Thank you VERY much! :-)
Turing Complete
But tell me, how do you get the Container in "GetCreationExpression()"?
Turing Complete
Ahh, that's a bit of a trick. Container.Resolved<> is NOT a call to an instance of Unity, or any other container. Container here is actually a static class inside of Entlib, and if you look at the implementation of Resolved or ResolvedIfNotNull you'll see they're noops.
Chris Tavares
(Ran out of room)What those actually are used for are as markers. Notice this method doesn't return a Func, it returns Expression<Func>. What we do under the hood is pick through the expression, and generate the appropriate calls to whatever container you're actually using. Container.Whatever is a known marker we can look for to put in "resolve from container here please."
Chris Tavares