ASP.NET MVC View and Transaction

Scott showed how to render the Grid in a Transaction. Certainly it does the job but in my opinion view component should not be responsible for this kind of cross cutting concerns, instead we can use the Action Filters. Lets see how we can utilize the Action Filter in this scenario instead of modifying the Grid code. What Scott is trying to do is encapsulate the data access operation in a transaction, the Action Filter has several methods which the ASP.NET MVC framework executes in different stages of a request. In this case, we will use the OnActionExecuting which fires before the code enters into the controller method to start a transaction and OnResultExecuted which fires when the view is processed, we will commit/rollback based upon the status, here is the code that would process the action result in a transaction:

namespace AltNorthwind
{
    using System.Web.Mvc;
    using System.Transactions;

    public class TransactionAttribute : ActionFilterAttribute
    {
        private CommittableTransaction transaction;

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            transaction = new CommittableTransaction();

            base.OnActionExecuting(filterContext);
        }

        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            base.OnResultExecuted(filterContext);

            try
            {
                if ((filterContext.Exception != null) && (!filterContext.ExceptionHandled))
                {
                    transaction.Rollback();
                }
                else
                {
                    transaction.Commit();
                }
            }
            finally
            {
                transaction.Dispose();
            }
        }
    }
}

Next, we will decorate the Controller Action method with this attribute like the following:

[Transaction]
public ActionResult Index()
{
    return View(repository.All());
}

Hope, you would find it useful.

Shout it

9 Comments

  • Hi,

    Assume you have this sequence in your controller:
    (1 )operation X
    (2) exception
    (3) operation Y

    If the exception is not handled, the transaction Rollback never occurs and the system is left with operation X executed and not rolled back.

    Edmund

  • This will create seperate transaction when you use things like RenderAction in views. Why don't you use global.asax.beginrequest to start a transaction?

  • I agree that OnActionExecuting/OnResultExecuted is better solution then Grid modification. But your solution is absolutelly wrong and doesn't work! There is only one instance of attribute class so your private "transaction" member is always the same and this will not work when there will be more requests in parallel. You should save current transaction into some storage that is unique for each request - i.e. you should use ViewData dictionary with some unique key (ugly but works).

  • I'm so sorry! I have thought that GetCustomAttributes gets the same instance (that they are cached). I'm wrong :(

  • In order to get this to work with LINQ to SQL, I had to add this line after the new CommittableTransaction() call:

    Transaction.Current = transaction;
    After the commit and rollback statements I also had to add:

    Transaction.Current = null;

    You could also preserve the existing transaction as shown here (but I think it will always be null if this is run in an ActionFilter):

    http://msdn.microsoft.com/en-us/library/ms172146.aspx


    Thanks for the great post, it helped me better understand filters and that their memory exists for the duration of the ActionResult call.

  • Per Edmund's suggestion I added the following to handle the case where an error is thrown during the Controller Action Executing:

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
    if ((filterContext.Exception != null) && (!filterContext.ExceptionHandled))
    {
    transaction.Rollback();
    Transaction.Current = null;
    }
    }

  • Hi,

    Combining CharlieK's and Charlie's last comments, I got this to work in my LINQ2SQL MVC solution.

    Thanks !

    Edmund

  • We take a very similar approach within S#arp Architecture using NHibernate. The code for the attribute is at http://github.com/codai/Sharp-Architecture/blob/master/src/SharpArch/SharpArch.Web/NHibernate/TransactionAttribute.cs

  • Amen!!

Comments have been disabled for this content.