Skip to content

Support completing request units of work before responses start - #26017

Open
maliming wants to merge 11 commits into
rel-10.7from
maliming/uow-commit-before-response
Open

Support completing request units of work before responses start#26017
maliming wants to merge 11 commits into
rel-10.7from
maliming/uow-commit-before-response

Conversation

@maliming

@maliming maliming commented Aug 19, 2026

Copy link
Copy Markdown
Member

AbpUnitOfWorkMiddleware completes the request's unit of work after await next(context) returns. But a response can reach the client before that: for the OpenIddict token endpoint, the ABP controller returns a SignInResult and OpenIddict's response handler writes the token JSON to the response while the downstream pipeline is still running.

A client that receives the token and immediately calls an API can then read data written while the token was issued before it is committed:

// sign in, then immediately call an API with the returned token
const { access_token } = await signIn();
const res = await fetch("/api/some-endpoint", { headers: { Authorization: `Bearer ${access_token}` } });
// res can fail: the data written while issuing the token is not committed yet

This adds an opt-in that completes the request's unit of work on HttpResponse.OnStarting, before the first response byte is sent, so the data is committed before the client receives the response. It only completes the middleware's own request unit of work; if a nested requiresNew unit of work is current, the request unit of work is completed after next returns, as before.

It is off by default. Enable it for every request the middleware handles with CompleteUnitOfWorkOnResponseStarting, or for selected path prefixes with CompleteUnitOfWorkOnResponseStartingUrls. The OpenIddict module enables it for its authorization, token, device authorization, pushed authorization, end-session, revocation, and end-user verification endpoints. Other requests keep the existing behavior.

Covered by added integration tests.

Scenario (opted-in requests) Before After
Data written during the request Committed after next returns, so an immediate follow-up request may not see it yet Committed before the response is sent, so a follow-up request sees it
Unit of work events and completed handlers Run after next returns Run before the first response byte
A database write after the response has started (e.g. a streaming response) Continues in the request unit of work Runs outside the request unit of work; provider access that requires an ambient unit of work throws
An exception after the response has started The request unit of work is not completed; an uncommitted transaction is disposed without committing The request unit of work is already completed, so the exception cannot undo its persisted changes

@maliming
maliming requested a lite review from Copilot August 20, 2026 06:37
@maliming maliming added this to the 10.7-final milestone Aug 20, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR changes AbpUnitOfWorkMiddleware to complete (commit) the ambient unit of work on HttpResponse.OnStarting, ensuring data written during a request is committed before the first response bytes can be flushed to the client. It also adds MVC integration tests to cover response-flush timing, post-flush exceptions, and behavior of repository/raw provider access after the response has started.

Changes:

  • Complete the ambient UoW via HttpResponse.OnStarting (and keep the existing post-pipeline completion as a fallback).
  • Add integration-test endpoints that flush the response mid-pipeline and then validate UoW completion / post-flush behavior.
  • Add integration tests verifying commit-before-flush, commit persistence on post-flush exception, and post-flush UoW behavior for repositories vs raw provider access.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpUnitOfWorkMiddleware.cs Commits the ambient UoW on Response.OnStarting before response bytes are sent.
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestController.cs Adds endpoints that flush responses mid-request to exercise UoW completion timing and post-flush access patterns.
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Tests.cs Adds integration tests validating commit-before-flush and post-flush semantics.
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/TestUnitOfWorkConfig.cs Adds a test-only flag to assert the UoW completion state after a response flush.
Suppressed comments (1)

framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Tests.cs:62

  • This assertion hard-codes the expected seed count ("(1)"), which can make the test fail if the test data builder changes or additional records are seeded in the future. It’s enough to assert that the count is unchanged across the flush boundary and that ambient=null.
        // After the response starts the request uow is gone; a repository still works via its
        // own implicit uow (ambient=null), so it no longer joins the request transaction.
        var body = await GetResponseAsStringAsync("/api/unitofwork-test/ReadRepositoryAfterResponseFlush");
        body.ShouldBe("before=ok(1);after=ok(1,ambient=null)");
    }

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

- Enable globally via CompleteUnitOfWorkOnResponseStarting or per path via the Urls list
- OpenIddict opts in its endpoints so token/session rows commit before the response
- Rename completedOnResponseStarting and fix response-start completion comments and docs
@maliming maliming changed the title Commit the ambient unit of work before the response starts Support completing request units of work before responses start Aug 21, 2026
@maliming
maliming marked this pull request as ready for review August 21, 2026 05:24
@maliming
maliming requested a balanced review from Copilot August 21, 2026 05:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

Suppressed comments (1)

framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Tests.cs:77

  • This assertion cannot hold with the response-start completion implemented here. Once uow.CompleteAsync() runs, AmbientUnitOfWork.GetCurrentByChecking() hides the completed UOW, and MemoryDbRepository.GetListAsync() calls GetDatabaseAsync(), which requires an ambient UOW and throws AbpException; the controller therefore writes after=threw:AbpException, not after=ok(1,ambient=null). Update the expected body (or explicitly start a new UOW if a successful post-response query is what this test intends to cover).
        body.ShouldBe("before=ok(1);after=ok(1,ambient=null)");

@maliming
maliming requested review from EngincanV and hikalkan August 21, 2026 06:25
@codecov

codecov Bot commented Aug 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 28.83436% with 116 lines in your changes missing coverage. Please review.
✅ Project coverage is 49.22%. Comparing base (e7a2255) to head (b2316be).
⚠️ Report is 264 commits behind head on rel-10.7.

Files with missing lines Patch % Lines
...Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestController.cs 0.00% 63 Missing ⚠️
...p/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Tests.cs 0.00% 49 Missing ⚠️
...Volo/Abp/AspNetCore/Uow/AbpUnitOfWorkMiddleware.cs 91.30% 0 Missing and 2 partials ⚠️
...olo/Abp/AspNetCore/Mvc/Uow/TestUnitOfWorkConfig.cs 0.00% 1 Missing ⚠️
...lo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs 96.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           rel-10.7   #26017      +/-   ##
============================================
+ Coverage     48.97%   49.22%   +0.24%     
============================================
  Files          3805     3805              
  Lines        132259   132421     +162     
  Branches      10027    10038      +11     
============================================
+ Hits          64774    65183     +409     
+ Misses        65559    65277     -282     
- Partials       1926     1961      +35     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@EngincanV
EngincanV removed their request for review August 21, 2026 12:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants