Skip to content

Instantly share code, notes, and snippets.

@jasondentler
Last active July 11, 2017 14:32
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jasondentler/b9ea3d83586102eb9a67 to your computer and use it in GitHub Desktop.
Save jasondentler/b9ea3d83586102eb9a67 to your computer and use it in GitHub Desktop.
TransactionScope + NHibernate unit of work in Web API
protected virtual bool InsideTransactionScope()
{
return Transaction.Current != null;
}
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;
using JobService.Filters;
namespace JobService
{
public class WebApiApplication : HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configuration.MessageHandlers.Add(new UnitOfWorkHandler());
GlobalConfiguration.Configuration.MessageHandlers.Add(new SerializationHandler());
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
FilterConfig.RegisterWebApiFilters(GlobalConfiguration.Configuration.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
}
}
using System;
using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using JobService.Extensions;
namespace JobService.Filters
{
internal class SerializationHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
if (response == null)
{
Log("Response is null");
return null;
}
if (response.Content == null)
{
Log("Response.Content is null");
return response;
}
var objectContent = response.Content as ObjectContent;
if (objectContent == null)
{
Log("Response content is {0}, not ObjectContent", response.Content.GetType());
return response;
}
Log("Serializing {0} to byte array.", objectContent.ObjectType);
response.Content = await Serialize(objectContent);
return response;
}
private static async Task<ByteArrayContent> Serialize(ObjectContent objectContent)
{
var data = await objectContent.ReadAsByteArrayAsync();
var serializedContent = new ByteArrayContent(data);
Log("Copying headers from ObjectContent to ByteArrayContent");
objectContent.CopyHeadersTo(serializedContent);
Log("Setting Content-Length");
serializedContent.Headers.ContentLength = data.LongLength;
Log("Set Content-Length to {0}", serializedContent.Headers.ContentLength);
return serializedContent;
}
private static void Log(string formatString, params object[] args)
{
Trace.WriteLine(string.Format(formatString, args), typeof (SerializationHandler).Name);
}
}
}
using System;
using System.Diagnostics;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Transactions;
using NHibernate;
namespace JobService.Filters
{
internal class UnitOfWorkHandler : DelegatingHandler
{
private readonly Func<HttpRequestMessage, TransactionScope> _transactionScopeFactory;
private readonly Func<HttpRequestMessage, ISession> _sessionFactory;
public UnitOfWorkHandler() : this(
request => request.GetDependencyScope().GetInstance<TransactionScope>(),
request => request.GetDependencyScope().GetInstance<ISession>())
{
}
public UnitOfWorkHandler(
Func<HttpRequestMessage, TransactionScope> transactionScopeFactory,
Func<HttpRequestMessage, ISession> sessionFactory)
{
_transactionScopeFactory = transactionScopeFactory;
_sessionFactory = sessionFactory;
}
public UnitOfWorkHandler(HttpMessageHandler innerHandler) : this(
request => request.GetDependencyScope().GetInstance<TransactionScope>(),
request => request.GetDependencyScope().GetInstance<ISession>(),
innerHandler)
{
}
public UnitOfWorkHandler(
Func<HttpRequestMessage, TransactionScope> transactionScopeFactory,
Func<HttpRequestMessage, ISession> sessionFactory,
HttpMessageHandler innerHandler) : base(innerHandler)
{
_transactionScopeFactory = transactionScopeFactory;
_sessionFactory = sessionFactory;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
Trace.WriteLine("awaiting SendAsync", "UnitOfWorkHandler");
var response = await base.SendAsync(request, cancellationToken);
Trace.WriteLine("SendAsync, awaited base.SendAsync", "UnitOfWorkHandler");
EndUnitOfWork(request, response);
return response;
}
protected virtual void EndUnitOfWork(HttpRequestMessage request, HttpResponseMessage response)
{
Trace.WriteLine("EndUnitOfWork", "UnitOfWorkHandler");
// Database transactions and service bus messages will have automatically been added to the
// TransactionScope so the "work" will all be committed or rolled back together as a single unit.
// This helps prevent race conditions and messages from being created that could reference an invalid
// state of the application or database.
using (var scope = _transactionScopeFactory(request))
using (var session = _sessionFactory(request))
using (var tx = session.Transaction)
{
if (IsActive(tx) && !Success(response))
{
Trace.WriteLine("Rolling back transaction due to unsuccessful response.", "UnitOfWorkHandler");
RollBack(tx);
return;
}
if (!IsActive(tx))
{
Trace.WriteLine("No active transaction.", "UnitOfWorkHandler");
return;
}
try
{
// NHibernate transactions need an explicit call to .Commit() even though they're enlisted in
// the action's distributed transaction (TransactionScope). This is a
Trace.WriteLine("Committing NHibernate transaction.", "UnitOfWorkHandler");
Commit(tx);
// When the TransactionScope is marked complete, it will finalize all NHibernate transactions
// that have been committed, as well as completing any NSB messages that were also automatically
// enlisted in the TransactionScope. The NSB messages do not require any special attention like
// the NH transactions above.
Trace.WriteLine("Completing transaction scope.", "UnitOfWorkHandler");
Complete(scope);
}
catch (Exception ex)
{
// Additionally, NHibernate transactions are not automatically rolled back if the TransactionScope
// is disposed without being completed. If an NHibernate transaction fails on .Commit(), a call
// to .Rollback() is necessary.
Trace.WriteLine("Caught exception while committing. Rolling back.", "UnitOfWorkHandler");
Trace.WriteLine(ex.ToString(), "UnitOfWorkHandler");
RollBack(tx);
throw;
}
}
}
protected virtual bool InsideTransactionScope()
{
return Transaction.Current != null;
}
protected virtual bool IsActive(ITransaction transaction)
{
return transaction.IsActive;
}
protected virtual bool Success(HttpResponseMessage response)
{
return response.IsSuccessStatusCode;
}
protected virtual void Commit(ITransaction transaction)
{
transaction.Commit();
}
protected virtual void RollBack(ITransaction transaction)
{
transaction.Rollback();
}
protected virtual void Complete(TransactionScope transactionScope)
{
transactionScope.Complete();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment