Neuerungen in ASP.NET Web API 2.1 und MVC 5.1

ASP.NET reloaded
Kommentare

ASP.NET Web API 2.1 und ASP.NET MVC 5.1 bringen einige Abrundungen, darunter die Möglichkeit zur Beeinflussung der generierten API-Dokumentation durch Attribute, die Auswahl von Routen unter Berücksichtigung der gesamten HTTP-Anfrage und das Rendern von Drop-down-Feldern für Enums.

Nachdem Microsoft im vergangenen Herbst gemeinsam mit Visual Studio 2013 die Versionen 2.0 von ASP.NET Web API und 5.0 von ASP.NET MVC veröffentlicht hat, wurden bereits im Januar 2014 ohne großes Aufsehen ASP.NET Web API 2.1 und ASP.NET MVC 5.1 nachgeliefert. Durch solche von Microsoft nun angestrebten kurzen Releaseintervalle kommen Entwickler rascher in den Genuss der Anstrengungen des Produktteams. Auf der anderen Seite darf man allerdings von Releases, an denen lediglich ein Quartal lang gearbeitet wurde, kein allzu großes Featurefeuerwerk erwarten. Dennoch bringen die genannten und über NuGet verfügbaren Versionen einige Abrundungen, die dem Entwickler seine Arbeit erheblich vereinfachen können.

Mehr Daten auf Hilfeseiten

Die Action-Methoden von Web-API-basierten Controllern liefern entweder direkt jenen Wert, der im Rahmen der Nutzdaten dem Aufrufer zurückzusenden ist, oder ein Objekt, das die gesamte Antwortnachricht und nicht nur ihre Nutzdaten beschreibt. Im letzteren Fall gibt der Entwickler eine Instanz von HttpResponseMessage zurück. Seit Version 2 lässt sich die Erzeugung dieser HttpResponseMessage auch in eine Klasse auslagern, die das Interface IHttpActionResult implementiert. Liefert eine Action-Methode eine Instanz solch einer Klasse zurück, verwendet Web API diese Instanz, um das zu verwendende HttpResponseMessage-Objekt zu erhalten.Beim Einsatz einer HttpResponseMesage– oder IHttpActionResult-Implementierung geht aus der Signatur der Methode nicht hervor, von welchem Typ das Objekt ist, das Web API dem Aufrufer im Rahmen der Nutzdaten zurückgibt. Aus diesem Grund muss der Entwickler diese Information an einer anderen Stelle hinterlegen, damit sie in der automatisch generierten API-Dokumentation aufscheint. Bis dato waren dazu Methodenaufrufe an einer zentralen Stelle nötig. Ab Version 2.1 kann der Entwickler diese Information nun auch an Ort und Stelle unter Verwendung des Attributs ResponseType deponieren:

[ResponseType(typeof(HotelV2))]
public IHttpActionResult Post(HotelV2 hotel) { […] }

Wie aus Abbildung 1 hervorgeht, zeigt die API-Dokumentation nicht nur den mit ResponseType hinterlegten Typ an, sondern bietet auch mit der Spalte ADDITIONAL INFORMATION Zusatzinformationen zu dessen Eigenschaften. Diese Informationen leitet Web API aus eventuellen Data Annotations ab, mit denen diese Eigenschaften markiert wurden. Im betrachteten Fall kamen die Annotationen Required, MaxLength und Range zum Einsatz.

Abb. 1: Generierte API-Dokumentation mit zusätzlichen Metadaten

Um weitere Annotationen zu unterstützen oder standardmäßige Kurzbeschreibungen anzupassen, kann der Entwickler die Klasse ModelDescriptionGenerator anpassen, die beim Einsatz der API-Dokumentation als Quellcode vorliegt. Darin befindet sich ein privates Dictionary mit dem Namen AnnotationTextGenerator, das die Typen der einzelnen Attribute jeweils auf eine Func abbildet. Diese Func nimmt eine Instanz des jeweiligen Attributs entgegen und liefert dafür eine Beschreibung in Form eines Strings.

