I'm building 3-tier ASP.NET Core web API. It consist of Data, Business (Core) and WebAPI layers:
- Core layer is independent (has not knowledge of EFCore nor any other project)
- Data layer - has reference to Core and EFCore
- WebAPI layer - final layer, knows about Core, Data and EFCore
I was struggling making decision how to handle db transactions. I'm injecting DbContext (scoped) to my Data classes and well as to the Controller (I'm omitting Business project, since it doesn't know EFCore at all). Because there is only one instance of the DbContext per request, it's the same object in the Data as well as in the Controller.
So, the Business logic is doing, what's supposed to do, calling objects in the Data layer. Whenever Data layer needs to Save changes into DB, it does. Everything is wrapped around transaction per request. So if something goes wrong... all changes are rolledback.
This is sample controller's method that shows how I did it (simplified):
[HttpPut("{id}")]
public IActionResult UpdateMeeting(int id, [FromBody] MeetingDto meeting)
{
using (var transaction = _dbContext.Database.BeginTransaction())
{
if (meeting == null)
{
return BadRequest();
}
_meetingService.AddMeetingChanges(meeting);
meeting.Id = id;
_meetingService.UpdateMeeting(meeting);
}
return NoContent();
}
Everything works great. So what's the issue? I need to repeat this:
using (var transaction = _dbContext.Database.BeginTransaction())
{
}
... in every operation, that needs transaction.
So I was thinking, is it possible to start the transaction in the middleware / pipeline (I'm not sure about terminology). Simply speaking - I want to begin transaction explicitly per EVERY request. I want to hide in the middleware. So that whenever I inject DbContext to the Data classes, there is transaction started already
EDIT: Possible solution:
Created a
UnitOfWorkclass:public class UnitOfWork { private readonly RequestDelegate _next; public UnitOfWork(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext httpContext, MyContext ctx) { using (var transaction = ctx.Database.BeginTransaction()) { await _next(httpContext); transaction.Commit(); } } }Injected
UnitOfWorkclass as a middleware afterUseHttpsRedirectionand beforeUseMvc:public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler(appBuilder => { appBuilder.Run(async context => { context.Response.StatusCode = 500; await context.Response.WriteAsync("An unexpected error happened. Please contact IT."); }); }); } app.UseHttpsRedirection(); app.UseMiddleware<UnitOfWork>(); app.UseMvc(); }