diff --git a/src/ReverseProxy/Transforms/RequestTransformContext.cs b/src/ReverseProxy/Transforms/RequestTransformContext.cs
index 74d87f21e..52255e428 100644
--- a/src/ReverseProxy/Transforms/RequestTransformContext.cs
+++ b/src/ReverseProxy/Transforms/RequestTransformContext.cs
@@ -53,8 +53,9 @@ public QueryTransformContext Query
///
/// The URI prefix for the proxy request. This includes the scheme and host and can optionally include a
/// port and path base. The 'Path' and 'Query' properties will be appended to this after the transforms have run.
+ /// Changing this value can have side effects on load balancing and health checks.
///
- public string DestinationPrefix { get; init; } = default!;
+ public string DestinationPrefix { get; set; } = default!;
///
/// A indicating that the request is being aborted.
diff --git a/test/ReverseProxy.Tests/Forwarder/HttpForwarderTests.cs b/test/ReverseProxy.Tests/Forwarder/HttpForwarderTests.cs
index 39f087566..87039551d 100644
--- a/test/ReverseProxy.Tests/Forwarder/HttpForwarderTests.cs
+++ b/test/ReverseProxy.Tests/Forwarder/HttpForwarderTests.cs
@@ -153,10 +153,12 @@ public async Task NormalRequestWithTransforms_Works()
httpContext.Response.Body = proxyResponseStream;
var destinationPrefix = "https://localhost:123/a/b/";
+ Uri originalRequestUri = null;
var transforms = new DelegateHttpTransforms()
{
OnRequest = (context, request, destination) =>
{
+ originalRequestUri = request.RequestUri;
request.RequestUri = new Uri(destination + "prefix"
+ context.Request.Path + context.Request.QueryString);
request.Headers.Remove("transformHeader");
@@ -214,6 +216,7 @@ public async Task NormalRequestWithTransforms_Works()
var proxyError = await sut.SendAsync(httpContext, destinationPrefix, client, ForwarderRequestConfig.Empty, transforms);
+ Assert.Null(originalRequestUri); // Should only be set by the transformer
Assert.Equal(ForwarderError.None, proxyError);
Assert.Equal(234, httpContext.Response.StatusCode);
var reasonPhrase = httpContext.Features.Get().ReasonPhrase;
diff --git a/test/ReverseProxy.Tests/Forwarder/HttpTransformerTests.cs b/test/ReverseProxy.Tests/Forwarder/HttpTransformerTests.cs
index abdb72e0a..ff451ed75 100644
--- a/test/ReverseProxy.Tests/Forwarder/HttpTransformerTests.cs
+++ b/test/ReverseProxy.Tests/Forwarder/HttpTransformerTests.cs
@@ -13,6 +13,7 @@
using Microsoft.Net.Http.Headers;
using Xunit;
using Yarp.ReverseProxy.Transforms;
+using Yarp.ReverseProxy.Transforms.Builder;
using Yarp.ReverseProxy.Transforms.Builder.Tests;
using Yarp.Tests.Common;
@@ -116,6 +117,28 @@ public async Task TransformRequestAsync_ContentLengthAndTransferEncoding_Content
Assert.False(proxyRequest.Headers.TryGetValues(HeaderNames.TransferEncoding, out var _));
}
+ [Fact]
+ public async Task TransformRequestAsync_SetDestinationPrefix()
+ {
+ const string updatedDestinationPrefix = "https://contoso.com";
+ var transformer = TransformBuilderTests.CreateTransformBuilder().CreateInternal(context =>
+ {
+ context.AddRequestTransform(transformContext =>
+ {
+ transformContext.DestinationPrefix = updatedDestinationPrefix;
+ return ValueTask.CompletedTask;
+ });
+ });
+ var httpContext = new DefaultHttpContext();
+ var proxyRequest = new HttpRequestMessage(HttpMethod.Get, requestUri: (string)null)
+ {
+ Content = new ByteArrayContent(Array.Empty()),
+ };
+
+ await transformer.TransformRequestAsync(httpContext, proxyRequest, "https://localhost", CancellationToken.None);
+ Assert.Equal(new Uri(updatedDestinationPrefix), proxyRequest.RequestUri);
+ }
+
[Theory]
[InlineData(HttpStatusCode.Continue)]
[InlineData(HttpStatusCode.SwitchingProtocols)]
diff --git a/test/ReverseProxy.Tests/Transforms/DestinationPrefixTransformTests.cs b/test/ReverseProxy.Tests/Transforms/DestinationPrefixTransformTests.cs
new file mode 100644
index 000000000..c6b6ba8be
--- /dev/null
+++ b/test/ReverseProxy.Tests/Transforms/DestinationPrefixTransformTests.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Yarp.ReverseProxy.Transforms.Tests;
+
+public class DestinationPrefixTransformTests
+{
+ [Fact]
+ public async Task UpdateDestinationPrefix()
+ {
+ const string newDestinationPrefix = "http://localhost:8080";
+ var context = new RequestTransformContext()
+ {
+ DestinationPrefix = "http://contoso.com:5000"
+ };
+ var transform = new DestinationPrefixTransform(newDestinationPrefix);
+ await transform.ApplyAsync(context);
+ }
+
+ private class DestinationPrefixTransform(string newDestinationPrefix) : RequestTransform
+ {
+ public override ValueTask ApplyAsync(RequestTransformContext context)
+ {
+ context.DestinationPrefix = newDestinationPrefix;
+ return ValueTask.CompletedTask;
+ }
+ }
+}