Microsoft

Enabling OpenAPI Specifications for Azure Function

Istock 536316429

What is an OpenAPI Document ?

According to Swagger – OpenAPI Document is a document (or set of documents) that defines or describes an API. An OpenAPI definition uses and conforms to the OpenAPI Specification.

“The OpenAPI Specification (OAS) defines a standard, language-agnostic interface to RESTful APIs which allows both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection. When properly defined, a consumer can understand and interact with the remote service with a minimal amount of implementation logic.

An OpenAPI definition can then be used by documentation generation tools to display the API, code generation tools to generate servers and clients in various programming languages, testing tools, and many other use cases.”

In November 2021, Azure Functions provided support to add OpenAPI Document and definitions to functions triggered by HTTP method. This was made possible by the usage of NuGet Package Microsoft.Azure.WebJobs.Extensions.OpenApi . The package was initially created as a community-driven project which later got supported and maintained as an official project by the Microsoft team.

Why do we need an OpenAPI Document ?

When APIs expose or provide an OpenAPI document, it can be used to learn more about the API operations, as well as the request parameters and response parameters supported by the API. In short, it serves as a documentation for the API allowing third party integrators and other developers to consume the API methods with ease. This helps to reduce the conversations around the need to find how one can consume the APIs.

By using an OpenAPI Document, we can have the testing team and third party developers who are consuming our Function get an overview of methods which are supported in our function, the request schema, the response schema, the error message format and sample request and response message. This way external teams and testing team does not have to depend on the development team to initiate their portion of work. OpenAPI Document also provides a self explanatory document to understand the different operations supported by the API and there is no need for the development team to spend time additionally to write documentations explaining the methods of the API.

How do we add OpenAPI Document ?

Adding an OpenAPI Document specification to Azure Function is straightforward. We will start by creating an Azure Function which uses HTTP as trigger and use Authorization Level as Function.

In Visual Studio 2022 we can now choose the template as Http Trigger with OpenAPI. 

Createazurefunction

Visual Studio 2022 Azure Function Project Template

At the time of this writing,  it is no longer needed to manually add the Microsoft.Azure.WebJobs.Extensions.OpenApi NuGet package to the Functions project. The package is installed by default through the template. Now that we have created our first project using the default template, let us understand some key concepts.

[FunctionName("Function1")]
[OpenApiOperation(operationId: "Run", tags: new[] { "name" })]
[OpenApiSecurity("function_key", SecuritySchemeType.ApiKey, Name = "code", In = OpenApiSecurityLocationType.Query)]
[OpenApiParameter(name: "name", In = ParameterLocation.Query, Required = true, Type = typeof(string), Description = "The **Name** parameter")]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "text/plain", bodyType: typeof(string), Description = "The OK response")]
public async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req)
{
    _logger.LogInformation("C# HTTP trigger function processed a request.");

    string name = req.Query["name"];

    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    dynamic data = JsonConvert.DeserializeObject(requestBody);
    name = name ?? data?.name;

    string responseMessage = string.IsNullOrEmpty(name)
        ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
        : $"Hello, {name}. This HTTP triggered function executed successfully.";

    return new OkObjectResult(responseMessage);
}

If we notice the initial template generated code, we will find new attributes starting with OpenApi over our Function. These attributes control what gets generated as part of the OpenAPI Document Specification. For more details to under attributes you can refer the References section.

Below are the key attributes that we need to look at.

  • OpenApiOperation – This maps to “Operation Object” from the OpenAPI Specification.
  • OpenApiResponseWithBody – This maps to “Responses Object” from the OpenAPI Specification.
  • OpenApiParameter – This corresponds to “Parameter Object” from the OpenAPI Specification.
  • OpenApiSecurity – This corresponds to “Security Scheme Object” from the OpenAPI Sepcification.

Not often we will be creating functions that would pass parameters in the Query string of the Function Url.

There are situations where we have to send a request object in JSON Format to Function and we generally expect a response in JSON Format. The default template holds good for Function which are invoked using a parameter in Query string.

Now let us see how we can update the Function to suit our requirement to accept a request and return a response in JSON Format. Let us go ahead and build our fictional function.

As part of our Function we will go ahead and create two entities – BookRequest and BookResponse.

   [OpenApiExample(typeof(BookRequestExample))]
    public class BookRequest
    {
        /// <summary>The name of the book</summary>
        [OpenApiProperty]
        public string Name { get; set; }

        /// <summary>The description of the book</summary>
        [OpenApiProperty]
        public string Description { get; set; }
    }

    public class BookRequestExample : OpenApiExample<BookRequest>
    {
        public override IOpenApiExample<BookRequest> Build(NamingStrategy namingStrategy = null)
        {

           this.Examples.Add(
                OpenApiExampleResolver.Resolve(
                    "BookRequestExample",
                    new BookRequest()
                    {
                       Name = "Sample Book",
                       Description = "This is a great book on learning Azure Functions"
                    },
                    namingStrategy
                ));

            return this;
        }
    }
