StructureMap: Pass in runtime arguments to a selected constructor

945 Views Asked by At

I want to select a constructor and pass in runtime arguments. I know how I can select a constructor with registry provided arguments and I also know how to provide runtime arguments. But I can’t see a way to combine both together.

The class:

public class SomeClass
{
    // This is the one I want to be the default by selecting it.
    public SomeClass(string arg1, AnArgClass arg2) { }

    // This is default if I don't purposely select it.
    public SomeClass(string arg1, string arg2, string arg3) { }
}

How I might register it ( I know this doesn’t work):

ForConcreteType<SomeClass>()
    .Configure.SelectConstructor(() => new SomeClass(arg1?, arg2?));  I don’t see a way to get the runtime args in…

This is how I would create it and provide arguments if I could get it to register:

var obj1 = _container.With(“arg1”).EqualTo(aRunTimeArg1)
    .With<AnArgClass>(aRunTimeArg2)
    .GetInstance<SomeClass>();

Thanks in advance.

(Note: I'm looking for a StructureMap 3.x solution. Some of options that look like they almost work are using 2.x syntax that doesn't appear available in 3.x - or it moved)

1

There are 1 best solutions below

0
On BEST ANSWER

I found a couple solutions.

  1. [DefaultConstructor]

Tag the attribute the constructor you want and off you go. Definitely not my first choice since it splits registration out into implementation, among other problems using attributes with IoC. But is it the easiest and if you have no issue with IoC container attributes in your code, have at it.

  1. Policies.ConstructorSelector()

Below is a way to use a constructor selector policy to find the constructor, and with more than one selector separated by type. Note that this can be made into an extension method solution, but I didn't want to complicate it anymore than needed.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using StructureMap;
using StructureMap.Configuration.DSL;
using StructureMap.Pipeline;
using StructureMap.TypeRules;

public class SomeClass
{
    // This is the one I want to be the default by selecting it.
    public SomeClass(string arg1, AnArgClass arg2) { }

    // This is default if I don't purposely select it.
    public SomeClass(string arg1, string arg2, string arg3) { }
}

public class AnArgClass { }

public class SampleRegistry : Registry
{
    public SampleRegistry()
    {
        var selectors = new SelectorsList();
        Policies.ConstructorSelector(selectors);

        For<SomeClass>().Use<SomeClass>();
        selectors.Add<SomeClass>(new SelectorByTypes(new[] { typeof(string), typeof(AnArgClass) }));
    }
}

public class SelectorByTypes : IConstructorSelector
{
    private Type[] mArgumentsTypes;

    public SelectorByTypes(IEnumerable<Type> argumentsTypes)
    {
        mArgumentsTypes = argumentsTypes.ToArray();
    }

    public ConstructorInfo Find(Type pluggedType)
    {
        return pluggedType.GetConstructor(mArgumentsTypes); // GetConstructor() ext in SM.TypeRules
    }
}

public class SelectorsList : IConstructorSelector
{
    // Holds the selectors by type
    private Dictionary<Type, IConstructorSelector> mTypeSelectors = new Dictionary<Type, IConstructorSelector>();
    // The usual default, from SM.Pipeline
    private GreediestConstructorSelector mDefaultSelector = new GreediestConstructorSelector(); 

    public void Add<T>(IConstructorSelector selector)
    {
        mTypeSelectors.Add(typeof(T), selector);
    }

    public ConstructorInfo Find(Type pluggedType)
    {
        ConstructorInfo selected = null;
        if (mTypeSelectors.ContainsKey(pluggedType))
        {
            var selector = mTypeSelectors[pluggedType];
            selected = selector.Find(pluggedType);
        }
        else
        {
            selected = mDefaultSelector.Find(pluggedType);
        }
        return selected;
    }
}

As an extension, it would be something like:

 For<SomeClass>()
      .Use<SomeClass>()
      .SetConstructor(selectors, new Type[] { typeof(string), typeof(AnArgClass) });


public static class Extensions
{
    public static SmartInstance<TConcreteType, TPluginType> SetConstructor<TConcreteType, TPluginType>(
        this SmartInstance<TConcreteType, TPluginType> instance,
        ConstructorSelectors constructors,
        IEnumerable<Type> types)
        where TConcreteType : TPluginType
    {
        constructors.Add(typeof(TPluginType), new ArgTypesConstructorSelector(types));
        return instance;
    }
}