StuctureMap: Advanced-level Usage Scenarios (Part 1: Type/Convention Scanners)
I’ll start with my strong hand on the advanced StructureMap-foo and go straight to the type scanners (a.k.a. convention scanners). Thankfully, there was a thread on the StructureMap Users mailing list with just the kind of problem these things solve. I’ll use that problem as the strawman for this post.
The Problem
Let’s say you had an IRepository
public interface IRepository<T> { // intersting stuff here } public abstract class RepositoryBase<T> : IRepository<T> { // interesting abstract base stuff here } public class UserRepository : RepositoryBase<User> { // interesting abstract base stuff here } // ... a bunch more of these CustomerRepository, SaleRepository, etc
And you you may add more in the future and you’d rather not have to register them all individually in StructureMap like this:
// You don't have to do this!
StructureMapConfiguration
.ForRequestedType<IRepositor<User>>.TheDefaultIsConcreteType<UserRepository>()
.ForRequestedType<IRepositor<Customer>>.TheDefaultIsConcreteType<CustomerRepository>()
.ForRequestedType<IRepositor<Sale>>.TheDefaultIsConcreteType<SaleRepository>();
</p> </p> </p> </p> </p> </p>
What would be nice is if StructureMap could automatically figure my convention out. Well, unfortunately it won’t do that. BUT you can give it enough of a hint so that it CAN do it using your own ITypeScanner implementation. ITypeScanner has one method on it: Process(Type, Registry). StructureMap will call Process() for each Type it finds when scanning (using ScanAssemblies). Your scanner can, if it wants, add things to the container (through the ‘Registry’ parameter). This allows you to do all sorts of cool things like automatically find any types Foo which implement an interface named IFoo (or Bar/IBar, Something/ISomething, etc) and register them for you. In fact, this last example is done for you using StructureMap.Graph.DefaultConventionScanner. Here, let me show you:
ScanAssemblies().IncludeTheCallingAssembly() .IncludeTheCallingAssembly() .With<DefaultConventionScanner>()
Implementing our Repository Convention ITypeScanner
First, create a class that implements ITypeScanner. Let’s call it RepositoryConvention.
The next thing we’ll need is a method that will, given a type, see if it’s the type of generic type we’re looking for (i.e. RepositoryBase
private static Type GetGenericParamFor(Type typeToInspect, Type genericType) { var baseType = typeToInspect.BaseType; if (baseType != null && baseType.IsGenericType && baseType.GetGenericTypeDefinition().Equals(genericType)) { return baseType.GetGenericArguments()[0]; } return null; }
</p> </p> </p> </p> </p> </p> </p> </p> </p>
This will take a given type (i.e. UserRepository) and check it’s base type (i.e. RepositoryBase
Now, our process method looks something like:
public void Process(Type type, Registry registry) { Type entityType = GetGenericParamFor(type, typeof(RepositoryBase<>)); if (entityType != null) { var genType = typeof(IRepository<>).MakeGenericType(entityType); registry.ForRequestedType(genType).AddInstance(new ConfiguredInstance(type)); } }</p>
We try to get the entityType (i.e. User). If present, then we create a new specific type from the generic IRepository
public class RepositoryConvention : ITypeScanner { public void Process(Type type, Registry registry) { Type entityType = GetGenericParamFor(type, typeof(RepositoryBase<>)); if (entityType != null) { var genType = typeof(IRepository<>).MakeGenericType(entityType); registry.ForRequestedType(genType).AddInstance(new ConfiguredInstance(type)); } } private static Type GetGenericParamFor(Type typeToInspect, Type genericType) { var baseType = typeToInspect.BaseType; if (baseType != null && baseType.IsGenericType && baseType.GetGenericTypeDefinition().Equals(genericType)) { return baseType.GetGenericArguments()[0]; } return null; } }</p>
Now, we need to let StructureMap know about it…
Configuring StructureMap with the new Scanner
Now, somewhere in your bootstrapping/startup routines for your app, add the With() call to your ScanAssemblies configuration. It should end up looking like this:
StructureMapConfiguration .ScanAssemblies() .IncludeTheCallingAssembly() // maybe other assemblies, too? .With(new RepositoryConvention()); // add our convention for wiring up the repos</p>
As StructureMap mines through all the types in your assemblies, it’ll pass them to your Process() method where you can evaluate whether they meet your Repository convention and register them accordingly.
Using the Repository
Finally, to grab one of your newly configured repositories, you can simple make a normal ObjectFactory GetInstance call:
var userRepo = ObjectFactory.GetInstance<IRepository<User>>();</p>
</p> </p> </p> </p> </p> </p> </p> </p> </p> </p> </p> </p>
Also, it’ll work with for normal constructor injection scenarios. For example:
public class SomeService { private readonly IRepository<User> _userRepo; public SomeService(IRepository<User> userRepo) { _userRepo = userRepo; } }
What else can you do?
I hope as you come up with clever uses for this convention technique you let us know about them! As a teaser, here’s something Jeremy and I are doing:
public class ControllerConvention : TypeRules, ITypeScanner { public void Process(Type type, Registry registry) { if (CanBeCast(typeof (IController), type)) { string name = type.Name.Replace("Controller", "").ToLower(); registry.AddInstanceOf(typeof (IController), new ConfiguredInstance(type).WithName(name)); } } }
</p> </p> </p> </p>
See if you can guess what that’s for… 😉