Write Your Own Diagnostics for StructureMap (and yet more fun with Linq)

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(“”);

            });

        }

About Jeremy Miller

Jeremy is the Chief Software Architect at Dovetail Software, the coolest ISV in Austin. Jeremy began his IT career writing "Shadow IT" applications to automate his engineering documentation, then wandered into software development because it looked like more fun. Jeremy is the author of the open source StructureMap tool for Dependency Injection with .Net, StoryTeller for supercharged acceptance testing in .Net, and one of the principal developers behind FubuMVC. Jeremy's thoughts on all things software can be found at The Shade Tree Developer at http://codebetter.com/jeremymiller.
This entry was posted in Presentation Patterns, StoryTeller, StructureMap. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Sunit

    Sweet! I started using it after playing with NInject, Autofaq and love it so far. The diagnostics is the very reason I’ll cling on to it. Once I can get over the AutoMocker stuff, I”ll be good !