Building arrays in StructureMap 2.5


Although it was possible in previous versions of StructureMap, the new fluent interface for StructureMap configuration in version 2.5 allows easy configuration of array type constructor parameters.  For example, consider a simple order processor pipeline:

public class OrderPipeline
{
    private readonly IOrderPipelineStep[] _steps;

    public OrderPipeline(IOrderPipelineStep[] steps)
    {
        _steps = steps;
    }

    public IOrderPipelineStep[] Steps
    {
        get { return _steps; }
    }

    public OrderPipelineResponse Execute(OrderPipelineRequest request)
    {
        OrderPipelineResponse response = null;
        foreach (var step in _steps)
        {
            response = step.ExecuteStep(request);
            if (!response.IsSuccessful)
                return response;
        }
        return response;
    }
}

This pipeline takes an array of pipeline steps.  Given a pipeline request, which may contain information necessary for the steps, it executes each of the steps in order.  If any of the responses is not successful, the pipeline stops executing and returns the current response.

No where in this pipeline do we see which steps should be created.  Some steps may require service location from StructureMap, while others may not have any dependencies.  In any case, we want the construction of the pipeline steps for the pipeline to be external from the pipeline, as we can see it’s only concerned with executing steps.

But someone has to be concerned with which steps can be executed.  For many of our dependencies, the DefaultConventionScanner is all we need to construct our dependencies.  With an array parameter, there is no way StructureMap could automatically figure out which dependencies to create, and which order.  Instead, we can create a custom Registry to configure our dependency:

public class PipelineRegistry : Registry
{
    protected override void configure()
    {
        ForRequestedType<OrderPipeline>()
            .TheDefault.Is.OfConcreteType<OrderPipeline>()
            .TheArrayOf<IOrderPipelineStep>()
            .Contains(x =>
                          {
                              x.OfConcreteType<ValidationStep>();
                              x.OfConcreteType<SynchronizationStep>();
                              x.OfConcreteType<RoutingStep>();
                              x.OfConcreteType<PersistenceStep>();
                          });
    }
}

In this Registry, I tell StructureMap first what the requested and default concrete types are.  Next, I tell StructureMap that the array of IOrderPipelineStep contains a set of concrete types.  The Contains method takes a delegate, so I can use a lambda to configure all of the individual concrete types.  Each step is created in the order I specify in the lambda block.  Here’s the passing test:

[Test]
public void Should_construct_the_pipeline_steps_correctly()
{
    StructureMapConfiguration
        .ScanAssemblies()
        .IncludeTheCallingAssembly()
        .With<DefaultConventionScanner>();

    var pipeline = ObjectFactory.GetInstance<OrderPipeline>();

    pipeline.Steps.Length.ShouldEqual(4);
    pipeline.Steps[0].ShouldBeOfType(typeof (ValidationStep));
    pipeline.Steps[1].ShouldBeOfType(typeof (SynchronizationStep));
    pipeline.Steps[2].ShouldBeOfType(typeof (RoutingStep));
    pipeline.Steps[3].ShouldBeOfType(typeof (PersistenceStep));
}

Notice that I did not need to specify the individual Registry.  StructureMap scans the given assemblies for Registries, and automatically adds them to the configuration.  Next, I ask StructureMap for an instance of the OrderPipeline.  Again, nowhere do we see any code for constructing the correct list of IOrderPipelineSteps, this is encapsulated in our Registry.  Finally, the rest of the test asserts that both the correct steps were created, and in the right order.

With the new fluent interface in StructureMap 2.5, I get a nice declarative interface to configure all of the special dependencies.  Although the DefaultConventionScanner picks up almost all of my dependencies, in some special cases I still need to configure them.  Array dependencies are created simply enough just with a lambda specifying the correct steps.

One of those days