Filters are cached by the MVC framework. Do not use InRequest scoped references but inject a factory instead.
https://github.com/ninject/ninject.web.mvc/wiki/Filters-and-Scoped
Pregunta
Problem:
I use Ninject in my webapplication as IoC-Framework. I have tried to inject the Entity-Framework DbContext class to several classes in my code. I want to share a object instance by request. So I tried using the InRequestScope method from ninject. Problem is the instance is not shared. Instead I always get a new instance of DbContext.
Code:
First of all my implementation of DbContext class:
public class BudgetContext : DbContext, IUnitOfWork
{
public BudgetContext() : base("DefaultConnection")
{
Database.SetInitializer<BudgetContext>(new DropCreateDatabaseIfModelChanges<BudgetContext>());
}
public DbSet<Expense> Expenses { get; set; }
public int Commit()
{
return SaveChanges();
}
}
I want to inject the BudgetContext (my DbContext and also UnitOfWork) in two different places:
public class UnitOfWorkAttribute : ActionFilterAttribute
{
[Inject]
public IUnitOfWork UnitOfWork { get; set; } // injecting DbContext here (1)
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
if (filterContext.Exception == null)
{
UnitOfWork.Commit();
}
}
}
public class ExpenseRepository : IExpenseRepository
{
private readonly BudgetContext context;
public ExpenseRepository(IUnitOfWork context) // injecting DbContext here (2)
{
this.context = (BudgetContext)context;
}
public void Add(Expense expense)
{
context.Expenses.Add(expense);
}
}
Both injected DbContext classes should be the same object instance for one request. My Ninject configuration in NinjectWebCommon.cs looks like this:
...
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IUnitOfWork>().To<BudgetContext>().InRequestScope();
kernel.Bind<IExpenseRepository>().To<ExpenseRepository>();
// ...
kernel.Bind<ExpensesController>().To<ExpensesController>();
}
...
Did I configure the Ninject-Framework correct? What am I missing? Thanks for helping me out.
**Update:**
I have implemented a factory to create my UnitOfWork(which basically is my DbContext class). The implementation of the factory looks like this:
public class UnitOfWorkFactory : IUnitOfWorkFactory
{
private IUnitOfWork unitOfWork;
public IUnitOfWork Get()
{
if (unitOfWork == null)
unitOfWork = new BudgetContext();
return unitOfWork;
}
}
In Ninject I use the following configuration for the new factory:
kernel.Bind<IUnitOfWorkFactory>().To<UnitOfWorkFactory>().InRequestScope();
In my attribute I inject it this way:
[Inject]
public IUnitOfWorkFactory UnitOfWorkFactory{ get; set; }
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
if (filterContext.Exception == null)
{
var unitOfWork = UnitOfWorkFactory.Get();
unitOfWork.Commit();
}
}
Unfortunately I have no success. My DbContext always gets resetted after it reaches the attribute class.
Solución
Filters are cached by the MVC framework. Do not use InRequest scoped references but inject a factory instead.
https://github.com/ninject/ninject.web.mvc/wiki/Filters-and-Scoped
Otros consejos
Thanks to @Remo Gloor I figured out that something must be wrong with my attribute. So I changed my implementation and implemented the interface IActionFilter instead of inheriting the class ActionFilterAttribute. Now I can injenct my DbContext InRequest scoped.
The Attribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class UnitOfWorkAttribute : Attribute { }
public class UnitOfWorkFilter : IActionFilter
{
private readonly IUnitOfWork unitOfWork;
public UnitOfWorkFilter(IUnitOfWork unitOfWork)
{
this.unitOfWork = unitOfWork;
}
public void OnActionExecuting(ActionExecutingContext filterContext) { }
public void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.Exception == null)
{
unitOfWork.Commit();
}
}
}
The Binding:
kernel.Bind<IUnitOfWork>().To<BudgetContext>().InRequestScope();
kernel.BindFilter<UnitOfWorkFilter>(FilterScope.Action, 0).WhenActionMethodHas<UnitOfWorkAttribute>();
You need three packages
Ninject
Ninject.Web
Ninject.Web.Common
And then you should register NinjectHttpModule
in both libraries Ninject.Web.Common
and Ninject.Web
public static void Start()
{
DynamicModuleUtility.RegisterModule(typeof (OnePerRequestHttpModule));
DynamicModuleUtility.RegisterModule(typeof (Ninject.Web.Common.NinjectHttpModule));
DynamicModuleUtility.RegisterModule(typeof (Ninject.Web.NinjectHttpModule));
bootstrapper.Initialize(createKernel);
}
And I'm not sure if you need to add Bind<UnitOfWorkAttribute>().ToSelf()