Fork me on GitHub

Handling Missing Named Instances Edit on GitHub


Let's say that something asks StructureMap to resolve a named instance of a type that StructureMap does not know about. What if instead of throwing the exception for an unknown named service, StructureMap could be taught to create a new registration for that type for the name requested? That's the exact purpose of the Missing Named Instance feature in StructureMap.

Using the contrived example from the StructureMap tests for this feature, let's say that you have a simple interface and object implementation like this:


public interface Rule
{
}

public class ColorRule : Rule
{
    public string Color { get; set; }

    public ColorRule(string color)
    {
        Color = color;
    }
}


If a user asks the container for a named Rule by a name and that rule doesn't exist, we'll just build a ColorRule where the Color property should be the name of the Instance requested. That registration and the usage is shown below:


[Fact]
public void configure_and_use_missing_instance()
{
    var container = new Container(x =>
    {
        x.For<Rule>().MissingNamedInstanceIs
            .ConstructedBy(context => new ColorRule(context.RequestedName));
    });

    container.GetInstance<Rule>("red")
        .ShouldBeOfType<ColorRule>().Color.ShouldBe("red");

    container.GetInstance<Rule>("green")
        .ShouldBeOfType<ColorRule>().Color.ShouldBe("green");

    container.GetInstance<Rule>("blue")
        .ShouldBeOfType<ColorRule>().Color.ShouldBe("blue");
}


The missing named instance rules are evaluated last, meaning that the container will still resolve explicitly registered instances:


[Fact]
public void does_not_override_explicit_registrations()
{
    var container = new Container(x =>
    {
        x.For<Rule>().Add(new ColorRule("DarkRed")).Named("red");

        x.For<Rule>().MissingNamedInstanceIs
            .ConstructedBy(context => new ColorRule(context.RequestedName));
    });

    container.GetInstance<Rule>("red")
        .ShouldBeOfType<ColorRule>()
        .Color.ShouldBe("DarkRed");
}


You also have the ability to explicitly supply an Instance to be evaluated in the missing named instance resolution:


[Fact]
public void configure_and_use_missing_instance_by_generic_registration()
{
    var instance = new LambdaInstance<ColorRule>(c => new ColorRule(c.RequestedName));

    var container = new Container(x =>
    {
        x.For(typeof(Rule))
            .MissingNamedInstanceIs(instance);
    });

    container.GetInstance<Rule>("red").ShouldBeOfType<ColorRule>().Color.ShouldBe("red");
    container.GetInstance<Rule>("green").ShouldBeOfType<ColorRule>().Color.ShouldBe("green");
    container.GetInstance<Rule>("blue").ShouldBeOfType<ColorRule>().Color.ShouldBe("blue");
}


Multi-Tenancy

To the best of my recollection, the feature described in this section was designed for a multi-tenancy situation where we needed to allow the business rules to vary by client, but most clients would still be using the default rules. In our implemenation, we would make a service location call to the container for the rules by the client id and use the object returned to calculate the business rules (it was an invoice processing service).

If our rules logic class structure looked like:


public interface Rule
{
}

public class DefaultRule : Rule
{
}

public class Client1Rule : Rule
{
}

public class Client2Rule : Rule
{
}


then we could allow client specific rules while still allowing the container to fall through to the default rules for clients that don't need customized rules:


[Fact]
public void use_customer_overrides()
{
    var container = new Container(_ =>
    {
        _.For<Rule>().MissingNamedInstanceIs.Type<DefaultRule>();

        _.For<Rule>().Add<Client1Rule>().Named("client1");
        _.For<Rule>().Add<Client2Rule>().Named("client2");
    });

    // Client1 & Client2 have explicit registrations
    container.GetInstance<Rule>("client1").ShouldBeOfType<Client1Rule>();
    container.GetInstance<Rule>("client2").ShouldBeOfType<Client2Rule>();

    // Client3 has no explicit registration, so falls through to
    // DefaultRule
    container.GetInstance<Rule>("client3").ShouldBeOfType<DefaultRule>();
}


In a more complex usage, maybe you need to pull client specific information from a database or configuration files to construct the rules object. The code below is a partial sample of how you might use the missing named instance feature to do data lookups inside of StructureMap:


public interface IClientRulesRepsitory
{
    ClientRulesData Find(string clientName);
}

public class ClientRulesData
{
}

public class DataUsingRule : Rule
{
    private readonly ClientRulesData _data;

    public DataUsingRule(ClientRulesData data)
    {
        _data = data;
    }
}

[Fact]
public void register_by_looking_up_data()
{
    var container = new Container(_ =>
    {
        _.For<Rule>().MissingNamedInstanceIs.ConstructedBy(
            "Building client rules by looking up client data", c =>
            {
                var data = c.GetInstance<IClientRulesRepsitory>()
                    .Find(c.RequestedName);

                return new DataUsingRule(data);
            });
    });
}