WebClient Class: Gotchas and Basics

By Peter Bromberg

Some basics of the useful WebClient class along with some things to watch out for.

For Oscar Peterson, who died today. We will miss you.

 

Trailing Slashes

When making a request for an HTTP resource, the trailing slash is significant in a URI and makes a difference in how the webserver processes the request.

For example, if you have the URI http://www.mysite.com/userstuff/, you can expect the server to look in the userstuff subdirectory and return the default document (provided directory browsing is turned off). But without the trailing slash, the server will look for a file called "userstuff" in the site's root folder - not what you want. If no such file is present, most webservers will return a 301 Permanent Redirect response, suggesting that the client try again with the trailing slash. A .NET WebClient or WebRequest will respond just like a browser would - by re-requesting the resource with the trailing slash appended.

What this means is that when you leave out a trailing slash when it should have been included, your request will still work, but you'll have created an extra round trip to the server.

WebProxy

If you do not have a proxy in use, you should set the Proxy property on all your WebClient and WebRequest objects to null. If you do not do this, you run the risk of having the Framework attempt to "autodetect" proxy settings, which can add up to an additional 30 seconds to your requests. As an alternative to having to set the Proxy property on every request, you can set global defaults like so:

WebRequest.DefaultWebProxy =null;

The above will apply for the lifetime of the application domain.

WebClient Class

WebClient is simply a wrapper over the WebRequest and WebResponse classes which can save a lot of extra lines of code. WebClient allows you to deal with your requests and responses with strings, byte arrays, files, or streams. Unfortunately, you can't use WebClient all the time; some features such as cookies are only available with WebRequest and WebResponse. You can't use the same instance of WebClient to perform repeated requests in sequence. For example if you are using a ThreadPool to handle multiple requests, you need to create a separate WebClient object in each thread.

The following are the steps to use WebClient:

1. Instantiate a WebClient instance

2. Assign the Proxy property

3. Assign the Credentials property if authentication is required.

4. Call one of the DownloadXXX or UploadXXX methods with the correct URI.

Each method of WebClient is overloaded to accept a Uri instance instead of a string address. The Upload methods are similar and each returns the response from the server:

  UploadData Overloaded. Uploads a data buffer to a resource with the specified URI.
  UploadDataAsync Overloaded. Uploads a data buffer to a resource identified by a URI. This method does not block the calling thread.
  UploadFile Overloaded. Uploads a local file to a resource with the specified URI.
  UploadFileAsync Overloaded. Uploads the specified local file to the specified resource. These methods do not block the calling thread.
  UploadString Overloaded. Uploads the specified string to the specified resource.
  UploadStringAsync Overloaded. Uploads the specified string to the specified resource. These methods do not block the calling thread.
  UploadValues Overloaded. Uploads a name/value collection to a resource with the specified URI.
  UploadValuesAsync Overloaded. Uploads the specified name/value collection to the resource identified by the specified URI. These methods do not block the calling thread.
  OpenWrite Overloaded. Opens a stream for writing data to a resource with the specified URI.
  OpenWriteAsync Overloaded. Opens a stream for writing data to the specified resource. These methods do not block the calling thread.

UploadValues is used with the POST verb to upload a NameValueCollection just like a Form Post. The Content-Type header must not be null , you must set it to "application/x-www-form-urlencoded".

The Download methods are similar:

  DownloadData Overloaded. Downloads with the specified URI as a Byte array.
  DownloadDataAsync Overloaded. Downloads the specified resource as a Byte array. These methods do not block the calling thread.
  DownloadFile Overloaded. Downloads the resource with the specified URI to a local file.
  DownloadFileAsync Overloaded. Downloads the specified resource to a local file. These methods do not block the calling thread.
  DownloadString Overloaded. Downloads the requested resource as a String. The resource to download may be specified as either String containing the URI or a Uri.
  DownloadStringAsync Overloaded. Downloads the resource specified as a String or a Uri. These methods do not block the calling thread.

 

Since the asynchronous methods are new in .NET Framework 2.0, here is a short example of a Windows Form with a textbox for the url, a button and a multiline textbox for display, using DownloadStringAsync:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Text;
using System.Windows.Forms;

namespace WebClientTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            WebClient wc = new WebClient();
            wc.DownloadProgressChanged += new DownloadProgressChangedEventHandler(wc_DownloadProgressChanged);
            wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
            wc.DownloadStringAsync(new Uri(textBox2.Text),textBox2.Text);
            while(wc.IsBusy)
                System.Threading.Thread.Sleep(0);
            wc.Dispose();
        }

        void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            textBox1.Text += "Result: " + e.Result + " State:" + e.UserState.ToString() + "\r\n";
        }

        void wc_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            textBox1.Text += e.BytesReceived.ToString() + " bytes  of " + e.TotalBytesToReceive.ToString() + " total\r\n";
        }
    }
}

Authentication

You can provide a username / password combination to an HTTP or FTP site by creating the NetworkCredential object and assigning it to the Credentials property of your WebClient instance:

using (WebClient wc = new WebClient() )
{
wc.Proxy=null;
wc.BaseAddress = "ftp://yoursite.com/files/";

string userName="me@mysite.com";
string password = "dorkusMcDweeb";
wc.Credentials = new NetworkCredential(userName, password);
wc.DownloadFile("mydocument.doc", "mydocument.doc");
wc.UploadFile("userlist.txt", "userlist.txt");
}

