Coder Perfect

Choosing between HttpClient and WebClient is a difficult task.

Problem

Our web application is now running in. Net Framework 4.0 is the latest version of the framework. Ajax calls are used by the UI to call controller methods.

Our vendor’s REST service must be consumed. In.Net 4.0, I’m analyzing the optimal technique to call a REST API. The Basic Authentication Scheme is required for the REST service, which can provide data in both XML and JSON formats. There is no requirement for uploading/downloading large amounts of data, and I see no reason for it in the future. I looked at a few open source code projects for REST consumption and couldn’t find enough value in them to justify the project’s additional reliance. I began testing WebClient and HttpClient. I used NuGet to get HttpClient for.Net 4.0.

When I looked into the differences between WebClient and HttpClient, I found that a single HttpClient may handle several calls and reuse resolved DNS, cookie configuration, and authentication. I have yet to see any practical benefits that we might get as a result of the changes.

I ran a fast performance test to see how WebClient (synchronous calls) and HttpClient (asynchronous and synchronous calls) performed. Here are the outcomes:

For all requests, the same HttpClient instance is used (min – max)

For each request, a new HttpClient is used (min – max)

public class AHNData
{
    public int i;
    public string str;
}

public class Program
{
    public static HttpClient httpClient = new HttpClient();
    private static readonly string _url = "http://localhost:9000/api/values/";

    public static void Main(string[] args)
    {
       #region "Trace"
       Trace.Listeners.Clear();

       TextWriterTraceListener twtl = new TextWriterTraceListener(
           "C:\\Temp\\REST_Test.txt");
       twtl.Name = "TextLogger";
       twtl.TraceOutputOptions = TraceOptions.ThreadId | TraceOptions.DateTime;

       ConsoleTraceListener ctl = new ConsoleTraceListener(false);
       ctl.TraceOutputOptions = TraceOptions.DateTime;

       Trace.Listeners.Add(twtl);
       Trace.Listeners.Add(ctl);
       Trace.AutoFlush = true;
       #endregion

       int batchSize = 1000;

       ParallelOptions parallelOptions = new ParallelOptions();
       parallelOptions.MaxDegreeOfParallelism = batchSize;

       ServicePointManager.DefaultConnectionLimit = 1000000;

       Parallel.For(0, batchSize, parallelOptions,
           j =>
           {
               Stopwatch sw1 = Stopwatch.StartNew();
               GetDataFromHttpClientAsync<List<AHNData>>(sw1);
           });
       Parallel.For(0, batchSize, parallelOptions,
            j =>
            {
                Stopwatch sw1 = Stopwatch.StartNew();
                GetDataFromHttpClientSync<List<AHNData>>(sw1);
            });
       Parallel.For(0, batchSize, parallelOptions,
            j =>
            {
                using (WebClient client = new WebClient())
                {
                   Stopwatch sw = Stopwatch.StartNew();
                   byte[] arr = client.DownloadData(_url);
                   sw.Stop();

                   Trace.WriteLine("WebClient Sync " + sw.ElapsedMilliseconds);
                }
           });

           Console.Read();
        }

        public static T GetDataFromWebClient<T>()
        {
            using (var webClient = new WebClient())
            {
                webClient.BaseAddress = _url;
                return JsonConvert.DeserializeObject<T>(
                    webClient.DownloadString(_url));
            }
        }

        public static void GetDataFromHttpClientSync<T>(Stopwatch sw)
        {
            HttpClient httpClient = new HttpClient();
            var response = httpClient.GetAsync(_url).Result;
            var obj = JsonConvert.DeserializeObject<T>(
                response.Content.ReadAsStringAsync().Result);
            sw.Stop();

            Trace.WriteLine("HttpClient Sync " + sw.ElapsedMilliseconds);
        }

        public static void GetDataFromHttpClientAsync<T>(Stopwatch sw)
        {
           HttpClient httpClient = new HttpClient();
           var response = httpClient.GetAsync(_url).ContinueWith(
              (a) => {
                 JsonConvert.DeserializeObject<T>(
                    a.Result.Content.ReadAsStringAsync().Result);
                 sw.Stop();
                 Trace.WriteLine("HttpClient Async " + sw.ElapsedMilliseconds);
              }, TaskContinuationOptions.None);
        }
    }
}

Asked by user3092913

Solution #1

HttpClient is the newest of the APIs, and it offers the following features:

If you’re developing a web service that makes REST calls to other web services, you should use an async programming paradigm for all of your REST requests to avoid thread starvation. You’ll almost certainly want to use the most recent C# compiler, which supports async/await.

Note that, AFAIK, it is not more performant. If you develop a fair test, it will most likely perform similarly.

Answered by 2 revs

Solution #2

Understanding HttpClientFactory is a key element of evaluating the various ways you might generate a HttpClient.

https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests

I realize this isn’t a direct answer, but it’s better to start here than to wind up with new HttpClient(…) all over the place.

Answered by Simon_Weaver

Solution #3

2020’s unpopular viewpoint:

When it comes to ASP.NET applications, I still favor WebClient to HttpClient for the following reasons:

Answered by Alex from Jitbit

Solution #4

To begin with, I am not an expert on WebClient vs. HttpClient in particular. Second, based on your comments, it appears like WebClient is only for Sync, but HttpClient is for both.

That, in my opinion, makes a significant impact when planning for the future, such as long-running procedures, a responsive user interface, and so on (add to the benefit you suggest by framework 4.5 – which in my actual experience is hugely faster on IIS)

Answered by Anthony Horne

Solution #5

Perhaps you could consider the issue from a different perspective. WebClient and HttpClient are essentially the same thing implemented differently. I advocate using an IoC Container to apply the Dependency Injection pattern throughout your application. A client interface with a higher level of abstraction than the low-level HTTP transfer should be built. You can write concrete classes that use both WebClient and HttpClient, and then use the IoC container to inject the implementation via config.

This would allow you to switch between HttpClient and WebClient with ease, allowing you to test objectively in the production environment.

So questions like:

Can be objectively addressed by using the IoC container to swap between the two client implementations. Here’s an example of a user interface that doesn’t offer any information regarding HttpClient or WebClient.

/// <summary>
/// Dependency Injection abstraction for rest clients. 
/// </summary>
public interface IClient
{
    /// <summary>
    /// Adapter for serialization/deserialization of http body data
    /// </summary>
    ISerializationAdapter SerializationAdapter { get; }

    /// <summary>
    /// Sends a strongly typed request to the server and waits for a strongly typed response
    /// </summary>
    /// <typeparam name="TResponseBody">The expected type of the response body</typeparam>
    /// <typeparam name="TRequestBody">The type of the request body if specified</typeparam>
    /// <param name="request">The request that will be translated to a http request</param>
    /// <returns></returns>
    Task<Response<TResponseBody>> SendAsync<TResponseBody, TRequestBody>(Request<TRequestBody> request);

    /// <summary>
    /// Default headers to be sent with http requests
    /// </summary>
    IHeadersCollection DefaultRequestHeaders { get; }

    /// <summary>
    /// Default timeout for http requests
    /// </summary>
    TimeSpan Timeout { get; set; }

    /// <summary>
    /// Base Uri for the client. Any resources specified on requests will be relative to this.
    /// </summary>
    Uri BaseUri { get; set; }

    /// <summary>
    /// Name of the client
    /// </summary>
    string Name { get; }
}

public class Request<TRequestBody>
{
    #region Public Properties
    public IHeadersCollection Headers { get; }
    public Uri Resource { get; set; }
    public HttpRequestMethod HttpRequestMethod { get; set; }
    public TRequestBody Body { get; set; }
    public CancellationToken CancellationToken { get; set; }
    public string CustomHttpRequestMethod { get; set; }
    #endregion

    public Request(Uri resource,
        TRequestBody body,
        IHeadersCollection headers,
        HttpRequestMethod httpRequestMethod,
        IClient client,
        CancellationToken cancellationToken)
    {
        Body = body;
        Headers = headers;
        Resource = resource;
        HttpRequestMethod = httpRequestMethod;
        CancellationToken = cancellationToken;

        if (Headers == null) Headers = new RequestHeadersCollection();

        var defaultRequestHeaders = client?.DefaultRequestHeaders;
        if (defaultRequestHeaders == null) return;

        foreach (var kvp in defaultRequestHeaders)
        {
            Headers.Add(kvp);
        }
    }
}

public abstract class Response<TResponseBody> : Response
{
    #region Public Properties
    public virtual TResponseBody Body { get; }

    #endregion

    #region Constructors
    /// <summary>
    /// Only used for mocking or other inheritance
    /// </summary>
    protected Response() : base()
    {
    }

    protected Response(
    IHeadersCollection headersCollection,
    int statusCode,
    HttpRequestMethod httpRequestMethod,
    byte[] responseData,
    TResponseBody body,
    Uri requestUri
    ) : base(
        headersCollection,
        statusCode,
        httpRequestMethod,
        responseData,
        requestUri)
    {
        Body = body;
    }

    public static implicit operator TResponseBody(Response<TResponseBody> readResult)
    {
        return readResult.Body;
    }
    #endregion
}

public abstract class Response
{
    #region Fields
    private readonly byte[] _responseData;
    #endregion

    #region Public Properties
    public virtual int StatusCode { get; }
    public virtual IHeadersCollection Headers { get; }
    public virtual HttpRequestMethod HttpRequestMethod { get; }
    public abstract bool IsSuccess { get; }
    public virtual Uri RequestUri { get; }
    #endregion

    #region Constructor
    /// <summary>
    /// Only used for mocking or other inheritance
    /// </summary>
    protected Response()
    {
    }

    protected Response
    (
    IHeadersCollection headersCollection,
    int statusCode,
    HttpRequestMethod httpRequestMethod,
    byte[] responseData,
    Uri requestUri
    )
    {
        StatusCode = statusCode;
        Headers = headersCollection;
        HttpRequestMethod = httpRequestMethod;
        RequestUri = requestUri;
        _responseData = responseData;
    }
    #endregion

    #region Public Methods
    public virtual byte[] GetResponseData()
    {
        return _responseData;
    }
    #endregion
}

Full code

HttpClient Implementation

In WebClient’s implementation, Task.Run can be used to make it run asynchronously.

Dependency When done correctly, injection can help to alleviate the issue of needing to make low-level decisions up front. Finally, the only way to tell for sure is to test both in a real-world setting and observe which one performs best. It’s possible that WebClient will be more suitable for some consumers, while HttpClient will be more suitable for others. It is for this reason that abstraction is so crucial. It means that code may be swiftly swapped in or updated through configuration without affecting the app’s core design.

BTW, there are a slew of other reasons to utilize an abstraction rather than contacting one of these low-level APIs directly. One of the most important is unit-testability.

Answered by Christian Findlay

Post is based on https://stackoverflow.com/questions/20530152/deciding-between-httpclient-and-webclient