Exploiting Generic Types with StructureMap

I’ve got a short window at work that I’m using to try to finally fill in the holes in StructureMap documentation. Everything I’m showing here is old, but some of it I’ve never documented or written about before and the usage of generic types has spawned a lot of questions on the StructureMap list over the years. 

The content of this blog post looks a lot better in the actual StructureMap documentation site here.

Example 1: Visualizing an Activity Log

I worked years ago on a system that could be used to record and resolve customer support problems. Since it was very workflow heavy in its logic, we tracked user and system activity as an event stream of small objects that reflected all the different actions or state changes that could happen to an issue. To render and visualize the activity log to HTML, we used many of the open generic type capabilities shown in this topic to find and apply the correct HTML rendering strategy for each type of log object in an activity stream.

Given a log object, we wanted to look up the right visualizer strategy to render that type of log object to html on the server side.

To start, we had an interface like this one that we were going to use to get the HTML for each log object:

    public interface ILogVisualizer
    {
        // If we already know what the type of log we have
        string ToHtml<TLog>(TLog log);

        // If we only know that we have a log object
        string ToHtml(object log);
    }

So for an example, if we already knew that we had an IssueCreated object, we should be able to use StructureMap like this:

            // Just setting up a Container and ILogVisualizer
            var container = Container.For<VisualizationRegistry>();
            var visualizer = container.GetInstance<ILogVisualizer>();

            // If I have an IssueCreated lob object...
            var created = new IssueCreated();

            // I can get the html representation:
            var html = visualizer.ToHtml(created);

If we had an array of log objects, but we do not already know the specific types, we can still use the more generic ToHtml(object) method like this:

            var logs = new object[]
            {
                new IssueCreated(), 
                new TaskAssigned(), 
                new Comment(), 
                new IssueResolved()
            };

            // SAMPLE: using-visualizer-knowning-the-type   
            // Just setting up a Container and ILogVisualizer
            var container = Container.For<VisualizationRegistry>();
            var visualizer = container.GetInstance<ILogVisualizer>();

            var items = logs.Select(visualizer.ToHtml);
            var html = string.Join("<hr />", items);

The next step is to create a way to identify the visualization strategy for a single type of log object. We certainly could have done this with a giant switch statement, but we wanted some extensibility for new types of activity log objects and even customer specific log types that would never, ever be in the main codebase. We settled on an interface like the one shown below that would be responsible for rendering a particular type of log object (“T” in the type):

    public interface IVisualizer<TLog>
    {
        string ToHtml(TLog log);
    }

Inside of the concrete implementation of ILogVisualizer we need to be able to pull out and use the correct IVisualizer<T> strategy for a log type. We of course used a StructureMap Container to do the resolution and lookup, so now we also need to be able to register all the log visualization strategies in some easy way. On top of that, many of the log types were simple and could just as easily be rendered with a simple html strategy like this class:

    public class DefaultVisualizer<TLog> : IVisualizer<TLog>
    {
        public string ToHtml(TLog log)
        {
            return string.Format("
{0}
"
, log); } }

Inside of our StructureMap usage, if we don’t have a specific visualizer for a given log type, we’d just like to fallback to the default visualizer and proceed.

Alright, now that we have a real world problem, let’s proceed to the mechanics of the solution.

Registering Open Generic Types

Let’s say to begin with all we want to do is to always use the DefaultVisualizer for each log type. We can do that with code like this below:

        [Test]
        public void register_open_generic_type()
        {
            var container = new Container(_ =>
            {
                _.For(typeof (IVisualizer<>)).Use(typeof (DefaultVisualizer<>));
            });

            
            Debug.WriteLine(container.WhatDoIHave(@namespace:"StructureMap.Testing.Acceptance.Visualization"));
            

            container.GetInstance<IVisualizer<IssueCreated>>()
                .ShouldBeOfType<DefaultVisualizer<IssueCreated>>();

            Debug.WriteLine(container.WhatDoIHave(@namespace: "StructureMap.Testing.Acceptance.Visualization"));
            


            container.GetInstance<IVisualizer<IssueResolved>>()
                .ShouldBeOfType<DefaultVisualizer<IssueResolved>>();
        }

With the configuration above, there are no specific registrations for IVisualizer<IssueCreated>. At the first request for that interface, StructureMap will run through its “missing family policies“, one of which is to try to find registrations for an open generic type that could be closed to make a valid registration for the requested type. In the case above, StructureMap sees that it has registrations for the open generic type IVisualizer<T> that could be used to create registrations for the closed type IVisualizer<IssueCreated>.

