Last active
July 11, 2017 14:32
-
-
Save jasondentler/b9ea3d83586102eb9a67 to your computer and use it in GitHub Desktop.
TransactionScope + NHibernate unit of work in Web API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
protected virtual bool InsideTransactionScope() | |
{ | |
return Transaction.Current != null; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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