Поделиться через


Отправка данных HTML-формы в веб-API ASP.NET: отправка файлов и многокомпонентный MIME

Часть 2. Отправка файлов и многокомпонентный MIME

В этом руководстве показано, как отправлять файлы в веб-API. В нем также описывается обработка составных данных MIME.

Ниже приведен пример HTML-формы для отправки файла:

<form name="form1" method="post" enctype="multipart/form-data" action="api/upload">
    <div>
        <label for="caption">Image Caption</label>
        <input name="caption" type="text" />
    </div>
    <div>
        <label for="image1">Image File</label>
        <input name="image1" type="file" />
    </div>
    <div>
        <input type="submit" value="Submit" />
    </div>
</form>

Снимок экрана: HTML-форма с полем

Эта форма содержит элемент управления текстовым вводом и элементом управления ввода файла. Если форма содержит элемент управления входными данными файла, атрибут enctype всегда должен быть "multipart/form-data", который указывает, что форма будет отправлена в виде многокомпонентного сообщения MIME.

Формат многокомпонентного сообщения MIME проще всего понять, просмотрев пример запроса:

POST http://localhost:50460/api/values/1 HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------41184676334
Content-Length: 29278

-----------------------------41184676334
Content-Disposition: form-data; name="caption"

Summer vacation
-----------------------------41184676334
Content-Disposition: form-data; name="image1"; filename="GrandCanyon.jpg"
Content-Type: image/jpeg

(Binary data not shown)
-----------------------------41184676334--

Это сообщение разделено на две части, по одной для каждого элемента управления формой. Границы частей обозначаются линиями, начинающимися с дефисов.

Примечание

Граница части включает случайный компонент ("41184676334"), чтобы гарантировать, что строка границы случайно не появится внутри части сообщения.

Каждая часть сообщения содержит один или несколько заголовков, за которыми следует содержимое части.

  • Заголовок Content-Disposition содержит имя элемента управления . Для файлов он также содержит имя файла.
  • Заголовок Content-Type описывает данные в части . Если этот заголовок не указан, значение по умолчанию — text/plain.

В предыдущем примере пользователь загрузил файл с именем GrandCanyon.jpg с типом контента image/jpeg; и значением текстового ввода было "Летние каникулы".

Передача файла

Теперь рассмотрим контроллер веб-API, который считывает файлы из составного сообщения MIME. Контроллер будет асинхронно считывать файлы. Веб-API поддерживает асинхронные действия с использованием модели программирования на основе задач. Во-первых, вот код, если вы нацеливаетесь на платформа .NET Framework 4.5, который поддерживает ключевые слова async и await.

using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;

public class UploadController : ApiController
{
    public async Task<HttpResponseMessage> PostFormData()
    {
        // Check if the request contains multipart/form-data.
        if (!Request.Content.IsMimeMultipartContent())
        {
            throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
        }

        string root = HttpContext.Current.Server.MapPath("~/App_Data");
        var provider = new MultipartFormDataStreamProvider(root);

        try
        {
            // Read the form data.
            await Request.Content.ReadAsMultipartAsync(provider);

            // This illustrates how to get the file names.
            foreach (MultipartFileData file in provider.FileData)
            {
                Trace.WriteLine(file.Headers.ContentDisposition.FileName);
                Trace.WriteLine("Server file path: " + file.LocalFileName);
            }
            return Request.CreateResponse(HttpStatusCode.OK);
        }
        catch (System.Exception e)
        {
            return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
        }
    }

}

Обратите внимание, что действие контроллера не принимает никаких параметров. Это связано с тем, что мы обрабатываем текст запроса внутри действия без вызова модуля форматирования типа мультимедиа.

Метод IsMultipartContent проверяет, содержит ли запрос составное сообщение MIME. В противном случае контроллер возвращает код состояния HTTP 415 (неподдерживаемый тип носителя).

Класс MultipartFormDataStreamProvider — это вспомогательный объект, который выделяет потоки файлов для отправленных файлов. Чтобы прочитать многокомпонентное сообщение MIME, вызовите метод ReadAsMultipartAsync . Этот метод извлекает все части сообщения и записывает их в потоки, предоставляемые MultipartFormDataStreamProvider.

