Manage Transactions in ASP.NET Core Razor Pages 2.0

When building traditional .NET MVC web apps, one of the first things I like to do is setup transaction management using an IActionFilter. Once this is in place, it is easy to use conventions to identify which controller actions I want participate within a database transaction.

However, in ASP.NET Core Razor Pages 2.0, code using IActionFilter does not work. Wait...what?

You can use all of the existing kinds of filters with Razor Pages except for Action filters, which apply only to action methods within Controllers. Razor Pages also introduce the new Page filter, represented by IPageFilter (or IAsyncPageFilter). This filter lets you add code that will run after a particular page handler has been selected, or before or after a handler method executes.
-- Steve Smith

With that in mind, I converted my IActionFilter to a IAsyncPageFilter.

In the code below:

  • AppDbContext is an Entity Framework Core DbContext object (the full Entity Framework works as well).
  • BeginTransactionAsync is a convenience method used to create a database transaction.
  • CommitTransactionAsync is a convenience method used to dispose the database transaction.

Take a look - it is pretty straight forward.

public class AppDbContextTransactionFilter : IAsyncPageFilter
{
    async Task IAsyncPageFilter.OnPageHandlerExecutionAsync(
        PageHandlerExecutingContext context, 
        PageHandlerExecutionDelegate next)
    {
        var db = 
            context
                .HttpContext.RequestServices
                .GetService(typeof(AppDbContext)) as AppDbContext;
        try
        {
            await db.BeginTransactionAsync();
            await next();
            await db.CommitTransactionAsync();
        }
        catch (Exception)
        {
            db.RollbackTransaction();
            throw;
        }
    }
    
    Task IAsyncPageFilter.OnPageHandlerSelectionAsync(
        PageHandlerSelectedContext context)
    {
        return Task.CompletedTask;
    }
}

Finally, we need to register our new page filter in Startup.cs.

services.AddMvc()
    .AddRazorPagesOptions(o=> 
    {
        o.Conventions.AddFolderApplicationModelConvention(
            "/", 
            e=> e.Filters.Add(new AppDbContextTransactionFilter()));
    });

With Razor Pages, conventions are applied either to a single page or to a folder. I want this filter to run on every page, so I will use the folder convention, and apply it to the root folder ("/").

That is it!

Matt Watson has an awesome article on the new features of Razor Pages 2.0. Check it out!

Enjoy!

Show Comments