MVC3 added support for Dependency Injection frameworks. To take advantage of these features I did a completely new implementation of the Ninject.Web.Mvc extension. Unlike other existing MVC3 implementations for Ninject, this extension goes further than just adding a IDependencyResolver for Ninject. It has tightly Ninject integrated support for various things such as Filters and Validators.
This post assumes that you have a basic knowledge about MVC, Ninject and Dependency Injection in general.
Let’s get started
First you have to get a complied version of the extension. You either have to get the sources or the binaries from Github. The binaries are also available from the build server. The sources also come with a sample project that demonstrates all features.
The next step is to setup the application to use the Ninject extension. Therefore change the global.asax to use NinjectHttpApplication instead of HttpApplication and override CreateKernel to create a kernel and load all the modules that you need in your application. One way to load all modules is to tell Ninject to load all modules from your application assemblies. Here is an example of the global.asax.
public class MvcApplication : NinjectHttpApplication { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); } public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional }); } protected override IKernel CreateKernel() { var kernel = new StandardKernel(); kernel.Load(Assembly.GetExecutingAssembly()); return kernel; } protected override void OnApplicationStarted() { base.OnApplicationStarted(); AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); } }
Dependency Injection for Controllers
In the previous step we already prepared anything that is necessary for controller injection. The constructor of controllers can now be changed to accept dependencies. The only thing that has to be done is to configure the Ninject bindings for its dependencies. The controller itself will be found by Ninject even without adding a binding. Of course, you can still add a binding for the controller in case you need to specify more information for the binding (e.g. an additional constructor argument).
Here is an example of a controller that has the welcome message service as dependency.
public class HomeController : Controller { private readonly IWelcomeMessageService welcomeMessageService; public HomeController(IWelcomeMessageService welcomeMessageService) { this.welcomeMessageService = welcomeMessageService; } public void Index() { ViewModel.Message = this.welcomeMessageService.TodaysWelcomeMessage; return View(); } } public class WelcomeMessageServiceModule : NinjectModule { public override void Load() { this.Bind<IWelcomeMessageService>().To<WelcomeMessageService>(); } }
Dependency Injection for filters
With MVC3 I introduced a completely new pattern to configure filters for controllers and its actions. While injection of filter attributes is still supported I recommend using this new pattern for filter configuration because it has the advantage to support constructor injection and does not require the InjectAttribute anymore.
But let’s see how this new pattern works. First of all you have to create your filter class by implementing one of the filter interfaces e.g. IActionFilter. All the dependencies are added to the constructor. The following example of a logging filter has a logger as dependency and can be configured with the level that is used to log.
public class LogFilter : IActionFilter { private readonly ILog log; private readonly Level logLevel; public LogFilter(ILog log, Level logLevel) { this.log = log; this.logLevel = logLevel; } public void OnActionExecuting(ActionExecutingContext filterContext) { var message = string.Format( CultureInfo.InvariantCulture, "Executing action {0}.{1}", filterContext.ActionDescriptor.ControllerDescriptor.ControllerName, filterContext.ActionDescriptor.ActionName), this.log.Logger.Log(typeof(LogFilter), this.logLevel, message, null); } public void OnActionExecuted(ActionExecutedContext filterContext) { var message = string.Format( CultureInfo.InvariantCulture, "Executed action {0}.{1}", filterContext.ActionDescriptor.ControllerDescriptor.ControllerName, filterContext.ActionDescriptor.ActionName), this.log.Logger.Log(typeof(LogFilter), this.logLevel, message, null); } }
To apply this filter to an action or controller we need to specify a binding. But unlike other bindings filters require that BindFilter is used instead of the normal Bind. Additionally to the type of the filter you have to specify the filter scope and the filter order. More information about these two parameters can be found Brad Wilsons Blog.
In the example below the LogFilter is applied to every action and configured with the log level info.
public class LoggingModule : NinjectModule { public override void Load() { this.Bind<ILog>().ToMethod(GetLogger); this.BindFilter<LogFilter>(FilterScope.Controller, 0) .WithConstructorArgument("logLevel", Level.Info); } private static ILog GetLogger(IContext ctx) { var filterContext = ctx.Request.ParentRequest.Parameters .OfType<FilterContextParameter>().SingleOrDefault(); return LogManager.GetLogger(filterContext == null ? ctx.Request.Target.Member.DeclaringType : filterContext.ActionDescriptor.ControllerDescriptor.ControllerType); } }
Conditional bindings for filters
Usually, a filter is applied to a few actions. With the MVC3 extension for Ninject this is done by adding a condition to the filter binding. The available overloads of “When” are a bit different than for other Ninject bindings. Blow you find examples of the available When overloads.
// LogFilter is applied to controllers that have the LogAttribute this.BindFilter<LogFilter>(FilterScope.Controller, 0) .WhenControllerHas<LogAttribute>() .WithConstructorArgument("logLevel", Level.Info); // LogFilter is applied to actions that have the LogAttribute this.BindFilter<LogFilter>(FilterScope.Action, 0) .WhenActionHas<LogAttribute>() .WithConstructorArgument("logLevel", Level.Info); // LogFilter is applied to all actions of the HomeController this.BindFilter<LogFilter>(FilterScope.Action, 0) .WhenControllerTypeIs<HomeController>() .WithConstructorArgument("logLevel", Level.Info); // LogFilter is applied to all Index actions this.BindFilter(FilterScope.Action, 0) .When((controllerContext, actionDescriptor) => actionDescriptor.ActionName == "Index") .WithConstructorArgument("logLevel", Level.Info);
Filter Configurations
In the previous examples you have already seen that a filter can be configured by adding one or more With statements to the binding configuration. But some times it is necessary to have different configurations for different actions. In this case it’s possible to get the configuration value from an attribute of the action or controller. The next example shows all new With overloads that come with the MVC3 extension.
[Log(LogLevel = Level.Debug)] void Index() {} this.BindFilter<LogFilter>(FilterScope.Controller, 0) .WhenControllerHas<LogAttribute>() .WithConstructorArgumentFromControllerAttribute<LogAttribute>( "logLevel", attribute => attribute.LogLevel); // For property injection WithPropertyValueFromControllerAttribute instead this.BindFilter<LogFilter>(FilterScope.Action, 0) .WhenActionHas<LogAttribute>() .WithConstructorArgumentFromActionAttribute<LogAttribute>( "logLevel", attribute => attribute.LogLevel); // For property injection WithPropertyValueFromActionAttribute instead this.BindFilter<LogFilter>(FilterScope.Action, 0) .WhenActionHas<LogAttribute>() .WithConstructorArgument(( "logLevel", (context, controllerContext, actionDescriptor) => actionDescriptor.ActionName == "Index" ? Level.Info : Level.Warn);
Injection of Filter Attributes
As already mentioned, the extension supports injection of filter attributes. But unlike with the Ninject configured attributes it is restricted to property injection. That’s the reason why I recommend not to use this feature. It is only there for backward compatibility. Here is an example of the log filter as filter attribute.
public class LogFilterAttribute : ActionFilterAttribute { [Inject] public ILog Log { get; set; } public Level LogLevel { get; set; } public void OnActionExecuting(ActionExecutingContext filterContext) { var message = string.Format( CultureInfo.InvariantCulture, "Executing action {0}.{1}", filterContext.ActionDescriptor.ControllerDescriptor.ControllerName, filterContext.ActionDescriptor.ActionName), this.Log.Logger.Log(typeof(LogFilter), this.LogLevel, message, null); } public void OnActionExecuted(ActionExecutedContext filterContext) { var message = string.Format( CultureInfo.InvariantCulture, "Executed action {0}.{1}", filterContext.ActionDescriptor.ControllerDescriptor.ControllerName, filterContext.ActionDescriptor.ActionName), this.Log.Logger.Log(typeof(LogFilter), this.LogLevel, message, null); } }
Injection of Validators
Another new feature of the MVC3 extension is the support for injection of validation attributes. But as with all other attributes it only allows property injection. The following example demonstrates how to create a zip code validation attribute that uses a service to check if the value is valid and that the zip code exists.
public class ZipCodeAttribute : ValidationAttribute { [Inject] public IZipCodeService ZipCodeService { get; set; } public override bool IsValid(object value) { if (value == null) { return true; } if ((value is string) && string.IsNullOrEmpty((string)value)) { return true; } var zipCode = Convert.ToInt32(value); return this.ZipCodeService.IsValidZipCode(zipCode); } }
What’s more?
Because the MVC3 extension is based on the IDependencyResolver of MVC3 it is also possible to replace most of the involved components such as the view engine, view page activator, model binder, and more. Brad Wilsons Blog post gives an excelent introduction what can be done with MVC3.
Hy Remo,
Thanks for the article. I wrote a more aggressive property injection heuristic for the NServiceBus Object Builder for Ninject which allows to declare property dependencies without having the need to use the InjectAttribute. Maybe this would be a nice approach to support the old style without having to declare the attributes for injection.
Daniel
Hey Remo, seems to me this looks almost the same as in the MVC2 with Ninject 2 (the inheritance from NinjectHttpApplication, modules, registration, etc.). Could you post an example of how to do it the MVC3 way, with IDependencyResolver etc.?
This is the MVC3 way. The extension uses IDependencyResolver internally. All the things that are requested by MVC can be replaced by adding a simple binding to Ninject. You don’t need to care about IDependencyResolver anymore.
Thank you for the clear and thorough explanation.
[…] because it was easily installable by NuGet. Unfortunately, this package does not provide the full power of the official implementation Ninject.Web.MVC3. Therefore, we decided to replace this package with […]
Remo,
Great article going over all the ninject 2 features with MVC3, all very helpful information. I know this isn’t a help forum, but I’m having some trouble with a base controller class using ninject in MVC3 and was wondering if you had any insight. I have all of my controllers inheriting from:
public class FacebookBaseController : Controller
{
private readonly IFacebookContext _facebookContext;
private readonly IFacebookApplication _applicationContext;
public FacebookBaseController(IFacebookContext facebookContext) : base()
{
this._facebookContext = facebookContext;
this._applicationContext = facebookContext.GetApplication(this.RouteData.Values);
}
}
The injection aspect seems to be working fine, the trouble is there doesn’t seem to be any route, request or context data at all on the base object (System.Web.Mvc.Controller). Its almost as if the base constructor on the Controller class isn’t being called. I could give you more info if you’re interested, but I figured I’d just ask if this looks like an easy fix.
Thanks for your time and the awesome blog,
Matthew
@Matthew Tschiegg
You know what, I think I misunderstood the MVC pipeline. I guess the route, request and context members are populated until Initialize() is called. I just moved the “facebookContext.GetApplication(this.RouteData.Values)” outside of the constructor so it can be called when the route data is populated.
Sorry for taking up any of your time.
controller itself will be found by Ninject even without adding a binding
—————–
I can’t understand this sentens. I read the source code and can’t find the bindging, and also can’t foundn when and where the Controller was created by the Ninject?
I’m new to the concept of DI and Ninject and I’m currently using the “MvcApplication : NinjectHttpApplication” + “WelcomeMessageServiceModule : NinjectModule” way for the implementation of my MVC 3 application. Everything works fine.
The things is I can’t quite figure out how to do DI outside of the Controller… for example, if I have a user function defined in one of my custom library codes which is outside of the MVC controller… and in that function, I wanted to access the same database table that the controller is using… how do I do the DI in a proper way… last time I tried by doing something like [Inject] …. it worked.. but… I have this problem when I updated say Database Table ‘A’ record ‘X’ via the MVC Controller… and later accessed the same database table via my “user function” and I somehow didn’t get the updated version of record ‘X’, unless I restart the application.
Any advice or help would be very much appreciated. Give me a simple example would help. Thanks.
i just grabbed the latest version of the ninject.web.mvc and it appears to have no ability to inhert NinjectHttpApplication, any ideas if this has been replaced?
This version has not yet been officially released. A detail blog post and updated documentation about the changes will follow. At the moment the only support I give is the information that you also need the Ninject.Web.Common extension. For everything else have a look at the source code and sample project.
Does the Ninject Mvc extension work with a controller factory or does the setup have to change if you use a controller factory?
I dont see where
ControllerBuilder.Current.SetControllerFactory(new MyControllerFactory());
is used with MVC extension
The extensions implements a IDependencyResolver. MVC uses the dependency resolver to get an instance of the controller factory. If you want to replace the default one you just have to add a new binding for the factory. But be aware that you have to call Ninject (or DependencyResolver) in your implementation in case you want to use depencency injection in your controllers.
I liked the MVC filter injection pattern as an alternative to injecting the properties into the FilterAttribute. I have a filter with an accompanying attribute marked as Method ‘or’ Class. In order to allow this, I had to use:
this.BindFilter(FilterScope.Controller, 0)
.When((controllerContext, actionDescriptor) =>
actionDescriptor.ControllerDescriptor
.GetCustomAttributes(typeof(MyAttribute), true)
.Any()
||
actionDescriptor
.GetCustomAttributes(typeof(MyAttribute), true)
.Any());
Any cleaner way of doing this?
What about getting instance(s) of my own object(s)? If I create a binding to MyObject in my NinjectModule, how do I get an instance of it from, say, the middle of a given controller?
Thanks,
Scott
Hy Scott,
There are multiple ways to do this.
Hope that helps
Daniel
Hmmm, ok. Why would any of that be better than just using IDependancyResolver? (…which Remo says to NOT use when your MVC 3 app is inheriting from NinjectHttpApplication.) There seems to be (IS, in my case) quite a bit of confusion about how to use Ninject in an MVC 3 app due to how many ways it COULD be used. When I started with Ninject, I always had/have an instance of the kernel to use to kernel.Get(), but if you use a Module, and thus don’t really have said kernel instance laying about, I’m not groking how to do a Get().
I really appreciate your response Daniel, but I’m not quite getting it. Any chance you could .. ‘spell out’ (Scaffold?) what the code above would look like?
Thanks again,
Scott
Regarding the question why it is better: Using DependencyResolver is using the ServiceLocator pattern. There are various articles why you shouldn’t use Service Locator e.g. http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx
Now to your inital question. In an MVC application there are very few reasons why you can’t create everything together with you controller. So in the first place you should ask yourself why do you need to create an other instance during processing. And isn’t there a way to get everything at the beginning.
If this is not possible then do what Dani recommends. Create an interface that has a CreateSomething method that takes everything you need to create the instance and have it return the instance. Then in your configuration you implement this interface and add an IResolutionRoot to its constructor and use this instace to Get the required object.
Thank you very much, it’s just a super feature. SUPER!
[…] http://www.planetgeek.ch/2010/11/13/official-ninject-mvc-extension-gets-support-for-mvc3/ ( This blog post shows you how to use Ninject for Attribute bindings.) […]
[…] http://www.planetgeek.ch/2010/11/13/official-ninject-mvc-extension-gets-support-for-mvc3/ ( This blog post shows you how to use Ninject for Attribute bindings.) […]
And about DbContext per Request lifetime?
How can I do?
Nice! I wonder if this can be used for MVC 4 projects as well…? As far as I can see, yes, but I would like to know if anyone has any knowledge this not being compatible with MVC 4…
Thanks for the post though!
Like the documentation (https://github.com/ninject/ninject.web.mvc/wiki) says MVC4 is supported.
[…] ninject MVC3, I used Remo.gloo’s blog to help me create the Global.asax file as posted here http://www.planetgeek.ch/2010/11/13/official-ninject-mvc-extension-gets-support-for-mvc3/ and am following along with some additional code on p.164 of the the Pro ASP.NET MVC3 Framework […]