If you are building properly structured software where you have correctly implemented separation of concerns (SOC) then wiring up dependencies for classes starts to become quite an exercise. Here's an example from the a great series of articles on the topic of IoC and DI that I recommend you read to learn more about the topic which shows how verbose and complex things can become:
IFileDownloader downloader = new HttpFileDownloader();
ITitleScraper scraper = new StringParsingTitleScraper();
HtmlTitleRetriever retriever = new HtmlTitleRetriever(downloader, scraper);
Commonly when you use simple dependency injection in this manner you find that some services are reliant upon many other components and their construction becomes very messy. You can imagine what this starts to look like when you have lots of type registration and dependency injection to do right across your application!
In this article I am going to get you up and running using Autofac as the IoC container that will handle all of the lifetime management of your dependencies and do the dependency injection for you. To get the ball rolling, go ahead and create a new solution called SimpleAutofac:
I like to structure my source code so that the main branch of the application lives within a folder named trunk and that my external dependencies are referenced from a folder which lives above that. Create a folder for your Autofac dependencies, grab the latest build of Autofac and add its binaries to the folder you just created.
You should get the following list of assemblies added to your Autofac dependencies folder
Next add a reference from your SimpleAutofac project in Visual Studio to the Autofac.dll, Autofac.Configuration.dll and the Autofac.Integration.Web.dll assemblies. Having done that, we can wire up Autofac into our web application and start registering types. The first thing is to take advantage of the Autofac.Integration.Web.dll which contains the AutofacControllerFactory class that can be used at the ControllerBuilder’s current ControllerFactory.
private static IContainerProvider _containerProvider;
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterModule(new AutofacControllerModule(Assembly.GetExecutingAssembly()));
_containerProvider = new ContainerProvider(containerBuilder.Build());
ControllerBuilder.Current.SetControllerFactory(new AutofacControllerFactory(_containerProvider));
}
We need to make a minor alteration to the web.config file to include an Http Module that Autofac will use to dispose
<httpModules>
<add name="ContainerDisposal" type="Autofac.Integration.Web.ContainerDisposalModule, Autofac.Integration.Web"/>
</httpModules>
The ContainerDisposal module requires us to implement the Autofac IContainerProviderAccessor interface on our Application class, which, in-turn, mandates that we expose the ContainerProvider as a property on the class. Go ahead and add the following lines of code to your Application class:
public IContainerProvider ContainerProvider
{
get { return _containerProvider; }
}protected void Application_EndRequest(object sender, EventArgs e)
{
ContainerProvider.EndRequestLifetime();
}
You can read more about the Autofac MVC integration on the project's Wiki page. That's all there is to it, press F5 and your application should now run.
Note that the Controllers are now being served by our Autofac IoC container and so we can now demonstrate the advantages that we get from this by going and adding some dependencies to our Controller classes and see that they get injected at runtime for us.
Create a new interface called IMessageProvider in the Models folder of the web project. We will use this as a dependency that we’ll then pass to controller classes:
namespace SimpleAutofac.Models
{
public interface IMessageProvider
{
string EchoMessage(string message);
}
}
And now create a concrete instance of that interface that we will use in our application. For the sake of giving meaning to the demo, let's name our class SqlMessageProvider and imagine that this class is responsible for retrieving messages from a SQL Server database. Our class will look like this:
namespace SimpleAutofac.Models
{
public class SqlMessageProvider : IMessageProvider
{
public string EchoMessage(string message)
{
return string.Format("{0} returned from message provider.", message);
}
}
}
Next we will wire up our implementation with our container so that it knows what class to return within our web application whenever an IMessageProvider is required. Go back to the Application class in Global.asax.cs and add the following registration instruction to our container builder:
containerBuilder.RegisterType<SqlMessageProvider>().As<IMessageProvider>();
Finally, go to the HomeController class and create a constructor which takes an IMessageProvider instance and then change the code in the Index action handler so that it gets its message from the IMessageProvider service as opposed to being a raw string:
namespace SimpleAutofac.Controllers
{
public class HomeController : Controller
{
private readonly IMessageProvider messageProvider;
public HomeController(IMessageProvider messageProvider)
{
this.messageProvider = messageProvider;
}
public ActionResult Index()
{
ViewData["Message"] = this.messageProvider.EchoMessage("Welcome to ASP.NET MVC!");
return View();
}
public ActionResult About()
{
return View();
}
}
}
Now press F5 to run the application and you should see that our Autofac container did indeed handle the type registration for us and it successfully injected the correct IMessageProvider instance into our HomeController class.
The last thing that I want to show is how to pass a connection string to our SqlMessageProvider instance whenever it is instantiated.
Go back to our SqlMessageProvider class and add a constructor that takes a connection string as an argument. Change the EchoMessage so that it shows us which connection our message came from:
private string connectionString = "";
public SqlMessageProvider(string connectionString)
{
this.connectionString = connectionString;
}
public string EchoMessage(string message)
{
return string.Format("{0} returned from {1} provider.",
message,
this.connectionString);
}
Now go back to the Application class and change the IMessageProvider registration so that it takes a specific instance that we've already pre-configured with our connection string information
var connectionString = "DarrensSqlServer";
var containerBuilder = new ContainerBuilder();
containerBuilder.Register(c => new SqlMessageProvider(connectionString)).As<IMessageProvider>();
containerBuilder.RegisterModule(new AutofacControllerModule(Assembly.GetExecutingAssembly()));
_containerProvider = new ContainerProvider(containerBuilder.Build());
ControllerBuilder.Current.SetControllerFactory(new AutofacControllerFactory(_containerProvider));
Cool Darren!
ReplyDeleteAutofac is indeed the bomb, but I don't think you need both ContainerProvider.EndRequestLifetime(); in EndRequest AND the http module, because they both do the same thing. I could be wrong though
Thanks Vijay, I'll have to test that. I was only going by the code that was provided on the Autofac Wiki page - http://code.google.com/p/autofac/wiki/MvcIntegration
ReplyDeleteOoops! That's indeed an error in the Autofac docs - nice spotting Vijay! Fixed now.
ReplyDeleteDarren - nice job on the article :)
Thanks guys... article code snippet updated! :-)
ReplyDeleteCool Darren
ReplyDeleteDo you have sample in VB.net. I tried this on VB.net but no luck. I've got an error on this line
containerBuilder.Register(Function(c) New SimpleAutofacVB.SqlMessageProvider(connectionString)).As(Of SimpleAutofacVB.IMessageProvider)()
This is the error message
The component 'VB$AnonymousDelegate_0`2[System.Object,SimpleAutofacVB.SqlMessageProvider]' does not support the service 'SimpleAutofacVB.IMessageProvider'.
Do you have any idea on this issue.
Thanks
Akaka
Hi Akaka, I might struggle with this as I'm not too familiar with VB, but judging by the error message I would say that you should double-check to ensure that your SqlMessageProvider class does implement the IMessageProvider interface? Just as I have done in the code sample:
ReplyDeletepublic class SqlMessageProvider : IMessageProvider
Akka, it looks like you're using Autofac 1.4 and the differences in method resolution between C# and VB are biting you. The compiler thinks your Register call is Register(object), but in C# as per Darren's example, this will compile as Register<T>(Func<IContext, T> creator).
ReplyDeleteThe good news: this is fixed in Autofac 2 - these methods now have different names (Register for the delegate version, and RegisterInstance for the other one.) Grab the beta from http://autofac.org and you'll be right as rain :)
Hi Darren,
ReplyDeleteI have implemented IMessageProvider on SqlMessageProvider.
Nick,
Yes, I'm using Autofac 1.4 and thanks for your advise to use Autofac 2, but I solve this issue by coding this in c# and then using reflector to see code in VB, now I can solved this issue by this line of code and still using Autofac 1.4
containerBuilder.Register(Of SqlMessageProvider)(Function(c) New SqlMessageProvider(connectionString)).As(Of IMessageProvider)()
Now every thing works as expected.
Thanks
Akaka
Excellent, glad you got it working Akaka.
ReplyDeleteAnd thanks for helping out Nick! :-)