The above works with Basic and Digest authentication, and you can extend it via the AuthenticationManager class. You can also use NTLM and Kerberos if you supply the domain name before the userName. However this does not work with FormsAuthentication as it requires the Forms cookie ticket which WebClient does not support. For that, you must use WebRequest / WebResponse.

Header Support

WebClient (as does WebRequest) allows you to add custom HTTP headers as well as to enumerate the headers in a response. Headers are just key-value pairs. You add a header like this:

wc.Headers.Add ("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)");

If your request contains a query, you'll need to add a user-agent header.

WebClient has a Dispose method which should be called as soon as your code has obtained the return data or any headers that are to be examined. Or, you can employ the using ( ... ) {   } construct as shown above.

 Find out more about the WebClient class, its methods, properties and events.

 

Popularity  (7877 Views)
Picture
Biography - Peter Bromberg
Peter Bromberg is a C# MVP, MCP, and .NET expert who has worked in banking, financial and telephony for over 20 years. Pete focuses exclusively on the .NET Platform, and currently develops SOA and other .NET applications for a Fortune 500 clientele. Peter enjoys producing digital photo collage with Maya,playing jazz flute, the beach, and fine wines. You can view Peter's UnBlog and IttyUrl sites. Follow Microsoft MVP
Create New Account
Article Discussion: WebClient Class: Gotchas and Basics
Peter Bromberg posted at Monday, December 24, 2007 2:07 PM
reply
Dinesh replied to Peter Bromberg at Monday, December 24, 2007 2:07 PM
Hi,

I am using Webclient.UploadData() method to upload binary data on requested(third party) url where they reads uploaded data using Request.InputStream. But, I want to open the requested url in new window while uploading the data and keep the existing browser opend. I tried to open new window using javascript window.open() but it creates new instance.

Can you please give me some solution to resolve this problem.. its urgent.

Thanks
reply
Sergey replied to Dinesh at Monday, December 24, 2007 2:07 PM

Try this one:

Process.Start("<requested URL>");

//Process.Start("http://www.eggheadcafe.com") - something like this

It will start default browser and load the requested URL. I think it is the easiest solution that fits your needs. 

reply
Dinesh replied to Sergey at Monday, December 24, 2007 2:07 PM
Thanks for the information.

But, it opens the requested URL in same window while it should be opened in new window by keeping existing window opened.

Let  me know if is there any way resolve this issue.

Thanks
Dinesh
reply
Sergey replied to Dinesh at Monday, December 24, 2007 2:07 PM

If you are creating ASP.NET web site try this:

Response.Write("<script type='text/javascript'>detailedresults=window.open('<requested URL>');</script>");

That works for me, but some pop-up blockers may block it.

reply
Viva replied to Peter Bromberg at Monday, December 24, 2007 2:07 PM
Hello,
I am using WebClient class for sending and receiving data every 20 seconds from a web server; so I create just one instance of the WebClient class when stating the application and dispose it at the end of the application.
Is this correct or should I create a new instance for every request and dispose it after getting the data?
Thanks
reply
Peter Bromberg replied to Viva at Monday, December 24, 2007 2:07 PM
I would create and dispose the object for every request.
reply
Tharn replied to Peter Bromberg at Monday, December 24, 2007 2:07 PM
I have a problem on a specific application and the WebClient Digest authentication (same problem if using WebRequest btw).

We have a provider from where we need to download content. They use Digest authentication. Unfortunately, when we try to get date through WebClient it always ends in 400 Bad Request. Doing same request through Firefox works just fine.
So we investigated using proxy logging to see what was wrong/different, and it showed that the WebClient Digest authentication uses as uri in the Authorization the requested Uri WITHOUT the querystring, where browsers do the authentication with the querystring included in the authorization computation.
We prepared two tests and it confirmed our finding, the request is rejected on their server because the authorization uri does not match the full uri of the page (querystring included).

Is there any way to have that working with full uri in digest authentication... we're not really hot at the idea to start using sockets for doing it...

Logged examples about the problem:
Browser authenticated request (working):
GET /someurl/somepage.jsp?data=xxxxxxxxxx&size=extra&base=yyyyyy&submit=submit HTTP/1.1
Host: somehost
Authorization: Digest username=".............", realm="............", nonce="jr+dCw+XBAA=97ba0c6bce5d21e3c28bfc7b82917ddfb5a7f682", uri="/someurl/somepage.jsp?data=xxxxxxxxxx&size=extra&base=yyyyyy&submit=submit", algorithm=MD5, response="..........................................", qop=auth, nc=00000001, cnonce="e390d080e9a19022"

WebClient authenticated request (failing):
GET /someurl/somepage.jsp?data=xxxxxxxxxx&size=extra&base=yyyyyy&submit=submit HTTP/1.1
Host: somehost
Authorization: Digest username=".............", realm="............", nonce="eUN9kw6XBAA=dd034e8c685c21caec34d46b15da3df22929b70b", uri="/someurl/somepage.jsp", algorithm=MD5, response="..........................................", qop=auth, nc=00000001, cnonce="e390d080e9a19022"

/tharn
reply