Auto-registration in ASP.NET MVC

Coming off a week at DevTeach. Others have commented on how great it is so I’ll just use my own observation as a lead-in for the real meat. DevTeach gives me a renewed interest in learning. After seeing presentations on topics I’ve kinda sorta grokked/solubled, it’s nice to gain further insight into them. So I come away with a laundry list of patterns, techniques, tools, and frameworks to look into.

And one of those techniques is auto-registration of components in an IoC container. Ayende has talked about this quite a bit with Binsor and I did get to see it in action. But recently, there have been enhancements to Windsor itself to allow for this in a relatively clean(er) way. Hammett has an example here as does Ken Egozi (who took it directly from the Castle dev list). Note that the syntax is slightly different ‘twixt the two (AllTypes<T>.Of vs. AllTypesOf<T>). I just got the latest from the trunk and it appears to be the former syntax at the moment.

A concrete example. Here is the former code in my ASP.NET MVC application (modified, as usual, from the CodeCampServer sample app):

foreach ( var type in Assembly.GetExecutingAssembly( ).GetTypes( ) )
{
    if ( typeof (IController).IsAssignableFrom( type ) )
    {
        _container.AddComponentWithLifestyle( type.Name.ToLower( ), type, LifestyleType.Transient );
    }
}

And here is the new version:

_container.Register(
    AllTypes.Of( )
        .FromAssembly( Assembly.GetExecutingAssembly( ) )
        .Configure( c => c.LifeStyle.Transient.Named( c.Implementation.Name.ToLower( ) ) )
    );

Note the call to Configure. In most cases, this won’t be necessary. But with MvcContrib, it seems to retrieve controllers from the container based on the controller name in lower case. So we need to register them with that same name.

Also note that this is based on a version of Windsor from the trunk (I think). Which is why I didn’t update the CodeCampServer project myself (though there are already comments in there to do exactly that).

Here’s another comparison. In this case, it’s a more "traditional" registration in that we loop through all the classes in an assembly and register it with the container based on its first interface. The before

var presentationTypes = Assembly.Load( "Trilogy.Gunton.Presentation.Services" )
    .GetTypes( )
    .Where(
        t => t.IsClass == true
        && t.IsAbstract == false 
        && t.GetInterfaces( ).Length > 0
    );
foreach ( var type in presentationTypes )
{
    _container.AddComponentLifeStyle( type.Name.ToLower( ), type.GetInterfaces( )[0], type, LifestyleType.Transient );
}

And after:

_container.Register(
    AllTypes.Pick( )
        .FromAssemblyNamed( "Trilogy.Gunton.Presentation.Services" )
        .WithService.FirstInterface( )
        );
Side note on the use of var in this code. How many of you looked at it and cared that Where( ) returns an IEnumerable of Type as opposed to an array of Type?

I do have classes in this assembly that are concrete but it appears the FirstInterface call will ignore them. Not so much when I do it more manually.

The last example is more interesting. In this case, I have a bunch of repositories, each of which derive from a RepositoryBase class, which implements IRepository. But the classes also implement their own interfaces.

For example, JobRepository would derive from RepositoryBase<Job> as well as implement IJobRepository. RepositoryBase<T> implements IRepository<T> while IJobRepository implements IRepository<Job>. A lot of the time, the specific interfaces (like IJobRepository, IOfficeRepository, etc.) will be empty but I’ve been burned before so I usually create them so I can add functionality later if I need it. Plus it makes things clearer when I’m passing them around.

For the purpose of container registration, this presents a slight problem because the repositories now implement two interfaces. For example, IJobRepository implements both IJobRepository and IRepository<Job>. But I want it registered against IJobRepository in the container.

Here’s the previous code I had to do this, which I will admit could use some tuning:

var repositoryTypes = Assembly.Load( "Trilogy.Gunton.DataAccess" )
    .GetTypes( )
    .Where( t => t.IsClass && t.GetInterfaces( ).Length > 0 );