Einschränkungen beim Attribute-Routing in Web API

Ab Web API 2.1 lassen sich nun auch Einschränkungen im Rahmen des attributbasierten Routings hinterlegen. Mit diesen Einschränkungen kann der Entwickler festlegen, dass die konfigurierte Route nur dann von Web API zu berücksichtigen ist, wenn bestimmte Bedingungen erfüllt sind. Um eine Einschränkung bereitzustellen, implementiert der Entwickler das Interface IHttpRouteConstraint. Dieses gibt eine Methode Match vor, die Web API aufruft, wenn es eine Route in Erwägung zieht. Dabei übergibt Web API Informationen über den aktuellen Aufruf. Gibt Match den Wert true zurück, zieht Web API die damit assoziierte Action-Methode in die engere Auswahl. Liefert Match hingegen false, wird die jeweilige Route in weiterer Folge ignoriert.Listing 1 zeigt eine IHttpRouteConstraint-Implementierung, die prüft, ob im Rahmen der benutzerdefinierten Kopfzeile api-version eine bestimmte, über den Konstruktor festgelegte Versionsnummer übergeben wurde. Zu den Kontextinformationen, die Web API an Match übergibt, gehört eine HttpRequestMessage, die den aktuellen Aufruf beschreibt, eine IHttpRoute-Implementierung, die Auskunft über die assoziierte Route gibt, und ein Dictionary mit den Routing-Parametern. Dazu zählen sämtliche Werte, die in die Platzhalter der Route eingesetzt wurden, sowie die restlichen URL-Parameter. Der Parameter parameterName weist einen in diesem Fall frei wählbaren Namen an. Einschränkende Routing-Parameter würden hingegen als parameterName den Namen des jeweiligen Routing-Parameters übergeben bekommen. Der Wert des Enums HttpRouteDirection informiert darüber, ob die Einschränkung beim Auflösen eines URLs auf eine Route oder beim Auflösen einer Route auf einen URL zur Anwendung kommt.

public class VersionConstraint : IHttpRouteConstraint
{
    private string version;

    public VersionConstraint(string version)
    {
        this.version = version;
    }
            
    public bool Match(
                System.Net.Http.HttpRequestMessage request, 
                IHttpRoute route, 
                string parameterName, 
                IDictionary values, 
                HttpRouteDirection routeDirection)
    {
        var version = request
                            .Headers
                            .GetValues("api-version")
                            .FirstOrDefault();
        if (version == null) return false;
        if (version == this.version) return true;
        return false;
    }
}

Um eine IHttpRouteConstraint-Implementierung mit einer Route zu assoziieren, leitet der Entwickler von der Klasse RouteFactoryAttribute ab und überschreibt deren Property Constraints, sodass sie ein Dictionary mit den gewünschten IHttpRouteConstraint-Implementierungen zurückgibt. Als Key wird dabei jener Parametername verwendet, den Web API an Match übergeben soll. Listing 2 demonstriert dies für den zuvor betrachteten VersionConstraint.

public class VersionedRoute : RouteFactoryAttribute
{
    private string version;

    public VersionedRoute(string template, string version)
        : base(template)
    {
        this.version = version;
    }

    public override IDictionary Constraints
    {
        get
        {
            var result = new Dictionary();
            result.Add("version", new VersionConstraint(version));
            return result;
        }
    }

}

Das auf diese Weise erhaltene RouteFactoryAttribute-Derivat kann nun anstatt des Attributs Route verwendet werden, um eine attributbasierte Route zu definieren. Listing 3 demonstriert dies für das hier betrachtete Beispiel anhand zweier Action-Methoden, denen derselbe URL (/api/hotel/{id}) zugewiesen wird. Um die erste der beiden Methoden zu adressieren, muss der Aufrufer nun entsprechend der oben beschriebenen Einschränkung über den benutzerdefinierten Kopfzeileneintrag api-version den Wert 1 übergeben; für die zweite Methode hingegen den Wert 2.

Listing 3 [RoutePrefix("api/hotel")]
public class HotelController {

    [VersionedRoute("{id}", "1")]
    public HotelV1 Get(string id) { […] }

    [VersionedRoute("{id}", "2")]
    public HotelV2 Get(int id) { […] }

}

Einschränkungen beim Attribute-Routing in MVC

Die im letzten Abschnitt beschriebene Möglichkeit zur Definition von Einschränkungen beim attributbasierten Routing wurde auch für ASP.NET MVC 5.1 implementiert. Zur Implementierung einer Einschränkung für MVC implementiert der Entwickler das Interface IRouteConstraint, und um eine Einschränkung mit einer Route zu assoziieren, leitet er von RouteFactoryAttribute ab. Diese beiden Typen entsprechen den beiden zuvor besprochenen Klassen HttpRouteContraint und RouteFactoryAttribute aus der Welt von Web API.

Wie bei allen Konzepten, die sowohl für MVC als auch für Web API vorliegen, ist auch hier darauf zu achten, die Typen aus dem richtigen Namensraum heranzuziehen: Während z. B. die Klasse RouteFactoryAttribute  aus dem Namensraum System.Web.Mvc.Routing für den Einsatz mit MVC vorgesehen ist, ist die gleichnamige Klasse aus dem Namensraum System.Web.Http.Routing der Verwendung im Web-API-Umfeld vorbehalten.

[ header = Seite 2: Unbehandelte Ausnahmen protokollieren ]

Unbehandelte Ausnahmen protokollieren

Zum Protokollieren sämtlicher unbehandelter Ausnahmen kann der Entwickler seit Web API 2.1 entweder von der abstrakten Basisklasse ExceptionLogger ableiten oder das Interface IExceptionLogger implementieren. In beiden Fällen ist die vorgegebene Methode Log mit Leben zu füllen, die Web API beim Auftreten einer unbehandelten Ausnahme zur Ausführung bringt. Beim Aufruf von Log übergibt Web API ein Kontextobjekt. Darin befinden sich Informationen wie das aktuelle Exception-Objekt, das verwendete HTTP-Verb (GET, POST etc.) sowie der  angefragte URL.

Damit Web API einen implementierten ExceptionLogger nutzt, muss der Entwickler ihn registrieren. Dazu fügt er ihn zur Services-Auflistung der verwendeten HttpConfiguration hinzu:

config.Services.Add(typeof(IExceptionLogger), new SimpleLogger());

Beim Einsatz der Vorlagen von Visual Studio bietet sich hierzu die Klasse WebApiConfig im Ordner App_Start an.

BSON-Formatter

Zur (De-)Serialisierung einer Nachricht greift Web API auf so genannte Formatter zurück. Um einen geeigneten Formatter zu finden, geht Web API eine Liste mit sämtlichen registrierten Formattern durch und prüft der Reihe nach, ob sie den jeweiligen Daten- sowie Content Type unterstützen. Der erste Formatter, auf den dies zutrifft, wird herangezogen.

Standardmäßig befinden sich in dieser Liste die Formatter für JSON, XML sowie Formatter, die mit HTML-Formularen versendete Daten verarbeiten können. Um eigene Datenformate zu unterstützen, leitet der Entwickler von der abstrakten Basisklasse MediaTypeFormatter oder von BufferedMediaTypeFormatter ab und registriert die auf diesem Weg erhaltene Formatter-Implementierung in der Konfiguration von Web API.

Im Falle des Formats BSON (Binary JSON) kann sich der Entwickler diese Aufgabe nun sparen, denn Web API beinhaltet ab Version 2.1 hierfür einen Formatter. Bei BSON handelt es sich, wie der ausgeschriebene Name Binary JSON schon vermuten lässt, um eine binäre Alternative zu JSON. Durch seine binäre Natur erfolgt die Serialisierung bzw. Deserialisierung von Objekten mit BSON effizienter als mit JSON. In einigen Fällen ist das Ergebnis einer BSON-Serialisierung auch kompakter als das einer entsprechenden JSON-Serialisierung. Dies ist vor allem dann der Fall, wenn in erster Linie numerische Datentypen zum Einsatz kommen, zumal diese nicht als Strings kodiert werden müssen.Um diesen Formatter mit dem Namen BsonMediaTypeFormatter zu registrieren, hinterlegt der Entwickler eine Instanz davon in der Auflistung Formatters des verwendeten Konfigurationsobjekts:

config.Formatters.Add(new BsonMediaTypeFormatter());

Standardmäßig zieht Web API den BsonMediaTypeFormatter in Erwägung, wenn eine Serialisierung bzw. Deserialisierung für den Content Type application/bson durchzuführen ist. Weitere Content Types können in seiner Auflistung SupportedMediaTypes registriert werden.

Alternativ dazu kann der Entwickler den BsonMediaTypeFormatter, wie alle anderen Formatter auch, manuell verwenden, indem er z. B. beim Lesen von Daten am Client oder zum Serialisieren von Objekten mit einer Instanz von ObjectContent genutzt wird.

Das Produktteam hat die Implementierung dieses Formatters auch dazu genutzt, jene Teile des JsonMediaTypeFormatters in eine abstrakte Basisklasse auszulagern, die auch bei der Implementierung weiterer Formate nützlich sein können. Diese Klasse, von der nun sowohl der JsonMediaTypeFormatter als auch der neue BsonMediaTypeFormatter ableiten, nennt sich BaseJsonMediaTypeFormatter. Hierdurch soll die Umsetzung weiterer Formatter für JSON-ähnliche Formate wie MessagePack erleichtert werden.

Asynchrone Filter

Filter geben dem Entwickler die Möglichkeit, Querschnittsfunktionen an globaler Stelle zu implementieren und an bestimmten Stellen im Rahmen der Abarbeitung einer Anfrage ausführen zu lassen. Seit seiner ersten Stunde unterstützt Web API sowohl synchrone als auch asynchrone Filtermethoden. Allerdings musste der Entwickler zur Nutzung der asynchronen Filtermethoden vor Version 2.1 umständliche Interfaces implementieren.

Ab Version 2.1 kann der Entwickler stattdessen von etwas einfacher handzuhabenden abstrakten Klassen ableiten, die in der Vergangenheit den Einsatz synchroner Filtermethoden erzwungen haben. Hierbei handelt es sich um die Klassen AuthorizationFilterAttribute, ActionFilterAttribute und ExceptionFilterAttribute. Diese Klassen bieten nun für jede synchrone Filtermethode ein asynchrones Gegenstück.

URL-Parameter in Portable Libraries parsen

Eine kleine, jedoch äußerst nützliche Erweiterungsmethode, die ab Web API 2.1 auch in Portable Libraries zur Verfügung steht, ist die Methode ParseQueryString aus dem Namensraum System.Net.Http. Sie parst den Query String eines URIs und liefert die darin gefundenen Parameter in Form einer Auflistung zurück. Innerhalb dieser Auflistung kann der Entwickler die einzelnen Parameter bearbeiten bzw. neue Parameter hinzufügen. Durch einen Aufruf von ToString erhält er anschließend einen neuen Query String, der seine Modifikationen widerspiegelt. Dabei muss sich der Entwickler an keiner Stelle mit dem Kodieren bzw. Dekodieren von Sonderzeichen belasten, zumal diese Aufgabe von den verwendeten Objekten wahrgenommen wird. Das nachfolgende Snippet demonstriert dies:

var url = new Uri("http://api/something?id=7&group=8");
var query = url.ParseQueryString();
query["id"] = "8";
query["otherId"] = "10";
var strQuery = query.ToString();

Um einem Parameter mehrfach mit unterschiedlichen Werten aufzunehmen, verwendet der Entwickler die Methode Add der von ParseQueryString zurückgegebenen Auflistung. Um sämtliche Werte der Parameter mit einem bestimmten Namen abzurufen, nutzt er hingegen GetValues.

Unterstützung von Enums durch MVC 5.1

