6 min read

Introducing Swagger UI on Azure Functions

Justin Yoo

DISCLAIMER: This post is purely a personal opinion, not representing or affiliating my employer’s.

There's an awesome library called Swashbuckle in ASP.NET Core Application. With Swashbuckle, it can't never be easier to build Swagger UI automatically. On the other hand, Azure Functions hasn't been invited to that party yet.

In Azure Functions 1.x, it's been offering Swagger document as a preview feature. I wrote a blog post about that. The downside of it is that preview feature is still immature. Even worse, Azure Functions 2.x hasn't offered that preview feature at all. In the end, I wrote another blog post to render a Swagger document that was already written and uploaded to somewhere. But this doesn't still look good. This is based on Design-First Approach draws the Open API definition first then does the implementation later. This is only good when designing the API at the first time. However, over time it's not always the case during the operation phase. Therefore a library like Swashbuckle is very useful. The lack of this feature in Azure Functions has brought out my attention and, as a result, I built an extension library, called Aliencube.AzureFunctions.Extensions.OpenApi.

I'm not too sure how the internal discussion in Microsoft product group goes on and they put a priority on this. But I'm sure this would be very useful until an official library or extension comes out. I'm going to discuss here how to use my library, Aliencube.AzureFunctions.Extensions.OpenApi.

NOTE: All the code sample used here can be found at here.

What Version of Azure Functions Should I Use?

As this library uses the HTTP triggers on Azure Functions, you can use this on both version 1.x and 2.x.

Download NuGet Package

Here's the NuGet package download:

Writing HTTP Triggers

Now, in order to generate Open API document, we need HTTP endpoints. Let's write two basic HTTP triggers – one for GET and the other for POST.

// GET Method
[FunctionName(nameof(GetSample))]
[OpenApiOperation("list", "sample")]
[OpenApiParameter("name", In = ParameterLocation.Query, Required = true,Type = typeof(string))]
[OpenApiParameter("limit", In = ParameterLocation.Query, Required = false Type = typeof(int))]
[OpenApiResponseBody(HttpStatusCode.OK, "application/json", typeo(SampleResponseModel))]
public static async Task<IActionResult> GetSample(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "samples")] HttpRequest req,
ILogger log)
{
...
}
// POST Method
[FunctionName(nameof(PostSample))]
[OpenApiOperation("add", "sample")]
[OpenApiRequestBody("application/json", typeof(SampleRequestModel))]
[OpenApiResponseBody(HttpStatusCode.OK, "application/json", typeo(SampleResponseModel))]
public static async Task<IActionResult> PostSample(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "samples")] HttpRequest req,
ILogger log)
{
...
}

As you can see, the library uses many decorators like what Swashbuckle does, to define Open API document. In addition to this, the library is based on OpenAPI.NET, which follows Open API 3.0.1 spec. Therefore, all decorators also follows that spec. If you want to know further details of each decorator, this document would be worth reading. The latest version of my extension library is 1.1.0 and it has implemented the minimum viable working level.

Configuring Metatdata for Open API

It is mandatory to define Info Object. As this is basically metadata, it's better to define thorough environment variables like local.settings.json or App Settings. Here's an example:

{
"IsEncrypted": false,
"Values": {
...
"OpenApi__Info__Version": "2.0.0",
"OpenApi__Info__Title": "Open API Sample on Azure Functions",
"OpenApi__Info__Description": "A sample API that runs on Azure Functions either 1.x or 2.x using Open API specification.",
"OpenApi__Info__TermsOfService": "https://github.com/aliencube/AzureFunctions.Extensions",
"OpenApi__Info__Contact__Name": "Aliencube Community",
"OpenApi__Info__Contact__Email": "no-reply@aliencube.org",
"OpenApi__Info__Contact__Url": "https://github.com/aliencube/AzureFunctions.Extensions/issues",
"OpenApi__Info__License__Name": "MIT",
"OpenApi__Info__License__Url": "http://opensource.org/licenses/MIT",
"OpenApi__ApiKey": ""
}
}

