Kyle Baley - The Coding Hillbilly

Sponsors

The Lounge

Advertisement

Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
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


Posted Sun, May 18 2008 12:42 PM by Kyle Baley

[Advertisement]

Comments

Dew Drop - May 18, 2008 | Alvin Ashcraft's Morning Dew wrote Dew Drop - May 18, 2008 | Alvin Ashcraft's Morning Dew
on Sun, May 18 2008 9:15 PM

Pingback from  Dew Drop - May 18, 2008 | Alvin Ashcraft's Morning Dew

Reflective Perspective - Chris Alcock » The Morning Brew #96 wrote Reflective Perspective - Chris Alcock &raquo; The Morning Brew #96
on Mon, May 19 2008 3:01 AM

Pingback from  Reflective Perspective - Chris Alcock  &raquo; The Morning Brew #96

Bill wrote re: Auto-registration in ASP.NET MVC
on Fri, May 23 2008 9:50 AM

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?

Kyle Baley wrote re: Auto-registration in ASP.NET MVC
on Fri, May 23 2008 11:28 AM

@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<Job>). 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.

Kyle Baley - The Coding Hillbilly wrote "Server cannot modify cookies" error in ASP.NET MVC
on Sat, May 24 2008 12:41 PM

This post falls into the &quot;Google-able errors&quot; category and won&#39;t be of much use to anyone

Community Blogs wrote "Server cannot modify cookies" error in ASP.NET MVC
on Sat, May 24 2008 1:05 PM

This post falls into the &quot;Google-able errors&quot; category and won&#39;t be of much use to anyone

"Server cannot modify cookies" error in ASP.NET MVC | Developer Home wrote &quot;Server cannot modify cookies&quot; error in ASP.NET MVC | Developer Home
on Thu, May 29 2008 6:01 AM

Pingback from  &quot;Server cannot modify cookies&quot; error in ASP.NET MVC | Developer Home

ASP.NET MVC Archived Buzz, Page 1 wrote ASP.NET MVC Archived Buzz, Page 1
on Thu, Sep 4 2008 12:19 AM

Pingback from  ASP.NET MVC Archived Buzz, Page 1

Taliesin wrote re: Auto-registration in ASP.NET MVC
on Fri, Jan 16 2009 8:17 AM

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

               );

       }

Add a Comment

(required)  
(optional)
(required)  
Remember Me?