Envoi de données de formulaire HTML dans API Web ASP.NET : Chargement de fichiers et MIME en plusieurs parties
Partie 2 : Chargement de fichiers et MIME en plusieurs parties
Ce tutoriel montre comment charger des fichiers dans une API web. Il décrit également comment traiter des données MIME en plusieurs parties.
Voici un exemple de formulaire HTML pour le chargement d’un fichier :
<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>
Ce formulaire contient un contrôle d’entrée de texte et un contrôle d’entrée de fichier. Lorsqu’un formulaire contient un contrôle d’entrée de fichier, l’attribut enctype doit toujours être « multipart/form-data », ce qui spécifie que le formulaire sera envoyé en tant que message MIME en plusieurs parties.
Le format d’un message MIME en plusieurs parties est plus facile à comprendre en examinant un exemple de demande :
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--
Ce message est divisé en deux parties, une pour chaque contrôle de formulaire. Les limites des parties sont indiquées par les lignes qui commencent par des tirets.
Notes
La limite de la partie inclut un composant aléatoire (« 41184676334 ») pour garantir que la chaîne de limite n’apparaît pas accidentellement à l’intérieur d’un composant de message.
Chaque partie de message contient un ou plusieurs en-têtes, suivis du contenu de la partie.
- L’en-tête Content-Disposition inclut le nom du contrôle. Pour les fichiers, il contient également le nom de fichier.
- L’en-tête Content-Type décrit les données du composant. Si cet en-tête est omis, la valeur par défaut est text/plain.
Dans l’exemple précédent, l’utilisateur a chargé un fichier nommé GrandCanyon.jpg, avec le type de contenu image/jpeg ; et la valeur de l’entrée de texte était « Vacances d’été ».
Chargement de fichiers
Examinons maintenant un contrôleur d’API web qui lit des fichiers à partir d’un message MIME en plusieurs parties. Le contrôleur lit les fichiers de manière asynchrone. L’API web prend en charge les actions asynchrones à l’aide du modèle de programmation basé sur les tâches. Tout d’abord, voici le code si vous ciblez .NET Framework 4.5, qui prend en charge les mots clés async et 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);
}
}
}
Notez que l’action du contrôleur n’accepte aucun paramètre. Cela est dû au fait que nous traitons le corps de la requête à l’intérieur de l’action, sans appeler un formateur de type média.
La méthode IsMultipartContent vérifie si la requête contient un message MIME en plusieurs parties. Si ce n’est pas le cas, le contrôleur retourne http status code 415 (type de média non pris en charge).
La classe MultipartFormDataStreamProvider est un objet d’assistance qui alloue des flux de fichiers pour les fichiers chargés. Pour lire le message MIME en plusieurs parties, appelez la méthode ReadAsMultipartAsync . Cette méthode extrait toutes les parties de message et les écrit dans les flux fournis par MultipartFormDataStreamProvider.
Une fois la méthode terminée, vous pouvez obtenir des informations sur les fichiers à partir de la propriété FileData , qui est une collection d’objets MultipartFileData .
- MultipartFileData.FileName est le nom de fichier local sur le serveur, où le fichier a été enregistré.
- MultipartFileData.Headers contient l’en-tête de composant (et non l’en-tête de requête). Vous pouvez l’utiliser pour accéder aux en-têtes Content_Disposition et Content-Type.
Comme son nom l’indique, ReadAsMultipartAsync est une méthode asynchrone. Pour effectuer le travail une fois la méthode terminée, utilisez une tâche de continuation (.NET 4.0) ou le mot clé await (.NET 4.5).
Voici la version .NET Framework 4.0 du code précédent :
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;
}
Lecture des données de contrôle de formulaire
Le formulaire HTML que j’ai montré précédemment avait un contrôle d’entrée de texte.
<div>
<label for="caption">Image Caption</label>
<input name="caption" type="text" />
</div>
Vous pouvez obtenir la valeur du contrôle à partir de la propriété FormData du 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 est un NameValueCollection qui contient des paires nom/valeur pour les contrôles de formulaire. La collection peut contenir des clés en double. Considérez ce formulaire :
<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>
Le corps de la demande peut ressembler à ceci :
-----------------------------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--
Dans ce cas, la collection FormData contient les paires clé/valeur suivantes :
- trip : aller-retour
- options : non-stop
- options : dates
- seat: window