[OpenApiExample(typeof(BookResponseExample))] public class BookResponse { /// <summary> /// The name of the book /// </summary> [OpenApiProperty] public string Name { get; set; } /// <summary> /// The Id of the Book in Guid Format /// </summary> [OpenApiProperty] public Guid BookId { get; set; } /// <summary> /// The description of the book /// </summary> [OpenApiProperty] public string Description { get; set; } } public class BookResponseExample : OpenApiExample<BookResponse> { public override IOpenApiExample<BookResponse> Build(NamingStrategy namingStrategy = null) { this.Examples.Add( OpenApiExampleResolver.Resolve( "BookResponseExample", new BookResponse() { Name = "Sample Book", Description = "This is a great book on learning Azure Functions", BookId = new Guid() }, namingStrategy )); return this; } }

If we notice the above code we use two Attributes – OpenApiProperty and OpenApiExample. 

  • OpenApiProperty – This corresponds to Parameter Object from the OpenAPI Sepcification. Each field in our class will have the OpenApiProperty attribute.
  • OpenApiExample – We use the OpenApiExample attribute to map the sample example with the BookRequest and BookResponse.

The Example class should override the Build method. Using OpenApiExampleResolver we map the Example class with an Valid Example Response.

Our modified Function will look as shown in below code snippet. 

[FunctionName("Function1")]
[OpenApiOperation(operationId: "Run", tags: new[] { "run" })]
[OpenApiSecurity("function_key", SecuritySchemeType.ApiKey, Name = "code", In = OpenApiSecurityLocationType.Query)]        
[OpenApiRequestBody(contentType: "application/json; charset=utf-8", bodyType: typeof(BookRequest), Description = "Sample Book Request", Required = true)]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "application/json; charset=utf-8", bodyType: typeof(BookResponse), Description = "The OK response")]
public async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req)
{
    _logger.LogInformation("C# HTTP trigger function processed a request.");
    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    var receivedRequest = JsonConvert.DeserializeObject<BookRequest>(requestBody);

    var receivedBook = new BookResponse();
    receivedBook.Name = receivedRequest.Name;
    receivedBook.Description = receivedRequest.Description;
    receivedBook.BookId = System.Guid.NewGuid();
    var result = JsonConvert.SerializeObject(receivedBook, Formatting.None);
    return new OkObjectResult(result);
}

We introduced a new attribute which is OpenApiRequestBody. This corresponds to the “Request Body Object” from OpenAPI Specification.

Let us go ahead and build and Function and run the function locally. On successful build we should be able to see the Azure Functions Console Window with the Url which can be used to invoke the Function.

Azurefunctionconsolewindow

We should be able to access the Swagger UI and OpenApiDocument using below Url.

RenderOpenApiDocument – http://localhost:7071/api/openapi/1.0 

RenderSwaggerUI – http://localhost:7071/api/swagger/ui

OpenAPI Document Swagger

This way we can expose the operations of our Function using OpenAPI Document. We can see the operation “run” and the request objects and response object fields along with sample example. We can expand the operation “run” and perform a test using the Swagger UI.

Openapidocumentoperationrun

The OpenAPI Document works perfectly fine when the Request and Response Objects are simple.

While writing this blog, I have noticed that the OpenAPI Document generation currently has certain issues when the request objects use nested classes or array of type BookRequest. I think this would be fixed in a future release from the Functions team.

The sample code for this Function can be found in below repository.

https://github.com/baskarmib/AzureFunctionOpenAPISample

References-

https://swagger.io/specification/

 

https://techcommunity.microsoft.com/t5/apps-on-azure-blog/general-availability-of-azure-functions-openapi-extension/ba-p/2931231

 

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/attributes/

Why Perficient?

We’ve helped clients across industries develop strategic solutions and accelerate innovative cloud projects. As a certified Azure Direct Cloud Solution Provider (CSP), we help you drive innovation by providing support to keep your Microsoft Azure operations running.

Whether your IT team lacks certain skills or simply needs support with serving a large business, we’re here to help. Our expertise and comprehensive global support will help you make the most of Azure’s rich features.

Contact our team to learn more.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Baskar Rao Dandlamudi

Baskar has been working in the IT industry for the past 14 years serving clients in different domains like HealthCare, Insurance, and Pharmaceuticals space. He has played various roles, starting as a Developer and progressing to Technical Lead and Application Architect positions in previous assignments and engagements before joining Perficient. At Perficient, Baskar Rao works as a Solution Architect in Azure Practice under Microsoft Business Unit. You can find him speaking at developer community meetups and events and organizing an annual conference outside of work.

More from this Author

Follow Us
TwitterLinkedinFacebookYoutubeInstagram