Milestones

Publish each ASP.NET Core Controller as individual APIs in Azure API Management

5.11.2020

DevOps Development Blog Azure

Goal

The goal is to automatically publish each ASP.NET Core Controller as individual API in Azure API Management from Azure DevOps pipelines.

Background

Swashbuckle can be used to generate OpenAPI Specification (OAS) of ASP.NET Core application. There is also dotnet tool called swagger that can be used to generate OAS from command line.

dotnet swagger tofile --output swagger.V1.json My.ApibinReleasenetcoreapp3.1My.Api.dll V1

Generated OAS can be used to import API to Azure API Management.

Here is how APIs are shown in Azure API management before doing any changes.

APIs imported before any changes done

All this is cool, but it would be even better to have each API inside OAS as separate API in Azure API Management.

The generated OAS contains all APIs in single file.

"paths": {
"/api/Users": {...},
"/api/Users/search": {...},
"/api/Roles": {...}
}

Server address is also included in OpenAPI specification file.

"servers": [
{
"url": "http://localhost:10586/"
}
]

Divide generated OAS in API specific OASes

I built simple command line application called SwaggerCli that

  • reads generated OAS
  • removes API specific path from "paths" node (f.ex. /api/Users in example above)
  • changes server address so that it includes original API endpoint path (f.ex. /api/Users in example above)
  • saves each API into own OAS file

In Users -endpoint example, Users API -specific OAS contains

"paths": {
"/": {...},
"/search": {...},
},
"servers": [
{
"url": "http://localhost:10586/api/Users"
}
]

All Roles -API specific data has been removed, and server url contains /api/Users path.

Build pipeline

The API specific OAS files are generated in build pipeline, and published as build results.

Following changes were required in build pipeline:

  1. Include dotnet tool swagger
    1. dotnet tool install Swashbuckle.AspNetCore.Cli --version 5.6.2
  2. Include custom built SwaggerCli into Solution file
  3. Run dotnet tool restore as part of pipeline
  4. Build SwaggerCli as part of pipeline
  5. Generate OpenApi specification with dotnet tool swagger
  6. Run SwaggerCli to divide OAS file to API specific OAS files
  7. Publish API specific OAS files as build results

Steps 5-7 as YAML:

- task: CmdLine@2
displayName: Create specs directory
inputs:
script: 'mkdir $(Pipeline.Workspace)specs'

- task: DotNetCoreCLI@2
displayName: 'Generate OpenAPI spec document'
inputs:
command: custom
custom: swagger
arguments: 'tofile --output $(Pipeline.Workspace)specs$.V1.json $.ApibinReleasenetcoreapp3.1$.Api.dll V1'

- task: DotNetCoreCLI@2
displayName: "Divide OpenAPI spec document to API specific documents"
inputs:
command: custom
custom: SwaggerClibinReleasenetcoreapp3.1SwaggerCli.dll
arguments: '--inputFilename $(Pipeline.Workspace)specs$.V1.json --outputPath $(Pipeline.Workspace)specs'

- task: CmdLine@2
displayName: Delete original OpenAPI spec file
inputs:
script: 'del $(Pipeline.Workspace)specs$.V1.json'

- task: PublishPipelineArtifact@1
displayName: "Publish Artifact: OpenAPI specs"
inputs:
targetPath: '$(Pipeline.Workspace)specs'
artifactName: 'OpenAPI'

Release pipeline

For each API specific OAS, new task in Release pipeline was added:

- task: AzurePowerShell@4
displayName: Update API Management for Users API
inputs:
azureSubscription: '$'
scriptType: 'FilePath'
scriptPath: '$(Pipeline.Workspace)/Deployment/Scripts/Update-ApimOpenAPISpec.ps1'
scriptArguments: '-ResourceGroupName "$" -ServiceName "$" -ServiceUrl "$api/users" -ApiPath "api/users" -ApiName "Users" -SpecificationFilePath $(Pipeline.Workspace)OpenAPIUsers.$.json'
errorActionPreference: 'stop'
azurePowerShellVersion: 'latestVersion'

Task is calling PowerShell script to import OpenAPI specification into Azure API Management:

[CmdletBinding()]
Param(
[string] [Parameter(Mandatory=$true)] $ResourceGroupName,
[string] [Parameter(Mandatory=$true)] $ServiceName,
[string] [Parameter(Mandatory=$true)] $ServiceUrl,
[string] [Parameter(Mandatory=$true)] $ApiPath,
[string] [Parameter(Mandatory=$true)] $ApiName,
[string] [Parameter(Mandatory=$true)] $SpecificationFilePath
)

$apiMgmtContext = New-AzApiManagementContext -ResourceGroupName $ResourceGroupName -ServiceName $ServiceName

$api = Get-AzApiManagementApi -Context $apiMgmtContext -Name $ApiName
if ($null -eq $api) {
$api = New-AzApiManagementApi -Context $apiMgmtContext -Name $ApiName -ServiceUrl $ServiceUrl -Path $ApiPath -Protocols @("https")
}

Import-AzApiManagementApi -Context $apiMgmtContext `
-SpecificationPath $SpecificationFilePath `
-SpecificationFormat 'OpenApi' `
-Path $api.Path `
-ApiId $api.ApiId

Final words

Here is how APIs are shown in Azure API management after changes were made.

APIs imported after changes are done

Now I can define multiple API Management Products and include selected APIs for each Product. This was not possible without importing each ASP.NET Core Controller into own API Management API.

Thanks Twitter user @domze for great article below, it helped a lot to accomplish the goal!

https://www.domstamand.com/automating-your-openapi-updates-to-api-management-through-your-cicd-pipeline/

Sami Ovaska

Sami Ovaska

Sami has worked in product and software development projects for large and small companies using Microsoft technologies almost 30 years. He has experience in different roles ranging from developer to director, his passion is in architectures and backend development and naturally Azure.

Use H2 for the title

Write your content

Use H2 for the title

Write your content