Freigeben über


Senden von HTML-Formulardaten in ASP.NET Web-API: Dateiupload und mehrteiliges MIME

Teil 2: Dateiupload und mehrteiliges MIME

In diesem Lernprogramm wird gezeigt, wie Sie Dateien in eine Web-API hochladen. Außerdem wird beschrieben, wie mehrteilige MIME-Daten verarbeitet werden.

Hier ist ein Beispiel für ein HTML-Formular zum Hochladen einer Datei:

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

Screenshot eines HTML-Formulars mit einem Bildbeschriftungsfeld mit dem Text

Dieses Formular enthält ein Texteingabesteuerelement und ein Dateieingabesteuerelement. Wenn ein Formular ein Dateieingabe-Steuerelement enthält, sollte das Attribut enctype immer "multipart/form-data" sein, was angibt, dass das Formular als mehrteilige MIME-Nachricht gesendet wird.

Das Format einer mehrteiligen MIME-Nachricht ist am einfachsten zu verstehen, indem Sie sich eine Beispielanforderung ansehen:

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

Diese Nachricht ist in zwei Teile unterteilt, eine für jedes Formularsteuerelement. Teilbegrenzungen werden durch die Linien angegeben, die mit Bindestrichen beginnen.

Hinweis

Die Teilgrenze enthält eine zufällige Komponente ("41184676334"), um sicherzustellen, dass die Begrenzungszeichenfolge nicht versehentlich in einem Nachrichtenteil angezeigt wird.

Jeder Nachrichtenteil enthält eine oder mehrere Kopfzeilen, gefolgt vom Teilinhalt.

  • Der Content-Disposition-Header enthält den Namen des Steuerelements. Für Dateien enthält sie auch den Dateinamen.
  • Der Inhaltstypheader beschreibt die Daten im Teil. Wenn diese Kopfzeile weggelassen wird, ist der Standardwert Text/Plaintext.

Im vorherigen Beispiel hat der Benutzer eine Datei mit dem Namen GrandCanyon.jpgmit dem Inhaltstyp "image/jpeg" hochgeladen; und der Wert der Texteingabe lautete "Sommerurlaub".

Dateiupload

Sehen wir uns nun einen Web-API-Controller an, der Dateien aus einer mehrteiligen MIME-Nachricht liest. Der Controller liest die Dateien asynchron vor. Die Web-API unterstützt asynchrone Aktionen mithilfe des aufgabenbasierten Programmiermodells. Zunächst ist hier der Code, wenn Sie auf .NET Framework 4.5 abzielen, das die async- und await-Schlüsselwörter unterstützt.

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

}

Beachten Sie, dass die Controlleraktion keine Parameter ausführt. Das liegt daran, dass der Anforderungstext innerhalb der Aktion verarbeitet wird, ohne einen Medientypformatierer aufzufordern.

Die IsMultipartContent-Methode überprüft, ob die Anforderung eine mehrteilige MIME-Nachricht enthält. Wenn nicht, gibt der Controller DEN HTTP-Statuscode 415 (Nicht unterstützter Medientyp) zurück.

Die MultipartFormDataStreamProvider-Klasse ist ein Hilfsobjekt, das Dateidatenströme für hochgeladene Dateien zuordnet. Rufen Sie zum Lesen der mehrteiligen MIME-Nachricht die ReadAsMultipartAsync-Methode auf. Diese Methode extrahiert alle Nachrichtenteile und schreibt sie in die Datenströme, die vom MultipartFormDataStreamProvider bereitgestellt werden.

Nach Abschluss der Methode können Sie Informationen zu den Dateien aus der FileData-Eigenschaft abrufen, bei der es sich um eine Auflistung von MultipartFileData-Objekten handelt.

  • MultipartFileData.FileName ist der lokale Dateiname auf dem Server, auf dem die Datei gespeichert wurde.
  • MultipartFileData.Headers enthält den Teilheader (nicht den Anforderungsheader). Sie können dies verwenden, um auf die Kopfzeilen Content_Disposition und Inhaltstyp zuzugreifen.

Wie der Name schon sagt, ist ReadAsMultipartAsync eine asynchrone Methode. Verwenden Sie eine Fortsetzungsaufgabe (.NET 4.0) oder das await-Schlüsselwort (.NET 4.5).

Dies ist die .NET Framework 4.0-Version des vorherigen Codes:

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

Lesen von Formularsteuerelementdaten

Das HTML-Formular, das ich zuvor gezeigt habe, hatte ein Texteingabesteuerelement.

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

Sie können den Wert des Steuerelements aus der FormData-Eigenschaft des MultipartFormDataStreamProvider abrufen.

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 ist eine NameValueCollection , die Name/Wert-Paare für die Formularsteuerelemente enthält. Die Auflistung kann doppelte Schlüssel enthalten. Betrachten Sie dieses Formular:

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

Screenshot des HTML-Formulars mit dem ausgefüllten Round-Trip-Kreis und den angekreuzten Kontrollkästchen

Der Anforderungstext sieht möglicherweise wie folgt aus:

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

In diesem Fall würde die FormData-Auflistung die folgenden Schlüssel-Wert-Paare enthalten:

  • Reise: Hin- und Rückreise
  • options:durchgehend
  • Optionen: Datumsangaben
  • Sitz: Fenster