How to handle transactions in ASP.NET MVC3
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
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)
{
// Adds an error to prevent commit.
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();
}
Reference: How to handle transactions in ASP.NET MVC3 from our NCG partner Jonas Gauffin at the jgauffin’s coding den blog.