Skip to content

Implement Open API on Azure Functions App

Justin Yoo edited this page Mar 30, 2019 · 5 revisions

Prerequisites

Create Azure Functions App Instance with Managed Identity

Create an Azure Functions App instance from Azure Portal. Note the name of Azure Functions App instance for future use.

Once it's created, add app settings value like:

Activate Managed Identity.

Update Azure Key Vault for Function App

Add the Function App instance to the access policy of the Azure Key Vault instance.

Update Azure Function App for Open API

Checkout Bootstrap Code

In order to start from the baseline, run the following command.

git checkout step-03
cd KeyVault.FunctionApp

A bootstrap HTTP trigger has been created.

Restore NuGet Packages

Run the following command to add NuGet package.

dotnet add . package Aliencube.AzureFunctions.Extensions.OpenApi -v 1.4.2

NuGet package has been added.

Update local.settings.json

Update local.settings.json

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet",

    "KeyVaultName": "gib2019kv",

    "OpenApi__Info__Version": "2.0.0",
    "OpenApi__Info__Title": "GIB2019 Key Vault API",
    "OpenApi__Info__Description": "A Key Vault API that runs on Azure Functions 2.x for GIB2019.",
    "OpenApi__Info__TermsOfService": "https://github.com/fairdincom/GIB2019-LogicApps-KeyVault-Demo",
    "OpenApi__Info__Contact__Name": "fairdin.com",
    "OpenApi__Info__Contact__Email": "[email protected]",
    "OpenApi__Info__Contact__Url": "https://github.com/fairdincom/GIB2019-LogicApps-KeyVault-Demo/issues",
    "OpenApi__Info__License__Name": "MIT",
    "OpenApi__Info__License__Url": "http://opensource.org/licenses/MIT",

    "OpenApi__ApiKey": ""
  }
}

Create Response Models

  1. Create HTTP response models on SecretResponseModel.cs.

    namespace KeyVault.FunctionApp
    {
        public class SecretResponseModel
        {
            public virtual string Name { get; set; }
            public virtual string Value { get; set; }
        }
    }
  2. Create HTTP response models on SecretCollectionResponseModel.cs.

    using System.Collections.Generic;
    
    namespace KeyVault.FunctionApp
    {
        public class SecretCollectionResponseModel
        {
            public SecretCollectionResponseModel()
            {
                this.Items = new List<SecretResponseModel>();
            }
    
            public virtual List<SecretResponseModel> Items { get; set; }
        }
    }

Create App Settings

Create AppSettings.cs

using Aliencube.AzureFunctions.Extensions.OpenApi.Configurations;

namespace KeyVault.FunctionApp
{
    public class AppSettings : OpenApiAppSettingsBase
    {
        public AppSettings() : base()
        {
        }
    }
}

Update Function Codes

  1. Open the SecretsHttpTrigger.cs file.

  2. Replace all the namespace declarations at the top of the file with this:

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Threading.Tasks;
    
    using Aliencube.AzureFunctions.Extensions.OpenApi.Attributes;
    using Aliencube.AzureFunctions.Extensions.OpenApi.Enums;
    
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Azure.KeyVault;
    using Microsoft.Azure.KeyVault.Models;
    using Microsoft.Azure.Services.AppAuthentication;
    using Microsoft.Azure.WebJobs;
    using Microsoft.Azure.WebJobs.Extensions.Http;
    using Microsoft.Extensions.Logging;
    using Microsoft.OpenApi.Models;
    
    using Newtonsoft.Json;
  3. Replace the GetSecrets function with this.

    [FunctionName(nameof(GetSecrets))]
    [OpenApiOperation("list", "secret", Summary = "Gets the list of secrets", Description = "This gets the list of secrets.", Visibility = OpenApiVisibilityType.Important)]
    [OpenApiResponseBody(HttpStatusCode.OK, "application/json", typeof(SecretCollectionResponseModel), Summary = "Secret collection response.")]
    public static async Task<IActionResult> GetSecrets(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "secrets")] HttpRequest req,
        ILogger log)
    {
        IActionResult result;
        try
        {
            var response = await kv.GetSecretsAsync(keyVaultBaseUrl)
                                   .ConfigureAwait(false);
            var models = response.OfType<SecretItem>()
                                 .Select(p => new SecretResponseModel() { Name = p.Identifier.Name })
                                 .ToList();
    
            result = new OkObjectResult(new SecretCollectionResponseModel() { Items = models });
        }
        catch (KeyVaultErrorException ex)
        {
            var statusCode = (int)ex.Response.StatusCode;
            var value = new { StatusCode = statusCode, Message = ex.Body?.Error?.Message };
    
            result = new ObjectResult(value) { StatusCode = statusCode };
        }
        catch (Exception ex)
        {
            var statusCode = (int)HttpStatusCode.InternalServerError;
            var value = new { StatusCode = statusCode, Message = ex.Message };
    
            result = new ObjectResult(value) { StatusCode = statusCode };
        }
    
        return result;
    }
  4. Replace the GetSecret function with this.

    [FunctionName(nameof(GetSecret))]
    [OpenApiOperation("view", "secret", Summary = "Gets the secret", Description = "This gets the secret.", Visibility = OpenApiVisibilityType.Important)]
    [OpenApiParameter("name", In = ParameterLocation.Path, Required = true, Type = typeof(string), Description = "The secret name.")]
    [OpenApiResponseBody(HttpStatusCode.OK, "application/json", typeof(SecretResponseModel), Summary = "Secret response.")]
    public static async Task<IActionResult> GetSecret(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "secrets/{name}")] HttpRequest req,
        string name,
        ILogger log)
    {
        IActionResult result;
        try
        {
            var response = await kv.GetSecretAsync(keyVaultBaseUrl, name)
                                   .ConfigureAwait(false);
            var model = new SecretResponseModel() { Name = response.SecretIdentifier.Name, Value = response.Value };
    
            result = new OkObjectResult(model);
        }
        catch (KeyVaultErrorException ex)
        {
            var statusCode = (int)ex.Response.StatusCode;
            var value = new { StatusCode = statusCode, Message = ex.Body?.Error?.Message };
    
            result = new ObjectResult(value) { StatusCode = statusCode };
        }
        catch (Exception ex)
        {
            var statusCode = (int)HttpStatusCode.InternalServerError;
            var value = new { StatusCode = statusCode, Message = ex.Message };
    
            result = new ObjectResult(value) { StatusCode = statusCode };
        }
    
        return result;
    }

