For a recent web project, we had the opportunity to use the state of the art .NET tools. Naturally we took the opportunity to take advantage of the new ASP.NET MVC goodness. We didn't stop there and wanted to use a modern Inversion of Control/Dependency Injection framework too.
ASP.NET MVC encourages better architectual design over regular ASP.NET. Inversion of Control (IoC) implemented by way of the Depedenncy Injection (DI) pattern takes it a step further. It allows your classes to be less tighly coupled, which gives you less duplication, cleaner code, better maintainability and (unit) testability. It does not solve world hunger though. What we want to avoid is something like:
public MyController() { myService = new MyService(new MyDataAccessObject(ConfigurationManager.AppSettings["dbcon"))); }
In this way the controller nor the service nor the dao cannot be unit tested and they are tighly coupled, so replacing them with other implementations is not easy.
There are several .NET IoC/DI frameworks. I had previous experience with Spring.NET which is okay. I especially liked its AOP (Aspect Oriented Programming) capabilities. But it also quite verbose with its XML configuration. The documentation was unreadable and user hostile. Therefore we went on the hunt for something better. Key requirement was that it should be quick and easy to implement in the project. We would like to get rid of XML and use regular code, so we have IntelliSense and compile time errors.
The choice fell on StructureMap as it was easy to get running. The negatives were never a serious problem.
Rather than creating the objects in your controller, you put the interfaces in its constructor. When a request to the controller is made (and it does not exist yet), the controller object is created and the depedencies are resolved by StructureMap. I.e. it looks at the arguments of the constructor and tries to match the interfaces to actual classes. For example, the controller would be:
protected IMyService myService; public MyController(IMyService arg_myService) { myService = arg_myService; }
The service interface and class would be:
public interface IMyService { } public class MyService : IMyService { protected IMyDataAccessObject myDao; public MyService(IMyDataAccessObject arg_myDao) { myDao = arg_myDao; } }
Unlike Spring.NET, where you have to configure every mapping of FoobarService to IFoobarService, StructureMap can do so automatically based on the convention that the class maps to the interface with an "I" before its name. Naturally you can implement your own convention if you like, or you can override it for specific cases. We'll see how later on. You can also use Setter Injection like you can in Spring.NET (see example below), but the favoured way of injection is this way, Constructor Injection. While this excellent as it is clearer to the developer what is injected, it has also a pitfall. This constructor injection does not allow cycles: you cannot have MyService referring to IMySecondService and MySecondService to IMyService. You'll have to defer shared code to a third class or find another solution. Finally, I could not get Setter Injection to work with ASP.NET MVC Controllers.
I like to keeps things separated. So if you're like me, your project will be structured somewhat like this (three-tier architecture):
As the web app and the scheduled task will share some of the IoC configuration it makes sense to keep it in a separate project as well. So:
StructureMap is contained in one dll that is referenced by the App and IoC projects.
The StructureMap configuration is done at application start. We create a ContainerBootstrap class as start point to bootstrap the IoC container. Such a bootstrap should be in the App.UI and App.ScheduledTask. The bootstrap in the web app is called from Global.asax:
protected void Application_Start() { log4net.Config.XmlConfigurator.Configure(); AreaRegistration.RegisterAllAreas(); RegisterRoutes(RouteTable.Routes); // Bootstrap StructureMap IoC container IoC.ContainerBootstrap.Bootstrap(); // Wire up controller factory to StructureMap ControllerBuilder.Current.SetControllerFactory(new IoC.ControllerFactory()); }
The bootstrap class looks like this (in our case, located in the IoC folder below the root of the web app):
namespace MyProject.App.UI.IoC { public static class ContainerBootstrap { public static void Bootstrap() { ObjectFactory.Initialize(x => { x.For<ILog>().Singleton().Use(LogManager.GetLogger("UILogger")); x.Scan(y => { y.Assembly("MyProject.IoC"); y.TheCallingAssembly(); // auto discover registries in registries folder y.LookForRegistries(); y.WithDefaultConventions(); }); }); } } }
We use a log4net logger. We tell StructureMap to resolve every ILog to the logger UILogger, configured in the web.config, with:
x.For<ILog>().Singleton().Use(LogManager.GetLogger("UILogger"));
It loads the assembly MyProject.IoC thanks to: y.Assembly("MyProject.IoC"). With y.LookForRegistries() we tell it look for separate registries in the loaded assemblies: the current from the web app and registries from the IoC project we just told it to load. Such a registiry is the UIRegistry in the UI folder below the IoC folder and looks something like this (just an example):
namespace MyProject.UI.IoC.Registries { public class UIRegistry : Registry { public UIRegistry() { For<IFormsAuthenticationService>().Singleton().Use<FormsAuthenticationService>(); For<IMembershipService>().Singleton().Use<AccountMembershipService>(); For<MembershipProvider>().Singleton().UseMembershipProvider>(); } } }
The rest in the UI is done automatically by convention with: y.WithDefaultConventions().
The rest of classes in the web app however won't be much. The main things are the controllers. For the controllers you need a specific ControllerFactory, as you saw declared in the Global.asax. The ControllerFactory is really simple:
namespace Trijact.UI.IoC { public class ControllerFactory : DefaultControllerFactory { protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, System.Type controllerType) { if (controllerType == null) { return base.GetControllerInstance(requestContext, controllerType); } return (Controller) ObjectFactory.GetInstance(controllerType); } } }
This works in both ASP.NET MVC 2 en 3. I've read that it can be even simpler in ASP.NET MVC 3. But this is simple enough.
In the IoC project, we have a registries folder with a BusinessRegistry class. Sample registry for the business tier:
namespace MyProject.IoC.Registries { public class BusinessRegistry : Registry { public BusinessRegistry() { string filesDir = ConfigurationManager.AppSettings["filesDir"]; For<IFileService>().Singleton().Use<FileService>().Ctor<string>("filesDirectory").Is(filesDir); Scan(x => { x.Assembly("MyProject.Business"); x.Convention<SingletonConvention>(); }); } } }
As strings don't have interfaces, you have to tell StructureMap what it should use. The FileService requires a string fileDirectory as argument, (apparently) a files directory set in the web.config.
We wanted to improve performance by not having the objects created at each request to the Controller. So we can declare it a singleton. The default convention is that objects are not singleton. You could declare each mapping singleton as shown above for the
FileService (For&tlt;IFileService>().Singleton().Use<FileService>())
, but you can also create your own convention to do so automatically:
namespace MyProject.IoC { public class SingletonConvention : DefaultConventionScanner { public override void Process(Type type, Registry registry) { if (type.IsAbstract) return; Type pluginType = FindPluginType(type); if (pluginType != null && Constructor.HasConstructors(type)) { registry.AddType(pluginType, type); ConfigureFamily(registry.For(pluginType).Singleton()); } } } }
This is just a copy of the default implementation of Process, with exception for the Singleton declaration at the end.
Using NHibernate or another ORM framework is a separate topic, but this is how the DataRegistry might look like:
namespace MyProject.IoC.Registries { public class DataRegistry : Registry { public DataRegistry() { var dbSettings = new DatabaseSettings(); dbSettings.ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings["trijact_default"].ToString(); For<DatabaseSettings>().Singleton().Use(dbSettings); var unitOfWorkFactory = new UnitOfWorkFactory(); For<IUnitOfWorkFactory>().Singleton().Use(unitOfWorkFactory); string filesDir = SysConfig.ConfigurationManager.AppSettings["filesDir"]; var dbConfig = new NHibernateDatabaseConfig(dbSettings, null, filesDir); For<Configuration>().Singleton().Use(dbConfig.Configuration); For<ISessionFactory>().Singleton().Use(dbConfig.SessionFactory); Scan(x => { x.AssemblyContainingType<ProjectDao>(); x.Convention<SingletonConvention>(); }); // Setter injection of DatabaseConfig FillAllPropertiesOfType<IDatabaseConfig>().Singleton().Use(dbConfig); } } }
In our case, every dao inherits from a BaseDao. It is easier to use Setter Injection then, so you won't have to have the database configuration object in every Dao constructor. The setter injection is done by:
FillAllPropertiesOfType<IDatabaseConfig>().Singleton().Use(dbConfig);
Once you have it setup, you'll hardly notice the presence of StructureMap. It is silently doing its job, like technologies should.