diff --git a/framework/test/Volo.Abp.AspNetCore.Uow.Tests/Volo/Abp/AspNetCore/Uow/UnitOfWorkMiddleware_Relational_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Uow.Tests/Volo/Abp/AspNetCore/Uow/UnitOfWorkMiddleware_Relational_Tests.cs index 00d4cdd2ec..72924981b5 100644 --- a/framework/test/Volo.Abp.AspNetCore.Uow.Tests/Volo/Abp/AspNetCore/Uow/UnitOfWorkMiddleware_Relational_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Uow.Tests/Volo/Abp/AspNetCore/Uow/UnitOfWorkMiddleware_Relational_Tests.cs @@ -117,4 +117,25 @@ public class UnitOfWorkMiddleware_Relational_Tests : AbpWebApplicationFactoryInt surfaced.ShouldNotBeNull(); (await CountAsync(name)).ShouldBe(0); } + + [Fact] + public async Task Result_Serialization_Failure_Rolls_Back_And_Does_Not_Commit_On_The_Error_Response() + { + EnableCompleteOnResponseStarting(); + var name = Guid.NewGuid().ToString("N"); + + try + { + var response = await Client.GetAsync("/api/uow-visibility/insert-then-throw-in-serialization?name=" + name); + await response.Content.ReadAsStringAsync(); + } + catch (Exception) + { + } + + // The action saved the row, then serializing the result failed before the response started. The error + // response is written by the upstream exception middleware after the request unit of work is disposed, + // so response-start completion must not commit the failed request. + (await CountAsync(name)).ShouldBe(0); + } } diff --git a/framework/test/Volo.Abp.AspNetCore.Uow.Tests/Volo/Abp/AspNetCore/Uow/UowVisibilityController.cs b/framework/test/Volo.Abp.AspNetCore.Uow.Tests/Volo/Abp/AspNetCore/Uow/UowVisibilityController.cs index 83d30d8410..ad0af449a2 100644 --- a/framework/test/Volo.Abp.AspNetCore.Uow.Tests/Volo/Abp/AspNetCore/Uow/UowVisibilityController.cs +++ b/framework/test/Volo.Abp.AspNetCore.Uow.Tests/Volo/Abp/AspNetCore/Uow/UowVisibilityController.cs @@ -104,4 +104,21 @@ public class UowVisibilityController : AbpController await Response.WriteAsync("inserted"); await Response.Body.FlushAsync(); } + + // The action succeeds (so the action filter saves changes), then serializing the object result throws + // before the response starts. The upstream exception middleware writes the error response after the + // request unit of work is disposed, so response-start completion must not commit the failed request. + [HttpGet] + [Route("insert-then-throw-in-serialization")] + [UnitOfWork(isTransactional: true)] + public async Task InsertThenThrowInSerialization(string name) + { + await _repository.InsertAsync(new UowVisibilityTestEntity(Guid.NewGuid(), name)); + return Ok(new ThrowingOnSerializeDto()); + } + + public class ThrowingOnSerializeDto + { + public string Value => throw new AbpException("boom while serializing the object result"); + } }