{"record":{"id":"b3fdf15b3c00fb22","repo":"dotnet/yarp","slug":"replacing-the-yarp-outgoing-request-httpcontent-is","errorCode":null,"errorMessage":"Replacing the YARP outgoing request HttpContent is not supported. You should configure the HttpContext.Request instead.","messagePattern":"Replacing the YARP outgoing request HttpContent is not supported\\. You should configure the HttpContext\\.Request instead\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/ReverseProxy/Forwarder/HttpForwarder.cs","lineNumber":450,"sourceCode":"        else\n        {\n            Debug.Assert(http1IsAllowed || outgoingVersion.Major != 1);\n            destinationRequest.Method = RequestUtilities.GetHttpMethod(context.Request.Method);\n            destinationRequest.Version = outgoingVersion;\n            destinationRequest.VersionPolicy = outgoingPolicy;\n        }\n\n        // :: Step 2: Setup copy of request body (background) Client --► Proxy --► Destination\n        // Note that we must do this before step (3) because step (3) may also add headers to the HttpContent that we set up here.\n        var requestContent = SetupRequestBodyCopy(context, isStreamingRequest, activityToken);\n        destinationRequest.Content = requestContent;\n\n        // :: Step 3: Copy request headers Client --► Proxy --► Destination\n        await transformer.TransformRequestAsync(context, destinationRequest, destinationPrefix, activityToken.Token);\n\n        if (!ReferenceEquals(requestContent, destinationRequest.Content) && destinationRequest.Content is not EmptyHttpContent)\n        {\n            throw new InvalidOperationException(\"Replacing the YARP outgoing request HttpContent is not supported. You should configure the HttpContext.Request instead.\");\n        }\n\n        // The transformer generated a response, do not forward.\n        if (RequestUtilities.IsResponseSet(context.Response))\n        {\n            return (destinationRequest, requestContent, false);\n        }\n\n        // Transforms may have taken a while, especially if they buffered the body, they count as forward progress.\n        activityToken.ResetTimeout();\n\n        FixupUpgradeRequestHeaders(context, destinationRequest, outgoingUpgrade, outgoingConnect);\n\n        // Allow someone to custom build the request uri, otherwise provide a default for them.\n        var request = context.Request;\n        destinationRequest.RequestUri ??= RequestUtilities.MakeDestinationAddress(destinationPrefix, request.Path, request.QueryString);\n\n        if (requestConfig?.AllowResponseBuffering != true)","sourceCodeStart":432,"sourceCodeEnd":468,"githubUrl":"https://github.com/dotnet/yarp/blob/bd11867bee7df522e7fd3effb08a9c85fd616908/src/ReverseProxy/Forwarder/HttpForwarder.cs#L432-L468","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before — throws: replacing HttpContent in a transform\npublic override ValueTask TransformRequestAsync(\n    HttpContext context, HttpRequestMessage request,\n    string destinationPrefix, CancellationToken ct)\n{\n    request.Content = new StringContent(\"{ \\\"modified\\\": true }\", Encoding.UTF8, \"application/json\");\n    return default;\n}\n// after — modify HttpContext.Request.Body instead\npublic override async ValueTask TransformRequestAsync(\n    HttpContext context, HttpRequestMessage request,\n    string destinationPrefix, CancellationToken ct)\n{\n    context.Request.Body = new MemoryStream(\n        Encoding.UTF8.GetBytes(\"{ \\\"modified\\\": true }\"));\n    context.Request.ContentLength = context.Request.Body.Length;\n    context.Request.Headers[\"Content-Type\"] = \"application/json\";\n    context.Request.Body.Position = 0;\n}","handlingStrategy":"validation","validationCode":"// In a unit test for your custom HttpTransformer\n// Verify the transform does NOT assign destinationRequest.Content\nvar request = new HttpRequestMessage();\nvar content = new StreamCopyHttpContent(/* ... */);\nrequest.Content = content;\nawait transformer.TransformRequestAsync(context, request, prefix, default);\nDebug.Assert(ReferenceEquals(content, request.Content),\n    \"Transform must not replace request.Content — modify HttpContext.Request instead\");","typeGuard":"// No type guard applicable — this is a behavioral contract on HttpTransformer.\n// Key rule: never assign to HttpRequestMessage.Content in TransformRequestAsync.","tryCatchPattern":"// Not applicable — this is a programming error that should be fixed at the source,\n// not caught at runtime. Fix the transformer to not replace HttpContent.","preventionTips":["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."],"tags":["transformer","request-body","api-misuse","httpcontent"],"backgroundTag":null,"analyzedSha":"bd11867bee7df522e7fd3effb08a9c85fd616908","analyzedAt":"2026-08-13T21:29:49.359Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}