foreach ( var type in repositoryTypes )
{
    var types = type.GetInterfaces( ).Where(
            t => t.IsGenericType == false 
            && t.Namespace.StartsWith( "Trilogy.Gunton" ) );
    if ( types.Count( ) > 0 )
    {
        _container.AddComponentLifeStyle( type.Name.ToLower( ), types.ElementAt( 0 ), type, LifestyleType.Transient );
    }
}

The corresponding code isn’t quite as terse:

_container.Register(
    AllTypes.Pick( )
        .FromAssemblyNamed( "Trilogy.Gunton.DataAccess" )
        .WithService.Select(
        delegate( Type type )
            {
                var interfaces = type.GetInterfaces( )
                    .Where(
                    t => t.IsGenericType == false && t.Namespace.StartsWith( "Trilogy.Gunton" )
                    );
                if ( interfaces.Count( ) > 0 )
                {
                    return interfaces.ElementAt( 0 );
                }
                return null;
            }
        )
    );

But we can extract the delegate into an extension method that we can use similar to the FirstInterface method:

public static class CastleExtensions
{
    public static TypesDescriptor FirstNonGenericTrilogyInterface( this ServiceDescriptor descriptor )
    {
        return descriptor.Select(
                delegate( Type type )
                {
                    var interfaces = type.GetInterfaces( )
                        .Where(
                        t => t.IsGenericType == false && t.Namespace.StartsWith( "Trilogy.Gunton" )
                        );
                    if ( interfaces.Count() > 0 )
                    {
                        return interfaces.ElementAt( 0 );
                    }
                    return null;
                }
            );
    }
}

And now our new code to register repositories is:

_container.Register(
    AllTypes.Pick( )
        .FromAssemblyNamed( "Trilogy.Gunton.DataAccess" )
        .WithService.FirstNonGenericTrilogyInterface( )
    );

The condition to check that the namespace is mine is because I have a UnitOfWork class in there that implements IDisposable as well as IUnitOfWork. So I want that one in the container only for the IUnitOfWork interface.

Putting it all together, here is the complete code to register my repositories, services, and controllers in the Windsor container:

_container.Register(
    AllTypes.Pick( )
        .FromAssemblyNamed( "Trilogy.Gunton.DataAccess" )
        .WithService.FirstNonGenericTrilogyInterface( )
);

_container.Register(
    AllTypes.Pick( )
        .FromAssemblyNamed( "Trilogy.Gunton.Presentation.Services" )
        .WithService.FirstInterface( )
);

_container.Register(
    AllTypes.Of( )
        .FromAssembly( Assembly.GetExecutingAssembly( ) )
        .Configure( c => c.LifeStyle.Transient.Named( c.Implementation.Name.ToLower( ) ) )
);

Phew, so much for being billable today…

Kyle the Registered

This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Taliesin

    I had some hassles with registering my service layer until I added the “unless”. Problem was caused by multiple namespaces in same assembly.

    public void RegisterServiceLayer(string serviceLayerAssemblyName, string namespaceStartWith)
    {
    container.Register(
    AllTypes.Pick()
    .FromAssemblyNamed(serviceLayerAssemblyName)
    .Unless(x => !x.Namespace.StartsWith(namespaceStartWith))
    .WithService.FirstInterface()
    );
    }

  • Kyle Baley

    @Bill

    Sounds familiar but I haven’t run into it while working on this post. None of the types I’m registering have generic interfaces. Or rather, I’m not registering them against generic interfaces. I’ve created specific interfaces (e.g. IJobRepository) that implement the generic ones (IRepository). I like having the explicit interface even if it doesn’t add anything. Makes the code easier to read. Plus I read that that’s what all the cool kids were doing.

  • http://16randombytes.blogspot.com Bill

    I ran into a problem when using the fluent impl where it didn’t like generic interfaces. I got everything to register fine but when it went to instantiate an object that was generic, it threw an error about making a type generic.

    Did you see the same bug?