Post multiple files and form values using .NET (co

2019-02-07 05:04发布

问题:

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.

回答1:

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.



回答2:

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.