HTTPで複数ファイルをPOSTしたい

WebClientクラスにはUploadFileとかDownloadFileとか便利なメソッドがたくさんありますが、複数ファイルのPOST機能は用意されていないらしく、仕方なくWebRequestを使って自作した。

  • RequestのContentTypeは「multipart/form-data; boundary=1234567890」とする。(boundaryは何でも良さそうなので、適当な英数字を指定)
  • RequestのStreamには、区切り文字列、ヘッダ、ファイル内容を、ファイルの数だけ繰り返す。この時、改行コードの位置と個数は重要。
--1234567890
Content-Disposition: form-data; name="フォーム名1"; filename="ファイル名1"
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary

(ここにファイル内容)
--1234567890
Content-Disposition: form-data; name="フォーム名2"; filename="ファイル名2"
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary

(ここにファイル内容)
--1234567890
Content-Disposition: form-data; name="フォーム名3"; filename="ファイル名3"
・
・
・
以下略


コードはこんな感じ

using System;
using System.Collections.Generic;
using System.Net;
using System.IO;

namespace Upluad
{
    class Program
    {
        static void Main(string[] args)
        {
            string url = "http://localhost:1588/Upload.aspx";
            List<string> files = new List<string>();
            files.Add("C:\\Tmp\\up\\test.zip");
            files.Add("C:\\Tmp\\up\\test2.zip");

            UploadFiles(url, files);
        }

        private static void UploadFiles(string url, List<string> files)
        {
            string tick = Environment.TickCount.ToString();
            System.Text.Encoding enc = System.Text.Encoding.UTF8;

            WebRequest req = WebRequest.Create(url);
            req.Method = "POST";
            req.ContentType = "multipart/form-data; boundary=" + tick;

            byte[] boundary = enc.GetBytes("--" + tick);
            byte[] crlf = enc.GetBytes("\r\n");
            List<byte[]> headers = new List<byte[]>();
            
            //ヘッダの作成とデータサイズの計算
            long contentLen = 0;
            for (int i = 0; i < files.Count; i++)
            {
                //ヘッダ
                string header = "Content-Disposition: form-data; name=\"upfile" + i.ToString() + "\"; filename=\"" + Path.GetFileName(files[i]) + "\"\r\n" +                     
                                "Content-Type: application/octet-stream\r\n" +
                                "Content-Transfer-Encoding: binary\r\n\r\n";

                headers.Add(enc.GetBytes(header));

                //1ファイルごとのデータサイズ
                contentLen += headers[i].Length + new FileInfo(files[i]).Length;
            }
            //全体のデータサイズ
            req.ContentLength = contentLen + ((boundary.Length + crlf.Length + crlf.Length) * files.Count) + boundary.Length;


            //送信
            using (Stream reqStream = req.GetRequestStream())
            {
                for (int i = 0; i < files.Count; i++)
                {
                    //送信ファイル
                    using (FileStream fs = new FileStream(files[i], FileMode.Open, FileAccess.Read))
                    {
                        //ヘッダ
                        reqStream.Write(boundary, 0, boundary.Length);
                        reqStream.Write(crlf, 0, crlf.Length);
                        reqStream.Write(headers[i], 0, headers[i].Length);

                        //ファイル内容
                        byte[] buf = new byte[0x1000];
                        int readSize = 0;
                        while (true)
                        {
                            readSize = fs.Read(buf, 0, buf.Length);
                            if (readSize == 0) break;

                            reqStream.Write(buf, 0, readSize);
                        }

                        reqStream.Write(crlf, 0, crlf.Length);
                    }
                }
                reqStream.Write(boundary, 0, boundary.Length);

                WebResponse res = req.GetResponse();
            }
        }
    }
}