Click here to Skip to main content
15,860,943 members
Articles / Web Development / HTML

How to Handle Transactions in ASP.NET MVC3

Rate me:
Please Sign up or sign in to vote.
4.89/5 (2 votes)
6 Jun 2012LGPL31 min read 21.1K   2   2
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 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:

C#
[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:

C#
[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.

C#
[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:

C#
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:

C#
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:

C#
public interface IUnitOfWork : IDisposable
{
	void SaveChanges();
}

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Founder 1TCompany AB
Sweden Sweden

Comments and Discussions

 
QuestionCatch Exceptions Pin
bruno12346-Jun-12 9:02
bruno12346-Jun-12 9:02 
AnswerRe: Catch Exceptions Pin
jgauffin6-Jun-12 9:08
jgauffin6-Jun-12 9:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.