Fork me on GitHub

Lamar 5.0.0


Next

Decorators

Previous

Resolving Services

Generic Types


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


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


    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, Lamar will run through its "missing service 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, Lamar 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>     Lamar.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>     Lamar.Testing.Acceptance.Visualization     Transient     DefaultVisualizer<IssueCreated> ('548b4256-a7aa-46a3-8072-bd8ef0c5c430')     (Default)
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
IVisualizer<TLog>             Lamar.Testing.Acceptance.Visualization     Transient     DefaultVisualizer<TLog>                                                      (Default)
====================================================================================================================================================================================


Generic Registrations and Default Fallbacks

A powerful feature of generic type support in Lamar 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 Lamar'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 : ServiceRegistry
{
    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                 Lamar.Testing.Acceptance.Visualization     Transient     Lamar.Testing.Acceptance.Visualization.LogVisualizer               (Default)
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
IVisualizer<IssueResolved>     Lamar.Testing.Acceptance.Visualization     Transient     Lamar.Testing.Acceptance.Visualization.IssueResolvedVisualizer     (Default)
                                                                                 Transient     DefaultVisualizer<IssueResolved>                                                   
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
IVisualizer<IssueCreated>      Lamar.Testing.Acceptance.Visualization     Transient     Lamar.Testing.Acceptance.Visualization.IssueCreatedVisualizer      (Default)
                                                                                 Transient     DefaultVisualizer<IssueCreated>                                                    
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
IVisualizer<TLog>              Lamar.Testing.Acceptance.Visualization     Transient     DefaultVisualizer<TLog>                                                   (Default)
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
IVisualizer<TLog>              Lamar.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>();


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