Monday, November 16, 2009 10:01 AM srkirkland

Making ASP.NET MVC Actions be Transactional By Default

In an earlier post I talked about writing a Transaction attribute for MVC using NHibernate (though it isn’t really NHibernate specific).  The basic idea is that when an action marked with [Transaction] is executing (OnActionExecuting) you begin a transaction, and some time later (I use OnActionExecuted, but Kazi Manzur Rashid makes a good argument for OnResultExecuted) you commit that transaction if there was no error, or rollback if there was an error.
 
With that in mind, the implementation details aren’t really important because my motivation in this post is to make every action transactional by default.  Most of the actions on any real site will require retrieving, updating, saving or removing data so as a best practice I would like the user to have to specify if they choose not to use a transaction (maybe for a static about page).
 

What I’d Like to Accomplish

Given any action method (we’ll use Index), if there is no attribute it should execute in a Transaction:

public ActionResult Index()
{
    var data = //get data
    return View(data);
}

Now if we explicitly use a [Transaction] attribute it should still execute in a Transaction:

[Transaction]
public ActionResult Index()
{
    var data = //get data
    return View(data);
}

However, we can choose to use a self-defined [HandleTransactionManually] attribute which will Not Use a Transaction:

[HandleTransactionManually]
public ActionResult Index()
{
    var data = //get data
    return View(data);
}

 

The UseTransactionByDefaultAttribute and the SuperController

To enforce “global” rules like this it is necessary for all controllers to inherit from a custom controller I call the “SuperController”.  This is a class I was using anyway for helper/common methods and I assume a lot of other people don’t inherit directly from the Controller class as well.  The SuperController just inherits from System.Web.Mvc.Controller for the purposes of this post.

Now I am going to start by making a class attribute for the SuperController (or any controller really) which will cause us to use transactions by default.  First I’ll show the implementation and then explain it:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class UseTransactionsByDefaultAttribute : ActionFilterAttribute
{
    private IDbContext _dbContext;
    private bool _delegateTransactionSupport;
 
    public IDbContext DbContext
    {
        get
        {
            if (_dbContext == null) _dbContext = SmartServiceLocator<IDbContext>.GetService();
 
            return _dbContext;
        }
    }
 
 
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        _delegateTransactionSupport = ShouldDelegateTransactionSupport(filterContext);
 
        if (_delegateTransactionSupport) return;
 
        DbContext.BeginTransaction();
    }
 
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (_delegateTransactionSupport) return;
 
        if (DbContext.IsActive)
        {
            if (filterContext.Exception == null)
            {
                DbContext.CommitTransaction();
            }
            else
            {
                DbContext.RollbackTransaction();
            }
        }
    }
 
    private static bool ShouldDelegateTransactionSupport(ActionExecutingContext context)
    {
        var attrs = context.ActionDescriptor.GetCustomAttributes(typeof (TransactionalActionBaseAttribute), false);
 
        return attrs.Length > 0;
    }
}

Explanation – So this is an action filter attribute of course, and it works only on classes.  When the action executes (OnActionExecuting) I check to see if I should “delegate” the transaction support, and if so I’ll return and do nothing else.  If I am not delegating transaction support, I’ll begin the transaction.  The OnActionExecuted method does something similar and if we are not delegating it takes care of committing or doing the rollback depending on need.

The ShouldDelegateTransactionSupport is pretty interesting in that it check the custom attributes on the current action (using ActionDescriptor.GetCustomAttributes()) and if it sees any attributes that inherit from my custom TransactionalActionBaseAttribute class then it return true, meaning that we should delegate transaction support to the attributes.

This base class is pretty simple:

public class TransactionalActionBaseAttribute : ActionFilterAttribute
{
    
}

 

Handling Transactions Manually if Needed

If you want to override the default transaction then we need another attribute which will provide this support for us.  Since we have seen that inheriting from TransactionalActionBaseAttribute means we are delegated responsibility for the transaction, we can just ask for responsibility and then do nothing with it, as below

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class HandleTransactionManuallyAttribute : TransactionalActionBaseAttribute
{
}

 

Results – Show and Tell

Let’s see how this works for us.

Situation One – We don’t specify any transaction attribute and we get a transaction

public ActionResult Index()
{
    var data = //get data
    return View(data);
}

image

 

 

 

 

Situation Two – We specify a transaction attribute and we get the exact same result

