Skip to content

Commit

Permalink
Fixed error handling in the unit of work middleware - now it properly…
Browse files Browse the repository at this point in the history
… rolls back DB transaction. Allowing to pass isolation level into the middleware (created a new issue for Windsor Castle - castleproject/Windsor#411).
  • Loading branch information
xhafan committed Jun 14, 2018
1 parent 235a239 commit 4446991
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 44 deletions.
4 changes: 2 additions & 2 deletions src/EmailMaker.Website/EmailMaker.Website.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,8 @@
<DependentUpon>Global.asax</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TransactionScopeUnitOfWorkHttpModule.cs" />
<Compile Include="UnitOfWorkHttpModule.cs" />
<Compile Include="HttpModules\TransactionScopeUnitOfWorkHttpModule.cs" />
<Compile Include="HttpModules\UnitOfWorkHttpModule.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="Content\js\emailmaker.jquery.extensions.js" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
using CoreUtils.Storages;
using Rebus.TransactionScopes;

namespace EmailMaker.Website
namespace EmailMaker.Website.HttpModules
{
// register TransactionScopeUnitOfWorkHttpModule in the web.config (system.webServer -> modules)
// transaction scope is needed to send messages to EmailMaker service only when the DB transaction successfully commits
// https://stackoverflow.com/a/8169117/379279
public class TransactionScopeUnitOfWorkHttpModule : IHttpModule
{
private const IsolationLevel DefaultIsolationLevel = IsolationLevel.ReadCommitted;

public void Init(HttpApplication application)
{
application.BeginRequest += Application_BeginRequest;
Expand Down Expand Up @@ -66,7 +68,7 @@ private TransactionScope GetTransactionScopePerWebRequest()
{
var newTransactionScope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions {IsolationLevel = IsolationLevel.ReadCommitted},
new TransactionOptions {IsolationLevel = DefaultIsolationLevel},
TransactionScopeAsyncFlowOption.Enabled
);
transactionScopeStoragePerWebRequest.Set(newTransactionScope);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
using System;
using System.Data;
using System.Web;
using CoreDdd.Domain.Events;
using CoreDdd.UnitOfWorks;
using CoreIoC;

namespace EmailMaker.Website
namespace EmailMaker.Website.HttpModules
{
// register UnitOfWorkHttpModule in the web.config (system.webServer -> modules)
public class UnitOfWorkHttpModule : IHttpModule
{
private const IsolationLevel DefaultIsolationLevel = IsolationLevel.ReadCommitted;

public void Init(HttpApplication application)
{
DomainEvents.EnableDelayedDomainEventHandling(); // messages sent from domain event handlers would not be sent if the main DB transaction rolls back
Expand All @@ -21,7 +24,7 @@ public void Init(HttpApplication application)
private void Application_BeginRequest(Object source, EventArgs e)
{
var unitOfWork = GetUnitOfWorkPerWebRequest();
unitOfWork.BeginTransaction();
unitOfWork.BeginTransaction(DefaultIsolationLevel);
}

private void Application_EndRequest(Object source, EventArgs e)
Expand All @@ -40,7 +43,7 @@ private void _DomainEventHandlingSurroundingTransaction(Action domainEventHandli

try
{
unitOfWork.BeginTransaction();
unitOfWork.BeginTransaction(DefaultIsolationLevel);

domainEventHandlingAction();

Expand Down
4 changes: 2 additions & 2 deletions src/EmailMaker.Website/Web.config
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<modules runAllManagedModulesForAllRequests="false">
<add name="TransactionScopeUnitOfWork" type="EmailMaker.Website.TransactionScopeUnitOfWorkHttpModule, EmailMaker.Website" preCondition="managedHandler" />
<!-- <add name="UnitOfWork" type="EmailMaker.Website.UnitOfWorkHttpModule, EmailMaker.Website" preCondition="managedHandler" />-->
<add name="TransactionScopeUnitOfWork" type="EmailMaker.Website.HttpModules.TransactionScopeUnitOfWorkHttpModule, EmailMaker.Website" preCondition="managedHandler" />
<!-- <add name="UnitOfWork" type="EmailMaker.Website.HttpModules.UnitOfWorkHttpModule, EmailMaker.Website" preCondition="managedHandler" />-->
<add name="PerRequestLifestyle" type="Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule, Castle.Windsor" preCondition="managedHandler" />
</modules>
</system.webServer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,28 @@ namespace EmailMaker.WebsiteCore.Middleware
// https://stackoverflow.com/a/8169117/379279
public class TransactionScopeUnitOfWorkMiddleware : IMiddleware
{
private readonly IsolationLevel _isolationLevel;

public TransactionScopeUnitOfWorkMiddleware(IsolationLevel isolationLevel = IsolationLevel.ReadCommitted)
{
_isolationLevel = isolationLevel;
}

public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
_BeginRequest();
try
{
_BeginRequest();

await next.Invoke(context);
await next.Invoke(context);

_EndRequest();
_EndRequest();
}
catch
{
_HandleErrorInRequest();
throw;
}
}

private void _BeginRequest()
Expand All @@ -32,8 +47,6 @@ private void _BeginRequest()

private void _EndRequest()
{
//if (HttpContext.Current.Server.GetLastError() != null) return; // todo: check out asp.net core error handling

var unitOfWork = GetUnitOfWorkPerWebRequest();
unitOfWork.Commit();

Expand All @@ -42,14 +55,14 @@ private void _EndRequest()
transactionScope.Dispose();
}

// private void Application_Error(Object source, EventArgs e) // todo: check out asp.net core error handling
// {
// var unitOfWork = GetUnitOfWorkPerWebRequest();
// unitOfWork.Rollback();
//
// var transactionScope = GetTransactionScopePerWebRequest();
// transactionScope.Dispose();
// }
private void _HandleErrorInRequest()
{
var unitOfWork = GetUnitOfWorkPerWebRequest();
unitOfWork.Rollback();

var transactionScope = GetTransactionScopePerWebRequest();
transactionScope.Dispose();
}

private IUnitOfWork GetUnitOfWorkPerWebRequest()
{
Expand All @@ -63,7 +76,7 @@ private TransactionScope GetTransactionScopePerWebRequest()
{
var newTransactionScope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions {IsolationLevel = IsolationLevel.ReadCommitted},
new TransactionOptions {IsolationLevel = _isolationLevel},
TransactionScopeAsyncFlowOption.Enabled
);
transactionScopeStorage.Set(newTransactionScope);
Expand Down
38 changes: 24 additions & 14 deletions src/EmailMaker.WebsiteCore/Middleware/UnitOfWorkMiddleware.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Data;
using System.Threading.Tasks;
using CoreDdd.Domain.Events;
using CoreDdd.UnitOfWorks;
Expand All @@ -9,30 +10,39 @@ namespace EmailMaker.WebsiteCore.Middleware
{
public class UnitOfWorkMiddleware : IMiddleware
{
public UnitOfWorkMiddleware()
private readonly IsolationLevel _isolationLevel;

public UnitOfWorkMiddleware(IsolationLevel isolationLevel = IsolationLevel.ReadCommitted)
{
_isolationLevel = isolationLevel;
DomainEvents.EnableDelayedDomainEventHandling(); // make sure messages sent from domain event handlers will not be sent if the main DB transaction rolls back
}

public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
_BeginRequest();
try
{
_BeginRequest();

await next.Invoke(context);
await next.Invoke(context);

_EndRequest();
_EndRequest();
}
catch
{
_HandleErrorInRequest();
throw;
}
}

private void _BeginRequest()
{
var unitOfWork = GetUnitOfWorkPerWebRequest();
unitOfWork.BeginTransaction();
unitOfWork.BeginTransaction(_isolationLevel);
}

private void _EndRequest()
{
//if (HttpContext.Current.Server.GetLastError() != null) return; // todo: check out asp.net core error handling

var unitOfWork = GetUnitOfWorkPerWebRequest();
unitOfWork.Commit();

Expand All @@ -45,7 +55,7 @@ private void _DomainEventHandlingSurroundingTransaction(Action domainEventHandli

try
{
unitOfWork.BeginTransaction();
unitOfWork.BeginTransaction(_isolationLevel);

domainEventHandlingAction();

Expand All @@ -57,12 +67,12 @@ private void _DomainEventHandlingSurroundingTransaction(Action domainEventHandli
throw;
}
}
//
// private void Application_Error(Object source, EventArgs e) // todo: check out asp.net core error handling
// {
// var unitOfWork = GetUnitOfWorkPerWebRequest();
// unitOfWork.Rollback();
// }

private void _HandleErrorInRequest()
{
var unitOfWork = GetUnitOfWorkPerWebRequest();
unitOfWork.Rollback();
}

private IUnitOfWork GetUnitOfWorkPerWebRequest()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Castle.MicroKernel.Registration;
using Castle.Windsor;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;

namespace EmailMaker.WebsiteCore
{
public static class WindsorRegistrationExtensions
{
// method taken from https://github.com/fir3pho3nixx/Windsor/blob/aspnet-core-windsor-final/src/Castle.Facilities.AspNetCore/WindsorRegistrationExtensions.cs
// this version allows to pass parameters into the middleware
public static void UseMiddlewareFromWindsor<T>(this IApplicationBuilder app, IWindsorContainer container, object argumentsAsAnonymousType)
where T : class, IMiddleware
{
container.Register(Component.For<T>());
app.Use(async (context, next) =>
{
var resolve = container.Resolve<T>(argumentsAsAnonymousType);
try
{
await resolve.InvokeAsync(context, async (ctx) => await next());
}
finally
{
container.Release(resolve);
}
});
}
}
}
13 changes: 6 additions & 7 deletions src/EmailMaker.WebsiteCore/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Data.SQLite;
using System.IO;
using System.Reflection;
using System.Transactions;
using Castle.Facilities.AspNetCore;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
Expand Down Expand Up @@ -37,7 +36,7 @@

namespace EmailMaker.WebsiteCore
{
public class Startup
public class Startup
{
private readonly WindsorContainer _windsorContainer = new WindsorContainer();

Expand Down Expand Up @@ -68,9 +67,6 @@ public IServiceProvider ConfigureServices(IServiceCollection services)
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddlewareFromWindsor<TransactionScopeUnitOfWorkMiddleware>(_windsorContainer);
//app.UseMiddlewareFromWindsor<UnitOfWorkMiddleware>(_windsorContainer);

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
Expand All @@ -80,6 +76,9 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
app.UseExceptionHandler("/Home/Error");
}

app.UseMiddlewareFromWindsor<TransactionScopeUnitOfWorkMiddleware>(_windsorContainer, new { isolationLevel = IsolationLevel.ReadCommitted });
//app.UseMiddlewareFromWindsor<UnitOfWorkMiddleware>(_windsorContainer, new { isolationLevel = IsolationLevel.ReadCommitted });

app.UseStaticFiles();

//app.UseAuthentication();
Expand Down Expand Up @@ -118,8 +117,8 @@ private void _RegisterApplicationComponents()
void _registerTransactionScopeStoragePerWebRequest()
{
_windsorContainer.Register(
Component.For<IStorage<TransactionScope>>()
.ImplementedBy<Storage<TransactionScope>>()
Component.For<IStorage<System.Transactions.TransactionScope>>()
.ImplementedBy<Storage<System.Transactions.TransactionScope>>()
.LifestyleScoped());
}

Expand Down

0 comments on commit 4446991

Please sign in to comment.