I want to POST multiple files and form variables to a CGI script, all in one HTTP request. I believe this requires a HTTP post with multipart/form-data
encoding. Here is a sample HTML form that sends the required information; I need to send the same information through the application:
<form action="/process.php" enctype="multipart/form-data" method="post">
<input type="text" name="foo" value="bar">
<input type="text" name="blah" value="baz">
<input type="file" name="file1">
<input type="file" name="file2">
<input type="file" name="file3">
</form>
Please note that the application is a C# .NET GUI (or console) application, not an ASP.Net application.
I did this last year based upon some code that I found. It supports exactly what you want, both files and values.
Here is the class called HttpForm
:
public class HttpForm {
private Dictionary<string, string> _files = new Dictionary<string, string>();
private Dictionary<string, string> _values = new Dictionary<string, string>();
public HttpForm(string url) {
this.Url = url;
this.Method = "POST";
}
public string Method { get; set; }
public string Url { get; set; }
//return self so that we can chain
public HttpForm AttachFile(string field, string fileName) {
_files[field] = fileName;
return this;
}
public HttpForm ResetForm(){
_files.Clear();
_values.Clear();
return this;
}
//return self so that we can chain
public HttpForm SetValue(string field, string value) {
_values[field] = value;
return this;
}
public HttpWebResponse Submit() {
return this.UploadFiles(_files, _values);
}
private HttpWebResponse UploadFiles(Dictionary<string, string> files, Dictionary<string, string> otherValues) {
var req = (HttpWebRequest)WebRequest.Create(this.Url);
req.Timeout = 10000 * 1000;
req.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
req.AllowAutoRedirect = false;
var mimeParts = new List<MimePart>();
try {
if (otherValues != null) {
foreach (var fieldName in otherValues.Keys) {
var part = new MimePart();
part.Headers["Content-Disposition"] = "form-data; name=\"" + fieldName + "\"";
part.Data = new MemoryStream(Encoding.UTF8.GetBytes(otherValues[fieldName]));
mimeParts.Add(part);
}
}
if (files != null) {
foreach (var fieldName in files.Keys) {
var part = new MimePart();
part.Headers["Content-Disposition"] = "form-data; name=\"" + fieldName + "\"; filename=\"" + files[fieldName] + "\"";
part.Headers["Content-Type"] = "application/octet-stream";
part.Data = File.OpenRead(files[fieldName]);
mimeParts.Add(part);
}
}
string boundary = "----------" + DateTime.Now.Ticks.ToString("x");
req.ContentType = "multipart/form-data; boundary=" + boundary;
req.Method = this.Method;
long contentLength = 0;
byte[] _footer = Encoding.UTF8.GetBytes("--" + boundary + "--\r\n");
foreach (MimePart part in mimeParts) {
contentLength += part.GenerateHeaderFooterData(boundary);
}
req.ContentLength = contentLength + _footer.Length;
byte[] buffer = new byte[8192];
byte[] afterFile = Encoding.UTF8.GetBytes("\r\n");
int read;
using (Stream s = req.GetRequestStream()) {
foreach (MimePart part in mimeParts) {
s.Write(part.Header, 0, part.Header.Length);
while ((read = part.Data.Read(buffer, 0, buffer.Length)) > 0)
s.Write(buffer, 0, read);
part.Data.Dispose();
s.Write(afterFile, 0, afterFile.Length);
}
s.Write(_footer, 0, _footer.Length);
}
var res = (HttpWebResponse)req.GetResponse();
return res;
} catch (Exception ex) {
Console.WriteLine(ex.Message);
foreach (MimePart part in mimeParts)
if (part.Data != null)
part.Data.Dispose();
return (HttpWebResponse)req.GetResponse();
}
}
private class MimePart {
private NameValueCollection _headers = new NameValueCollection();
public NameValueCollection Headers { get { return _headers; } }
public byte[] Header { get; protected set; }
public long GenerateHeaderFooterData(string boundary) {
StringBuilder sb = new StringBuilder();
sb.Append("--");
sb.Append(boundary);
sb.AppendLine();
foreach (string key in _headers.AllKeys) {
sb.Append(key);
sb.Append(": ");
sb.AppendLine(_headers[key]);
}
sb.AppendLine();
Header = Encoding.UTF8.GetBytes(sb.ToString());
return Header.Length + Data.Length + 2;
}
public Stream Data { get; set; }
}
}
You can use it like this:
var file1 = @"C:\file";
var file2 = @"C:\file2";
var yourUrl = "http://yourdomain.com/process.php";
var httpForm = new HttpForm(yourUrl);
httpForm.AttachFile("file1", file1).AttachFile("file2", file2);
httpForm.setValue("foo", "some foo").setValue("blah", "rarrr!");
httpForm.Submit();
Let me know if it works for you.
I had to do the same thing on a project last year. After some looking around, I found this:
Upload files with HTTPWebrequest (multipart/form-data)
The second answer in there should be what you're looking for. When I was trying to do this I ran into some trouble getting that exact method to work. The problem is that C#.NET does not have any support for multiple key/value pairs in a POST. So you must construct the HTTP request content header yourself. I believe the answer in the link above writes it directly to the request stream. I was able to convert the code found at the link below to build the header, and then wrote the bytes to the request stream.
http://code.activestate.com/recipes/146306-http-client-to-post-using-multipartform-data/
My code looked something like the following (I've had to modify it below for readability because a lot of abstracted out as part of the whole project, so it may not be perfect syntactically).
public void BuildHeader(string contentType, string filename)
{
if ((contentType == null) ||
(contentType.Length == 0))
{
contentType = "application/octet-stream";
}
// Create the boundary string for the POST message header
string boundary = "----------" + DateTime.Now.Ticks.ToString("x");
// Build up the POST message header
StringBuilder sb = new StringBuilder();
// The specific format used can be found in the HTTP protocol specs.
// The 'name' variable indicates the field-name, and the last variable
// added to the string before another boundary is the value for that field.
sb.Append("--");
sb.Append(boundary);
sb.Append("\r\n");
sb.Append("Content-Disposition: form-data; name=\"");
sb.Append("path");
sb.Append("\"");
sb.Append("\r\n\r\n");
sb.Append(fileName);
sb.Append("--");
sb.Append(boundary);
sb.Append("\r\n");
sb.Append("Content-Disposition: form-data; name=\"");
sb.Append("contents");
sb.Append("\"; fileName=\"");
sb.Append("abc");
sb.Append("\"");
sb.Append("\r\n");
sb.Append("Content-Type: ");
sb.Append(contentType);
sb.Append("\r\n");
sb.Append("\r\n");
using (Stream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
// Add the file contents to the POST message
byte[] buffer = new Byte[checked((uint)Math.Min(4096, (int)fileStream.Length))];
int bytesRead = 0;
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
{
sb.Append(ASCIIEncoding.ASCII.GetString(buffer));
}
// Get the byte array of the POST message, and its length
string totalContents = sb.ToString();
byte[] totalUpload = Encoding.UTF8.GetBytes(totalContents);
int length = totalUpload.Length;
}
}
Note that this only prepares a header for the upload of a single file.