How to change the timeout on a .NET WebClient object
Asked Answered
I

13

259

I am trying to download a client's data to my local machine (programatically) and their webserver is very, very slow which is causing a timeout in my WebClient object.

Here is my code:

WebClient webClient = new WebClient();

webClient.Encoding = Encoding.UTF8;
webClient.DownloadFile(downloadUrl, downloadFile);

Is there a way to set an infinite timeout on this object? Or if not can anyone help me with an example on an alternate way to do this?

The URL works fine in a browser - it just takes about 3 minutes to show.

Insomuch answered 24/11, 2009 at 11:59 Comment(1)
Thanks for asking this. If I had know you could not set the timeout on a WebClient I would never have used it. It's annoying when the new tools are inferior to the current tools.Forefather
M
430

You can extend the timeout: inherit the original WebClient class and override the webrequest getter to set your own timeout, like in the following example.

MyWebClient was a private class in my case:

private class MyWebClient : WebClient
{
    protected override WebRequest GetWebRequest(Uri uri)
    {
        WebRequest w = base.GetWebRequest(uri);
        w.Timeout = 20 * 60 * 1000;
        return w;
    }
}
Moratorium answered 9/8, 2011 at 9:58 Comment(11)
what's the default timeout??Hanfurd
The default timeout is 100 seconds. Although it seems to run for 30 seconds.Deni
+1 for the easiest solution, without having to rewrite all the code involved, when using HttpWebRequest & HttpWebResponseExhilaration
A little easier to set the Timeout with a Timespan TimeSpan.FromSeconds(20).Milliseconds ... great solution though!Tory
@Tory One should use .TotalMilliseconds and not .Milliseconds!Vander
The name of the class should be: PatientWebClient ;)Highflown
Default timeout 100 seconds has been confirmed in this answer: https://mcmap.net/q/11363/-webclient-default-timeoutVastha
@CarterMedlin I think the 30 seconds are related to the timeout for establishing the connection, while the 100 seconds are related to the response timeout after successfully sending the request.Alon
this just saved me a bunch of code-rewriting. We are generating a 120mb file before sending it and it takes a few minutes.Pustulant
Not a good solution if you need download progress and download completed event handlers etc. which is not available in WebRequest.Wastebasket
@Moratorium did you seriously put 20 min timeout in your example or your intention was 20 seconds?Experienced
H
35

The first solution did not work for me but here is some code that did work for me.

    private class WebClient : System.Net.WebClient
    {
        public int Timeout { get; set; }

        protected override WebRequest GetWebRequest(Uri uri)
        {
            WebRequest lWebRequest = base.GetWebRequest(uri);
            lWebRequest.Timeout = Timeout;
            ((HttpWebRequest)lWebRequest).ReadWriteTimeout = Timeout;
            return lWebRequest;
        }
    }

    private string GetRequest(string aURL)
    {
        using (var lWebClient = new WebClient())
        {
            lWebClient.Timeout = 600 * 60 * 1000;
            return lWebClient.DownloadString(aURL);
        }
    }
Harebrained answered 24/7, 2015 at 15:0 Comment(0)
P
24

You need to use HttpWebRequest rather than WebClient as you can't set the timeout on WebClient without extending it (even though it uses the HttpWebRequest). Using the HttpWebRequest instead will allow you to set the timeout.

