In the previous series of articles (Part 1, Part 2, Part 3), we went over how to create Self-Documenting Azure Functions with C# and OpenAPI with generated OpenAPI specs. At the time, this was done with the current LTS versions, which meant Azure Functions V3 and .NET 3.1.
Since then, .NET 6 and Azure Functions V4 have been released. Along with those, another very useful package has also been released: Microsoft.Azure.WebJobs.Extensions.OpenApi. This package removes the need for many of the extra steps taken in the previous articles.
In this article, I will walk you through the process of upgrading our “Bmazon” application to support .NET 6 and convert our OpenAPI document generation to use the new NuGet package.
The new OpenAPI Package
The Microsoft.Azure.WebJobs.Extensions.OpenApi package incorporates a lot of logic that we did in a custom way in the previous posts. Here is a quick comparison of our old approach with this new package:
- Basic Configuration
- Old – Previously, we used a call to
builder.AddSwashBuckle()
in the Startup.cs file. - New – Works Automagically, but it is also customizable by implementing an interface (see below).
- API Endpoints for UI / Document Download
- Old – Create custom Azure Functions to handle UI and json document downloads
- New – Works Automagically
- Document Parameters/Return Values
- Old – Combination of XML documentation and Attributes from disparate namespaces
- New – OpenAPI Attributes
- Schema/Validation
- Old – Use Data Annotations
- New – Use Data Annotations or JsonProperty Attributes
- Create Separate Documents
- Old – Lots of Custom Startup Code
- New – Not Supported. Not really the best approach anyway, since it doesn’t restrict users. Better to use separate Function apps or an API Gateway like Azure API Management.
Learn more about integrating this with Azure API Management
Update Frameworks
Now, we need to update to the latest framework versions (.Net6 and Functions v4).
Remove global.json
First, if you were using a global.json
file to lock things to the dotnet 3.1 version, go ahead and delete that now. Alternately, you could update the contents to lock it to version 6.
Download the SDKs
You’ll need to download and install the following
Update the Project
Next, you need to update your Bmazon.csproj
file to target the new SDKs.
Remove XML documentation
Unless you need it for another reason, you can remove the following line from the csproj file:
<DocumentationFile>Bmazon.xml</DocumentationFile>
Old Frameworks
<PropertyGroup> <TargetFramework>netcoreapp3.1</TargetFramework> <AzureFunctionsVersion>v3</AzureFunctionsVersion> ... </PropertyGroup>
New Frameworks
<PropertyGroup> <TargetFramework>net6.0</TargetFramework> <AzureFunctionsVersion>v4</AzureFunctionsVersion> ... </PropertyGroup>
Then you need to change your package references as well. We also will need to add the Microsoft.Azure.Functions.Extensions
because that was being pulled in as a secondary dependency in the past, but not now.
> dotnet remove package AzureExtensions.Swashbuckle > dotnet add package Microsoft.NET.Sdk.Functions > dotnet add package Microsoft.Azure.Functions.Extensions > dotnet add package Microsoft.Azure.WebJobs.Extensions.OpenApi
This will update the package to the latest versions and install the new OpenApi package.
Now we’re ready to update the code.
Update Startup.cs
Configuration is handled differently with the new package, so the entire builder.AddSwashBuckle
method call should be removed. Your Startup class should look like this now:
public class Startup : FunctionsStartup { public override void Configure(IFunctionsHostBuilder builder) { builder.Services.AddSingleton<OrderService>(); } }
Update OpenAPI Config
First, the new package automatically creates the Azure Functions for the API endpoints for you, so you can just delete the OpenApiJson and OpenApiUi files from the “OpenApi” folder.
Once those files are removed, you need to add a new class that implements the IOpenApiConfigurationOptions
interface so that we can make some minor configuration changes. The package will find this class at runtime to get its configuration from.
The implementation is quite simple. We are defaulting to OpenAPI V3 and setting the title and API version here. All the rest are effectively defaults.
public class OpenApiConfigurationOptions : IOpenApiConfigurationOptions { public OpenApiInfo Info { get; set; } = new OpenApiInfo { Title = "Bmazon APIs", Version = "1.0" }; public List<OpenApiServer> Servers { get; set; } = new(); public OpenApiVersionType OpenApiVersion { get; set; } = OpenApiVersionType.V3; public bool IncludeRequestingHostName { get; set; } = false; public bool ForceHttp { get; set; } = true; public bool ForceHttps { get; set; } = false; }
Using the New Attributes
Since we’ve switch packages away from the Swashbuckle library, we need to change the attributes we use to annotate our Azure Functions. Follow a few simple steps to update your code.
OpenApiOperation
OpenApiOperation
is a new attribute that each API Function needs to be decorated with to designate it as an API. This attribute takes the name of the function along with a description and some optional “tags” to be used for categorizing the function. In our case, we will be adding a tag for the same groupings we used before (“Shopping” or “Warehouse”) to designate the target audience.
Here’s an example for the CreateOrder
Function:
[OpenApiOperation( "CreateOrder", tags: new[] { "Shopping" }, Description = "Creates an Order that ...")]
OpenApiRequestBody
Each instance of RequestBodyType
should be replaced with OpenApiRequestBody
Old (attribute on the HttpRequestMessage
parameter):
[RequestBodyType( typeof(Order), "The Order To Create")]
New:
[OpenApiRequestBody( contentType: "application/json", bodyType: typeof(Order), Description = "The Order To Create")]
OpenApiResponseWithBody
Everywhere your code has ProducesResponseType
, it should be replaced with OpenApiResponseWithBody
.
Old (description was in the XML comments):
[ProducesResponseType( typeof(string), StatusCodes.Status200OK)]
New:
[OpenApiResponseWithBody( statusCode: HttpStatusCode.OK, contentType: "application/json", bodyType: typeof(string), Description = "Indicates success and returns a user-friendly message")]
OpenApiResponseWithoutBody
Any API methods/scenarios that do not return a Body in the response should also have an OpenApiResponseWithoutBody
attribute
[OpenApiResponseWithoutBody( statusCode: HttpStatusCode.OK, Description = "Indicates success. Returns no payload")]
NOTE: Since the Descriptions for the Responses are now in these attributes, you can remove all the <response code="XXX">
tags in your XML comments
Full Example
Here is a comparison of the entire CreateOrder method definition:
Old:
[ProducesResponseType( typeof(string), StatusCodes.Status200OK)] [ProducesResponseType( typeof(IEnumerable<string>), StatusCodes.Status400BadRequest)] [FunctionName("CreateOrder")] [ApiExplorerSettings(GroupName = "Shopping")] public async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "order")] [RequestBodyType(typeof(Order), "The Order To Create")] HttpRequestMessage req, ILogger log) { ... }
New:
[OpenApiOperation("CreateOrder", tags: new[] { "Shopping" }, Description = "Creates an Order that will be shipped to the Warehouse for fulfillment.")] [OpenApiRequestBody( contentType: "application/json", bodyType: typeof(Order), Description = "The Order To Create")] [OpenApiResponseWithBody( statusCode: HttpStatusCode.OK, contentType: "application/json", bodyType: typeof(string), Description = "Indicates success and returns a user-friendly message")] [OpenApiResponseWithBody( statusCode: HttpStatusCode.BadRequest, contentType: "application/json", bodyType: typeof(IEnumerable<string>), Description = "Indicates a data validation issue and will return a list of data validation errors")] [FunctionName("CreateOrder")] public async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "order")] HttpRequestMessage req, ILogger log) { ... }
Test It All
Now that things are all updated, you should be able to build the code with dotnet build
.
NOTE: If you’re updating your own project, you may want to check the Microsoft documentation on Breaking changes between 3.x and 4.x to be sure there’s nothing else you need to be concerned with.
The new URLs
Once you update everything and run func host start
, you should see these new URLs replace the old ones:
RenderSwaggerUI
This method renders the default view as configured.
RenderSwaggerDocument
This URL allows you to download the document directly. Just substitute “{extension}” with either of the supported file extensions (json
or yaml
) and it will download the appropriate file
RenderOpenApiDocument
If you are looking for a specific OpenAPI version, you can use this URL to do that by adding a supported OpenAPI version (“v2”, “v3”) along with the extension in order to get that specific version. (e.g. http://localhost:7071/api/openapi/v2.yaml
)
Conclusion
Now that we’ve gone through this exercise, you are now generating Swagger/OpenAPI documents in your Azure Functions in a better way using a Microsoft-recommended approach.
Happy Coding!
TOP MODERNIZATION APPROACHES
Learn about the top three modernization approaches — rehosting, replatforming, and refactoring — uncovering pros, cons, and process ideas as you go.