I got annoyed about having to repeat the transaction handling in every POST
method of my controllers. First of all: I don’t want to have to take the unit of work in the constructor of each controller, since that implicitly says that all/most methods use transactions. And I do not want to have to resolve a factory, since that says that all methods might use transactions. Finally, using a singleton is the worst of solutions.
Before
The problem wasn’t that hard to solve, thanks to the flexibility of ASP.NET MVC3. But let’s start by looking at a typical post method:
[HttpPost]
public virtual ActionResult Create(CreateModel model)
{
if (!ModelState.IsValid)
return View(model);
var instruction = new Instruction(CurrentUser);
Mapper.Map(model, instruction);
using (var uow = _unitOfWorkFactory.Create())
{
_repository.Save(instruction);
uow.SaveChanges();
}
return RedirectToAction("Details", new {id = instruction.Id});
}
After
What I did was to create a new action filter called TransactionalAttribute
. It checks if the model state is valid and that no exceptions have been thrown. It uses the DependencyResolver
to find a IUnitOfWork
implementation if everything checks out OK. Since this is done in an action filter, the transaction will not be created unless it’s actually required. All you have to do is to register the UoW factory in your IoC container and tag your action:
[HttpPost, Transactional]
public virtual ActionResult Create(CreateModel model)
{
if (!ModelState.IsValid)
return View(model);
var instruction = new Instruction(CurrentUser);
Mapper.Map(model, instruction);
_repository.Save(instruction);
return RedirectToAction("Details", new {id = instruction.Id});
}
There is a small catch: You must add an error to the ModelState
if you catch exceptions in your class. The transaction will otherwise get committed.
[HttpPost, Transactional]
public virtual ActionResult Create(CreateModel model)
{
if (!ModelState.IsValid)
return View(model);
try
{
model.Category = model.Category ?? "Allmänt";
var instruction = new Instruction(CurrentUser);
Mapper.Map(model, instruction);
_repository.Save(instruction);
return RedirectToAction("Details", new {id = instruction.Id});
}
catch (Exception err)
{
ModelState.AddModelError("", err.Message);
Logger.Error("Failed to save instruction for app " + CurrentApplication, err);
return View(model);
}
}
Implementation
The attribute itself looks like this:
public class TransactionalAttribute : ActionFilterAttribute
{
private IUnitOfWork _unitOfWork;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.Controller.ViewData.ModelState.IsValid &&
filterContext.HttpContext.Error == null)
_unitOfWork = DependencyResolver.Current.GetService<IUnitOfWork>();
base.OnActionExecuting(filterContext);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.Controller.ViewData.ModelState.IsValid &&
filterContext.HttpContext.Error == null && _unitOfWork != null)
_unitOfWork.SaveChanges();
base.OnActionExecuted(filterContext);
}
}
Simple, but effective!
Extras
The actual unit of work implementation depends on which kind of data layer you are using. You can use the following code snippet if you are using nhibernate and have successfully registered the ISession
in your container:
public class NhibernateUnitOfWork : IUnitOfWork
{
private readonly ISession _session;
private ITransaction _transaction;
public NhibernateUnitOfWork(ISession session)
{
_session = session;
_transaction = session.BeginTransaction();
}
public void Dispose()
{
if (_transaction == null)
return;
if (!_transaction.WasCommitted)
_transaction.Rollback();
_transaction.Dispose();
_transaction = null;
}
public void SaveChanges()
{
_transaction.Commit();
}
}
The Unit of work interface is really simple:
public interface IUnitOfWork : IDisposable
{
void SaveChanges();
}