Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Часть 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>
Эта форма содержит элемент управления вводом текста и элемент управления входными данными файла. Если форма содержит элемент управления входными данными файла, атрибут 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с изображением типа контента/jpeg; и значение текстового ввода было "Летние каникулы".
Отправка файла
Теперь давайте рассмотрим контроллер веб-API, который считывает файлы из многопартийного сообщения MIME. Контроллер будет асинхронно считывать файлы. Веб-API поддерживает асинхронные действия с помощью модели программирования на основе задач. Во-первых, это код, если вы нацелены на .NET Framework 4.5, который поддерживает асинхронные и ожидаемые ключевые слова.
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 , содержащий пары name/value для элементов управления формами. Коллекция может содержать повторяющиеся ключи. Рассмотрим эту форму:
<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>
Текст запроса может выглядеть следующим образом:
-----------------------------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 будет содержать следующие пары "ключ-значение":
- поездка: круговая поездка
- параметры: без остановки
- параметры: даты
- место: окно