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;
        }


Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *