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

Sami Ovaska | 05.11.2020
Reading time 3 min

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.Api\bin\Release\netcoreapp3.1\My.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\${{parameters.projectName}}.V1.json ${{ parameters.projectName }}.Api\bin\Release\netcoreapp3.1\${{parameters.projectName}}.Api.dll V1'

- task: DotNetCoreCLI@2
displayName: "Divide OpenAPI spec document to API specific documents"
inputs:
command: custom
custom: SwaggerCli\bin\Release\netcoreapp3.1\SwaggerCli.dll
arguments: '--inputFilename $(Pipeline.Workspace)\specs\${{parameters.projectName}}.V1.json --outputPath $(Pipeline.Workspace)\specs'

- task: CmdLine@2
displayName: Delete original OpenAPI spec file
inputs:
script: 'del $(Pipeline.Workspace)\specs\${{parameters.projectName}}.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: '${{ parameters.azureSubscriptionName }}'
scriptType: 'FilePath'
scriptPath: '$(Pipeline.Workspace)/Deployment/Scripts/Update-ApimOpenAPISpec.ps1'
scriptArguments: '-ResourceGroupName "${{ parameters.resourceGroupName }}" -ServiceName "${{ parameters.apiManagementName }}" -ServiceUrl "${{ parameters.apiManagementServiceUrl }}api/users" -ApiPath "api/users" -ApiName "Users" -SpecificationFilePath $(Pipeline.Workspace)\OpenAPI\Users.${{ parameters.environment }}.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!

Automating your OpenAPI updates to API Management through your CI/CD pipeline