Monthly Archives: March 2010

Customer-Specific Behaviors in a Multi-Tenant Web Application Using Windsor

The main application that I work on is a multi-tennant web application, and we’ve always struggled a bit with the best way to separate the different behaviors and features required by the different customers who use our application.  Right now, it’s accomplished by a mixture of database lookups, subclasses, and the dreaded switch statement.

Lately, I’ve been working on a proof of concept for a new architecture.  We’re introducing several new things, including the Windsor inversion of control container.  After working with it a little bit and starting to get my mind around the benefits of leaving the responsibility of object construction to the container, I started to think that there must be a way to use the container to separate customer-specific behavior into different implementations of the same interface.  That way customers’ rules would be nicely isolated and easy to find.  In order to do that I needed to find a way to inject a particular interface implementation based on a run-time value, in my case the organization to which the logged-on user belongs.

After quite a bit Googling, I finally came across this post by Ayende Rahien.  The IHandlerSelector was exactly what I was looking for.  It works like this:  each time an object is constructed, Windsor calls the HasOpinionAbout method on each of the handler selectors you’ve defined, where you can determine based on the interface that’s being requested whether you want to decide yourself which implementation to use.  If you decide that you do, Windsor will call the SelectHandler method of your handler selector, giving you a full list of all the implementations of the interface that’s being requested that are registered with the container.  Based on whatever logic you want, you just return one of those implementations.

It’s a bit more clear with a concrete example.  One of the core concepts in my application is the inspection of certain kinds of machinery.  However, each organization that uses the application has different rules and processes around inspections.  So, I’ll define an interface called IInspectionService, and have an implementation per customer. Let’s say we have two customers that use the app, Acme and ServiceCo (note: totally made-up business names).

Now that we’ve got those defined, we need to register the interface and implementations with the container, then define our IHandlerSelector. As with any IoC registration, you’ll want to do this just once, as your application is starting (the simplest way is to do it inside the Global.ascx.cs of your web app).

The implementation of IHandlerSelector needs a bit of explaining. Whenever the container is about to create an instance of anything, it will call the HasOpinionAbout method on all IHandlerSelectors that you’ve registered. The container’s basically asking, “Do you want to get directly involved in choosing which implementation to use?” In our case, we only want to get our hands in there if the container is trying to select some implementation of our IInspectionService, so we return “true” from HasOpinionAbout if that’s the case.

If HasOpinionAbout returns “true” for an IHandlerSelector, the container will then call that IHandlerSelector’s implementation of SelectHandler.  The key parameter to that method is the third one, the array of IHandlers.  All the implementations that could possibly satisfy the interface in question (IInspectionService in this case) that have been registered with the container will be inside that array, you just have to pick the one you want to use, using any arbitrary criteria you like.  Since we’re talking about this in the context of a multi-tenant system, I based the decision here on the group that the currently logged-on user belongs to.

So what does this all this IoC stuff get us?  Well, it particularly shines in an ASP.NET MVC application, where you can have the IoC container take control of creating your controllers, and thusly specify all of your controllers’ dependencies in their constructors.  When you do that in combination with an IHandlerSelector, you completely remove all the messy “if…then” code related to different customers from your controller action methods.

In the code above, when the container creates the InspectionController, it will use our IHandlerSelector to pick the appropriate implementation of IInspectionService to pass in to the controller’s constructor.  So, if a customer from Acme is signed in, _inspectionService will be an AcmeInspectionService, and if a customer from Service Co. is logged in, _inspectionService will be a ServiceCoInspectionService.

I think this is a great way to segregate customer-specific logic.  It’s all in one class per customer, and doesn’t clutter up the entirety of your application.  You could also have a base class with operations that needed to happen regardless  of the customer to reduce duplication if needed.

I hope this is useful to somebody!

My First “Speaking Tour”

I took a trip up to northwest Arkansas earlier this week to speak to several different .NET User Groups in the area about NHibernate.  On Monday evening, I spoke at my user group “alma mater”, the Fort Smith .NET User Group.  It was great to see a bunch of my old friends from Data-Tronics, including group president David Mohundro.

The next day, I was quite busy.  I met Robby Gregory and a few other Wal-Mart employees for lunch, and at 1:30 I spoke at the Wal-Mart internal .NET User Group.  I then moved on to the Tyson internal user group (known as “DevLoop”) at 4:00, and directly on from there to the Northwest Arkansas .NET User Group at 6:00.  I enjoyed hanging out with Jay, John, Devlin, Michael, and several others at Jose’s afterwards, but by that time I was pretty beat.

I’m really glad that I’m getting to start speaking more (big thanks to my new employer Falcon Applications for letting me keep the dates only a week after I started the job!).  I’ve submitted a session for the Dallas TechFest, plan to submit one or two for DevLink, and will continue to try to speak at other user groups later this year, as well.  Looks like I’m well on track to meeting at least one of my goals for 2010!

Thanks to all the user group leaders who invited me up; I hope to be able to visit again some time!