9

I've spent quite a bit of time researching, but yet to find a reliable answer, so I'd like to start a fresh, new question.

What is the guidance for using the HttpClient class in an ASP.NET webforms based website? Note, this is not an MVC site, it's not .NET Core anything. It's an ASP.NET Webforms website. It is on the latest .NET Framework (4.8).

The technical challenge seems to be surrounding all the "you're doing it wrong" with HttpClient that leads to socket exhaustion. So the advice is to use dependency injection (presuming you're doing a .net core web app) or static instance (usually noted for console apps). But would you really declare a static variable for a webforms site (in App_Code or Global.asax)?

It seems incredible that to make use of any kind of REST API's, which is common, is so difficult to "get right."

The other challenge is that all its methods are async. However, I think that's doable after reading enough posts (and SO answers) from Stephen Cleary.

My main concern here, with HttpClient specifically, is it's proper usage (lifetime management, avoiding the socket exhaustion issue and also stale DNS issue with a "long lived" instance) in a webforms based website.

Margo Noreen
  • 396
  • 1
  • 3
  • 12
  • 1
    You could just use a library like [Flurl](https://flurl.dev/) that handles the socket exhaustion details for you, and has a nicer syntax. – mason Sep 24 '20 at 17:12
  • Thank you @mason. I have been looking at Flurl! I just didn't know if it ended up with the same questions... How best to make use of it? Wrap it up in a class in App_Code or just use it in any page's codebehind (.aspx.cs) that needs it? Etc. Have you used it in an ASP.NET Web Forms website? Any experiences you can share? – Margo Noreen Sep 28 '20 at 14:04
  • You can use it however you see fit. Yes, I have used it in Web Forms. It worked well, which is why I recommended it to you. – mason Sep 28 '20 at 14:06

3 Answers3

4

Quite simple really. Ignore the IDisposable interface it implements and create a single point/object like ExampleApiClient, from where you'll call it.

Declare your member as:

private static readonly HttpClient client = new HttpClient();

And encapsulate its use by using it in a public function.

There is a reason to create a new client though. If for example you want to call multiple different apis, with different proxies or default headers, create one for each api, but certainly not one for each request.

Athanasios Kataras
  • 25,191
  • 4
  • 32
  • 61
  • Thank you, and yes I have read that, but still had some edge questions. Do you declare the static clients in ... global.asax, in some custom class in App_Code? Any concerns about use under load (many page requests at the same time accessing and using (not the using statement, just, you know, using the variable in code)? – Margo Noreen Sep 28 '20 at 14:01
  • You declare it wherever it makes sense. My general rule is to create it where it is needed, inside the class that uses it. This way any additions like handles, will apply only to the local usage. By the way, check httpclientfactory which also solves the DNS change problem in modern .net world. – Athanasios Kataras Nov 29 '21 at 21:10
  • please can you share more details on it? like I am calling RestFull API (.Net Core 3.1 web API project) from Webform using HTTP Client then the page is continuously loading and not getting a response. But when I am calling that same API from MVC or .NET core then the response is getting successful. – Prashant Girase Mar 08 '23 at 09:16
  • I suggest you create a question sharing your code, environment details etc and I'll be happy to take a look at it and provide any insights I have on the problem. – Athanasios Kataras Mar 08 '23 at 10:50
  • @AthanasiosKataras thanks for the response. I will create a new question on Stack Overflow. – Prashant Girase Mar 08 '23 at 11:20
  • I added a new question. [How to call ASP.NET Core Web APIs from ASP.NET Webform using HttpClient?](https://stackoverflow.com/questions/75672894/how-to-call-asp-net-core-web-apis-from-asp-net-webform-using-httpclient) – Prashant Girase Mar 08 '23 at 12:14
3

The way to avoid socket exhaustion and the DNS issues you mention is to use the HttpClientFactory which manages client creation for you. It is described here in the Microsoft docs.

Here is some code which shows you how it can be used in a webforms app, assuming you are targeting the .NET framework version 4.7.2 or above which in your case is true. The basic idea is to new up a ServiceCollection and configure it to give you an httpClientfactory. Then build it and use it to give you and httpClientfactory.

First install the nuget package Microsoft.Extensions.Http. Then add a reference to System.Net.Http.

using Microsoft.Extensions.DependencyInjection;
using System;
using System.Net.Http;

namespace HttpClientExample
{
    public static class HttpClientFactoryProvider
    {
        private static IHttpClientFactory _httpClientFactory;
        private const string _ClientKey = "DotNetClientKey";

        private static IHttpClientFactory GetHttpClientFactory()
        {
            if (_httpClientFactory != null)
            {
                return _httpClientFactory;
            }
            var serviceCollection = new ServiceCollection();

            serviceCollection.AddHttpClient(_ClientKey);
            var serviceProvider = serviceCollection.BuildServiceProvider();
            _httpClientFactory = serviceProvider.GetService<IHttpClientFactory>();
            return _httpClientFactory;
        } 

        public static HttpClient GetClient()
        {
            return GetHttpClientFactory().CreateClient(_ClientKey);
        }
    }
 
    //here's an example of how a webform can accesses the httpclient through the above code
    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {    
            var client = HttpClientFactoryProvider.GetClient();
            //rest of code that uses the client
        }
    }
}

This is a simple example but you can configure things to do what you want just like you can in a dotnet core app. Again this is explained in here Microsoft docs.

Dave Barnett
  • 2,045
  • 2
  • 16
  • 32
  • 2
    thank you for reply! I hope to try some prototyping with that. I had not considered the new IHttpClientFactory in asp.net webforms due to the whole dependency injection thing that the docs constantly reference. I hadn't considered adding a ServiceCollection to webforms before ... just hadn't been on my radar and haven't really come across much mentioning it for webforms use. Worth an experiment or two, I suppose. Wondering if there are any real world applications doing so for reference. Thank you again. – Margo Noreen Oct 26 '20 at 16:50
1

This will avoid both the port exhaustion and the stale DNS issues in Web Forms.

private static readonly object CachedHttpClientLockObject = new object();
public static HttpClient CachedHttpClient {
    get {
        if (HttpRuntime.Cache.Get("Acme.HttpClient") is HttpClient client) {
            return client;
        }
        lock (CachedHttpClientLockObject) {
            client = HttpRuntime.Cache.Get("Acme.HttpClient") as HttpClient;
            if (client != null) {
                return client;
            }
            client = new HttpClient();
            HttpRuntime.Cache.Insert("Acme.HttpClient", client, null, DateTime.UtcNow.AddMinutes(30), Cache.NoSlidingExpiration, CacheItemPriority.Normal, new CacheItemRemovedCallback(CachedHttpClientRemovedCallback));
        }
        return client;
    }
}

private static void CachedHttpClientRemovedCallback(string key, object val, CacheItemRemovedReason reason) {
    (val as HttpClient)?.Dispose();
}