06 October, 2009

Simple Events In ASP.NET MVC

We've been busy improving our customer facing applications. Our new marketing VP noticed some significant improvements that could be made in the workflow of our customer acquisition funnel.

Making the changes that our marketing VP recommended required some major code changes so we took the opportunity to move our web apps into ASP.NET MVC.

One of the early decisions we made was to put as little business and application logic on our controllers as possible. Finding a place for business logic is straightforward most of the time; it usually belongs in your (domain) Models.

Placing application logic in the right place gets a little more complicated. We don't really have services for stuff like sending out emails, etc. so we have to write classes to do all that work.

Invoking email code, authentication code, etc. right in the controller is an ugly solution. To solve our problem, I came up with a simple eventing mechanism that allows us to move all application code out of our controllers.

Let's first start with what the code in one of the controllers would look like:


[AcceptVerbs(HttpVerbs.Post), ValidateAntiForgeryToken]
public ActionResult Index(IndexModel model)
{
//code to interact with your model goes here.

//And now the event code:
AcmeEvents.Raise(new IndexRanEvent { IndexModel = model });

}

The great thing about the code above is that all sort of things could now happen once your index runs; there may be 1 or N subscribers to the event above. Furthermore, you could (at run time) change the code that runs when the event triggers... but I'm getting ahead of my self; we'll look at how that could happen in just a second.

Let's now look at the "raise event code". To do this, I decided to just use Unity's IoC container to resolve the code should trigger when the event get's raised:


public static void Raise<T>(T args) where T : IAcmeEvent
{
foreach(var handler in unityContainer.ResolveAll<Handles<T>>())
{
handler.Handle(args);
}
}

Some assumptions the code above makes: (1) IAcmeEvent has a Hanlde method; (2) by the time the Raise method runs, the IoC container must have been initialized to map the different "event types" to the specific implementations that will execute.

How you initialize the container is really not that important: you could do it in code, through a config, or any other method of your choice. What is important, however, is that when you initialize your container, you do it in such a way that code from other assemblies can execute. If you do this, you could literally just drop assemblies in your application and add to or change the behavior of your app.

So there you have it; a simple eventing mechanism to keep your MVC controllers unpolluted from application logic. I'm really happy about coming up with this solution; I hope you find it useful too.

1 comments:

PapaBear said...

This is pretty slick. For large enterprise eventing, I still think BizTalk handles it the best (message-driven eventing). For medium-size stuff, I would say WF (state-driven). But for most applications (90% of the stuff out there), this kind of simple event code is ideal. I also really like the idea of dynamically loading drop-in assemblies. I've played with that sort of code myself (Assembly.LoadFromFile...) so I know that it is possible (my code used to be part of the Pegasus Library but it somehow got removed (http://pegasus.codeplex.com/)). In any case, I wanted to suggest one thing: you said that in order for the eventing to work the IoC container must already be initialized. That's great, but how do you handle drop-in assemblies? Simple: extend your model -- use a dedicated IoC container that keeps track of all the eventing IoC containers and the assemblies that are loaded/subscribed to them and have this dedicated IoC container have a single event that listens for when assemblies are registered or de-registered with it. Then, when that event is raised (i.e. an assembly has changed or new one has been added), this dedicated IoC container/event can restart/re-initialize all the other IoC containers that handle your app events. Just an idea. Keep writing, really good stuff.

Post a Comment