In the course of experimenting with the new MVC release for .Net, I stumbled across a post on the ASP forums where someone was explaining his approach to architecture using MVC. In reading further, I found that he was using dependency injection; a reusable design pattern presumably brought to .Net from Java world.
If you aren't familiar with DI [dependency injection], you really should read up on it
here and
here. Basically, it allows for loosely-coupled components through interface-driven coding. The general idea is that your web pages have an instance of the interface as part of the page's code-behind class. The interface is then instantiated as part of the page construction and its methods can be called without knowing the implementation behind them.
For example, if you were architecting a website that had a contact us page, you may have something called a ContactService, which allows you to send the contact information to an e-mail address. In your code, you would see something like this:
protected void uxContactSubmitButton_Click (object sender, EventArgs e)
{
ContactMessage message = new ContactMessage
{
Name = uxName.Text,
EmailAddress = uxEmailAddress.Text,
Subject = uxSubject.Text,
Message = uxMessage.Text
};
ContactService.Persist(message);
}
This service will formulate an e-mail message and send it to whichever e-mail address you want. As you can see, our page is tightly bound to the implementation of ContactService.Persist, which is bad for extensibility, maintenance, and testing (static methods are difficult to test). If any changes need to be made, you have to refactor the entire Persist method.
Now suppose you publish this website and find that you are receiving many more contact messages through your e-mail than you can handle. You decide it makes sense to store them in the database instead.
This is where a DI architecture would make this problem a cinch to solve.
Here's what the same page using DI would look like:
private IContactService _contactService;
public ClassNameConstructor()
{
_contactService = ResolveDependency<IContactService>();
}
protected void uxContactSubmitButton_Click (object sender, EventArgs e)
{
ContactMessage message = new ContactMessage
{
Name = uxName.Text,
EmailAddress = uxEmailAddress.Text,
Subject = uxSubject.Text,
Message = uxMessage.Text
};
_contactService.Persist(message);
}
As you can see from the code, an instance of the interface in this class is resolved during the construction of the page. This resolving process takes the registered implementation of the interface from the .config file and sets _contactService equal to that implementation. Now when you call Persist(message), the code doesn't know or care about the implementation.
This resolving is typically done through a DI framework. There are many DI frameworks to choose from... I'm particularly found of the Windsor Container from
Castle Project
.
To summarize, the framework loads all configured instances of all the interfaces in your application (through the app.config or web.config) into a container. In the case of web sites, this loading will be done on Application_Start so that it is only done once. The resolved interfaces are held in a static variable as part of the global.asax [more on that in a later post].
Going back to the original problem, in order to change the functionality of the contact form, all we need to do is to create a new class that implements IContactService. Let's assume that IContactService has one method:
public interface IContactService
{
void Persist(ContactMessage message);
}
Here is the implementation of our new SqlContactService class:
public class SqlContactService : IContactService
{
public void Persist(ContactMessage message)
{
//code to save message to database.
}
}
Finally, we swap out the
configured interface implementations in the .config file and everything will work automatically. Additionally, you now have two implementations of IContactService that you could swap out the in the future if you would like. Windsor Container also allows you
configure a list or array of implementations for each type, so if you wanted, you could loop through all configured instances of IContactService and call Persist() on each. This would save the message to the database and then send you an e-mail.
Cool stuff, huh?
In the future, I will write in more detail and cover some more advanced topics like setting up the config, method interception, facilities, and the ability to cache a method's results with a simple attribute.