dotnet/yarp · error · InvalidOperationException
Replacing the YARP outgoing request HttpContent is not suppo
Error message
Replacing the YARP outgoing request HttpContent is not supported. You should configure the HttpContext.Request instead.
What it means
YARP creates a `StreamCopyHttpContent` to stream the client request body to the destination. After calling `HttpTransformer.TransformRequestAsync`, it verifies by reference that the `destinationRequest.Content` was not replaced. Replacing the content object breaks YARP's internal body-copy plumbing (the background copy task, the TCS signaling, and content-length tracking), so it is treated as a programming error and throws InvalidOperationException.
Source
Thrown at src/ReverseProxy/Forwarder/HttpForwarder.cs:450
else
{
Debug.Assert(http1IsAllowed || outgoingVersion.Major != 1);
destinationRequest.Method = RequestUtilities.GetHttpMethod(context.Request.Method);
destinationRequest.Version = outgoingVersion;
destinationRequest.VersionPolicy = outgoingPolicy;
}
// :: Step 2: Setup copy of request body (background) Client --► Proxy --► Destination
// Note that we must do this before step (3) because step (3) may also add headers to the HttpContent that we set up here.
var requestContent = SetupRequestBodyCopy(context, isStreamingRequest, activityToken);
destinationRequest.Content = requestContent;
// :: Step 3: Copy request headers Client --► Proxy --► Destination
await transformer.TransformRequestAsync(context, destinationRequest, destinationPrefix, activityToken.Token);
if (!ReferenceEquals(requestContent, destinationRequest.Content) && destinationRequest.Content is not EmptyHttpContent)
{
throw new InvalidOperationException("Replacing the YARP outgoing request HttpContent is not supported. You should configure the HttpContext.Request instead.");
}
// The transformer generated a response, do not forward.
if (RequestUtilities.IsResponseSet(context.Response))
{
return (destinationRequest, requestContent, false);
}
// Transforms may have taken a while, especially if they buffered the body, they count as forward progress.
activityToken.ResetTimeout();
FixupUpgradeRequestHeaders(context, destinationRequest, outgoingUpgrade, outgoingConnect);
// Allow someone to custom build the request uri, otherwise provide a default for them.
var request = context.Request;
destinationRequest.RequestUri ??= RequestUtilities.MakeDestinationAddress(destinationPrefix, request.Path, request.QueryString);
if (requestConfig?.AllowResponseBuffering != true)View on GitHub (pinned to bd11867bee)
Solutions
- Modify the request body via `HttpContext.Request.Body` (the incoming request stream) instead of replacing `HttpRequestMessage.Content`. YARP will pick up the modified stream when it sets up the body copy.
- If you need to replace the body entirely, write the new bytes to `context.Request.Body` before the transformer runs, and set `context.Request.ContentLength` accordingly.
- To suppress the body, set `context.Request.Body = Stream.Null` and let YARP create an empty content naturally.
- If you need custom content serialization, buffer the new body into a `MemoryStream`, assign it to `context.Request.Body`, and reset the position to 0.
Example fix
// before — throws: replacing HttpContent in a transform
public override ValueTask TransformRequestAsync(
HttpContext context, HttpRequestMessage request,
string destinationPrefix, CancellationToken ct)
{
request.Content = new StringContent("{ \"modified\": true }", Encoding.UTF8, "application/json");
return default;
}
// after — modify HttpContext.Request.Body instead
public override async ValueTask TransformRequestAsync(
HttpContext context, HttpRequestMessage request,
string destinationPrefix, CancellationToken ct)
{
context.Request.Body = new MemoryStream(
Encoding.UTF8.GetBytes("{ \"modified\": true }"));
context.Request.ContentLength = context.Request.Body.Length;
context.Request.Headers["Content-Type"] = "application/json";
context.Request.Body.Position = 0;
} Defensive patterns
Strategy: validation
Validate before calling
// In a unit test for your custom HttpTransformer
// Verify the transform does NOT assign destinationRequest.Content
var request = new HttpRequestMessage();
var content = new StreamCopyHttpContent(/* ... */);
request.Content = content;
await transformer.TransformRequestAsync(context, request, prefix, default);
Debug.Assert(ReferenceEquals(content, request.Content),
"Transform must not replace request.Content — modify HttpContext.Request instead"); Type guard
// No type guard applicable — this is a behavioral contract on HttpTransformer. // Key rule: never assign to HttpRequestMessage.Content in TransformRequestAsync.
Try / catch
// Not applicable — this is a programming error that should be fixed at the source, // not caught at runtime. Fix the transformer to not replace HttpContent.
Prevention
- Never assign to `HttpRequestMessage.Content` inside a custom HttpTransformer — always modify `HttpContext.Request.Body` instead.
- If the body must change, write to `context.Request.Body` and update `context.Request.ContentLength`.
- Add a unit test that asserts `ReferenceEquals(originalContent, request.Content)` after the transform runs.
When it happens
Trigger: A custom `HttpTransformer` implementation sets `request.Content = new StringContent(...)`, `new ByteArrayContent(...)`, or any other `HttpContent` instance inside `TransformRequestAsync`. The post-transform check at line 448 finds `!ReferenceEquals(requestContent, destinationRequest.Content)` and the new content is not `EmptyHttpContent`, triggering the throw.
Common situations: A developer writes a custom transform to inject or replace the request body (e.g., adding a JSON payload, wrapping content, or signing the body). They assign a new `HttpContent` to the outgoing `HttpRequestMessage.Content`, unaware that YARP manages the content lifecycle internally.
Related errors
- Current request can't be delegated. Either the request body
- Unsupported request method '{method}'.
- An error occurred when reading the request body from the cli
- The request body copy was canceled.
- The {typeof(IReverseProxyFeature).FullName} is missing the {
AI-assisted analysis of dotnet/yarp@bd11867bee (2026-08-13).
Data as JSON: /api/errors/b3fdf15b3c00fb22.
Report an issue: GitHub.