Deciding between HttpClient and WebClient

后端 未结 6 1233
情话喂你
情话喂你 2020-11-22 14:37

Our web app is running in .Net Framework 4.0. The UI calls controller methods through ajax calls.

We need to consume REST service from our vendor. I am evaluating

相关标签:
6条回答
  • 2020-11-22 15:18

    Perhaps you could think about the problem in a different way. WebClient and HttpClient are essentially different implementations of the same thing. What I recommend is implementing the Dependency Injection pattern with an IoC Container throughout your application. You should construct a client interface with a higher level of abstraction than the low level HTTP transfer. You can write concrete classes that use both WebClient and HttpClient, and then use the IoC container to inject the implementation via config.

    What this would allow you to do would be to switch between HttpClient and WebClient easily so that you are able to objectively test in the production environment.

    So questions like:

    Will HttpClient be a better design choice if we upgrade to .Net 4.5?

    Can actually be objectively answered by switching between the two client implementations using the IoC container. Here is an example interface that you might depend on that doesn't include any details about 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

    You can use Task.Run to make WebClient run asynchronously in its implementation.

    Dependency Injection, when done well helps alleviate the problem of having to make low level decisions upfront. Ultimately, the only way to know the true answer is try both in a live environment and see which one works the best. It's quite possible that WebClient may work better for some customers, and HttpClient may work better for others. This is why abstraction is important. It means that code can quickly be swapped in, or changed with configuration without changing the fundamental design of the app.

    0 讨论(0)
  • 2020-11-22 15:21

    Firstly, I am not an authority on WebClient vs. HttpClient, specifically. Secondly, from your comments above, it seems to suggest that WebClient is Sync ONLY whereas HttpClient is both.

    I did a quick performance test to find how WebClient (Sync calls), HttpClient (Sync and Async) perform. and here are the results.

    I see that as a huge difference when thinking for future, i.e. long running processes, responsive GUI, etc. (add to the benefit you suggest by framework 4.5 - which in my actual experience is hugely faster on IIS)

    0 讨论(0)
  • 2020-11-22 15:27

    I have benchmark between HttpClient, WebClient, HttpWebResponse then call Rest Web Api

    and result Call Rest Web Api Benchmark

    ---------------------Stage 1  ---- 10 Request
    
    {00:00:17.2232544} ====>HttpClinet
    {00:00:04.3108986} ====>WebRequest
    {00:00:04.5436889} ====>WebClient
    
    ---------------------Stage 1  ---- 10 Request--Small Size
    {00:00:17.2232544}====>HttpClinet
    {00:00:04.3108986}====>WebRequest
    {00:00:04.5436889}====>WebClient
    
    ---------------------Stage 3  ---- 10 sync Request--Small Size
    {00:00:15.3047502}====>HttpClinet
    {00:00:03.5505249}====>WebRequest
    {00:00:04.0761359}====>WebClient
    
    ---------------------Stage 4  ---- 100 sync Request--Small Size
    {00:03:23.6268086}====>HttpClinet
    {00:00:47.1406632}====>WebRequest
    {00:01:01.2319499}====>WebClient
    
    ---------------------Stage 5  ---- 10 sync Request--Max Size
    
    {00:00:58.1804677}====>HttpClinet    
    {00:00:58.0710444}====>WebRequest    
    {00:00:38.4170938}====>WebClient
        
    ---------------------Stage 6  ---- 10 sync Request--Max Size
    
    {00:01:04.9964278}====>HttpClinet    
    {00:00:59.1429764}====>WebRequest    
    {00:00:32.0584836}====>WebClient
    

    _____ WebClient Is faster ()

    var stopWatch = new Stopwatch();
            stopWatch.Start();
            for (var i = 0; i < 10; ++i)
            {
                CallGetHttpClient();
                CallPostHttpClient();
            }
    
            stopWatch.Stop();
    
            var httpClientValue = stopWatch.Elapsed;
    
            stopWatch = new Stopwatch();
    
            stopWatch.Start();
            for (var i = 0; i < 10; ++i)
            {
                CallGetWebRequest();
                CallPostWebRequest();
            }
    
            stopWatch.Stop();
    
            var webRequesttValue = stopWatch.Elapsed;
    
    
            stopWatch = new Stopwatch();
    
            stopWatch.Start();
            for (var i = 0; i < 10; ++i)
            {
    
                CallGetWebClient();
                CallPostWebClient();
    
            }
    
            stopWatch.Stop();
    
            var webClientValue = stopWatch.Elapsed;
    

    //-------------------------Functions

    private void CallPostHttpClient()
        {
            var httpClient = new HttpClient();
            httpClient.BaseAddress = new Uri("https://localhost:44354/api/test/");
            var responseTask = httpClient.PostAsync("PostJson", null);
            responseTask.Wait();
    
            var result = responseTask.Result;
            var readTask = result.Content.ReadAsStringAsync().Result;
    
        }
        private void CallGetHttpClient()
        {
            var httpClient = new HttpClient();
            httpClient.BaseAddress = new Uri("https://localhost:44354/api/test/");
            var responseTask = httpClient.GetAsync("getjson");
            responseTask.Wait();
    
            var result = responseTask.Result;
            var readTask = result.Content.ReadAsStringAsync().Result;
    
        }
        private string CallGetWebRequest()
        {
            var request = (HttpWebRequest)WebRequest.Create("https://localhost:44354/api/test/getjson");
    
            request.Method = "GET";
            request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
    
            var content = string.Empty;
    
            using (var response = (HttpWebResponse)request.GetResponse())
            {
                using (var stream = response.GetResponseStream())
                {
                    using (var sr = new StreamReader(stream))
                    {
                        content = sr.ReadToEnd();
                    }
                }
            }
    
            return content;
        }
        private string CallPostWebRequest()
        {
    
            var apiUrl = "https://localhost:44354/api/test/PostJson";
    
    
            HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(new Uri(apiUrl));
            httpRequest.ContentType = "application/json";
            httpRequest.Method = "POST";
            httpRequest.ContentLength = 0;
    
            using (var httpResponse = (HttpWebResponse)httpRequest.GetResponse())
            {
                using (Stream stream = httpResponse.GetResponseStream())
                {
                    var json = new StreamReader(stream).ReadToEnd();
                    return json;
                }
            }
    
            return "";
        }
    
        private string CallGetWebClient()
        {
            string apiUrl = "https://localhost:44354/api/test/getjson";
    
    
            var client = new WebClient();
    
            client.Headers["Content-type"] = "application/json";
    
            client.Encoding = Encoding.UTF8;
    
            var json = client.DownloadString(apiUrl);
    
    
            return json;
        }
    
        private string CallPostWebClient()
        {
            string apiUrl = "https://localhost:44354/api/test/PostJson";
    
    
            var client = new WebClient();
    
            client.Headers["Content-type"] = "application/json";
    
            client.Encoding = Encoding.UTF8;
    
            var json = client.UploadString(apiUrl, "");
    
    
            return json;
        }
    
    0 讨论(0)
  • 2020-11-22 15:31

    Unpopular opinion from 2020:

    When it comes to ASP.NET apps I still prefer WebClient over HttpClient because:

    1. The modern implementation comes with async/awaitable task-based methods
    2. Has smaller memory footprint and 2x-5x faster (other answers already mention that)
    3. It's suggested to "reuse a single instance of HttpClient for the lifetime of your application". But ASP.NET has no "lifetime of application", only lifetime of a request.
    0 讨论(0)
  • 2020-11-22 15:35

    HttpClient is the newer of the APIs and it has the benefits of

    • has a good async programming model
    • being worked on by Henrik F Nielson who is basically one of the inventors of HTTP, and he designed the API so it is easy for you to follow the HTTP standard, e.g. generating standards-compliant headers
    • is in the .Net framework 4.5, so it has some guaranteed level of support for the forseeable future
    • also has the xcopyable/portable-framework version of the library if you want to use it on other platforms - .Net 4.0, Windows Phone etc.

    If you are writing a web service which is making REST calls to other web services, you should want to be using an async programming model for all your REST calls, so that you don't hit thread starvation. You probably also want to use the newest C# compiler which has async/await support.

    Note: It isn't more performant AFAIK. It's probably somewhat similarly performant if you create a fair test.

    0 讨论(0)
  • 2020-11-22 15:39

    HttpClientFactory

    It's important to evaluate the different ways you can create an HttpClient, and part of that is understanding HttpClientFactory.

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

    This is not a direct answer I know - but you're better off starting here than ending up with new HttpClient(...) everywhere.

    0 讨论(0)
提交回复
热议问题