//I wanted to change AutoRedirect per request using the same HttpClient.
//But this is not possible because it throws an exception: This instance has already started one or more requests. Properties can only be modified before sending the first request.
//Here is my solution, Please let me know if you have any notes or optimizations

//Usage:
var handler = HttpClientHelper.CreateHttpHandler();
var client = HttpClientHelper.CreateHttpClient(handler);

//redirects to https
var url = "http://stackoverflow.com/";

//AllowAutoRedirect = true
var content = await HttpClientHelper.SendAsync(client, url, autoRedirect: true).ConfigureAwait(false);
//AllowAutoRedirect = false
content = await HttpClientHelper.SendAsync(client, url, autoRedirect: false).ConfigureAwait(false);

//Class
public static class HttpClientHelper
{
    public const string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36";
    private const string AutoRedirectPropertyKey = "RequestAutoRedirect";
    private static readonly HttpRequestOptionsKey<bool?> AutoRedirectOptionsKey = new(AutoRedirectPropertyKey);

    public static CustomHttpClient CreateHttpClient(HttpMessageHandler handler, bool disposeHandler = true)
    {
        var client = new CustomHttpClient(handler, disposeHandler);
        client.DefaultRequestHeaders.UserAgent.ParseAdd(UserAgent);
        return client;
    }
    public static HttpClientHandler CreateHttpHandler(bool autoRedirect = true)
    {
        return new HttpClientHandler
        {
            AllowAutoRedirect = autoRedirect
        };
    }
    public static void SetAutoRedirect(this HttpRequestMessage request, bool autoRedirect)
    {
        if (request == null)
            throw new ArgumentNullException(nameof(request));

        request.Options.Set(AutoRedirectOptionsKey, autoRedirect);
    }
    public static bool? GetAutoRedirect(this HttpRequestMessage request)
    {
        if (request == null)
            throw new ArgumentNullException(nameof(request));

        return request.Options.TryGetValue(AutoRedirectOptionsKey, out var value) ? value : default;
    }
    public static Task<HttpResponseMessage> SendAsync(CustomHttpClient 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 HttpMessageHandler? GetMostInnerHandler(this HttpMessageHandler? self)
    {
        while (self is DelegatingHandler handler)
        {
            self = handler.InnerHandler;
        }

        return self;
    }
}
public class CustomHttpClient : HttpClient
{
    public CustomHttpDelegate HandlerWrapper;

    public CustomHttpClient(HttpMessageHandler handler, bool disposeHandler = true) : this(new CustomHttpDelegate(handler), disposeHandler)
    {    }
    private CustomHttpClient(CustomHttpDelegate handler, bool disposeHandler = true) : base(handler, disposeHandler)
    {
        HandlerWrapper = handler;
    }
}

public class CustomHttpDelegate : DelegatingHandler
{
    private int MaxAutomaticRedirections { get; set; }
    private bool InitialAutoRedirect { get; set; }
    private readonly HttpMessageHandler? _mostInnerHandler;
    private readonly ExpiringDictionary<HttpRequestMessage, bool?> _customAutoRedirectDic = new();

    public CustomHttpDelegate(HttpMessageHandler innerHandler) : base(innerHandler)
    {
        _mostInnerHandler = innerHandler.GetMostInnerHandler();
        SetupCustomAutoRedirect();
    }

    private void SetupCustomAutoRedirect()
    {
        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("[GetAndTurnOffAutoRedirect] 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
        Uri? redirectUri;
        while (IsRedirect(response) && (redirectUri = GetUriForRedirect(request.RequestUri!, response)) != null && IsRedirectAllowed(request))
        {
            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;
        }
    }
}