После завершения метода можно получить сведения о файлах из свойства FileData , которое представляет собой коллекцию объектов MultipartFileData .

  • MultipartFileData.FileName — это локальное имя файла на сервере, на котором был сохранен файл.
  • MultipartFileData.Headers содержит заголовок части (не заголовок запроса). Его можно использовать для доступа к заголовкам Content_Disposition и Content-Type.

Как следует из названия, ReadAsMultipartAsync является асинхронным методом. Чтобы выполнить работу после завершения метода, используйте задачу продолжения (.NET 4.0) или ключевое слово await (.NET 4.5).

Ниже приведена версия платформа .NET Framework 4.0 предыдущего кода:

public Task<HttpResponseMessage> PostFormData()
{
    // Check if the request contains multipart/form-data.
    if (!Request.Content.IsMimeMultipartContent())
    {
        throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
    }

    string root = HttpContext.Current.Server.MapPath("~/App_Data");
    var provider = new MultipartFormDataStreamProvider(root);

    // Read the form data and return an async task.
    var task = Request.Content.ReadAsMultipartAsync(provider).
        ContinueWith<HttpResponseMessage>(t =>
        {
            if (t.IsFaulted || t.IsCanceled)
            {
                Request.CreateErrorResponse(HttpStatusCode.InternalServerError, t.Exception);
            }

            // This illustrates how to get the file names.
            foreach (MultipartFileData file in provider.FileData)
            {
                Trace.WriteLine(file.Headers.ContentDisposition.FileName);
                Trace.WriteLine("Server file path: " + file.LocalFileName);
            }
            return Request.CreateResponse(HttpStatusCode.OK);
        });

    return task;
}

Чтение данных элемента управления формы

Html-форма, показанная ранее, имеет элемент управления вводом текста.

<div>
        <label for="caption">Image Caption</label>
        <input name="caption" type="text" />
    </div>

Значение элемента управления можно получить из свойства FormDataОбъекта MultipartFormDataStreamProvider.

public async Task<HttpResponseMessage> PostFormData()
{
    if (!Request.Content.IsMimeMultipartContent())
    {
        throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
    }

    string root = HttpContext.Current.Server.MapPath("~/App_Data");
    var provider = new MultipartFormDataStreamProvider(root);

    try
    {
        await Request.Content.ReadAsMultipartAsync(provider);

        // Show all the key-value pairs.
        foreach (var key in provider.FormData.AllKeys)
        {
            foreach (var val in provider.FormData.GetValues(key))
            {
                Trace.WriteLine(string.Format("{0}: {1}", key, val));
            }
        }

        return Request.CreateResponse(HttpStatusCode.OK);
    }
    catch (System.Exception e)
    {
        return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
    }
}

FormData — это nameValueCollection , содержащий пары "имя-значение" для элементов управления формы. Коллекция может содержать повторяющиеся ключи. Рассмотрим эту форму:

<form name="trip_search" method="post" enctype="multipart/form-data" action="api/upload">
    <div>
        <input type="radio" name="trip" value="round-trip"/>
        Round-Trip
    </div>
    <div>
        <input type="radio" name="trip" value="one-way"/>
        One-Way
    </div>

    <div>
        <input type="checkbox" name="options" value="nonstop" />
        Only show non-stop flights
    </div>
    <div>
        <input type="checkbox" name="options" value="airports" />
        Compare nearby airports
    </div>
    <div>
        <input type="checkbox" name="options" value="dates" />
        My travel dates are flexible
    </div>

    <div>
        <label for="seat">Seating Preference</label>
        <select name="seat">
            <option value="aisle">Aisle</option>
            <option value="window">Window</option>
            <option value="center">Center</option>
            <option value="none">No Preference</option>
        </select>
    </div>
</form>

Снимок экрана: HTML-форма с заполненным кругом Round-Trip и флажки Показывать только беспосадочные рейсы и Мои даты поездки являются гибкими.

Текст запроса может выглядеть следующим образом:

-----------------------------7dc1d13623304d6
Content-Disposition: form-data; name="trip"

round-trip
-----------------------------7dc1d13623304d6
Content-Disposition: form-data; name="options"

nonstop
-----------------------------7dc1d13623304d6
Content-Disposition: form-data; name="options"

dates
-----------------------------7dc1d13623304d6
Content-Disposition: form-data; name="seat"

window
-----------------------------7dc1d13623304d6--

В этом случае коллекция FormData будет содержать следующие пары "ключ-значение":

  • trip: туда-обратно
  • параметры: безостановочно
  • параметры: даты
  • seat: window