Click here to Skip to main content
Click here to Skip to main content
Go to top

How to handle transactions in ASP.NET MVC3

, 6 Jun 2012
Rate this:
Please Sign up or sign in to vote.
Handling 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();
}

License

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

Share

About the Author

jgauffin
Founder Gauffin Interactive AB
Sweden Sweden
Founder of OneTrueError, a .NET service which captures, analyzes and provide possible solutions for exceptions.
 
blog | twitter
Follow on   Twitter   LinkedIn

Comments and Discussions

 
QuestionCatch Exceptions Pinmemberbruno12346-Jun-12 9:02 
AnswerRe: Catch Exceptions Pinmemberjgauffin6-Jun-12 9:08 

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

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

| Advertise | Privacy | Mobile
Web02 | 2.8.140916.1 | Last Updated 6 Jun 2012
Article Copyright 2012 by jgauffin
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid