Fork me on GitHub

Generic Types Edit on GitHub


StructureMap comes with some power abilities to exploit open generic types in .Net for extensibility and flexible handling within your system.

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("<div>{0}</div>", 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:


[Fact]
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:


[Fact]
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

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:


[Fact]
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:


[Fact]
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: GetType().GetAssembly()));
}

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)
===================================================================================================================================================

Example 3: Interception Policy against Generic Types

Several years ago I described an approach for using an Event Aggregator in a WPF application that relied on StructureMap interception to register any object that StructureMap built with the active EventAggregator for the system if that object was recognized as a listener to the event aggregator. I thought that approach worked out quite well, so let's talk about how you could implement that same design with the improved interception model introduced by StructureMap 3.0 (the event aggregator and StructureMap interception worked out well, but I'm very happy now that I ditched the old WPF client and replaced it with a web application using React.js instead).

First off, let's say that we're going to have this interface for our event aggregator:


public interface IEventAggregator
{
    // Sending messages
    void SendMessage<T>(T message);

    void SendMessage<T>() where T : new();

    // Explicit registration
    void AddListener(object listener);

    void RemoveListener(object listener);
}

To register a listener for a particular type of event notification, you would implement an interface called IListener<T> shown below and directly add that object to the IEventAggregator:


public interface IListener<T>
{
    void Handle(T message);
}

In the application I'm describing, all of the listener objects were presenters or screen controls that were created by StructureMap, so it was convenient to allow StructureMap to register newly created objects with the IEventAggregator in an activation interceptor.

What we want to do though is have an interception policy that only applies to any concrete type that implements some interface that closes IListener<T>:


public class EventListenerRegistration : IInterceptorPolicy
{
    public string Description
    {
        get { return "Adds the constructed object to the EventAggregator"; }
    }

    public IEnumerable<IInterceptor> DetermineInterceptors(Type pluginType, Instance instance)
    {
        if (instance.ReturnedType.FindInterfacesThatClose(typeof(IListener<>)).Any())
        {
            Expression<Action<IContext, object>> register =
                (c, o) => c.GetInstance<IEventAggregator>().AddListener(o);
            yield return new ActivatorInterceptor<object>(register);
        }
    }
}

To see our new interception policy in action, see this unit test from GitHub:


[Fact]
public void use_the_event_listener_registration()
{
    var container = new Container(x =>
    {
        x.Policies.Interceptors(new EventListenerRegistration());
        x.For<IEventAggregator>().Use<EventAggregator>().Singleton();
    });

    var events = container.GetInstance<IEventAggregator>();
    var listener = container.GetInstance<BooMessageListener>();

    var message = new BooMessage();

    events.SendMessage(message);

    listener.Messages.Single().ShouldBeTheSameAs(message);
}