问题
my controllers use 2 or more services. In turn, my services construct and consume their own instance of a Unit of Work class (with access to the repositories).
I would like my services to share a same Unit of Work instance, and make it unit-testable. My questions are:
- Should I inject the Unit of Work and services to my controllers?
- I would need to inject the Unit of Work to my services as well. Where should I do that? Thank you so much.
回答1:
1) I dont think injecting Unit of Work in UI controller is a good idea, try to separate the logic and transaction from the UI.
2) Yes you can inject UoW in your service preferable as a constructor injected through IoC container. some people design it to be a static factory but i prefer use it as parameter injected in constructor
public class MyService : IMyService
{
IUnitOfWork _unitOfWork;
public MyService(IUnitOfWork uow)
{
_unitOfWork = uow;
}
public void DoSomeOperation(SampleParam param)
{
_unitOfWork.BeginTrx();
// do some work
_unitOfWork.Commit();
}
}
or using static factory
public class MyService : IMyService
{
public void DoSomeOperation(SampleParam param)
{
using(UnitOfWork.Start())
{
// do some work
}
}
}
回答2:
I like to combine DI with an action filter, this way the container can managed the scope of the unit of work, but begin/commit/rollback are called for you automatically and you don't have to fuss with that on every action.
This is a bit tricky, because normally, actionfilters are NOT re-instantiated per request, so when you have a dependency that you DO want to be per request (the unit of work) you need to work some magic.
Here is how I do this using Ninject, and Ninject.Web.Mvc
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class UnitOfWorkAction : Attribute
{
}
public class UnitOfWorkActionFilter : IActionFilter
{
private IUnitOfWork _unitOfWork;
public UnitOfWorkActionFilter(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
_unitOfWork.Begin();
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.Exception == null)
{
try
{
_unitOfWork.Commit();
}
catch
{
_unitOfWork.Rollback();
throw;
}
}
else
{
_unitOfWork.Rollback();
}
}
}
I then configure how the attribute should be used in the App_Start/NinjectMVC3.cs
kernel.BindFilter<UnitOfWorkActionFilter>(FilterScope.Action, 0)
.WhenActionMethodHas<UnitOfWorkAction>();
//also make sure your IUnitOfWork is bound per request, obviously
And finally, an example Action
[UnitOfWorkAction]
public ActionResult SomeAction(int id)
{
//invoke your services here, and the uow will be automatically committed or rolled back when the action returns
}
Also worth noting is that this approach lets you do constructor injection of dependencies to your action filter, rather than just property injection, which I very much prefer.
回答3:
I like Mohamed Abed's answer, I haven't gotten enough points yet to add a comment to his post so I will just try to mention that in these cases you need to make sure the the Unit of Work is marked as a single instance or it will not be shared between the services.
来源:https://stackoverflow.com/questions/7653020/sharing-a-unit-of-work-among-many-services-in-asp-net-mvc-3