.NET

How to handle transactions in ASP.NET MVC3

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 uses 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)
 {
  // 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.

Related Articles

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button