I've already answered this question [in another thread][1].
```CSharp
//Usage:
var handler = new RedirectHandler(new HttpClientHandler());
var client = new HttpClient(handler);
//redirects to HTTPS
var url = "http://stackoverflow.com/";
//AutoRedirect is true
var response = await HttpClientHelper.SendAsync(client, url, autoRedirect: true).ConfigureAwait(false);
//AutoRedirect is false
response = await HttpClientHelper.SendAsync(client, url, autoRedirect: false).ConfigureAwait(false);
public static class HttpClientHelper
{
private const string AutoRedirectPropertyKey = "RequestAutoRedirect";
private static readonly HttpRequestOptionsKey<bool?> AutoRedirectOptionsKey = new(AutoRedirectPropertyKey);
public static Task<HttpResponseMessage> SendAsync(HttpClient client, string url, bool autoRedirect = true)
{
var uri = new Uri(url);
var request = new HttpRequestMessage
{
RequestUri = uri,
Method = HttpMethod.Get
};
request.SetAutoRedirect(autoRedirect);
return client.SendAsync(request);
}
public static void SetAutoRedirect(this HttpRequestMessage request, bool autoRedirect)
{
request.Options.Set(AutoRedirectOptionsKey, autoRedirect);
}
public static bool? GetAutoRedirect(this HttpRequestMessage request)
{
request.Options.TryGetValue(AutoRedirectOptionsKey, out var value);
return value;
}
public static HttpMessageHandler? GetMostInnerHandler(this HttpMessageHandler? self)
{
while (self is DelegatingHandler handler)
{
self = handler.InnerHandler;
}
return self;
}
}
public class RedirectHandler : DelegatingHandler
{
private int MaxAutomaticRedirections { get; set; }
private bool InitialAutoRedirect { get; set; }
public RedirectHandler(HttpMessageHandler innerHandler) : base(innerHandler)
{
var mostInnerHandler = innerHandler.GetMostInnerHandler();
SetupCustomAutoRedirect(mostInnerHandler);
}
private void SetupCustomAutoRedirect(HttpMessageHandler? mostInnerHandler)
{
//Store the initial auto-redirect & max-auto-redirect values.
//Disabling auto-redirect and handle redirects manually.
try
{
switch (mostInnerHandler)
{
case HttpClientHandler hch:
InitialAutoRedirect = hch.AllowAutoRedirect;
MaxAutomaticRedirections = hch.MaxAutomaticRedirections;
hch.AllowAutoRedirect = false;
break;
case SocketsHttpHandler shh:
InitialAutoRedirect = shh.AllowAutoRedirect;
MaxAutomaticRedirections = shh.MaxAutomaticRedirections;
shh.AllowAutoRedirect = false;
break;
default:
Debug.WriteLine("[SetupCustomAutoRedirect] Unknown handler type: {0}", mostInnerHandler?.GetType().FullName);
InitialAutoRedirect = true;
MaxAutomaticRedirections = 17;
break;
}
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
InitialAutoRedirect = true;
MaxAutomaticRedirections = 17;
}
}
private bool IsRedirectAllowed(HttpRequestMessage request)
{
var value = request.GetAutoRedirect();
if (value == null)
return InitialAutoRedirect;
return value == true;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var redirectCount = 0;
var response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
//Manual Redirect
//https://github.com/dotnet/runtime/blob/ccfe21882e4a2206ce49cd5b32d3eb3cab3e530f/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RedirectHandler.cs
Uri? redirectUri;
while (IsRedirect(response) && IsRedirectAllowed(request) && (redirectUri = GetUriForRedirect(request.RequestUri!, response)) != null)
{
redirectCount++;
if (redirectCount > MaxAutomaticRedirections)
break;
response.Dispose();
// Clear the authorization header.
request.Headers.Authorization = null;
// Set up for the redirect
request.RequestUri = redirectUri;
if (RequestRequiresForceGet(response.StatusCode, request.Method))
{
request.Method = HttpMethod.Get;
request.Content = null;
if (request.Headers.TransferEncodingChunked == true)
request.Headers.TransferEncodingChunked = false;
}
// Issue the redirected request.
response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
return response;
}
private bool IsRedirect(HttpResponseMessage response)
{
switch (response.StatusCode)
{
case HttpStatusCode.MultipleChoices:
case HttpStatusCode.Moved:
case HttpStatusCode.Found:
case HttpStatusCode.SeeOther:
case HttpStatusCode.TemporaryRedirect:
case HttpStatusCode.PermanentRedirect:
return true;
default:
return false;
}
}
private static Uri? GetUriForRedirect(Uri requestUri, HttpResponseMessage response)
{
var location = response.Headers.Location;
if (location == null)
{
return null;
}
// Ensure the redirect location is an absolute URI.
if (!location.IsAbsoluteUri)
{
location = new Uri(requestUri, location);
}
// Per https://tools.ietf.org/html/rfc7231#section-7.1.2, a redirect location without a
// fragment should inherit the fragment from the original URI.
var requestFragment = requestUri.Fragment;
if (!string.IsNullOrEmpty(requestFragment))
{
var redirectFragment = location.Fragment;
if (string.IsNullOrEmpty(redirectFragment))
{
location = new UriBuilder(location) { Fragment = requestFragment }.Uri;
}
}
return location;
}
private static bool RequestRequiresForceGet(HttpStatusCode statusCode, HttpMethod requestMethod)
{
switch (statusCode)
{
case HttpStatusCode.Moved:
case HttpStatusCode.Found:
case HttpStatusCode.MultipleChoices:
return requestMethod == HttpMethod.Post;
case HttpStatusCode.SeeOther:
return requestMethod != HttpMethod.Get && requestMethod != HttpMethod.Head;
default:
return false;
}
}
}
```
The main idea is to disable automatic redirects and handle them manually using a custom **RedirectHandler**.
1. Before sending a request, we utilize the extension method **SetAutoRedirect** to store the redirect rule in the request's options dictionary.
2. Upon receiving a response, we check if it is a redirect. If it is, we then examine the request's options dictionary for a redirect rule using the extension method **GetAutoRedirect**.
3. Repeat #2 until **MaxAutomaticRedirections** is reached or there are no further redirects.
[1]: https://stackoverflow.com/a/76392591/3230716
3 years later, here is my implementation:
```CSharp
//Usage:
var handler = new RedirectHandler(new HttpClientHandler());
var client = new HttpClient(handler);
//redirects to HTTPS
var url = "http://stackoverflow.com/";
//AutoRedirect is true
var response = await HttpClientHelper.SendAsync(client, url, autoRedirect: true).ConfigureAwait(false);
//AutoRedirect is false
response = await HttpClientHelper.SendAsync(client, url, autoRedirect: false).ConfigureAwait(false);
public static class HttpClientHelper
{
private const string AutoRedirectPropertyKey = "RequestAutoRedirect";
private static readonly HttpRequestOptionsKey<bool?> AutoRedirectOptionsKey = new(AutoRedirectPropertyKey);
public static Task<HttpResponseMessage> SendAsync(HttpClient client, string url, bool autoRedirect = true)
{
var uri = new Uri(url);
var request = new HttpRequestMessage
{
RequestUri = uri,
Method = HttpMethod.Get
};
request.SetAutoRedirect(autoRedirect);
return client.SendAsync(request);
}
public static void SetAutoRedirect(this HttpRequestMessage request, bool autoRedirect)
{
request.Options.Set(AutoRedirectOptionsKey, autoRedirect);
}
public static bool? GetAutoRedirect(this HttpRequestMessage request)
{
request.Options.TryGetValue(AutoRedirectOptionsKey, out var value);
return value;
}
public static HttpMessageHandler? GetMostInnerHandler(this HttpMessageHandler? self)
{
while (self is DelegatingHandler handler)
{
self = handler.InnerHandler;
}
return self;
}
}
public class RedirectHandler : DelegatingHandler
{
private int MaxAutomaticRedirections { get; set; }
private bool InitialAutoRedirect { get; set; }
public RedirectHandler(HttpMessageHandler innerHandler) : base(innerHandler)
{
var mostInnerHandler = innerHandler.GetMostInnerHandler();
SetupCustomAutoRedirect(mostInnerHandler);
}
private void SetupCustomAutoRedirect(HttpMessageHandler? mostInnerHandler)
{
//Store the initial auto-redirect & max-auto-redirect values.
//Disabling auto-redirect and handle redirects manually.
try
{
switch (mostInnerHandler)
{
case HttpClientHandler hch:
InitialAutoRedirect = hch.AllowAutoRedirect;
MaxAutomaticRedirections = hch.MaxAutomaticRedirections;
hch.AllowAutoRedirect = false;
break;
case SocketsHttpHandler shh:
InitialAutoRedirect = shh.AllowAutoRedirect;
MaxAutomaticRedirections = shh.MaxAutomaticRedirections;
shh.AllowAutoRedirect = false;
break;
default:
Debug.WriteLine("[SetupCustomAutoRedirect] Unknown handler type: {0}", mostInnerHandler?.GetType().FullName);
InitialAutoRedirect = true;
MaxAutomaticRedirections = 17;
break;
}
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
InitialAutoRedirect = true;
MaxAutomaticRedirections = 17;
}
}
private bool IsRedirectAllowed(HttpRequestMessage request)
{
var value = request.GetAutoRedirect();
if (value == null)
return InitialAutoRedirect;
return value == true;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var redirectCount = 0;
var response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
//Manual Redirect
//https://github.com/dotnet/runtime/blob/ccfe21882e4a2206ce49cd5b32d3eb3cab3e530f/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RedirectHandler.cs
Uri? redirectUri;
while (IsRedirect(response) && IsRedirectAllowed(request) && (redirectUri = GetUriForRedirect(request.RequestUri!, response)) != null)
{
redirectCount++;
if (redirectCount > MaxAutomaticRedirections)
break;
response.Dispose();
// Clear the authorization header.
request.Headers.Authorization = null;
// Set up for the redirect
request.RequestUri = redirectUri;
if (RequestRequiresForceGet(response.StatusCode, request.Method))
{
request.Method = HttpMethod.Get;
request.Content = null;
if (request.Headers.TransferEncodingChunked == true)
request.Headers.TransferEncodingChunked = false;
}
// Issue the redirected request.
response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
return response;
}
private bool IsRedirect(HttpResponseMessage response)
{
switch (response.StatusCode)
{
case HttpStatusCode.MultipleChoices:
case HttpStatusCode.Moved:
case HttpStatusCode.Found:
case HttpStatusCode.SeeOther:
case HttpStatusCode.TemporaryRedirect:
case HttpStatusCode.PermanentRedirect:
return true;
default:
return false;
}
}
private static Uri? GetUriForRedirect(Uri requestUri, HttpResponseMessage response)
{
var location = response.Headers.Location;
if (location == null)
{
return null;
}
// Ensure the redirect location is an absolute URI.
if (!location.IsAbsoluteUri)
{
location = new Uri(requestUri, location);
}
// Per https://tools.ietf.org/html/rfc7231#section-7.1.2, a redirect location without a
// fragment should inherit the fragment from the original URI.
var requestFragment = requestUri.Fragment;
if (!string.IsNullOrEmpty(requestFragment))
{
var redirectFragment = location.Fragment;
if (string.IsNullOrEmpty(redirectFragment))
{
location = new UriBuilder(location) { Fragment = requestFragment }.Uri;
}
}
return location;
}
private static bool RequestRequiresForceGet(HttpStatusCode statusCode, HttpMethod requestMethod)
{
switch (statusCode)
{
case HttpStatusCode.Moved:
case HttpStatusCode.Found:
case HttpStatusCode.MultipleChoices:
return requestMethod == HttpMethod.Post;
case HttpStatusCode.SeeOther:
return requestMethod != HttpMethod.Get && requestMethod != HttpMethod.Head;
default:
return false;
}
}
}
```
The main idea is to disable automatic redirects and handle them manually using a custom **RedirectHandler**.
1. Before sending a request, we utilize the extension method **SetAutoRedirect** to store the redirect rule in the request's options dictionary.
2. Upon receiving a response, we check if it is a redirect. If it is, we then examine the request's options dictionary for a redirect rule using the extension method **GetAutoRedirect**.
3. Repeat #2 until **MaxAutomaticRedirections** is reached or there are no further redirects.