[Transaction]
public ActionResult Index()
{
    var data = //get data
    return View(data);
}

image

 

 

 

 

Situation Three – We specify HandleTransactionManually and we get no transaction

[HandleTransactionManually]
public ActionResult Index()
{
    var data = //get data
    return View(data);
}

image

 

I’m a big fan of setting best practice defaults and default transactions are a good way to help get devs to use transactions as much as possible (which for many reasons is a good thing).

Enjoy!

Filed under: , , , , ,

Comments

# re: Making ASP.NET MVC Actions be Transactional By Default

Tuesday, November 17, 2009 3:57 AM by Barry

Why you using a transaction for a SELECT?..

# Twitter Trackbacks for Making ASP.NET MVC Actions be Transactional By Default - Scott's Blog [asp.net] on Topsy.com

Pingback from  Twitter Trackbacks for                 Making ASP.NET MVC Actions be Transactional By Default - Scott's Blog         [asp.net]        on Topsy.com

# Dew Drop &#8211; November 17, 2009 | Alvin Ashcraft&#039;s Morning Dew

Pingback from  Dew Drop &#8211; November 17, 2009 | Alvin Ashcraft&#039;s Morning Dew

# re: Making ASP.NET MVC Actions be Transactional By Default

Tuesday, November 17, 2009 10:01 AM by bradvin

where can we download a demo project?

# re: Making ASP.NET MVC Actions be Transactional By Default

Tuesday, November 17, 2009 12:43 PM by srkirkland

@Barry -- See nhprof.com/.../DoNotUseImplicitTransactions for why you should do transactions even for SELECT statements.

# re: Making ASP.NET MVC Actions be Transactional By Default

Tuesday, November 17, 2009 12:48 PM by srkirkland

@bradvin -- All of the code you need is in the post, but as for a demo project I use this logic in my codeplex project here: http://ucdarch.codeplex.com/

# re: Making ASP.NET MVC Actions be Transactional By Default

Wednesday, November 25, 2009 9:38 PM by Jason

Great post. I've been playing with NHibernate lately and have been wondering how to enforce transactions for ALL queries (for want of a better word)! This is an awesome solution that will save some time! Thanks!

# re: Making ASP.NET MVC Actions be Transactional By Default

Wednesday, February 03, 2010 11:06 AM by Joe

What do you do if the Commit causes an exception?  An example would be a Database Foreign Key Constraint or Not Null field requirement.

The reason I ask is that in this scenario, the filter is an "OnActionExecuted", meaning it has passed out of the scope of the Controller method.  Any exceptions that occur can no longer be handled by the Controller Method, so how do you get the message back to the user?

# re: Making ASP.NET MVC Actions be Transactional By Default

Sunday, October 31, 2010 7:09 PM by used cars ilkeston

I just sent this post to a bunch of my friends as I agree with most of what you’re saying here and the way you’ve presented it is awesome.

# re: Making ASP.NET MVC Actions be Transactional By Default

Monday, January 24, 2011 4:46 PM by eluchelereeva

Great Blog. I add this Post to my bookmarks.

# re: Making ASP.NET MVC Actions be Transactional By Default

Sunday, February 27, 2011 5:21 PM by anirwayjady

You certainly deserve a round of applause for your post and more specifically, your blog in general. Very high quality material

# re: Making ASP.NET MVC Actions be Transactional By Default

Friday, April 01, 2011 9:04 PM by ZepUlcete

Thanks For This Blog, was added to my bookmarks.

# re: Making ASP.NET MVC Actions be Transactional By Default

Sunday, May 01, 2011 8:36 PM by public auction car

I just book marked your blog on Digg and StumbleUpon.I enjoy reading your commentaries.

# re: Making ASP.NET MVC Actions be Transactional By Default

Saturday, November 26, 2011 11:12 PM by CNA Training

Hi there! Do you know if they make any plugins to assist with SEO? I'm trying to get my blog to rank for some targeted keywords but I'm not seeing very good results. If you know of any please share. Kudos!

# re: Making ASP.NET MVC Actions be Transactional By Default

Monday, December 19, 2011 10:08 PM by Bielizna Damska

Please let me know if you're looking for a article writer for your weblog. You have some really great posts and I feel I would be a good asset. If you ever want to take some of the load off, I'd absolutely love to write some content for your blog in exchange for a link back to mine. Please shoot me an email if interested. Many thanks!

Leave a Comment

(required) 
(required) 
(optional)
(required)