Last-Minute-Ticket

Neuerungen in der finalen Version von ASP.NET Web API
Kommentare

Die finale Version von ASP.NET Web API bringt einige Neuerungen, darunter bessere Unterstützung für Dateiuploads und Fortschrittsanzeigen sowie die Möglichkeit einer feingranularen Konfiguration von Services auf den Ebenen von Routen und Controllern.

Nachdem die Entwicklergemeinde seit einigen Monaten mit stabilen Vorabversionen von ASP.NET Web API erste Erfahrungen sammeln konnten und sie dank Go-Live-Lizenz seitens Microsoft auch schon für produktive Zwecke einsetzen durften, war es Mitte August 2012 soweit: Die finale Version wurde veröffentlicht. Auch wir haben diese Zeit mit großer Aufmerksamkeit verfolgt und im Windows Developer 5.2012 und 6.2012 ausführlich über die Möglichkeiten dieses neuen Serviceframeworks berichtet. Da das Produktteam bei Microsoft jedoch auch noch nach der letzten Vorabversion weitere Features geliefert hat, gibt es an dieser Stelle noch einiges zu berichten.

Dateiupload

Um Dateiuploads, die von einem HTML-Formular aus erfolgen, verarbeiten zu können, muss ASP.NET Web API dazu gebracht werden, mit Daten, die sich am MIME-Type multipart/form-data orientieren, umzugehen. Dieser MIME-Type sieht vor, dass die übersendeten Informationen in mehrere Sektionen geteilt werden, wobei jede Sektion einen eigenen MIME-Type aufweisen kann. Diese Sektionen beinhalten zum Beispiel die hochzuladenden Dateien oder auch die Inhalte von Formularfeldern.

Zum Lesen einer solchen Nachricht verwendet der Entwickler die Methode Content.ReadAsMultipartAsync des aktuellen Request-Objekts (Listing 1). An diese kann er eine Instanz einer Subklasse von MultipartStreamProvider übergeben, wobei diese die Art der Verarbeitung der übersendeten Daten festlegt. Im betrachteten Fall kommt ein MultipartFormDataStreamProvider zum Einsatz. Dieser speichert die hochgeladenen Daten in jenem Ordner, den der Entwickler über dessen Konstruktor festgelegt hat. Darüber hinaus hält er die Daten von Formularfeldern im Hauptspeicher vor und macht sie über die Eigenschaft FormData zugänglich. Die Auflistung FileData beinhaltet daneben Informationen über die hochgeladenen Dateien, die im spezifizierten Verzeichnis abgelegt wurden. Die einzelnen Einträge sind vom Typ MultipartFileData, der zwei Eigenschaften aufweist: LocalFileName repräsentiert den vollständigen Namen der hochgeladenen Datei im festgelegten Uploadordner am Server; Headers liefert Kopfzeileneinträge, die der Browser für die jeweilige Datei übersendet hat. Über den Kopfzeileneintrag Content-Disposition kann zum Beispiel jener Name, den die Datei am Client inne gehabt hat, ermittelt werden; der Kopfzeileneintrag Content-Type gibt hingegen Auskunft über das Dateiformat (über den Content-Type) der Datei. Im betrachteten Beispiel werden diese Informationen zur Demonstration im Debug-Fenster ausgegeben. Listing 2 zeigt zur Demonstration jene Ausgaben, die beim Hochladen einer Zip-Datei entstanden sind. Eine HTML-Seite zum Testen dieser Action-Methode findet sich in Listing 3.

Listing 1
public async Task Post()  {     if (!Request.Content.IsMimeMultipartContent("form-data"))     {         throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);     }      var formDataProvider = new                 MultipartFormDataStreamProvider(@"c:tempbilder");     var bodyParts = await                Request.Content.ReadAsMultipartAsync(formDataProvider);      var hotelId = bodyParts.FormData["hotelId"];     Debug.WriteLine("HotelId: " + hotelId);      foreach (var file in bodyParts.FileData)     {         Debug.WriteLine("File: " + file.LocalFileName);         foreach (var h in file.Headers)         {             var array = h.Value as IEnumerable;             if (array != null) {                 Debug.WriteLine("   Header: " + h.Key + ": " + string.Join("|", array));             }             else {                 Debug.WriteLine("   Header: " + h.Key + ": " + h.Value);             }          }     }      return bodyParts.FileData.Count + " Dateien für Hotel #" + hotelId + " hochgeladen!";  }
Listing 2
HotelId: 333 File: c:tempbilderBodyPart_2dc6a787-06f1-4dff-91cc-d1f48cf7d6e6    Header: Content-Disposition: form-data; name="data";                       filename="C:UserssteyerDesktopExtendingMVC.zip"    Header: Content-Type: application/x-zip-compressed
Listing 3
[...]         
http://localhost:1307/api/Bilder" enctype="multipart/form-data"> [...]

