Fork me on GitHub

Using Attributes for Configuration Edit on GitHub


In the early days of StructureMap we had several attibutes for basic configuration that we'd just as soon forget ever existed. Further more, the StructureMap strongly believes that the usage of StructureMap should have as little impact on application code as possible -- and forcing users to spray .Net attributes all over their own code is in clear violation of this philosophy. In other words, we don't want to be MEF.

That being said, there are plenty of times when simple attribute usage is effective for one-off deviations from your normal registration conventions or cause less harm than having to constantly change a centralized Registry or just seem more clear and understandable to users than a convention. For those usages, StructureMap 4.0 has introduced a new base class that can be extended and used to explicitly customize your StructureMap configuration:


/// <summary>
/// Base class for custom configuration attributes
/// </summary>
public abstract class StructureMapAttribute : Attribute
{
    /// <summary>
    /// Override this method to apply a configuration change to an entire
    /// PluginFamily (every Instance of a certain PluginType)
    /// </summary>
    /// <param name="family"></param>
    public virtual void Alter(PluginFamily family)
    {
    }

    /// <summary>
    /// Make configuration alterations to a single IConfiguredInstance object
    /// </summary>
    /// <param name="instance"></param>
    public virtual void Alter(IConfiguredInstance instance)
    {
    }

    /// <summary>
    /// Apply configuration customization to a single setter property
    /// </summary>
    /// <param name="instance"></param>
    /// <param name="property"></param>
    public virtual void Alter(IConfiguredInstance instance, PropertyInfo property)
    {
    }

    /// <summary>
    /// Apply configuration customization for a single constructor parameter
    /// </summary>
    /// <param name="instance"></param>
    /// <param name="parameter"></param>
    public virtual void Alter(IConfiguredInstance instance, ParameterInfo parameter)
    {
    }
}

There's a couple thing to note, here about this new attibute:

  • StructureMap internals are looking for any attribute of the base class. Attributes that affect types are read and applied early, while attributes decorating properties or constructor parameters are only read and applied during the creation of Build Plans.
  • Unlike many other frameworks, the attributes in StructureMap are not executed at build time. Instead, StructureMap uses attributes one time to determine the build plan.

Attribute Targeting Plugin Type or Concrete Type

Take the new [Singleton] attribute shown below:


[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
public class SingletonAttribute : StructureMapAttribute
{
    // This method will affect the configuration for the
    // entire plugin type
    public override void Alter(PluginFamily family)
    {
        family.SetLifecycleTo<SingletonLifecycle>();
    }

    // This method will affect single registrations
    public override void Alter(IConfiguredInstance instance)
    {
        instance.SetLifecycleTo<SingletonLifecycle>();
    }
}

This new attribute can be used on either the plugin type (typically an interface) or on a concrete type to make an individual type registration be a singleton. You can see the usage on some types below:


[Singleton] // ALL Instance's of ITeamCache will be singletons by default
public interface ITeamCache { }

public class TeamCache : ITeamCache { }

public class OtherTeamCache : ITeamCache { }

public interface ITeam { }

public class Chargers : ITeam { }

[Singleton] // This specific type will be a singleton
public class Chiefs : ITeam { }


Attribute Targeting Constructor Parameters or Setter Properties

As an example, let's say that you want a new attribute type that can decorate either properties or constructor parameters to say "use the value from the old .Net AppSettings collection as the value for this property/parameter." To build that new custom attribute, you would create a new attribute that subclasses StructureMapAttribute and override the two methods shown below:


public class AppSettingAttribute : StructureMapAttribute
{
    private readonly string _key;

    public AppSettingAttribute(string key)
    {
        _key = key;
    }

    public override void Alter(IConfiguredInstance instance, PropertyInfo property)
    {
        var value = System.Configuration.ConfigurationManager.AppSettings[_key];

        instance.Dependencies.AddForProperty(property, value);
    }

    public override void Alter(IConfiguredInstance instance, ParameterInfo parameter)
    {
        var value = System.Configuration.ConfigurationManager.AppSettings[_key];

        instance.Dependencies.AddForConstructorParameter(parameter, value);
    }
}


To test out the new attribute above, say we have a concrete type like this one that we decorate with the new [AppSetting] attribute:


public class AppSettingTarget
{
    public string Name { get; set; }

    [AppSetting("homestate")]
    public string HomeState { get; set; }

    public AppSettingTarget([AppSetting("name")]string name)
    {
        Name = name;
    }
}


The following unit test demonstrates our new custom [AppSetting] attribute in action:


[Fact]
public void using_parameter_and_property_attibutes()
{
    System.Configuration.ConfigurationManager.AppSettings["name"] = "Jeremy";
    System.Configuration.ConfigurationManager.AppSettings["homestate"] = "Missouri";

    System.Configuration.ConfigurationManager.AppSettings["name"].ShouldBe("Jeremy");

    var container = new Container();

    var target = container.GetInstance<AppSettingTarget>();

    target.Name.ShouldBe("Jeremy");
    target.HomeState.ShouldBe("Missouri");

    Debug.WriteLine(container.Model.For<AppSettingTarget>().Default.DescribeBuildPlan());
}


The build plan for AppSettingTarget is determined by the active StructureMap container to be this:

PluginType: StructureMap.Testing.Acceptance.attribute_usage+AppSettingTarget
Lifecycle: Transient
new AppSettingTarget(String name)
  ┗ String name = Value: Jeremy
Set String HomeState = Value: Missouri

Note that the values retrieved from AppSettings are essentially hard coded into the underlying builder function that StructureMap compiled for AppSettingTarget. You could instead add a "lambda builder" dependency so that StructureMap had to use the live value for AppSettings as it constructs objects.

Built In Attributes

StructureMap supplies a handful of built in attributes for customizing configuration:

  • [ValidationMethod] - Allows you to expose Environment Tests in your StructureMap registrations
  • [SetterProperty] - See Setter Injection
  • [DefaultConstructor] - Declare which constructor function should be used by StructureMap. See Constructor Selection for more information
  • [AlwaysUnique] and [Singleton] - These attributes, new for StructureMap 4.0, just add another mechanism for lifecycle configuration