One place that I’ll swear up and down that StructureMap laps the other IoC containers is in the realm of diagnostics – mostly because StructureMap is the only one I know of with any diagnostics. You might already be using Container.WhatDoIHave() and/or Container.AssertConfigurationIsValid(), but did you know that you can also write your own queries on top of the configuration model now? The key is the IContainer.Model property that exposes this interface (some of these methods are only available in the StructureMap trunk):
public interface IModel
{
IEnumerable<PluginTypeConfiguration> PluginTypes { get; }
IEnumerable<IInstance> AllInstances { get; }
bool HasDefaultImplementationFor(Type pluginType);
bool HasDefaultImplementationFor<T>();
IEnumerable<IInstance> InstancesOf(Type pluginType);
IEnumerable<IInstance> InstancesOf<T>();
bool HasImplementationsFor(Type pluginType);
bool HasImplementationsFor<T>();
}
and
public interface IInstance
{
string Name { get; }
Type ConcreteType { get; }
string Description { get; }
}
You can use Linq functions and operations across the “AllInstances” member of the IModel interface to query a StructureMap Container’s configuration. Yesterday, I blogged about my Event Aggregator usage in StoryTeller. One of the things I’m doing in StoryTeller is using 100% convention based registration of subscribers to my EventAggregator object by looking for any object that implements a closed type of IListener<T>, where T is a type of event message. I mentioned in my post yesterday that this style leads to some “black magic” because it’s never obvious from looking at a call to IEventAggregator.SendMessage<T>(message) where the message is going and what will happen when it gets there. The decoupling has several advantages, but at some point it is nice to know who’s out there listening to what events. For my own sake (and a book example), I wrote a little bit of diagnostic code that can query the IoC container in StoryTeller and tell me what objects are configured to listen to what events. That diagnostic code generates this plain text at the moment:
StoryTeller.UserInterface.OpenItemMessage is received by: * StoryTeller.UserInterface.ShellConductor StoryTeller.Workspace.ProjectLoaded is received by: * StoryTeller.Execution.TestEngine * StoryTeller.UserInterface.Running.ExecutionQueue StoryTeller.Workspace.ForceBinaryRecycle is received by: * StoryTeller.Execution.TestEngine StoryTeller.Domain.Hierarchy is received by: * StoryTeller.UserInterface.Exploring.TestExplorer StoryTeller.UserInterface.TestAdded is received by: * StoryTeller.UserInterface.Exploring.TestExplorer * StoryTeller.UserInterface.Exploring.SuitePresenter StoryTeller.Workspace.ClearResultsMessage is received by: * StoryTeller.UserInterface.Exploring.TestExplorer * StoryTeller.UserInterface.Tests.TestPresenter StoryTeller.UserInterface.Running.TestRunEvent is received by: * StoryTeller.UserInterface.Exploring.TestExplorer * StoryTeller.UserInterface.StatusPresenter * StoryTeller.UserInterface.Tests.TestPresenter StoryTeller.Execution.FixtureLibraryLoaded is received by: * StoryTeller.UserInterface.TestService * StoryTeller.UserInterface.Exploring.FixtureExplorer * StoryTeller.UserInterface.StatusPresenter * StoryTeller.Examples.ExampleSource * StoryTeller.UserInterface.Tests.Outline.OutlineController * StoryTeller.UserInterface.Tests.Outline.OutlineTreeBuilder … and so on
That ugly report can help me troubleshoot issues related to event management in StoryTeller. Internally, the report above is generated by querying the StructureMap Container in StoryTeller to find all the registered objects that would subscribe to the event aggregator and which events that they subscribe to. I have a little class called ListenerToken just to store the diagnostic data:
public class ListenerToken
{
public Type ConcreteType { get; set; }
public Type EventType { get; set; }
}
I build up the diagnostic report by iterating over all the configured “Instances” in the StructureMap Container and look for any concrete type that implements one or more interfaces that close IListener<T>. That is done by this class below:
public class ListenerDiagnostics
{
private readonly IEnumerable<ListenerToken> _listeners;
public ListenerDiagnostics(IContainer container)
{
_listeners = container.Model.AllInstances
.Where(x => x.ConcreteType != null)
.Where(x => x.ConcreteType.ImplementsInterfaceTemplate(typeof (IListener<>)))
.Select(x => x.ConcreteType)
.SelectMany(x => tokensForType(x));
}
private static IEnumerable<ListenerToken> tokensForType(Type type)
{
return type.GetInterfaces()
.Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IListener<>))
.Select(x => new ListenerToken()
{
ConcreteType = type,
EventType = x.GetGenericArguments()[0]
});
}
public IEnumerable<ListenerToken> Listeners { get { return _listeners; } }
public IEnumerable<Type> WhoListensTo<T>()
{
return _listeners
.Where(x => x.EventType == typeof (T))
.Select(x => x.ConcreteType);
}
Just so the Type.ImplementsInterfaceTemplate() method above is not confusing, I’ve got several extension methods on the Type class in StructureMap for common type queries:
public static class TypeExtensions
{
public static bool ImplementsInterfaceTemplate(this Type pluggedType, Type templateType)
{
if (!pluggedType.IsConcrete()) return false;
foreach (var interfaceType in pluggedType.GetInterfaces())
{
if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == templateType)
{
return true;
}
}
return false;
}
}
Finally, I have just a unit test marked as “Explicit” that has this code:
[Test]
public void who_is_listening()
{
// Bootstrapper is a just a little helper class to bootstrap
// my IoC container. It’s very, very advantageous to decouple
// your IoC container setup away from the Global.asax or the
// main executable of a WPF/WinForms app for this very kind
// of scenario
IContainer container = Bootstrapper.BuildContainer();
var diagnostics = new ListenerDiagnostics(container);
diagnostics.Listeners.GroupBy(x => x.EventType).Each(group =>
{
Debug.WriteLine(“”);
Debug.WriteLine(group.Key.FullName + ” is received by:”);
group.Each(x => Debug.WriteLine(“ * “ + x.ConcreteType));
Debug.WriteLine(“”);
});
}