Lamar offers some mechanisms to conventionally determine missing registrations at runtime or apply some fine-grained control over how
services are constructed. All of these mechanisms are available under the ServiceRegistry.Policies
property when configuring a Lamar
container.
For more information on type scanning conventions, see Auto-Registration and Conventions
Auto Resolve Missing Services
Note! These policies are only evaluated the first time that a particular service type is requested through the container.
Lamar has a feature to create missing service registrations at runtime based on pluggable rules using the new IFamilyPolicy
interface:
public interface IFamilyPolicy : ILamarPolicy
{
ServiceFamily Build(Type type, ServiceGraph serviceGraph);
}
Internally, if you make a request to IContainer.GetInstance(type)
for a type that the active Container
does not recognize, StructureMap will next
try to apply all the registered IFamilyPolicy
policies to create a ServiceFamily
object for that plugin type that models the registrations for that plugin type, including the default, additional named instances, interceptors or decorators, and lifecycle rules.
The simplest built in example is the EnumerablePolicy
shown below that can fill in requests for IList<T>
, ICollection<T>
, and T[]
with a collection of all the known registrations of the type T
:
public class EnumerablePolicy : IFamilyPolicy
{
public ServiceFamily Build(Type type, ServiceGraph serviceGraph)
{
if (type.IsArray)
{
var instanceType = typeof(ArrayInstance<>).MakeGenericType(type.GetElementType());
var instance = Activator.CreateInstance(instanceType, type).As<Instance>();
return new ServiceFamily(type, new IDecoratorPolicy[0], instance);
}
if (type.IsEnumerable())
{
var elementType = type.GetGenericArguments().First();
var instanceType = typeof(ListInstance<>).MakeGenericType(elementType);
var ctor = instanceType.GetConstructors().Single();
var instance = ctor.Invoke(new object[]{type}).As<Instance>();
return new ServiceFamily(type, new IDecoratorPolicy[0], instance);
}
return null;
}
}
The result of EnumerablePolicy
in action is shown by the acceptance test below:
[Fact]
public void collection_types_are_all_possible_by_default()
{
// NOTE that we do NOT make any explicit registration of
// IList<IWidget>, IEnumerable<IWidget>, ICollection<IWidget>, or IWidget[]
var container = new Container(_ =>
{
_.For<IWidget>().Add<AWidget>();
_.For<IWidget>().Add<BWidget>();
_.For<IWidget>().Add<CWidget>();
});
// IList<T>
container.GetInstance<IList<IWidget>>()
.Select(x => x.GetType())
.ShouldHaveTheSameElementsAs(typeof(AWidget), typeof(BWidget), typeof(CWidget));
// ICollection<T>
container.GetInstance<ICollection<IWidget>>()
.Select(x => x.GetType())
.ShouldHaveTheSameElementsAs(typeof(AWidget), typeof(BWidget), typeof(CWidget));
// Array of T
container.GetInstance<IWidget[]>()
.Select(x => x.GetType())
.ShouldHaveTheSameElementsAs(typeof(AWidget), typeof(BWidget), typeof(CWidget));
}
For another example, consider this example from the Lamar unit tests. Say you have a service that looks like this:
public class Color
{
public string Name { get; set; }
}
And you build a policy that auto-resolves registrations for the Color
service if none previously exist:
public class ColorPolicy : IFamilyPolicy
{
public ServiceFamily Build(Type type, ServiceGraph serviceGraph)
{
if (type != typeof(Color)) return null;
return new ServiceFamily(type, serviceGraph.DecoratorPolicies,
ObjectInstance.For(new Color{Name = "Red"}).Named("Red"),
ObjectInstance.For(new Color{Name = "Blue"}).Named("Blue"),
ObjectInstance.For(new Color{Name = "Green"}).Named("Green")
);
}
}
You can register the new ColorPolicy
shown above like this:
var container = Container.For(_ =>
{
_.Policies.OnMissingFamily<ColorPolicy>();
});
Internally, Lamar uses this IFamilyPolicy
feature for its generic type support, the enumerable type support described a above, and the auto registration of concrete types.
Instance Construction Policies
Lamar allows you to create conventional build policies with a mechanism for altering
how object instances are built based on user created meta-conventions using the IInstancePolicy
shown below:
public interface IInstancePolicy : ILamarPolicy
{
void Apply(Instance instance);
}
These policies are registered as part of the ServiceRegistry DSL with
the Policies.Add()
method:
var container = new Container(_ =>
{
_.Policies.Add<MyCustomPolicy>();
// or
_.Policies.Add(new MyCustomPolicy());
});
The IInstancePolicy
mechanism probably works differently than other IoC containers in that the policy
is applied to the container's underlying configuration model instead of at runtime. Internally, StructureMap lazily creates
a "build plan" for each configured Instance at the first time
that that Instance is built or resolved. As part of creating that build plan, StructureMap runs
all the registered IInstancePolicy
objects against the Instance in question to capture any
potential changes before "baking" the build plan into a .Net Expression
that is then compiled into a Func
for actual construction.
The Instance
objects will give you access to the types being created, the configured name of the Instance (if any),
the ability to add interceptors and to modify the lifecycle. If you wish to add inline dependencies to
Instances that are built by calling constructor function and setter properties, you may find it easier
to use the ConfiguredInstancePolicy
base class as a convenience:
public abstract class ConfiguredInstancePolicy : IInstancePolicy
{
public void Apply(Instance instance)
{
if (instance is IConfiguredInstance)
{
apply(instance.As<IConfiguredInstance>());
}
}
protected abstract void apply(IConfiguredInstance instance);
}
For more information, see:
Example 1: Constructor arguments
So let me say upfront that I don't like this approach, but other folks have asked for this ability over the years. Say that you have some legacy code where many concrete classes have a constructor argument called "connectionString" that needs to be the connection string to the application database like these classes:
public class DatabaseUser
{
public string ConnectionString { get; set; }
public DatabaseUser(string connectionString)
{
ConnectionString = connectionString;
}
}
public class ConnectedThing
{
public string ConnectionString { get; set; }
public ConnectedThing(string connectionString)
{
ConnectionString = connectionString;
}
}
Instead of explicitly configuring every single concrete class in StructureMap with that inline constructor argument, we can make a policy to do that in one place:
public class ConnectionStringPolicy : ConfiguredInstancePolicy
{
protected override void apply(IConfiguredInstance instance)
{
var parameter = instance.ImplementationType
.GetConstructors()
.SelectMany(x => x.GetParameters())
.FirstOrDefault(x => x.Name == "connectionString");
if (parameter != null)
{
var connectionString = findConnectionStringFromConfiguration();
instance.Ctor<string>(parameter.Name).Is(connectionString);
}
}
// find the connection string from whatever configuration
// strategy your application uses
private string findConnectionStringFromConfiguration()
{
return "the connection string";
}
}
Now, let's use that policy against the types that need "connectionString" and see what happens:
[Fact]
public void use_the_connection_string_policy()
{
var container = new Container(_ =>
{
_.Policies.Add<ConnectionStringPolicy>();
});
container.GetInstance<DatabaseUser>()
.ConnectionString.ShouldBe("the connection string");
container.GetInstance<ConnectedThing>()
.ConnectionString.ShouldBe("the connection string");
}
Years ago StructureMap was knocked by an "IoC expert" for not having this functionality. I said at the time -- and still would -- that I would strongly recommend that you simply don't directly open database connections in more than one or a very few spots in your code anyway. If I did need to configure a database connection string in multiple concrete classes, I prefer strong typed configuration.
Example 2: Connecting to Databases based on Parameter Name
From another common user request over the years, let's say that your application needs to connect to
multiple databases, but your data access service in both cases is an interface called IDatabase
, and
that's all the consumers of any database should ever need to know.
To make this concrete, let's say that our data access is all behind an interface and concrete class pair named
Database/IDatabase
like so:
public interface IDatabase { }
public class Database : IDatabase
{
public string ConnectionString { get; set; }
public Database(string connectionString)
{
ConnectionString = connectionString;
}
public override string ToString()
{
return string.Format("ConnectionString: {0}", ConnectionString);
}
}
For a registration policy, let's say that the parameter name of an IDatabase
dependency
in a constructor function should match an identifier of one of the registered IDatabase
services.
That policy would be:
public class InjectDatabaseByName : ConfiguredInstancePolicy
{
protected override void apply(IConfiguredInstance instance)
{
instance.ImplementationType.GetConstructors()
.SelectMany(x => x.GetParameters())
.Where(x => x.ParameterType == typeof(IDatabase))
.Each(param =>
{
// Using ReferencedInstance here tells Lamar
// to "use the IDatabase by this name"
instance.Ctor<IDatabase>(param.Name).IsNamedInstance(param.Name);
});
}
}
And because I'm generally pretty boring about picking test data names, let's say that two of our databases are named "red" and "green" with this container registration below:
var container = new Container(_ =>
{
_.For<IDatabase>().Add<Database>().Named("red")
.Ctor<string>("connectionString").Is("*red*");
_.For<IDatabase>().Add<Database>().Named("green")
.Ctor<string>("connectionString").Is("*green*");
_.Policies.Add<InjectDatabaseByName>();
});
For more context, the classes that use IDatabase
would need to have constructor functions like
these below:
public class BigService
{
public BigService(IDatabase green)
{
DB = green;
}
public IDatabase DB { get; set; }
}
public class ImportantService
{
public ImportantService(IDatabase red)
{
DB = red;
}
public IDatabase DB { get; set; }
}
public class DoubleDatabaseUser
{
public DoubleDatabaseUser(IDatabase red, IDatabase green)
{
Red = red;
Green = green;
}
// Watch out for potential conflicts between setters
// and ctor params. The easiest thing is to just make
// setters private
public IDatabase Green { get; private set; }
public IDatabase Red { get; private set; }
}
Finally, we can exercise our new policy and see it in action:
// ImportantService should get the "red" database
container.GetInstance<ImportantService>()
.DB.As<Database>().ConnectionString.ShouldBe("*red*");
// BigService should get the "green" database
container.GetInstance<BigService>()
.DB.As<Database>().ConnectionString.ShouldBe("*green*");
// DoubleDatabaseUser gets both
var user = container.GetInstance<DoubleDatabaseUser>();
user.Green.As<Database>().ConnectionString.ShouldBe("*green*");
user.Red.As<Database>().ConnectionString.ShouldBe("*red*");
How I prefer to do this - my strong preference would be to use separate interfaces for the different databases even if that type is just an empty type marker that implements the same base. I feel like using separate interfaces makes the code easier to trace and understand than trying to make StructureMap vary dependencies based on naming conventions or what namespace a concrete type happens to be in. At least now though, you have the choice of my way or using policies based on naming conventions.
Example 3: Make objects singletons based on type name
Unlike the top two examples, this is taken from a strategy that I used in FubuMVC
for its service registration. In that case, we wanted any concrete type whose name ended with
"Cache" to be a singleton in the container registration. With the new IInstancePolicy
feature in StructureMap 4,
we could create a new policy class like so:
public class CacheIsSingleton : IInstancePolicy
{
public void Apply(Instance instance)
{
if (instance.ImplementationType.Name.EndsWith("Cache"))
{
instance.Lifetime = ServiceLifetime.Singleton;
}
}
}
Now, let's say that we have an interface named `IWidgets` and a single implementation called `WidgetCache` that
should track our widgets in the application. Using our new policy, we should see `WidgetCache` being
made a singleton:
[Fact]
public void set_cache_to_singleton()
{
var container = new Container(_ =>
{
_.Policies.Add<CacheIsSingleton>();
_.For<IWidgets>().Use<WidgetCache>();
});
// The policy is applied *only* at the time
// that StructureMap creates a "build plan"
container.GetInstance<IWidgets>()
.ShouldBeSameAs(container.GetInstance<IWidgets>());
// Now that the policy has executed, we
// can verify that WidgetCache is a SingletonThing
container.Model.For<IWidgets>().Default
.Lifetime.ShouldBe(ServiceLifetime.Singleton);
}