Create Swagger UI Function Code

  1. Open the OpenApiHttpTrigger.cs file.

  2. Replace all the namespace declarations at the top of the file with this:

    using System;
    using System.IO;
    using System.Net;
    using System.Reflection;
    using System.Threading.Tasks;
    
    using Aliencube.AzureFunctions.Extensions.OpenApi;
    using Aliencube.AzureFunctions.Extensions.OpenApi.Abstractions;
    using Aliencube.AzureFunctions.Extensions.OpenApi.Attributes;
    using Aliencube.AzureFunctions.Extensions.OpenApi.Extensions;
    
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Azure.WebJobs;
    using Microsoft.Azure.WebJobs.Extensions.Http;
    using Microsoft.Extensions.Logging;
    using Microsoft.OpenApi;
    
    using Newtonsoft.Json;
  3. Within the namespace block – namespace KeyVault.FunctionApp, add the following class block.

    public static class OpenApiHttpTrigger
    {
    }
  4. Within the class block, add the following fields.

    private static AppSettings settings = new AppSettings();
    private static IDocument doc = new Document(new DocumentHelper());
    private static ISwaggerUI swaggerUI = new SwaggerUI();
  5. Add two Function endpoints.

    [FunctionName(nameof(RenderSwaggerDocument))]
    [OpenApiIgnore]
    public static async Task<IActionResult> RenderSwaggerDocument(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "swagger.json")] HttpRequest req,
        ILogger log)
    {
        var assembly = Assembly.GetExecutingAssembly();
        var result = await doc.InitialiseDocument()
                              .AddMetadata(settings.OpenApiInfo)
                              .AddServer(req, settings.HttpSettings.RoutePrefix)
                              .Build(assembly)
                              .RenderAsync(OpenApiSpecVersion.OpenApi2_0, OpenApiFormat.Json)
                              .ConfigureAwait(false);
        var content = new ContentResult()
                          {
                              Content = result,
                              ContentType = "application/json",
                              StatusCode = (int)HttpStatusCode.OK
                           };
    
        return content;
    }
    
    [FunctionName(nameof(RenderSwaggerUI))]
    [OpenApiIgnore]
    public static async Task<IActionResult> RenderSwaggerUI(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "swagger/ui")] HttpRequest req,
        ILogger log)
    {
        var result = await swaggerUI.AddMetadata(settings.OpenApiInfo)
                                    .AddServer(req, settings.HttpSettings.RoutePrefix)
                                    .BuildAsync()
                                    .RenderAsync("swagger.json", settings.SwaggerAuthKey)
                                    .ConfigureAwait(false);
        var content = new ContentResult()
                          {
                              Content = result,
                              ContentType = "text/html",
                              StatusCode = (int)HttpStatusCode.OK
                          };
    
        return content;
    }

Deploy Azure Function App to Azure

Use Visual Studio Code

Use Visual Studio Code, deploy the Azure Function app onto Azure.

Use Azure CLI

Alternatively, Azure CLI can be used for app deployment.

dotnet build . -c Release
dotnet publish . -c Release
cd bin/Release/netcoreapp2.1/publish

zip -r publish.zip .

az functionapp deployment source config-zip \
    -n gib2019-fncapp-1 \
    -g gib2019 \
    --src publish.zip

Once deployed, this Swagger UI page should appear. Note the URL of swagger.json file. Also download the swagger.json file just in case.