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<T> interface implemented by an abstract base class (RepositoryBase<T>) which, in turn, is implemented by many classes – one for each entity.  Let’s say your implementations looked something like this:

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>();

 

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<T>), and return what the T parameter is (i.e. <User>). With this, we can register an IRepository<User> in StructureMap for that class (UserRepository, for example). It might look something like this:

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

This will take a given type (i.e. UserRepository) and check it’s base type (i.e. RepositoryBase<User>) to see if it’s generic and if it’s the generic type we’re looking for. If so, it’ll return the “User” portion of RepositorBase<User>, for example.

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));
    }
}

We try to get the entityType (i.e. User). If present, then we create a new specific type from the generic IRepository<T> and register it. In our UserRepository case, it’ll register IRepository<User> with the default concrete type of UserRepository. Our whole class now looks like this:

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

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

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>>();

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));
        }
    }
}

See if you can guess what that’s for… ;)

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Chad Myers

Chad Myers is the Director of Development for Dovetail Software, in Austin, TX, where he leads a premiere software team building complex enterprise software products. Chad is a .NET software developer specializing in enterprise software designs and architectures. He has over 12 years of software development experience and a proven track record of Agile, test-driven project leadership using both Microsoft and open source tools. He is a community leader who speaks at the Austin .NET User's Group, the ADNUG Code Camp, and participates in various development communities and open source projects.
This entry was posted in StructureMap. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://schambers.lostechies.com Sean Chambers

    Cool stuff Chad.

    I’m not familiar with StructureMap at all, but with your second example what would happen if I had two controllers named the same, ie. UserController in two different namespaces? Sometimes I will have a UserController in a namespace like Something.Admin.UserController and another in Something.UserController. Would that cause a problem when registering the “name” with structuremap? I’m not sure how StructureMap handles giving the instances an id in the registry.

    Just curious. Thanks!

  • http://chadmyers.lostechies.com Chad Myers

    @Sean:

    Yeah, it’d probably blow up there. That’s the point of a convention, though, it puts rails on your possibilities, but makes dev go real fast.

    If your convention is different, you could tweak it to your needs easily!

  • http://colinjack.blogspot.com Colin Jack

    Good stuff, took me a few reads of the code to work out what the heck you were doing but this blog entry cleared it right up.

    Convention stuff in StructureMap is great, really cool.