Pegues answered 24/11, 2009 at 12:5 Comment(4)
This is not true... you can see above that you can still use WebClient, albeit a custom implementation that overrides the WebRequest to set timeout.Rentier
"System.Net.HttpWebRequest.HttpWebRequest()' is obsolete: 'This API supports the .NET Framework infrastructure and is not intended to be used directly from your code"Klingensmith
@Klingensmith - because you shouldn't call that constructor: "Do not use the HttpWebRequest constructor. Use the WebRequest.Create method to initialize new HttpWebRequest objects." from msdn.microsoft.com/en-us/library/…. Also see #401065Oswald
Just to clarify: While this specific constructor should be avoided (it's no more part of newer .NET versions anyway), it is perfectly fine to use the Timeout property of HttpWebRequest. It's in milliseconds.Anticlastic
A
13

For anyone who needs a WebClient with a timeout that works for async/task methods, the suggested solutions won't work. Here's what does work:

public class WebClientWithTimeout : WebClient
{
    //10 secs default
    public int Timeout { get; set; } = 10000;

    //for sync requests
    protected override WebRequest GetWebRequest(Uri uri)
    {
        var w = base.GetWebRequest(uri);
        w.Timeout = Timeout; //10 seconds timeout
        return w;
    }

    //the above will not work for async requests :(
    //let's create a workaround by hiding the method
    //and creating our own version of DownloadStringTaskAsync
    public new async Task<string> DownloadStringTaskAsync(Uri address)
    {
        var t = base.DownloadStringTaskAsync(address);
        if(await Task.WhenAny(t, Task.Delay(Timeout)) != t) //time out!
        {
            CancelAsync();
        }
        return await t;
    }
}

I blogged about the full workaround here

Anagnorisis answered 9/9, 2019 at 20:42 Comment(1)
Or use HttpClientLangdon
I
11

For completeness, here's kisp's solution ported to VB (can't add code to a comment)

Namespace Utils

''' <summary>
''' Subclass of WebClient to provide access to the timeout property
''' </summary>
Public Class WebClient
    Inherits System.Net.WebClient

    Private _TimeoutMS As Integer = 0

    Public Sub New()
        MyBase.New()
    End Sub
    Public Sub New(ByVal TimeoutMS As Integer)
        MyBase.New()
        _TimeoutMS = TimeoutMS
    End Sub
    ''' <summary>
    ''' Set the web call timeout in Milliseconds
    ''' </summary>
    ''' <value></value>
    Public WriteOnly Property setTimeout() As Integer
        Set(ByVal value As Integer)
            _TimeoutMS = value
        End Set
    End Property


    Protected Overrides Function GetWebRequest(ByVal address As System.Uri) As System.Net.WebRequest
        Dim w As System.Net.WebRequest = MyBase.GetWebRequest(address)
        If _TimeoutMS <> 0 Then
            w.Timeout = _TimeoutMS
        End If
        Return w
    End Function

End Class

End Namespace
Ingratiating answered 19/1, 2012 at 11:39 Comment(2)
Author asked for a solution for C# language, not Visual Basic.Governorship
For completeness, it's a VB solution for people using VB who read this site and who may need a port of the C# code. Like myself.Ingratiating
L
11

Couldn't get the w.Timeout code to work when pulled out the network cable, it just wasn't timing out, moved to using HttpWebRequest and does the job now.

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(downloadUrl);
request.Timeout = 10000;
request.ReadWriteTimeout = 10000;
var wresp = (HttpWebResponse)request.GetResponse();

using (Stream file = File.OpenWrite(downloadFile))
{
    wresp.GetResponseStream().CopyTo(file);
}
Lemmueu answered 5/3, 2015 at 11:18 Comment(2)
This answer works great, but for anyone interested, if you use var wresp = await request.GetResponseAsync(); instead of var wresp = (HttpWebResponse)request.GetResponse(); you will get a massive timeout againHushaby
andrewjboyd: do you know why GetResponseAsync() does not work?Marjorymarjy
H
7

As Sohnee says, using System.Net.HttpWebRequest and set the Timeout property instead of using System.Net.WebClient.

You can't however set an infinite timeout value (it's not supported and attempting to do so will throw an ArgumentOutOfRangeException).

I'd recommend first performing a HEAD HTTP request and examining the Content-Length header value returned to determine the number of bytes in the file you're downloading and then setting the timeout value accordingly for subsequent GET request or simply specifying a very long timeout value that you would never expect to exceed.

Hower answered 24/11, 2009 at 12:40 Comment(0)
P
7
'CORRECTED VERSION OF LAST FUNCTION IN VISUAL BASIC BY GLENNG

Protected Overrides Function GetWebRequest(ByVal address As System.Uri) As System.Net.WebRequest
            Dim w As System.Net.WebRequest = MyBase.GetWebRequest(address)
            If _TimeoutMS <> 0 Then
                w.Timeout = _TimeoutMS
            End If
            Return w  '<<< NOTICE: MyBase.GetWebRequest(address) DOES NOT WORK >>>
        End Function
Partain answered 11/10, 2012 at 14:43 Comment(0)
S
7

Usage:

using (var client = new TimeoutWebClient(TimeSpan.FromSeconds(10)))
{
    return await client.DownloadStringTaskAsync(url).ConfigureAwait(false);
}

Class:

using System;
using System.Net;

namespace Utilities
{
    public class TimeoutWebClient : WebClient
    {
        public TimeSpan Timeout { get; set; }

        public TimeoutWebClient(TimeSpan timeout)
        {
            Timeout = timeout;
        }

        protected override WebRequest GetWebRequest(Uri uri)
        {
            var request = base.GetWebRequest(uri);
            if (request == null)
            {
                return null;
            }

            var timeoutInMilliseconds = (int) Timeout.TotalMilliseconds;

            request.Timeout = timeoutInMilliseconds;
            if (request is HttpWebRequest httpWebRequest)
            {
                httpWebRequest.ReadWriteTimeout = timeoutInMilliseconds;
            }

            return request;
        }
    }
}

But I recommend a more modern solution:

using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

public static async Task<string> ReadGetRequestDataAsync(Uri uri, TimeSpan? timeout = null, CancellationToken cancellationToken = default)
{
    using var source = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
    if (timeout != null)
    {
        source.CancelAfter(timeout.Value);
    }

    using var client = new HttpClient();
    using var response = await client.GetAsync(uri, source.Token).ConfigureAwait(false);

    return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}

It will throw an OperationCanceledException after a timeout.

Stav answered 20/2, 2019 at 19:14 Comment(2)
Didnt work, the async method still works indefinitelyAnagnorisis
Perhaps the problem is different and you need to use ConfigureAwait (false)?Stav
I
1

I had to fight with this issue yesterday and I've also ended up to write my custom extension class.

As you can see by looking at the code below and comparing it with the 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.

using System;
using System.Net;

namespace Ryadel.Components.Web
{
    /// <summary>
    /// An extension of the standard System.Net.WebClient
    /// featuring a customizable constructor and [Timeout] property.
    /// </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;
        }
        #endregion

        /// <summary>
        /// Web request timeout (in milliseconds)
        /// </summary>
        public int Timeout { get; set; }
    }
}

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.

In case you need additional info regarding this class or how to use it, check out this post I wrote on my blog.

Isogloss answered 4/7, 2020 at 1:51 Comment(0)
B
1

According to kisp solution this is my edited version working async:

Class WebConnection.cs

internal class WebConnection : WebClient
{
    internal int Timeout { get; set; }

    protected override WebRequest GetWebRequest(Uri Address)
    {
        WebRequest WebReq = base.GetWebRequest(Address);
        WebReq.Timeout = Timeout * 1000 // Seconds
        return WebReq;
    }
}

The async Task

private async Task GetDataAsyncWithTimeout()
{
    await Task.Run(() =>
    {
        using (WebConnection webClient = new WebConnection())
        {
            webClient.Timeout = 5; // Five seconds (the multiplication is in the override)
            webClient.DownloadData("https://www.yourwebsite.com");
        }
    });
} // await GetDataAsyncWithTimeout()

Else, if you don't want to use async:

private void GetDataSyncWithTimeout()
{
    using (WebConnection webClient = new WebConnection())
    {
        webClient.Timeout = 5; // Five seconds (the multiplication is in the override)
        webClient.DownloadData("https://www.yourwebsite.com");
    }
} // GetDataSyncWithTimeout()
Broadminded answered 17/7, 2020 at 16:4 Comment(2)
i'd say it would make more sense to return task instead of awaiting it. also you are missing multiplication by 1000 miliseconds in second caseHands
The multiplication by 1000 is directly in the override.Broadminded
D
0
using System.Net;

namespace Test.Services
{
    public class WebClientWithDelay : WebClient
    {
        public int TimeoutMinutes { get; set; }

        public WebClientWithDelay(int minutes)
        {
            TimeoutMinutes = minutes;
        }

        protected override WebRequest GetWebRequest(Uri uri)
        {
            WebRequest w = base.GetWebRequest(uri);
            var millisecond = TimeoutMinutes * 60000;
            w.Timeout = millisecond;
            return w;
        }
    }
}

To use it

 WebClientWithDelay webClient = new WebClientWithDelay(5);
Diatonic answered 14/9, 2023 at 9:31 Comment(0)
R
-1

In some cases it is necessary to add user agent to headers:

WebClient myWebClient = new WebClient();
myWebClient.DownloadFile(myStringWebResource, fileName);
myWebClient.Headers["User-Agent"] = "Mozilla/4.0 (Compatible; Windows NT 5.1; MSIE 6.0) (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)";

This was the solution to my case.

Credit:

http://genjurosdojo.blogspot.com/2012/10/the-remote-server-returned-error-504.html

Red answered 25/3, 2020 at 23:7 Comment(2)
Completely irrelevant to contest.Jacquelyn
@GrayProgrammerz no it is not Some servers will ignore your timeout if you do not specify the user agentRed

© 2022 - 2024 — McMap. All rights reserved.