In StructureMap 2.5 I added the ability to make “auto registration” policies that would enable StructureMap to just scan an assembly and figure out how to register types on its own. This is yet one more example of the new trend in .Net circles for adopting “Convention over Configuration” to make our work go faster. There’ll be a backlash against that sometime soon, but for now, I think it’s a significant trend to watch play out in the .Net OSS space.
This blog post is excerpted from the online documentation as http://structuremap.sourceforge.net/ScanningAssemblies.htm. The one and only Laribee asked me to write some samples of auto registration. My respect and affection for Dave is so strong that it only took me 6 weeks to get around to it.
StructureMap 2.5+ adds the ability to do convention based type registration.
Convention
over Configuration is becoming increasingly more common as a way to reduce
the overhead of using configuration intensive tools. Type registration
conventions can be mixed and matched with explicit configuration.
The Default Convention
When using StructureMap over the last couple years I saw a lot of type
registrations like:
Start<ICommandExecutor>().TheDefaultIsConcreteType<CommandExecutor>();
There was a reoccuring pattern. “Foo” was the default implementation of
“IFoo.” The “I” moniker is the last vestige of Hungarian Notation left in
my coding style, but it refuses to go away, so let’s just take advantage of that
naming convention with a new DefaultConvention.
Let’s say that we have a class and interface like:
public interface
IConvention
{
}
public class
Convention :
IConvention
{
}
To make Convention be the default type for IConvention, I can simply scan the
assembly containing these types with the new default convention:
var container = new
Container(registry =>
{
registry.Scan(x =>
{
x.TheCallingAssembly();
x.With<DefaultConventionScanner>();
});
});
container.GetInstance<IConvention>().ShouldBeOfType<Convention>();
Registering Types by Name
One of the simplest, but yet most useful, ways to use convention based auto
registration is just to add all concrete types that can be cast to a certain
PluginType and use a convention to determine the Instance name. The first
place I used this functionality was at the beginning of our MVC project.
Let’s say that we name our Controller classes like this:
public interface
IController{}
public class
AddressController :
IController{}
public class
SiteController :
IController{}
As a convention, we’d like to register AddressController as the “Address” of
IController, and “Site” or IController like this:
container =
new Container(x
=>
{
x.ForRequestedType<IController>().AddInstances(o
=>
{
o.OfConcreteType<AddressController>().WithName(“Address”);
o.OfConcreteType<SiteController>().WithName(“Site”);
})
});
Over 50+ Controller classes, that coding is going to get extremely tedious.
Let’s use a convention instead:
container =
new Container(x
=>
{
x.Scan(o =>
{
o.TheCallingAssembly();
o.AddAllTypesOf<IController>().NameBy(type
=> type.Name.Replace(“Controller”,
“”));
});
});
foreach (var instance
in container.Model.InstancesOf<IController>())
{
Debug.WriteLine(instance.Name +
” is “ + instance.ConcreteType.Name);
}
When this code is executed, we get this output:
Address is AddressController Site is SiteController
Here’s the unit test code for this functionality:
[Test]
public
void can_find_objects_later_by_name()
{
container.GetInstance<IController>(“Address”)
.ShouldBeOfType<AddressController>();
container.GetInstance<IController>(“Site”)
.ShouldBeOfType<SiteController>();
}
Creating and Using Your Own Convention
To write your own auto registration convention, write a concrete class that
implements the ITypeScanner interface:
public interface
ITypeScanner
{
void Process(Type type,
PluginGraph graph);
}
Inside a custom ITypeScanner, you add types to a Container by modifying the
PluginGraph object that is passed into the TypeScanner. While having
access to the PluginGraph allows you to make any possible type of configuration
change (PluginGraph is the
Semantic Model
behind both the Registry DSL and the Xml configuration), in practice you
probably only care about these three methods:
/// <summary>
/// Adds the concreteType as an Instance of the
pluginType
/// </summary>
/// <param
name=”pluginType”></param>
/// <param
name=”concreteType”></param>
void AddType(Type pluginType,
Type concreteType);
/// <summary>
/// Adds the concreteType as an Instance of the
pluginType with a name
/// </summary>
/// <param
name=”pluginType”></param>
/// <param
name=”concreteType”></param>
/// <param
name=”name”></param>
void AddType(Type pluginType,
Type concreteType,
string name);
/// <summary>
/// Add the pluggedType as an instance to any configured
pluginType where pluggedType
/// could be assigned to the pluginType
/// </summary>
/// <param
name=”pluggedType”></param>
void AddType(Type pluggedType);
Here’s a sample type convention from my current project:
///
<summary>/span>
/// This
TypeScanner looks for any concrete class that implements
/// an
IFlattenerFor<T> interface, and registers that type
/// against the
closed IFlattenerFor<T> interface,
/// i.e.
IFlattenerFor<Address> or IFlattenerFor<Site>
///
</summary>
public class
DtoFlattenerConventionScanner :
ITypeScanner
{
public
void Process(Type
type, PluginGraph graph)
{
Type interfaceType = type.FindInterfaceThatCloses(typeof (IFlattenerFor<>));
if
(interfaceType != null)
{
graph.AddType(interfaceType, type);
}
}
& }
You apply the custom ITypeScanner by simply calling With() inside of a Scan()
block:
Scan(x =>
{
// scan with a custom ITypeScanner
x.With<DomainEntityAliaser>();
x.With<QueueItemMappingScanner>();
});