Microsoft hat mit dem ersten Majorrelease im November 2021 der Extension für OpenAPI-Definition in Azure Functions endlich eine eigene Lösung veröffentlicht. Vor mittlerweile zwei Jahren hatte ich eine Serie zur Erstellung von Web APIs mit Azure Functions hier bei uns auf dem OKBlog geschrieben. Für die Neulinge: Hier gehts nochmal zum ersten Teil der Serie
.
In der damaligen Implementierung wurde .NET Core 3.1 als technische Basis und einem Drittanbieter-Package für die Erzeugung der OpenAPI-Spezifikation verwendet. Währenddessen wurde .NET 6 (LTS) und sogar .NET 7 released. Wenn du nochmal durchlesen möchtest, wie die OpenAPI-Spezifikation mit .NET Core 3.1 und Swashbuckle umgesetzt wurde, gehts hier entlang
.
Bereits im damaligen Beitrag hatte ich erwähnt, dass Microsoft an einer eigenen Lösung zur Implementierung der OpenAPI-Spezifikation arbeitete. In diesem Beitrag möchte ich nochmal das Beispiel-GitHub-Repo
verwenden, um aufzuzeigen, welche Anpassungen notwendig sind, um auf die von Microsoft bereitgestellte Lösung umzustellen.
Revisit Web APIs mit Azure Functions: Swagger UI (Teil 4)
Wie erwähnt, gab es seit der initialen Erstellung des Beispiel-Repos einige Neuerungen. Hieraus ergeben sich im Wesentlichen die folgenden:
- Upgrade von .NET Core 3.1 auf .NET 6
- Update/Upgrade der Nuget-Packages
- Ausbau von Swashbuckle aus der Solution
- Einbau der OpenAPI-Extension von Microsoft
Im Blogbeitrag werde ich insbesondere auf die Unterschiede zu Swashbuckle und die notwendigen Anpassungen eingehen, um die OpenAPI-Spezifikation der APIs mit Microsofts Extension zu realisieren.
Projektanpassung
Um die die Microsoft Extension nutzen zu können, muss das Nuget dem Function-Projekt zunächst hinzugefügt werden:
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.OpenApi" Version="1.x.x" />
Die entsprechenden Swashbuckle-Nuget-Verweise müssen natürlich entfernt werden. Dazu gehört auch die im Projekt befindliche Klasse SwaggerController.cs
, welche primär für die Erzeugung der OpenAPI-Spezifikation als JSON und dem Rendern von Swagger diente.
In der Klasse Startup.cs
sollte das Laden der Swashbuckle Dependency (AddSwashBuckle
) ebenfalls entfernt werden. Für Microsofts Lösung wird kein explizites Laden der Dependency benötigt. Falls man allerdings die Swagger-Dokumentation anpassen möchte, wie z.B. den Titel, die Beschreibung, den Kontakt oder die Lizenzbestimmung, so kann dies über eine separate Klasse definiert werden.
Diese könnte zum Beispiel so aussehen:
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums;
using Microsoft.OpenApi.Models;
using System;
namespace Application.Functions
{
public class MyOkBlogOpenApiConfigurationOptions : DefaultOpenApiConfigurationOptions
{
public override OpenApiInfo Info { get; set; } = new OpenApiInfo()
{
Version = GetOpenApiDocVersion(),
Title = "OKBlog ToDo-Functions",
Description = "Das ist eine Beschreibung für meine OKBlog ToDo-Function-App",
TermsOfService = new Uri("https://blog.objektkultur.de"),
Contact = new OpenApiContact()
{
Name = "Objektkultur",
Email = "kontakt@objektkultur.de",
Url = new Uri("https://objektkultur.de"),
},
License = new OpenApiLicense()
{
Name = "MIT",
Url = new Uri("http://opensource.org/licenses/MIT"),
}
};
public override OpenApiVersionType OpenApiVersion { get; set; } = GetOpenApiVersion();
}
}
Dabei muss die Datei auch nicht separat in der Startup
-Klasse eingebunden werden. Alles funktioniert automatisch.
OpenAPI-Spezifikation mit Microsofts Extension
Nachdem nun alle Swashbuckle-Verweise entfernt und mit Microsofts OpenAPI Extension ersetzt wurden, schauen wir uns einmal an, wie man nun die OpenAPI-Spezifikation für die Functions definiert.
Um einen besseren Überblick zu erhalten, schauen wir uns nun im folgenden die Attributierung der OpenAPI-Spezifikation für die Function StoreTodoItemCommandFunction
an.
// omitted usings
[FunctionName("StoreTodoItemCommandFunction")]
[OpenApiOperation(operationId: "todo", tags: new[] { "todo" }, Summary = "StoreTodoItem", Description = "Save a new ToDo-Item", Visibility = OpenApiVisibilityType.Important)]
[OpenApiSecurity("function_key", SecuritySchemeType.ApiKey, Name = "code", In = OpenApiSecurityLocationType.Query)]
[OpenApiRequestBody(contentType: "application/json", typeof(StoreTodoItemCommand))]
[OpenApiParameter(name: "x-my-custom-header", In = ParameterLocation.Header, Required = true, Type = typeof(string))]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "application/json", bodyType: typeof(TodoItem), Description = nameof(HttpStatusCode.OK))]
[OpenApiResponseWithoutBody(statusCode: HttpStatusCode.NotFound, Description = nameof(HttpStatusCode.NotFound))]
[OpenApiResponseWithoutBody(statusCode: HttpStatusCode.Unauthorized, Description = nameof(HttpStatusCode.Unauthorized))]
public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = "Todo")] HttpRequest req,
ILogger log)
{
// omitted C# Code
}
Hierbei fallen insgesamt zwei Hauptunterschiede zur Umsetzung mit Swashbuckle auf (nächstes Listing). Zum einen ist das Namensschema bei Microsofts OpenAPI-Lösung einheitlich (OpenApi als Prefix für die Attribute), zum anderen müssen bei Swashbuckle die Angabe von Requestbodies innerhalb der Run
-Parameter der Function-Methode eingefügt werden, was die Leserlichkeit stört und eine nachträgliche Pflege erschwert.
// omitted usings
[FunctionName("StoreTodoItemCommandFunction")]
[ApiExplorerSettings(GroupName = "todo")]
[ProducesResponseType(typeof(StoreTodoItemCommand), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = "Todo")] HttpRequest req,
[RequestBodyType(typeof(StoreTodoItemCommand), "StoreTodoItemCommand Request-Body")],
ILogger log)
{
// omitted C# Code
}
Sicherlich ist der Vergleich nicht ganz fair, werden Attribute bei der Swashbuckle Implementierung erst gar nicht verwendet (z.B. ResponseBody etc.), doch kann man anhand des Beispiels schnell erkennen, dass die Attributierung mit Microsofts Lösung sowohl inhaltlich umfangreicher, als auch leichter zu pflegen ist. Aufgrund der einheitlichen Namensgebung kann der Entwickler direkt sehen, welche Attribute zur OpenAPI-Spezifikation gehören.
Swagger
Beim Ausführen der Function fällt sofort auf, dass insgesamt vier Swagger-APIs bereitgestellt werden:
Nach Aufruf der Swagger-UI erscheinen alle definierten APIs aufgelistet. Zusätzlich erhält man hier auch die Möglichkeit, alle Endpunkte zu autorisieren (via “Authorize”).
Die APIs beinhalten sämtliche Informationen, welche vorher via Attributierung an den Functions dekoriert worden sind. Hierzu gehört z.B. der body
, welcher an die API gesendet wird, oder die Beschreibung der API im Allgemeinen.
Fazit
Die Extension von Microsoft beweist, dass die Nutzung und Integration in bestehende Projekte sehr gut gelingt. Hierdurch können Abhängigkeiten zu Drittanbieter-Packages reduziert werden. In der Vergangenheit musste ich leider öfter feststellen, dass aufgrund von Swashbuckle-Abhängigkeiten zu anderen Paketen wie z.B. System.Text.Json
bestand und wir somit nur mit bestimmten Versionen arbeiten konnten und einige Features dadurch verwehrt blieben.
Wenn man also auf .NET 6/7 upgraden möchte, kann man diesen Anlass auch dazu nutzen, Microsofts Extension zur OpenAPI-Definition zu verwenden. Diese bietet eine umfangreiche Anpassbarkeit der APIs und somit auch einen großen Mehrwert für die Konsumenten der APIs.
Um die Änderungen und Anpassungen nachvollziehen zu können, gelangst du hier direkt zum Repo und dem zugehörigen Commit
.