Neben dem MultipartFormDataStreamProvider existieren noch ein paar weitere Implementierungen, die in solchen Szenarien verwendet werden können. Die nachfolgende Tabelle 1 gibt Aufschluss darüber.

Tabelle 1: MultipartStreamProvider-Derivate
Implementierung Beschreibung
MultipartMemoryStreamProvider Speichert sämtliche Teile der Nachricht im Hautspeicher und stellt sie in Form von MemoryStreams bereit.
MultipartFileStreamProvider Speichert sämtliche Teile der Nachricht im Dateisystem.
MultipartFormDataStreamProvider Speichert nur hochgeladene Dateien im Dateisystem; Formularfelder werden im Hauptspeicher vorgehalten.
MultipartRelatedStreamProvider Unterstützt den MIME-Type Multipart/Related (RFC 2387), bei dem einzelne Nachrichtenteile aufeinander verweisen können (zum Beispiel eine XML-Nachricht im ersten Teil auf ein Bild im zweiten Teil).
Fortschritt ermitteln

Zum Ermitteln des Fortschritts eines Upload- bzw. Download-Vorgangs bietet ASP.NET Web API einen ProgressMessageHandler (Listing 4). Wie alle anderen MessageHandler auch ist er mit allen anderen zu verwendenden Handlern zu verketten, wobei sich am Ende dieser Kette ein HttpClientHandler befinden muss. Nachdem er sie erzeugt hat, übergibt der Entwickler diese MessageHandler-Kette an den Konstruktor von HttpClient. An die Eigenschaft HttpReceiveProgress von ProgressMessageHandler weist er zusätzlich eine Methode, die laufend über den Fortschritt eines Downloads informiert werden soll (Listing 5), zu. Diese Methode erhält bei jedem Aufruf ein Argument vom Typ HttpProgressEventArgs, das Zugriff auf Informationen, die den aktuellen Fortschritt repräsentieren, gewährt. Die Eigenschaft ProgressPercentage enthält zum Beispiel den Grad der Fertigstellung in Prozent. Im betrachteten Fall wird dieser Wert ausgegeben, sofern er sich seit dem letzten Aufruf der Benachrichtigungsmethode geändert hat. Dabei ist zu beachten, dass die Verwendung von PrograssMessageHandler den Einsatz von Streaming ausschließt.

Um sich über den Fortschritt eines Uploadvorgangs informieren zu lassen, geht der Entwickler analog vor. Allerdings verwendet er hierzu die Eigenschaft HttpSendProgress anstatt von HttpReceiveProgress.

Listing 4
static async void DownloadDemo() {      var progressMessageHandler = new ProgressMessageHandler()     {         InnerHandler = new HttpClientHandler()     };      progressMessageHandler.HttpReceiveProgress +=                        progressMessageHandler_HttpReceiveProgress;      HttpClient client = new HttpClient(progressMessageHandler);      var response = await client.GetAsync("http://localhost:1307/api/Bilder");     var stream = await response.Content.ReadAsStreamAsync();      Console.WriteLine("Habe Stream erhalten!");     long bytes = 0;      using (FileStream fs =                     new FileStream(@"c:tempdownload.dat", FileMode.Create))     {         int b;         while ((b = stream.ReadByte()) != -1)         {             fs.WriteByte((byte)b);             bytes++;             if (bytes % (200 * 1024) == 0)             {                 Console.WriteLine(bytes / 1024 + " KB gelesen ...");             }         }     }     Console.WriteLine("Fertig gelesen");  }
Listing 5
static int progress = -1; [...] static void progressMessageHandler_HttpReceiveProgress(                                  object sender, HttpProgressEventArgs e) {     if (progress == e.ProgressPercentage) return;     Console.WriteLine(e.ProgressPercentage + "% heruntergeladen");     progress = e.ProgressPercentage; }