Mit dem neuen HTML-Helper Html.EnumDropDownListFor kann der Entwickler nun in MVC-Views ein Drop-down-Feld für eine Eigenschaft eines Enum-Typs generieren lassen. Die Einträge in diesem Drop-down-Feld entsprechen den Namen der vom zugrunde liegenden Enum angebotenen Werte.

Alternativ dazu kann der Entwickler mit der neuen Hilfsmethode EnumHelper.GetSelectList eine IList ableiten. Übergibt der Entwickler an GetSelectList auch einen Wert dieses Enums, wird das SelectListItem, das diesen Wert repräsentiert, als ausgewählt markiert. Mit dieser Liste kann der Entwickler anschließend auf traditionellem Weg ein Drop-down-Feld unter Verwendung von Html.DropDownListFor rendern lassen. Der Vorteil dieser Vorgehensweise liegt darin, dass der Entwickler die Liste vor der Übergabe an Html.DropDownListFor bearbeiten kann.

HTML-Attribute an Templates übergeben

MVC erlaubt seit seinen ersten Tagen die Definition von Templates zum Rendern eines Models oder einer Eigenschaft eines Models. Die Idee dahinter ist, dass der Entwickler das Model bzw. die gewünschte Eigenschaft lediglich an den HTML-Helper Html.EditorFor übergibt und MVC anhand vorherrschender Kriterien ein geeignetes Template zum Rendern auswählt. Beispielsweise rendert EditorFor für eine String-Eigenschaft standardmäßig ein Textfeld, für eine Eigenschaft von Typ bool hingegen wird eine Checkbox erzeugt.

Während der Entwickler auch schon in der Vergangenheit an EditorFor ein Objekt mit zusätzlichen Parametern für das Template übergeben konnte, boten die standardmäßig von MVC bereitgestellten Templates keine Möglichkeit, die Attribute der von ihnen gerenderten HTML-Attribute zu beeinflussen. Dies ändert sich mit Version 5.1: Übergibt der Entwickler über die Eigenschaft htmlAttributes des Parameterobjekts ein weiteres Objekt, ziehen die Templates dessen Eigenschaften als zusätzliche Attribute für die gerenderten HTML-Elemente heran:

@Html.EditorFor(
            model => model, 
            new { htmlAttributes = new { @class = "form-control" }})

Weitere Neuerungen

Neben den hier besprochenen Neuerungen bringt ASP.NET MVC 5.1 noch ein paar kleinere Abrundungen. Beispielsweise werden nun die Attribute MinLengthAttribute und MaxLengthAttribute durch die unaufdringliche (engl. unobstrusive) JavaScript-Validierung berücksichtigt. Daneben gibt es auch eine Neuerung in den via @Ajax.ActionLink registrierten JavaScript-basierten Callback-Funktionen OnBegin, OnComplete, OnFailure und OnSuccess: Innerhalb dieser Funktionen verweist this nun auf jenes DOM-Element, das die definierte AJAX-Funktion angestoßen hat.

Zusammenfassung

ASP.NET Web API 2.1 bringt einige nützliche Abrundungen, wie die Möglichkeit zur Berücksichtigung der gesamten HTTP-Anfrage bei der Auswahl von Routen. Somit kann der Entwickler nun auf einfache Weise Routen mit bestimmten Kopfzeileneinträgen assoziieren. Daneben gibt es nun einen Formatter für BSON, eine bessere Unterstützung für asynchrone Filter sowie die Möglichkeit, via Attribute die gerenderte API-Dokumentation zu beeinflussen.

Bei ASP.NET MVC 5.1 hat sich im Vergleich zu ASP.NET Web API, wie schon bei Version 5, eher weniger getan. Das dürfte daran liegen, dass ASP.NET MVC zum einen schon länger existiert und zum anderen derzeit wohl das Produktteam den Fokus auf ASP.NET Web API legt. Hervorzuheben ist hier die Möglichkeit zur Beeinflussung der HTML-Attribute der von Templates gerenderten Elemente, zumal diese Möglichkeit in der Vergangenheit immer wieder schmerzlich vermisst wurde.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -