In it's most basic form, uploading a file or other data to an HTTP form handler using managed code is quite simple using the System.Net.WebClient class.
Listing 1: SimpleWebClientUpload
public void SimpleWebClientUpload()
{
string filePath = Path.GetFullPath("TestFiles/TextFile1.txt");
var client = new WebClient();
client.UploadFile("http://localhost/myhandler.ashx", filePath);
}
It is a common case that some cookies need to be maintained between requests or perhaps a header needs to be added to the request. This is possible using the WebClient
class but the technique is not immediately obvious.
The trick is to create a class that inherits WebClient
and overrides WebClient.GetWebRequest
.
You can then add your cookies or headers to the request before it is sent.
Listing 2: CustomWebClient
string filePath = Path.GetFullPath("TestFiles/TextFile1.txt");
var cookies = new CookieContainer();
var client = new CustomWebClient(cookies);
client.UploadFile(_postHandlerUri, filePath);
public class CustomWebClient : WebClient
{
private CookieContainer _cookies;
public CustomWebClient(CookieContainer cookies)
{
_cookies = cookies;
}
protected override WebRequest GetWebRequest(Uri address)
{
HttpWebRequest request = (HttpWebRequest)base.GetWebRequest(address);
request.CookieContainer = _cookies;
return request;
}
}
If this fulfills your requirements, then you are good to go.
Unfortunately, most file upload scenarios are HTML form based and may contain form fields in addition to the file data. This is where WebClient
falls flat. After review of the source code for WebClient
, it is obvious that there is no possibility of reusing it to perform a file upload including additional form fields.
So, the only option is to create a custom implementation that conforms to rfc1867, rfc2388 and the W3C multipart/form-data specification that will enable file upload with additional form fields and exposes control of cookies and headers.
Enumeration of the details of the implementation are beyond the intended scope of this posting, but the references are linked above and the code is quite brief and clear so to those interested, the process should be clear.
Note: This class is an excerpt from a slightly larger library, Salient.Web.HttpLib.HttpRequestUtility, that contains, amongst other functionality, many convenience overloads that eliminate unused parameters you can see in the usage implementation.
Listing 3: Upload Example Usage
using System;
using System.Collections.Specialized;
using System.IO;
using System.Net;
public class UploadUsage
{
public void UploadFile()
{
string filePath = Path.GetFullPath("TestFiles/TextFile1.txt");
string responseText;
Upload.PostFile(new Uri(http:
null, filePath, null, null, null, null);
}
public void UploadFileWithFormFields()
{
string filePath = Path.GetFullPath("TestFiles/TextFile1.txt");
NameValueCollection postData = new NameValueCollection();
postData.Add("fieldName", "fieldValue");
string responseText;
Upload.PostFile(new Uri(http:
postData, filePath, null, null, null, null);
}
public void UploadFileWithFormFieldsCookiesAndHeaders()
{
string filePath = Path.GetFullPath("TestFiles/TextFile1.txt");
NameValueCollection postData = new NameValueCollection();
postData.Add("fieldName", "fieldValue");
CookieContainer cookies = new CookieContainer();
NameValueCollection headers = new NameValueCollection();
headers.Add("x-headerName", "header value");
const string fileContentType = null;
const string fileFieldName = null;
string responseText;
Upload.PostFile(new Uri(http:
postData, filePath, fileContentType, fileFieldName,
cookies, headers);
}
public void UploadStream()
{
MemoryStream fileData = new MemoryStream(new byte[] {0, 1, 2, 3, 4});
const string fileName = "foo.bin";
Upload.PostFile(new Uri(http:
null, fileData, fileName, null, null, null, null);
}
public void UploadAndGetResponse()
{
MemoryStream fileData = new MemoryStream(new byte[] {0, 1, 2, 3, 4});
const string fileName = "foo.bin";
using (
WebResponse response = Upload.PostFile
(new Uri("http://localhost/myhandler.ashx"), null, fileData, fileName,
null, null, null, null))
{
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
string responseText = reader.ReadToEnd();
}
}
}
}
Listing 4: Upload.cs
#region
using System;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
using Microsoft.Win32;
#endregion
public static class Upload
{
public static WebResponse PostFile
(Uri requestUri, NameValueCollection postData, Stream fileData, string fileName,
string fileContentType, string fileFieldName, CookieContainer cookies,
NameValueCollection headers)
{
HttpWebRequest webrequest = (HttpWebRequest)WebRequest.Create(requestUri);
string ctype;
fileContentType = string.IsNullOrEmpty(fileContentType)
? TryGetContentType(fileName, out ctype) ?
ctype : "application/octet-stream"
: fileContentType;
fileFieldName = string.IsNullOrEmpty(fileFieldName) ? "file" : fileFieldName;
if (headers != null)
{
foreach (string key in headers.AllKeys)
{
string[] values = headers.GetValues(key);
if (values != null)
foreach (string value in values)
{
webrequest.Headers.Add(key, value);
}
}
}
webrequest.Method = "POST";
if (cookies != null)
{
webrequest.CookieContainer = cookies;
}
string boundary = "----------" + DateTime.Now.Ticks.ToString
("x", CultureInfo.InvariantCulture);
webrequest.ContentType = "multipart/form-data; boundary=" + boundary;
StringBuilder sbHeader = new StringBuilder();
if (postData != null)
{
foreach (string key in postData.AllKeys)
{
string[] values = postData.GetValues(key);
if (values != null)
foreach (string value in values)
{
sbHeader.AppendFormat("--{0}\r\n", boundary);
sbHeader.AppendFormat("Content-Disposition:
form-data; name=\"{0}\";\r\n\r\n{1}\r\n", key,
value);
}
}
}
if (fileData != null)
{
sbHeader.AppendFormat("--{0}\r\n", boundary);
sbHeader.AppendFormat("Content-Disposition: form-data;
name=\"{0}\"; {1}\r\n", fileFieldName,
string.IsNullOrEmpty(fileName)
?
""
: string.Format(CultureInfo.InvariantCulture,
"filename=\"{0}\";",
Path.GetFileName(fileName)));
sbHeader.AppendFormat("Content-Type: {0}\r\n\r\n", fileContentType);
}
byte[] header = Encoding.UTF8.GetBytes(sbHeader.ToString());
byte[] footer = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");
long contentLength = header.Length + (fileData != null ?
fileData.Length : 0) + footer.Length;
webrequest.ContentLength = contentLength;
using (Stream requestStream = webrequest.GetRequestStream())
{
requestStream.Write(header, 0, header.Length);
if (fileData != null)
{
byte[] buffer = new Byte[checked((uint)Math.Min(4096,
(int)fileData.Length))];
int bytesRead;
while ((bytesRead = fileData.Read(buffer, 0, buffer.Length)) != 0)
{
requestStream.Write(buffer, 0, bytesRead);
}
}
requestStream.Write(footer, 0, footer.Length);
return webrequest.GetResponse();
}
}
public static WebResponse PostFile
(Uri requestUri, NameValueCollection postData, string fileName,
string fileContentType, string fileFieldName, CookieContainer cookies,
NameValueCollection headers)
{
using (FileStream fileData = File.Open
(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
return PostFile(requestUri, postData, fileData,
fileName, fileContentType, fileFieldName, cookies,
headers);
}
}
public static bool TryGetContentType(string fileName, out string contentType)
{
try
{
RegistryKey key = Registry.ClassesRoot.OpenSubKey
(@"MIME\Database\Content Type");
if (key != null)
{
foreach (string keyName in key.GetSubKeyNames())
{
RegistryKey subKey = key.OpenSubKey(keyName);
if (subKey != null)
{
string subKeyValue = (string)subKey.GetValue("Extension");
if (!string.IsNullOrEmpty(subKeyValue))
{
if (string.Compare(Path.GetExtension
(fileName).ToUpperInvariant(),
subKeyValue.ToUpperInvariant(),
StringComparison.OrdinalIgnoreCase) ==
0)
{
contentType = keyName;
return true;
}
}
}
}
}
}
catch
{
}
contentType = "";
return false;
}
}
Using this class, you can easily post file data and form fields to form handlers and have full control of the cookies and headers that are sent/received.
The full implementation and tests can be found at http://salient.codeplex.com.
An alternate implementation of this code with a working Visual Studio 2008 solution can be found here.
History
- 04-18-2010: Removed licensing restriction