Using the WhatDoIHave() diagnostics, the original state of the container for the visualization namespace is:

===========================================================================================================================
PluginType            Namespace                                         Lifecycle     Description                 Name     
---------------------------------------------------------------------------------------------------------------------------
IVisualizer<TLog>     StructureMap.Testing.Acceptance.Visualization     Transient     DefaultVisualizer<TLog>     (Default)
===========================================================================================================================

After making a request for IVisualizer<IssueCreated>, the new state is:

====================================================================================================================================================================================
PluginType                    Namespace                                         Lifecycle     Description                                                                  Name     
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
IVisualizer<IssueCreated>     StructureMap.Testing.Acceptance.Visualization     Transient     DefaultVisualizer<IssueCreated> ('548b4256-a7aa-46a3-8072-bd8ef0c5c430')     (Default)
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
IVisualizer<TLog>             StructureMap.Testing.Acceptance.Visualization     Transient     DefaultVisualizer<TLog>                                                      (Default)
====================================================================================================================================================================================

Generic Registrations and Default Fallbacks

A powerful feature of generic type support in StructureMap is the ability to register specific handlers for some types, but allow users to register a “fallback” registration otherwise. In the case of the visualization, some types of log objects may justify some special HTML rendering while others can happily be rendered with the default visualization strategy. This behavior is demonstrated by the following code sample:

        [Test]
        public void generic_defaults()
        {
            var container = new Container(_ =>
            {
                // The default visualizer just like we did above
                _.For(typeof(IVisualizer<>)).Use(typeof(DefaultVisualizer<>));

                // Register a specific visualizer for IssueCreated
                _.For<IVisualizer<IssueCreated>>().Use<IssueCreatedVisualizer>();
            });


            // We have a specific visualizer for IssueCreated
            container.GetInstance<IVisualizer<IssueCreated>>()
                .ShouldBeOfType<IssueCreatedVisualizer>();

            // We do not have any special visualizer for TaskAssigned,
            // so fall back to the DefaultVisualizer<T>
            container.GetInstance<IVisualizer<TaskAssigned>>()
                .ShouldBeOfType<DefaultVisualizer<TaskAssigned>>();
        }

Connecting Generic Implementations with Type Scanning

It’s generally harmful in software projects to have a single code file that has to be frequently edited to for unrelated changes, and StructureMap Registry classes that explicitly configure services can easily fall into that category. Using type scanning registration can help teams avoid that problem altogether by eliminating the need to make any explict registrations as new providers are added to the codebase.

For this example, I have two special visualizers for the IssueCreated and IssueResolved log types:

    public class IssueCreatedVisualizer : IVisualizer<IssueCreated>
    {
        public string ToHtml(IssueCreated log)
        {
            return "special html for an issue being created";
        }
    }

    public class IssueResolvedVisualizer : IVisualizer<IssueResolved>
    {
        public string ToHtml(IssueResolved log)
        {
            return "special html for issue resolved";
        }
    }

In the real project that inspired this example, we had many, many more types of log visualizer strategies and it could have easily been very tedious to manually register all the different little IVisualizer<T> strategy types in a Registry class by hand. Fortunately, part of StructureMap’s type scanning support is the ConnectImplementationsToTypesClosing()auto-registration mechanism via generic templates for exactly this kind of scenario.

In the sample below, I’ve set up a type scanning operation that will register any concrete type in the Assembly that contains the VisualizationRegistry that closes IVisualizer<T> against the proper interface:

    public class VisualizationRegistry : Registry
    {
        public VisualizationRegistry()
        {
            // The main ILogVisualizer service
            For<ILogVisualizer>().Use<LogVisualizer>();

            // A default, fallback visualizer
            For(typeof(IVisualizer<>)).Use(typeof(DefaultVisualizer<>));

            // Auto-register all concrete types that "close"
            // IVisualizer<TLog>
            Scan(x =>
            {
                x.TheCallingAssembly();
                x.ConnectImplementationsToTypesClosing(typeof(IVisualizer<>));
            });

        }
    }

If we create a Container based on the configuration above, we can see that the type scanning operation picks up the specific visualizers for IssueCreated and IssueResolved as shown in the diagnostic view below:

==================================================================================================================================================================================
PluginType                     Namespace                                         Lifecycle     Description                                                               Name     
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ILogVisualizer                 StructureMap.Testing.Acceptance.Visualization     Transient     StructureMap.Testing.Acceptance.Visualization.LogVisualizer               (Default)
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
IVisualizer<IssueResolved>     StructureMap.Testing.Acceptance.Visualization     Transient     StructureMap.Testing.Acceptance.Visualization.IssueResolvedVisualizer     (Default)
                                                                                 Transient     DefaultVisualizer<IssueResolved>                                                   
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
IVisualizer<IssueCreated>      StructureMap.Testing.Acceptance.Visualization     Transient     StructureMap.Testing.Acceptance.Visualization.IssueCreatedVisualizer      (Default)
                                                                                 Transient     DefaultVisualizer<IssueCreated>                                                    
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
IVisualizer<TLog>              StructureMap.Testing.Acceptance.Visualization     Transient     DefaultVisualizer<TLog>                                                   (Default)
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
IVisualizer<TLog>              StructureMap.Testing.Acceptance.Visualization     Transient     DefaultVisualizer<TLog>                                                   (Default)
==================================================================================================================================================================================

The following sample shows the VisualizationRegistry in action to combine the type scanning registration plus the default fallback behavior for log types that do not have any special visualization logic:

        [Test]
        public void visualization_registry()
        {
            var container = Container.For<VisualizationRegistry>();

            Debug.WriteLine(container.WhatDoIHave(@namespace: "StructureMap.Testing.Acceptance.Visualization"));

            container.GetInstance<IVisualizer<IssueCreated>>()
                .ShouldBeOfType<IssueCreatedVisualizer>();

            container.GetInstance<IVisualizer<IssueResolved>>()
                .ShouldBeOfType<IssueResolvedVisualizer>();

            // We have no special registration for TaskAssigned,
            // so fallback to the default visualizer
            container.GetInstance<IVisualizer<TaskAssigned>>()
                .ShouldBeOfType<DefaultVisualizer<TaskAssigned>>();
        }

Building Closed Types with ForGenericType() and ForObject()

Working with generic types and the common IHandler<T> pattern can be a little bit tricky if all you have is an object that is declared as an object. Fortunately, StructureMap has a couple helper methods and mechanisms to help you bridge the gap between DoSomething(object something) and DoSomething<T>(T something).

If you remember the full ILogVisualizer interface from above:

    public interface ILogVisualizer
    {
        // If we already know what the type of log we have
        string ToHtml<TLog>(TLog log);

        // If we only know that we have a log object
        string ToHtml(object log);
    }

The method ToHtml(object log) somehow needs to be able to find the right IVisualizer<T> and execute it to get the HTML representation for a log object. The StructureMap IContainer provides two different methods called ForObject() and ForGenericType() for exactly this case, as shown below in a possible implementation of ILogVisualizer:

    public class LogVisualizer : ILogVisualizer
    {
        private readonly IContainer _container;

        // Take in the IContainer directly so that
        // yes, you can use it as a service locator
        public LogVisualizer(IContainer container)
        {
            _container = container;
        }

        // It's easy if you already know what the log
        // type is
        public string ToHtml<TLog>(TLog log)
        {
            return _container.GetInstance<IVisualizer<TLog>>()
                .ToHtml(log);
        }

        public string ToHtml(object log)
        {
            // The ForObject() method uses the 
            // log.GetType() as the parameter to the open
            // type Writer<T>, and then resolves that
            // closed type from the container and
            // casts it to IWriter for you
            return _container.ForObject(log)
                .GetClosedTypeOf(typeof (Writer<>))
                .As<IWriter>()
                .Write(log);
        }

        public string ToHtml2(object log)
        {
            // The ForGenericType() method is again creating
            // a closed type of Writer<T> from the Container
            // and casting it to IWriter
            return _container.ForGenericType(typeof (Writer<>))
                .WithParameters(log.GetType())
                .GetInstanceAs<IWriter>()
                .Write(log);
        }

        // The IWriter and Writer<T> class below are
        // adapters to go from "object" to <T>() signatures
        public interface IWriter
        {
            string Write(object log);
        }

        public class Writer<T> : IWriter
        {
            private readonly IVisualizer<T> _visualizer;

            public Writer(IVisualizer<T> visualizer)
            {
                _visualizer = visualizer;
            }

            public string Write(object log)
            {
                return _visualizer.ToHtml((T) log);
            }
        }
    }

The two methods are almost identical in result with some slight differences:

  1. ForObject(object subject) can only work with open types that have only one generic type parameter, and it will pass the argument subject to the underlying Container as an explicit argument so that you can inject that subject object into the object graph being created.
  2. ForGenericType(Type openType) is a little clumsier to use, but can handle any number of generic type parameters

Example #2: Generic Instance Builder

