Turing Complete
Turing Complete

Reputation: 930

Unity / EntLib: Injecting a dependency into a CustomTraceListener

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

However, I need to do the following thing:

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

However:

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;
    }

Upvotes: 2

Views: 1958

Answers (1)

Chris Tavares
Chris Tavares

Reputation: 30411

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

Upvotes: 6

Related Questions