C#→ Отправка multipart/form-data данных
Янв 22, 2011
Для меня было большим разочарованием узнать, что стандартный класс WebRequest (HttpWebRequest) не поддерживает отправку данных множественного содержимого. Конечно, в классе WebClient есть метод загрузки файла на удаленный узел, который как раз использует multipart, но он не поможет если кроме файла нужно передать еще и другие данные.
Например, при отправке данных на сервис распознавания каптчи, кроме файла с картинкой, нужно еще загрузить уникальный ключ для идентификации на сервисе.
Сначала я хотел создать класс, который наследовал бы HttpWebRequest, но как оказалось это не возможно. Поэтому пришлось создавать отдельный класс — WebTools.MultiPartForm. (архив не сохранился)
Пример использования
// using WebTools; HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create("http://antigate.com/in.php"); using (MultiPartForm multiPart = new MultiPartForm(webRequest)) { multiPart.AddData("method", "post"); multiPart.AddData("key", "e19567160d1dd900b436978ef3c0c060"); multiPart.AddFile("file", @"C:\captcha.jpg"); } HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse().Close();
Здесь можно использовать директиву using, так как класс наследует интерфейс IDisposable. При создании экземпляра класса MultiPartForm, в конструктор передается объект WebRequest (HttpWebRequest). После чего происходит добавление строковых данных и содержимого файлов.
Конструктор MultiPartForm
Создает экземпляр класса MultiPartForm.
new MultiPartForm(WebRequest Request)
В качестве аргумента передается объект ранее созданного WebRequest или HttpWebRequest.
Метод AddData
Добавляет в запрос секцию с указанным значением.
void AddData(string Name, string Value)
Метод добавляет секцию Name, значением которой является Value.
Метод AddFile
Добавляет в запрос секцию с содержимым указанного файла.
void AddFile(string Name, string FilePath)
Метод добавляет секцию Name, значением которой является содержимое файла FilePath, с типом данных application/octet-stream.
void AddFile(string Name, string FilePath, string FileType)
Метод добавляет секцию Name, значением которой является содержимое файла FilePath, с типом данных FileType.
void AddFile(string Name, string FilePath, Stream FileStream)
Метод добавляет секцию Name, значением которой является поток данных FileStream, с типом данных application/octet-stream.
void AddFile(string Name, string FilePath, Stream FileStream, string FileType)
Метод добавляет секцию Name, значением которой является поток данных FileStream, с типом данных FileType.
Код класса MultiPartForm
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net; using System.IO; using System.Security.Cryptography; namespace WebTools { class MultiPartForm : IDisposable { private Stream _stream; private string _boundary; private string _templateData = "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}\r\n"; private string _templateFile = "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\"\r\nContent-Type: {3}\r\n\r\n"; private string _templateEnd = "--{0}--\r\n\r\n"; public MultiPartForm(WebRequest Request) { _boundary = String.Format("--{0}", GetMD5()); Request.Method = "POST"; Request.ContentType = String.Format("multipart/form-data; boundary={0}", _boundary); _stream = Request.GetRequestStream(); } public void AddData(string Name, string Value) { byte[] contentData = Encoding.UTF8.GetBytes(String.Format(_templateData, _boundary, Name, Value)); _stream.Write(contentData, 0, contentData.Length); } public void AddFile(string Name, string FilePath) { AddFile(Name, FilePath, "application/octet-stream"); } public void AddFile(string Name, string FilePath, string FileType) { using (FileStream fileStream = new FileStream(FilePath, FileMode.Open)) { AddFile(Name, FilePath, fileStream, FileType); } } public void AddFile(string Name, string FilePath, Stream FileStream) { AddFile(Name, FilePath, FileStream, "application/octet-stream"); } public void AddFile(string Name, string FilePath, Stream FileStream, string FileType) { FileStream.Seek(0, SeekOrigin.Begin); byte[] contentFile = Encoding.UTF8.GetBytes(String.Format(_templateFile, _boundary, Name, FilePath, FileType)); _stream.Write(contentFile, 0, contentFile.Length); FileStream.CopyTo(_stream); byte[] _lineFeed = Encoding.UTF8.GetBytes("\r\n"); _stream.Write(_lineFeed, 0, _lineFeed.Length); } public void Dispose() { Close(); GC.SuppressFinalize(this); } public void Close() { byte[] contentEnd = Encoding.UTF8.GetBytes(String.Format(_templateEnd, _boundary)); _stream.Write(contentEnd, 0, contentEnd.Length); } private string GetMD5() { Random randNum = new Random(); MD5CryptoServiceProvider md5hash = new MD5CryptoServiceProvider(); byte[] randByte = Encoding.UTF8.GetBytes(randNum.NextDouble().ToString()); byte[] computeHash = md5hash.ComputeHash(randByte); string resultHash = String.Empty; foreach (byte currentByte in computeHash) { resultHash += currentByte.ToString("x2"); } return resultHash; } } }
UPDATE:
Убрал метод получения MIME типа, который использовал стороннюю библиотеку. Теперь MIME можно указать при вызове метода AddFile.
[DllImport("urlmon.dll", CharSet = CharSet.Auto)] private static extern int FindMimeFromData(IntPtr ptr, string url, byte[] buffer, int bufferSize, string mimeProposed, int mimeFlags, out string mimeType, int dwReserved); private string GetMimeType(Stream fileStream) { string resultMime; byte[] fileBytes = new byte[fileStream.Length]; fileStream.Read(fileBytes, 0, (int)fileStream.Length); FindMimeFromData(new IntPtr(), null, fileBytes, fileBytes.Length, null, 0, out resultMime, 0); return resultMime; }