Fork me on GitHub

Working with the IContext at Build Time Edit on GitHub


The IContext usage shown in this topic is certainly not going away in future versions of StructureMap, but if at all possible, the StructureMap team strongly recommends using Construction Policies to accomplish customized object building instead of relying on conditional logic using IContext at runtime.

The IContext interface is what StructureMap uses to expose the current state of the internal "build session" for the current object creation operation for usage in user-supplied lambda builders or interceptors. IContext allows you to:

  1. Retrieve other services from the current Container to use in a Lambda
  2. "BuildUp" objects with setter dependencies
  3. Retrieve all the objects created inside the current build session that could be cast to a certain interface or base class
  4. If an object is being built as a dependency to another object, see the parent type
  5. See the root type of the top level object that StructureMap is building in this request

The entire IContext interface is shown below:


/// <summary>
/// Access to the state and type resolution of the current "build session"
/// during a request to a StructureMap container
/// </summary>
public interface IContext
{
    /// <summary>
    /// The requested instance name of the object graph
    /// </summary>
    string RequestedName { get; }

    /// <summary>
    /// The "BuildUp" method takes in an already constructed object
    /// and uses Setter Injection to push in configured dependencies
    /// of that object
    /// </summary>
    /// <param name="target"></param>
    void BuildUp(object target);

    /// <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>
    /// Get a service from the current build session by type
    /// </summary>
    /// <param name="pluginType"></param>
    /// <returns></returns>
    object GetInstance(Type pluginType);

    /// <summary>
    /// Creates or finds the named instance of the pluginType
    /// </summary>
    /// <param name="pluginType"></param>
    /// <param name="instanceKey"></param>
    /// <returns></returns>
    object GetInstance(Type pluginType, string instanceKey);

    /// <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;

    /// <summary>
    /// Creates or finds the default instance of the pluginType. Returns null if the pluginType is not known to the container.
    /// </summary>
    /// <param name="pluginType"></param>
    /// <returns></returns>
    object TryGetInstance(Type pluginType);

    /// <summary>
    /// Creates or finds the named instance of the pluginType. Returns null if the named instance is not known to the container.
    /// </summary>
    /// <param name="pluginType"></param>
    /// <param name="instanceKey"></param>
    /// <returns></returns>
    object TryGetInstance(Type pluginType, string instanceKey);

    /// <summary>
    /// Gets all objects in the current object graph that can be cast
    /// to T that have already been created
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    IEnumerable<T> All<T>() where T : class;


    /// <summary>
    /// Creates/Resolves every configured instance of PlutinType T
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    IEnumerable<T> GetAllInstances<T>();

    /// <summary>
    /// Creates or resolves all registered instances of the pluginType
    /// </summary>
    /// <param name="pluginType"></param>
    /// <returns></returns>
    IEnumerable<object> GetAllInstances(Type pluginType);

    /// <summary>
    /// The type of the parent object.  Useful for constructing
    /// contextual logging dependencies
    /// </summary>
    Type ParentType { get; }

    /// <summary>
    /// The type of the requested object at the very top of the 
    /// object graph
    /// </summary>
    Type RootType { get; }
}

Example 1: Contextual Logging

All of the sample code in this example is in Github.

One of the canonical examples of using IContext is the integration of logging frameworks like NLog or Log4net that allow you to create logging policies by concrete type. With these tools, you generally have a static class (boo!) where you ask for the proper logging service for a type of object like this one:


public static class LoggerFactory
{
    public static Logger LoggerFor(Type type)
    {
        return new Logger(type);
    }
}


Now, say you would want to have the proper Logger injected into every object that StructureMap creates that depends on Logger matching the concrete type of the object being created. That registration is shown below in a unit test from StructureMap's codebase:


[Fact]
public void can_happily_use_the_parent_type()
{
    var container = new Container(x =>
    {
        // AlwaysUnique() is important so that every object created will get
        // their own Logger instead of sharing whichever one is created first
        x.For<Logger>().Use(c => LoggerFactory.LoggerFor(c.ParentType)).AlwaysUnique();
    });

    var top = container.GetInstance<LoggedClass1>();
    top.Logger.ParentType.ShouldBe(typeof(LoggedClass1));
    top.Child.Logger.ParentType.ShouldBe(typeof(LoggedClass2));
    top.Child.Child.Logger.ParentType.ShouldBe(typeof(LoggedClass3));
}


Just for fun, here's the equivalent with the new construction policies from 4.0:


public class LoggerConvention : ConfiguredInstancePolicy
{
    protected override void apply(Type pluginType, IConfiguredInstance instance)
    {
        instance.Constructor
            .GetParameters()
            .Where(param => param.ParameterType == typeof(Logger))
            .Each(param => instance.Dependencies.Add(LoggerFactory.LoggerFor(instance.PluggedType)));
    }
}

[Fact]
public void use_logger_convention()
{
    var container = new Container(_ =>
    {
        _.Policies.Add<LoggerConvention>();
    });

    var top = container.GetInstance<LoggedClass1>();
    top.Logger.ParentType.ShouldBe(typeof(LoggedClass1));
    top.Child.Logger.ParentType.ShouldBe(typeof(LoggedClass2));
    top.Child.Child.Logger.ParentType.ShouldBe(typeof(LoggedClass3));
}


The LoggerConvention way of accomplishing the logging integration is technically more code and possibly harder to understand, but it's significantly more efficient at runtime because the decision about which Logger to use is only done once upfront. The conventional approach should also be more evident in StructureMap diagnostics.

Example 2: NHibernate Integration

It's been several years since I used NHibernate, so I might not have the technical details exactly right here:)

If you're developing a system with NHibernate managed by StructureMap, you will frequently need to inject NHibernate's ISession service into your concrete classes. There's a little catch though, to create an ISession object you have to use NHibernate's ISessionFactory service:


public interface ISession { }

public interface ISessionFactory
{
    ISession Build();
}

Sidestepping the issue of how to build an ISessionFactory, here's a possible way to enable a StructureMap Container to build and resolve dependencies of ISession with a lambda builder:


public class SessionFactoryRegistry : Registry
{
    // Let's not worry about how ISessionFactory is built
    // in this example
    public SessionFactoryRegistry(ISessionFactory factory)
    {
        For<ISessionFactory>().Use(factory);

        // Build ISession with a lambda:
        For<ISession>().Use("Build ISession from ISessionFactory", c =>
        {
            // To resolve ISession, I first pull out
            // ISessionFactory from the IContext and use that
            // to build a new ISession. 
            return c.GetInstance<ISessionFactory>().Build();
        });
    }
}