[ header = Neuerungen in der finalen Version von ASP.NET Web API – Teil 2 ]

Controller-basierte Konfiguration

Obwohl die globale Konfiguration von ASP.NET Web API in vielen Fällen ausreicht, gibt es Situationen, in denen Einstellungen lediglich für einen einzelnen oder wenige Controller vorgenommen werden sollen. In diesen Situationen besteht die Möglichkeit, das Interface IControllerConfiguration zu implementieren (Listing 6). Dieses gibt die Methode Initialize vor, die die Konfiguration eines bestimmten Controllers übernimmt. Dazu übergibt ASP.NET Web API einen Parameter vom Typ HttpControllerSettings sowie einen HttpControllerDescriptor. An ersteren sind die gewünschten Konfigurationseinstellungen zu übergeben; letzterer bietet Informationen über den zu konfigurierenden Controller.

Im betrachteten Fall entfernt Initialize den ersten Formatter (es handelt sich dabei um den JSON-Formatter) und ersetzt die zu verwendende ITraceWriter-Implementierung durch eine benutzerdefinierte, die – um das Beispiel kurz zu halten – von der eigenen Klasse bereitgestellt wird, indem diese ITraceWriter implementiert und somit auch eine Methode Trace bereitstellt.

Listing 6
public class CustomConfig : Attribute, IControllerConfiguration, ITraceWriter {     public void Initialize(HttpControllerSettings controllerSettings,                                      HttpControllerDescriptor controllerDescriptor)     {         controllerSettings.Formatters.RemoveAt(0);         controllerSettings.Services.Replace(typeof(ITraceWriter), this);     }      public void Trace(HttpRequestMessage request, string category,                            System.Web.Http.Tracing.TraceLevel level, Action traceAction)     {         TraceRecord record = new TraceRecord(request, category, level);         traceAction(record);         Debug.WriteLine(category + ": " + level + " " + record.Message);     } }

Da die betrachtete Implementierung darüber hinaus auch von der Basisklasse Attribute erbt, kann sie zum Annotieren der zu konfigurierenden Controller herangezogen werden (Listing 7).

Listing 7
[CustomConfig] public class MiniBarController : ApiController {     public double GetPrice(int hotelId, int roomNumber)     {         return 42;     } }
Routenbasierte Konfiguration

Auch auf der Ebene von Routen kann der Entwickler die Standardkonfiguration variieren. Dazu hat er die Möglichkeit, einer Route eine Kette von MessageHandler zuzuordnen. Listing 8 weist zum Beispiel der Route BuchungenByHotelRoute eine Kette bestehend aus einem LimitResultMessageHandler und einem HttpControllerDispatcher zu. Dabei ist zu beachten, dass Ketten dieser Art immer mit einem HttpControllerDispatcher abzuschließen sind, da dieser die entsprechende Action-Methode anstößt.

Listing 8
public static void RegisterRoutes(RouteCollection routes) {     routes.IgnoreRoute("{resource}.axd/{*pathInfo}");       var handler = new LimitResultMessageHandler()     {         InnerHandler = new HttpControllerDispatcher(GlobalConfiguration.Configuration)     };      routes.MapHttpRoute(         name: "BuchungenByHotelRoute",         routeTemplate: "api/Hotels/{hotelId}/Buchungen",         defaults: new { controller = "Buchungen" },         constraints: null, // Notwendig, damit richtige Überladung gewählt wird!          handler: handler     );      [...] }
Ausblick

Die letzten Features, die ihren Weg in die finale Version von ASP.NET Web API geschafft haben, runden die damit einhergehenden Möglichkeiten ab. Damit ist es jedoch nicht getan, denn im selben Atemzug, mit dem die neue Version von ASP.NET Web API angekündigt wurde, wurde auch auf weitere Features, an denen gerade gearbeitet wird, verwiesen. Dabei handelt es sich um eine umfangreiche OData-Unterstützung, flexible Möglichkeiten zur Generierung von Hilfeseiten, die über jene von WCF weit hinausgehen, sowie die Bereitstellung eines anpassbaren Tracing-Frameworks. Wer heute schon mit diesen Möglichkeiten experimentieren möchte, kann den aktuellen Fortschritt in Form von Alphaversionen [1] über NuGet beziehen.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -