Here’s your StructureMap Tip O’ the Day. StructureMap 2.5+ introduces the “Build Session” that is a group of objects that give you access to information about the current object request. In several spots you can query the BuildSession (IContext) to alter construction or even to grab other services to do your construction. This is repeated from http://structuremap.sourceforge.net/UsingSessionContext.htm.
A new aspect of the StructureMap internal architecture is the BuildSession class
that tracks the objects being created within a single call to any of the
Container.GetInstance() methods. While it is mostly an internal feature,
in some cases users can take advantage of the exposed IContext interface to
query information about the current request and to even register or retrieve the
dependencies that will be used within that object request. The IContext
interface is shown below:
public interface
IContext
{
/// <summary>
/// Gets a reference to the
<see cref=”BuildStack”>BuildStack</see> for this build session
/// </summary>
BuildStack
BuildStack { get; }
/// <summary>
/// The concrete type of the immediate parent object in
the object graph
/// </summary>
Type ParentType
{ get; }
/// <summary>
/// Get the object of type T that is valid for this build
session.
/// </summary>
/// <typeparam
name=”T”></typeparam>
/// <returns></returns>
T GetInstance<T>();
/// <summary>
/// Get the object of type T that is valid for this build
session by name.
/// </summary>
/// <typeparam
name=”T”></typeparam>
/// <returns></returns>
T GetInstance<T>(string
name);
/// <summary>
/// Gets the root “frame” of the object request
/// </summary>
BuildFrame Root
{ get; }
/// <summary>
/// The requested instance name of the object graph
/// </summary>
string
RequestedName { get; }
/// <summary>
/// Register a default object for the given PluginType
that will
/// be used throughout the rest of the current object
request
/// </summary>
/// <param
name=”pluginType”></param>
/// <param
name=”defaultObject”></param>
void
RegisterDefault(Type pluginType,
object defaultObject);
/// <summary>
/// Same as GetInstance, but can gracefully return null
if
/// the Type does not already exist
/// </summary>
/// <typeparam
name=”T”></typeparam>
/// <returns></returns>
T TryGetInstance<T>()
where T : class;
/// <summary>
/// Same as GetInstance(name), but can gracefully return
null if
/// the Type and name does not already exist
/// </summary>
/// <typeparam
name=”T”></typeparam>
/// <param
name=”name”></param>
/// <returns></returns>
T TryGetInstance<T>(string
name) where T :
class;
}
Using the Build Stack
You can interrogate the IContext.BuildStack property to access the PluginType and
name of the instance being created at any time. BuildStack will give you
access both to the “root” of the request and the immediate “parent” of the
object being created. The BuildStack keeps track of a BuildFrame (somewhat
modeled on the StackFrame in the BCL) at each level of the request:
public interface
IBuildFrame
{
/// <summary>
/// The requested PluginType of the Instance being create
/// </summary>
Type
RequestedType { get; }
/// <summary>
/// The Name of the Instance being created
/// </summary>
string Name {
get; }
/// <summary>
/// The actual ConcreteType being created. This
will not always
/// be available
/// </summary>
Type
ConcreteType { get; }
}
IContext exposes a property for the full BuildStack, but also exposes some
convenience methods for getting at the immediate parent type. The driver
for this property was a request to recreate the
Logging Facility from
Windsor. Let’s say that you want some sort of generic setter policy
for logging. Any time StructureMap builds an object and sees a public
settable property of your “ILogger” instance, it should use the type of being
created to find the properly configured ILogger and then attach that new ILogger
to the parent object.
Here’s a sample of code that uses the IContext.ParentType to create the proper
ILogger.
var container = new
Container(r =>
{
r.FillAllPropertiesOfType<ILogger>().TheDefault.Is
.ConstructedBy(context => new
Logger(context.ParentType));
});
Using the Requested Name
The IContext interface will allow you to use the instance name that was requested
in a call to Container.GetInstance<T>(name) or
ObjectFactory.GetNamedInstance<T>(name). The most common usage is probably
going to be within the new “Missing Instance” feature (inspired by the
Missing Method feature of dynamic languages like Ruby). The
immediate way that my team is going to use the Missing Instance functionality is
to build an object for localization support based on the requested culture.
Here’s an example of using Missing Instance that queries the
IContext.RequestedName property in order to construct an object:
[Test]
public
void configure_and_use_missing_instance()
{
var container = new
Container(x =>
{
x.ForRequestedType<Rule>().MissingNamedInstanceIs.ConstructedBy(context
=> new ColorRule(context.RequestedName));
});
container.GetInstance<Rule>(“red”).ShouldBeOfType<ColorRule>().Color.ShouldEqual(“red”);
container.GetInstance<Rule>(“green”).ShouldBeOfType<ColorRule>().Color.ShouldEqual(“green”);
container.GetInstance<Rule>(“blue”).ShouldBeOfType<ColorRule>().Color.ShouldEqual(“blue”);
}
Retrieving a Service from IContext
You can also retrieve other services from the IContext during object
construction. Because the underlying BuildSession manages the
Auto Wiring, you can generally assume that you’re using the exact same
object instance for a PluginType that other objects in the same object graph
will receive. That’s a helpful feature when you’re talking about using
View’s within any type of desktop application or any kind of NHibernate object
where the state or identity of the object requested is important.
My team uses this functionality in our NHibernate bootstrapping. We have an
interface named ISessionSource tht is responsible for creating the NHibernate
ISession objects (it wraps a Sessio).
public interface
ISessionSource
{
ISession
CreateSession();
}
We can’t just walk up and create an ISession object directly. Instead, you
have to use the ISessionSource to create an ISession for you. We still
want StructureMap to inject the ISession objects into other classes, so we use
the IContext.GetService<ISession>() method from within a Lambda to build
ISession objects:
ForRequestedType<ISession>().TheDefault.Is.ConstructedBy(
context => context.GetInstance<ISessionSource>().CreateSession());
The IContext.GetInstance<T>() function will also allow you to retrieve objects
explicitly passed into the Container through the Container.With<T>(T
target).GetInstance() methods.
Registering a Service with IContext
You can alter the underlying BuildSession and control all following Dependency
Injection by overriding the default objects within a single object request by
using the IContext.RegisterDefault() method. Use this method with extreme caution.