As I recall, the following example was inspired by a question about how to use StructureMap to build out MongoDB MongoCollection objects from some sort of static builder or factory — but I can’t find the discussion on the mailing list as I write this today. This has come up often enough to justify its inclusion in the documentation.

Say that you have some sort of persistence tooling that you primarily interact with through an interface like this one below, where TDocument and TQuery are classes in your persistent domain:

    public interface IRepository<TDocument, TQuery>
    {

    }

Great, StructureMap handles generic types just fine, so you can just register the various closed types and off you go. Except you can’t because the way that your persistence tooling works requires you to create the IRepository<,>objects with a static builder class like this one below:

    public static class RepositoryBuilder
    {
        public static IRepository<TDocument, TQuery> Build<TDocument, TQuery>()
        {
            return new Repository<TDocument, TQuery>();
        }
    }

StructureMap has an admittedly non-obvious way to handle this situation by creating a new subclass of Instance that will “know” how to create the real Instance for a closed type of IRepository<,>.

First off, let’s create a new Instance type that knows how to build a specific type of IRepository<,> by subclassing the LambdaInstance type and providing a Func to build our repository type with the static RepositoryBuilder class:

    public class RepositoryInstance<TDocument, TQuery> : LambdaInstance<IRepository<TDocument, TQuery>>
    {
        public RepositoryInstance() : base(() => RepositoryBuilder.Build<TDocument, TQuery>())
        {
        }

        // This is purely to make the diagnostic views prettier
        public override string Description
        {
            get
            {
                return "RepositoryBuilder.Build<{0}, {1}>()"
                    .ToFormat(typeof(TDocument).Name, typeof(TQuery).Name);
            }
        }
    }

As you’ve probably surmised, the custom RepositoryInstance above is itself an open generic type and cannot be used directly until it has been closed. You could use this class directly if you have a very few document types like this:

            var container = new Container(_ =>
            {
                _.For<IRepository<string, int>>().UseInstance(new RepositoryInstance<string, int>());

                // or skip the custom Instance with:

                _.For<IRepository<string, int>>().Use(() => RepositoryBuilder.Build<string, int>());
            });

To handle the problem in a more generic way, we can create a second custom subclass of Instance for the open type IRepository<,> that will help StructureMap understand how to build the specific closed types of IRepository<,> at runtime:

    public class RepositoryInstanceFactory : Instance
    {
        // This is the key part here. This method is called by
        // StructureMap to "find" an Instance for a closed
        // type of IRepository<,>
        public override Instance CloseType(Type[] types)
        {
            // StructureMap will cache the object built out of this,
            // so the expensive Reflection hit only happens
            // once
            var instanceType = typeof (RepositoryInstance<,>).MakeGenericType(types);
            return Activator.CreateInstance(instanceType).As<Instance>();
        }

        // Don't worry about this one, never gets called
        public override IDependencySource ToDependencySource(Type pluginType)
        {
            throw new NotSupportedException();
        }

        public override string Description
        {
            get { return "Build Repository<T, T1>() with RepositoryBuilder"; }
        }

        public override Type ReturnedType
        {
            get { return typeof (Repository<,>); }
        }
    }

The key part of the class above is the CloseType(Type[] types) method. At that point, we can determine the right type of RepositoryInstance<,> to build the requested type of IRepository<,>, then use some reflection to create and return that custom Instance.

Here’s a unit test that exercises and demonstrates this functionality from end to end:

        [Test]
        public void show_the_workaround_for_generic_builders()
        {
            var container = new Container(_ =>
            {
                _.For(typeof (IRepository<,>)).Use(new RepositoryInstanceFactory());
            });

            container.GetInstance<IRepository<string, int>>()
                .ShouldBeOfType<Repository<string, int>>();

            Debug.WriteLine(container.WhatDoIHave(assembly:Assembly.GetExecutingAssembly()));
        }

After requesting IRepository<string, int> for the first time, the container configuration from Container.WhatDoIHave() is:

===================================================================================================================================================
PluginType                         Namespace                           Lifecycle     Description                                          Name     
---------------------------------------------------------------------------------------------------------------------------------------------------
IRepository<String, Int32>         StructureMap.Testing.Acceptance     Transient     RepositoryBuilder.Build<String, Int32>()             (Default)
---------------------------------------------------------------------------------------------------------------------------------------------------
IRepository<TDocument, TQuery>     StructureMap.Testing.Acceptance     Transient     Build Repository<T, T1>() with RepositoryBuilder     (Default)
===================================================================================================================================================

2 thoughts on “Exploiting Generic Types with StructureMap

Leave a comment