Today I had to fix an odd timeout issue that suddenly occurred to one of my ASP.NET web applications: when I looked at the logs, I easily figured out that the timeout was caused by a HTTP request issued by a System.Net.WebClient instance that couldn't retrieve some data from an external website which was down.
Here was the actual error raised from the WebClient:
System.Net.Sockets.SocketException: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond (server ip address).
And here's the code snippet that triggered it:
1 2 3 |
using (var wc = new WebClient()) { var result = wc.DownloadString("https://the.external.website"); } |
Needless to say, I had wrapped the call inside a try/catch block, therefore I "just" had to deal with the WebClient timeout... which sadly happens to be hard-coded to 100.000 milliseconds (!), with no way to change it (!!), as the underlying WebRequest object used to estabilish the connection is not exposed by the "wrapper" class (!!!).
Not bad, right? Luckily enough, I googled around and quickly found a neat way to handle such situation on the StackOverflow web site. As a matter of fact, I ended up writing the folllowing subclass, which I called RyadelWebClient:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
using System; using System.Net; using System.Threading.Tasks; namespace Ryadel.Components.Web { /// <summary> /// An extension of the standard System.Net.WebClient /// featuring a customizable constructor and [Timeout] property. /// ref.: https://www.ryadel.com/en/webclient-request-timeout-class-c-sharp-dot-net /// </summary> public class RyadelWebClient : WebClient { /// <summary> /// Default constructor (30000 ms timeout) /// NOTE: timeout can be changed later on using the [Timeout] property. /// </summary> public RyadelWebClient() : this(30000) { } /// <summary> /// Constructor with customizable timeout /// </summary> /// <param name="timeout"> /// Web request timeout (in milliseconds) /// </param> public RyadelWebClient(int timeout) { Timeout = timeout; } #region Methods protected override WebRequest GetWebRequest(Uri uri) { WebRequest w = base.GetWebRequest(uri); w.Timeout = Timeout; ((HttpWebRequest)w).ReadWriteTimeout = Timeout; return w; } public new async Task<string> DownloadStringTaskAsync(Uri address) { var t = base.DownloadStringTaskAsync(address); if (await Task.WhenAny(t, Task.Delay(Timeout)) != t) CancelAsync(); return await t; } #endregion /// <summary> /// Web request timeout (in milliseconds) /// </summary> public int Timeout { get; set; } } } |
As you can see by looking at the code and comparing it with the StackOverflow accepted answer, I tried to tweak the suggestion a little bit more in order to have a more versatile class: this way you can set a precise timeout either upon instancing the object or right before using a method that uses the internal WebRequest handler. While I was there, I also took the chance to lower the default Timeout value to 30 seconds, as 100 seemed way too much for me.
Conclusion
That's it, at least for now: I hope that this class will help other developers to fix similar issues related to the System.Net.WebClient class and its timeout flaws.