If this is for Azure Functions instance, its App Settings blade should include the following keys:

  • OpenApi__Info__Version: REQUIRED Version of Open API document. This is not the version of Open API spec. eg. 1.0.0
  • OpenApi__Info__Title: REQUIRED Title of Open API document. eg. Open API Sample on Azure Functions
  • OpenApi__Info__Description: Description of Open API document. eg. A sample API that runs on Azure Functions either 1.x or 2.x using Open API specification.
  • OpenApi__Info__TermsOfService: Terms of service URL. eg. https://github.com/aliencube/AzureFunctions.Extensions
  • OpenApi__Info__Contact__Name: Name of contact. eg. Aliencube Community
  • OpenApi__Info__Contact__Email: Email address for the contact. eg. no-reply@aliencube.org
  • OpenApi__Info__Contact__Url: Contact URL. eg. https://github.com/aliencube/AzureFunctions.Extensions/issues
  • OpenApi__Info__License__Name: REQUIRED License name. eg. MIT
  • OpenApi__Info__License__Url: License URL. eg. http://opensource.org/licenses/MIT
  • OpenApi__ApiKey: API Key of the endpoint that renders the Open API document.

Rendering Open API Document

We've setup environment variables and written two HTTP triggers. Now, it's time to render them. Let's have a look at another HTTP trigger, which uses the OpenApiIgnoreAttribute decorator to avoid it from being included to the Open API document.

[FunctionName(nameof(RenderOpenApiDocument))]
[OpenApiIgnore]
public static async Task<IActionResult> RenderOpenApiDocument(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "openapi/{version}.{extension}")] HttpRequest req,
string version,
string extension,
ILogger log)
{
var ver = GetSpecVersion(version);
var ext = GetExtension(extension);
var settings = new AppSettings();
var helper = new DocumentHelper();
var document = new Document(helper);
var result = await document.InitialiseDocument()
.AddMetadata(settings.OpenApiInfo)
.AddServer(req, settings.HttpSettings.RoutePrefix)
.Build(Assembly.GetExecutingAssembly())
.RenderAsync(ver, ext)
.ConfigureAwait(false);
var response = new ContentResult()
{
Content = result,
ContentType = "application/json",
StatusCode = (int)HttpStatusCode.OK
};
return response;
}

The function code above allows both version and extension bindings. As a result, you can find out the result like below:

  • /api/openapi/v2.json
  • /api/openapi/v2.yaml
  • /api/openapi/v3.json
  • /api/openapi/v3.yaml

Rendering Swagger UI Page

Once the Open API document rendering HTTP trigger is done, it's now the time to build the Swagger UI page. Likewise, the OpenApiIgnoreAttribute decorator is used to be excluded from the Open API document. The code below has the swagger.json endpoint hard-coded because the code above hard-coded swagger.json for Open API document endpoint.

The version of Swagger UI at the time of this writing is 3.20.5.

[FunctionName(nameof(RenderSwaggerUI))]
[OpenApiIgnore]
public static async Task<IActionResult> RenderSwaggerUI(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "swagger/ui")] HttpRequest req,
ILogger log)
{
var settings = new AppSettings();
var ui = new SwaggerUI();
var result = await ui.AddMetadata(settings.OpenApiInfo)
.AddServer(req, settings.HttpSettings.RoutePrefix)
.BuildAsync(typeof(SwaggerUI).Assembly)
.RenderAsync("swagger.json", settings.SwaggerAuthKey)
.ConfigureAwait(false);
var response = new ContentResult()
{
Content = result,
ContentType = "text/html",
StatusCode = (int)HttpStatusCode.OK
};
return response;
}

Once it's done, just hit the endpoint of /api/swagger/ui through your web browser, and you will be able to see the following screen, which is awesome!

This has been done in your local machine. Let's deploy it to Azure. In order to access to the HTTP trigger endpoint, we should use either code=xxx in the querystring or x-functions-key header. As a result, you will be able to see the page like below:


So far, we've walked through how to use the Aliencube.AzureFunctions.Extensions.OpenApi library to render both Open API document and Swagger UI page. With this library, your Azure Function instance will be much more useful than before.