Deprecated Actions III: Decorating with OpenAPI
In the previous blogs in this series, we created an azure function and custom connector and then updated the function to work more cleanly with the Power Platform. In this blog we’re going to decorate our azure function so that we can more easily create the OpenAPI definition used by the custom connector.
Scenario
Our durable function exists and is working nicely. We created a custom connector, but we had to carefully configure the expected inputs and outputs to match the azure function. Let’s decorate the code so that an OpenAPI definition (aka Swagger definition) can be automatically generated, thus simplifying the maintenance of the custom connector.
Azure Functions OpenAPI Extension
The ability to decorate an azure function has been around for a while. Initially developed by Justin Yoo it has now been adopted by Microsoft and is currently in preview status (0.81-preview). The Azure Functions OpenAPI extension github repo contains excellent documentation, and there are also examples on Justin’s blog.
Decorating the functions
We must first add the Microsoft.Azure.WebJobs.Extensions.OpenApi nuget package to our project via dotnet add package --prerelease Microsoft.Azure.WebJobs.Extensions.OpenApi
or using Visual Studio. Currently, we must use the –prerelease flag. Next, add the necessary ‘using’s shown below:
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums;
using Microsoft.OpenApi.Models;
Decorating the HTTP trigger function
We want to expose the HTTP trigger function (see part 1 of the series) as an action in our connector, therefore we’ll add the following decorators:
OpenApiOperation
defines the unique operationId, summary and description properties which will be shown when using the action in the custom connector. A ‘Visibility’ property of ‘Important’ indicates that action will always be shown in the cloud flow editor.OpenApiSecurity
to specify the security method.OpenApiRequestBody
to indicate that the triggering function accepts a request comprising a JSON object in the format of the serialised ScrapeConnectorsRequest class.OpenApiResponseWithBody
to indicate that upon successful completion, the output will be a JSON list of ‘ConnectorInfo’ objects.
[OpenApiOperation(operationId: nameof(ScrapeConnectors), tags: new[] { "default" },
Summary = "Get Actions", Description = "Gets the actions and their deprecation status for one or more custom connectors",
Visibility = OpenApiVisibilityType.Important)]
[OpenApiSecurity("function_key", SecuritySchemeType.ApiKey, Name = "x-functions-key", In = OpenApiSecurityLocationType.Header)]
[OpenApiRequestBody(contentType: "application/json", typeof(ScrapeConnectorsRequest), Required = true)]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "application/json", bodyType: typeof(List<ConnectorInfo>), Summary = "Connector information", Description = "The actions/operations within a connector and the status thereof")]
[FunctionName(nameof(ScrapeConnectors))]
public static async Task<IActionResult> ScrapeConnectors(
... snip ...
Decorating the status function
As covered in the part 2 of the series this is the key function needed to enable the Power Platform to work seamlessly with durable functions. Even though this is an internal function it still needs to be made known to the connector. We will decorate it with OpenApiVisibilityType.Internal
so that it is not displayed in the power platform user interface. We state that the instanceId
will be a required parameter on the path.
[OpenApiOperation(operationId: nameof(ScrapeConnectorsStatus), tags: new[] { "default" },
Summary = "Get Status of Durable Function", Description = "Gets the status of a durable function",
Visibility = OpenApiVisibilityType.Internal)]
[OpenApiParameter(name: "instanceId", In = ParameterLocation.Path, Required = true, Type = typeof(string))]
[OpenApiResponseWithoutBody(statusCode: HttpStatusCode.OK, Description = "default")]
[FunctionName(nameof(ScrapeConnectorsStatus))]
public static async Task<IActionResult> ScrapeConnectorsStatus(
... snip ...
As a minimum that’s all we need to do. The github docs explain how to customise the header, title, theme, security and much more.
Debugging locally
If we run the azure function locally, we see that new endpoints have been added.
It’s particulary nice that we can access the SwaggerUI at http://localhost:7071/api/swagger/ui.
Finally we can get the OpenAPI definition in JSON format via https://localhost:7071/api/swagger.json (or YAML by visiting swagger.yaml).
Updating the custom connector
When the azure function is deployed to the cloud both the OpenAPI definition and SwaggerUI are still available (this can be turned off if needed). In this example the OpenAPI definition is at https://funcdeprecatedactionsblog.azurewebsites.net/api/swagger.yaml. By toggling the ‘Swagger Editor’ switch we can copy and paste the YAML into the custom connector, as shown below:
This makes it much easier to keep the custom connector definition in sync with the azure function. There’s much more documentation on this process in the Create a custom connector from an OpenAPI definition documentation.
Testing in Power Automate
When we try to use our custom connector, we now see our ScrapeConnectors function exposed via the ‘Get Actions’ action with the summary and description taken from the OpenAPI definition. The ScrapeConnectorsStatus function isn’t displayed to the user because we set the visibility to ‘Internal’.
Power Automate now knows about the structure of the response that will be output by the action. This is due to the models contained within the OpenAPI definition that have been derived from our c# response classes.
Summary
- We’ve simplified the creation of a custom connector by decorating our azure function.
- The OpenAPI definition is generated by the azure function, so ’translation errors’ are less likely.
- We no longer have to manually edit the custom connector, just copy and paste in the generated YAML.
Improvements
The Azure Functions OpenAPI extension package doesn’t currently generate all possible OpenAPI definitions for a custom connector. However, the following github issues indicate this could happen in the fullness of time.
- Add broader support for Microsoft’s Custom Connector annotations
- Extend definition objects with custom properties - support Power Apps Custom Connector definition
During development, it would be great to automatically update the custom connector at the same time the Azure function is updated. Currently this is still a manual copy & paste.
Microsoft suggest using Azure API Management to centralise APIs and make available to the power platform rather than directly editing the custom connector.
Source Code
You can get the source code and solutions used on my github in the blog3 branch. Import the ‘DeprecatedConnectors’ solution before the ‘DeprecatedConnectorsFlow’ solution due to a known limitation that custom connectors must be installed first.