Does Ninject ensure my DbContext is cleaned up and disposed in a timely fashion?
As per this answer:
The CLR documentation states that whoever creates a Disposable object is responsible for calling Dispose. In this case the object is created by Ninject. That means you should not call Dispose explicitly.
Ninject disposes every Disposable object that has another scope other than InTransientScope
as soon as the scope object to which the created object is tied is collected by GC. That's why every Disposable object should be Bindd with a scope that is not InTransientScope(). E.g. you can use InParentScope() from the NamedScope extension which will Dispose the object as soon as the object it is injected into is garbage collected.
I have created a base class for all my application's controllers to handle any common initialization, etc. The base class accepts an instance of my DbContext argument in the constructor. But this requires me to also add this argument to every controller in my app. Is there any way to not require this?
Simply put, never use a common base class for MVC Controllers. Class inheritance tends to tightly couple your logic and it becomes difficult to maintain over time. It also tends to lead you to create a god object, because creating multiple levels of injected dependencies would mean even more required dependencies for every Controller.
If you have cross-cutting concerns, you should use globally registered filters instead. You can make a separate filter for each piece of logic, which doesn't violate the Single Responsibility Principle as a shared base class would. And if you register your filters globally, you can use constructor injection as in this action filter or this authorization filter. You can also make your own attributes (without behavior) to make them conditional per controller and/or action, if necessary.
Example:
Since you explicitly said you wanted to set common ViewBag
properties based on the current user, here is how that can be done with filters.
CurrentUserProfileFilter
public class CurrentUserProfileFilter : IAuthorizationFilter
{
private readonly MyDbContext context;
public CurrentUserAuthorizationFilter(MyDbContext context)
{
this.context = context;
}
public void OnAuthorization(AuthorizationContext filterContext)
{
var currentUserName = filterContext.HttpContext.User.Identity.Name;
// Set the ViewBag for the request.
filterContext.Controller.ViewBag.UserName = currentUserName;
var userBirthdate =
from user as this.context.AspNetUsers
where user.UserName == currentUserName
select birthdate;
if (userBirthdate.Date == DateTime.Now.Date)
{
filterContext.Controller.ViewBag.Message = "Happy Birthday!";
}
}
}
GlobalFilterProvider
MVC has a static GlobalFiltersCollection
where you are supposed to register filter instances globally. This isn't going to do for filters that have dependencies that have lifetimes that are managed by the DI container (such as DbContext
).
To ensure the filters are resolved on demand (per-request), we make an IFilterProvider
that resolves them through the container (assuming your Ninject container is registered with MVC as the DependencyResolver);
public class GlobalFilterProvider : IFilterProvider
{
private readonly IDependencyResolver dependencyResolver;
public GlobalFilterProvider(IDependencyResolver dependencyResolver)
{
this.dependencyResolver = dependencyResolver;
}
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
foreach (var filter in this.dependencyResolver.GetServices<IActionFilter>())
{
yield return new Filter(filter, FilterScope.Global, order: null);
}
foreach (var filter in this.dependencyResolver.GetServices<IAuthorizationFilter>())
{
yield return new Filter(filter, FilterScope.Global, order: null);
}
foreach (var filter in this.dependencyResolver.GetServices<IExceptionFilter>())
{
yield return new Filter(filter, FilterScope.Global, order: null);
}
foreach (var filter in this.dependencyResolver.GetServices<IResultFilter>())
{
yield return new Filter(filter, FilterScope.Global, order: null);
}
// If MVC 5, add these as well...
//foreach (var filter in this.dependencyResolver.GetServices<System.Web.Mvc.Filters.IAuthenticationFilter>())
//{
// yield return new Filter(filter, FilterScope.Global, order: null);
//}
}
}
Usage
In your Ninject composition root, register the instance of your filter with the kernel
for the type or types of filter interfaces it implements.
// Self-bind our filter, so dependencies can be injected.
kernel.Bind<IAuthorizationFilter>().To<CurrentUserProfileFilter>();
In FilterConfig
, register your filter provider.
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
// Register the filter provider with MVC.
FilterProviders.Providers.Insert(0, new GlobalFilterProvider(DependencyResolver.Current));
}
}
Now on every request, your user details are populated.
But more importantly, your ArticlesController
doesn't require MyDbContext
as a dependency, nor do the rest of your controllers.
I'm not sure how expensive it is to create an instance of my DbContext. Is there any way to make the optimization that it only gets created if the request actually requires me to access the database.
Have a look at this question: One DbContext per web request... why?
DbContext
being instantiated by Ninject is being passed as an argument to my controller's constructor. I store this argument in my base class. How else can I get this value for my base class's constructor besides accepting this argument in the derived class and then passing it to the base class? – Danyel