Search code examples
c#asynchronoushttpclientabstract-class

extend HttpClient


I'm trying to create a class that extends httpclient but still is abstract.

public abstract class Repository : HttpClient
{
    public override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        //retry logic here
        var response = await base.SendAsync(request, cancellationToken);
        while (!IsValid(response))
        {
            Attempts++;
            response = await Retry(request, cancellationToken);
        }
        Attempts = 0;
        return response;
    }
    public int Attempts { get; set; }
    public abstract bool IsValid(HttpResponseMessage response);
    public abstract Task<HttpResponseMessage> Retry(HttpRequestMessage request, CancellationToken cancellationToken);
    public abstract Type GetPageForUri(Uri uri);
    public abstract Task<object> GetItems(Uri uri);
}

but when a subclass calls getStringAsync it does not call SendAsync in Repository

public class EmailClient : Repository
{
    public override bool IsValid(HttpResponseMessage response)
    {
        return response.IsSuccessStatusCode;
    }
    public override Task<HttpResponseMessage> Retry(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return base.SendAsync(request, cancellationToken);
    }
    public override Type GetPageForUri(Uri uri)
    {
        return typeof(EmailPage);
    }
    public override async Task<object> GetItems(Uri uri)
    {
        var emails = await GetStringAsync(uri);
        return emails;
    }
}

How what am I doing wrong? should I make a HttpClientHandler and include that with HttpClient? Ive tried to read about virtual if that makes any diffrent. but i dont get it why it does not get called. Can anyone help me?


Solution

  • HttpClient is a thin wrapper around an HttpMessageHandler implementation (typically HttpClientHandler, which properties for features supported across most runtime implementations)

    There's actually an HttpMessageHandler base class intended for decorators like your retry scenario, DelegatingHandler, which you could implement like this:

    Something like:

    public class RetryMessageHandler : DelegatingHandler
    {
        readonly int retryCount;
    
        public RetryMessageHandler(int retryCount)
            : this(new HttpClientHandler(), retryCount)
        {
        }
    
        public RetryMessageHandler(HttpMessageHandler innerHandler, int retryCount)
            : base(innerHandler)
        {
            this.retryCount = retryCount;
        }
    
        public override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            HttpResponseMessage response = null;
    
            for (int i = 0; i<=retryCount; i++)
            {
                response = await base.SendAsync(request, cancellationToken);
    
                if (response.IsSuccessStatusCode)
                {
                    return response;
                }
            }
    
            return response;
        }
    }
    

    Which you would then use like this:

    HttpClient httpClient = HttpClientFactory.CreatePipeline(new HttpClientHandler(), 
        new [] { new RetryMessageHandler(3) });
    
    var response = await httpClient.SendAsync(request);
    

    Additionally, I'd recommend your EmailClient wraps/depends on HttpClient, rather than extending it. (Google "Composition vs Inheritance" for more information on why)