Creating Azure Functions OpenAPI Extension from SOAP Web Services

SOAP (Simple Object Access Protocol) based web services API is optimized for on-premise languages and platforms, which works really well from C#, Java and the likes. In recent years however, REST (Representational State Transfer) API has become increasingly popular, as it can be easily invoked from Frontend programming languages like JavaScript or from Power Platform.

In this post we will consume the Muhimbi SOAP service in an Azure Microsoft .NET Core function (as it  can easily handle those SOAP webservices through WCF by creating service references) that can further be invoked as a REST API in Microsoft Flow, PowerApps, and even from your Rest Client.

Note: This post covers fairly advanced topics, so you’ll need to have at least a basic knowledge of Azure and .Microsoft .NET Core in order to reproduce the methods described here.

If you are a IT Pro and you prefer PowerShell, have a look at this blog post of mine:

https://clavinfernandes.wordpress.com/2019/12/02/azure-functions-for-muhimbi-powerusers-and-itpros/

Why not API Management and Azure Logic Apps?

We all know that API Management and Azure Logic Apps supports SOAP out-of-the-box using WSDL and it might indeed work for very simple SOAP webservices, but when it comes to consuming a complex (powerful) webservice, it might just fall a little bit short, as both have restrictions on complex SOAP message structure.

Why Azure Functions?

As a developer, you want to concentrate on the application\code and not worry about Infrastructure and other underlying complexities. Azure Functions indeed is the perfect fit as it is “serverless” and you can simply add “service references”.

  • Azure Functions are “serverless” compute services that let you run event-triggered code without having to explicitly provision or manage infrastructure.
  • Azure Functions can directly use WCF proxy libraries, so it has virtually no limitation.

To add icing on the cake, Visual Studio v16 publishing support for creating OpenAPI enabled Azure Functions.

The OpenAPI Specification is an API description format for REST APIs and has become the leading convention for describing HTTP APIs. An OpenAPI description effectively describes your API surface; endpoints, operation parameters for each, authentication methods, and other metadata. As a part of the ecosystem already rich with tools and open-source packages for .NET, we wanted to extend this capability to Azure Functions.

Before we begin, please make sure the following prerequisites are in place:

  • Install and configure a Windows Server or VM with a public facing IP address.
  • Download the fully functional free trial of The Muhimbi PDF Converter Services here.
  • Please make sure Muhimbi PDF Converter Services  is installed exactly as described in chapter 2 of the Administration Guide.  Please follow that guide to the letter and make sure you pay particular good attention to the section about ‘dependencies’.  The Administration Guide is included in the download and available on-line here.
  • You’ll also need an Azure subscription and appropriate privileges to create Azure Functions.
  • Visual Studio 2019, version 16.10, or a later version. Make sure you select the Azure development workload during installation.

In this tutorial, you will learn how to:

  • Step 1 – Create a serverless function project in Visual Studio.
  • Step 2 – Integrating the Web Service(WSDL) with Azure Functions.
  • Step 3 – Write a .Microsoft .NET Core code to invoke a SOAP API to convert documents to PDF.
  • Step 4 – Test function APIs locally using built-in OpenAPI functionality.
  • Step 5 – Publish project to a function app in Azure.

Step 1 – Create a serverless function project in Visual Studio.

The Azure Functions project template in Visual Studio creates a project that you can publish to a function app in Azure.

You’ll also create an HTTP triggered function that supports OpenAPI definition file (formerly Swagger file) generation.

  • From the Visual Studio menu, select File > New > Project.
  • In Create a new project, enter functions in the search box, choose the Azure Functions template, and then select Next.
  • In Configure your new project, enter a Project name “MuhimbiConvertFunction”, and then select Create.
  • For the Create a new Azure Functions application settings, use the values in the following table:
SettingValueDescription
.NET version.NET Core 3 (LTS)This value creates a function project that uses the version 3.x runtime of Azure Functions. OpenAPI file generation is only supported for version 3.x of the Functions runtime.
Function templateHTTP trigger with OpenAPIThis value creates a function triggered by an HTTP request, with the ability to generate an OpenAPI definition file.
Storage account (AzureWebJobsStorage)Storage emulatorYou can use the emulator for local development of HTTP trigger functions. Because a function app in Azure requires a storage account, one is assigned or created when you publish your project to Azure.
Authorization levelFunctionWhen running in Azure, clients must provide a key when accessing the endpoint. For more information about keys and authorization, see function access keys.

  • Select ‘Create’ to create the function project and ‘HTTP trigger with OpenAPI’.

Visual Studio creates a project and class named Function1 that contains boilerplate code for the HTTP trigger function type.

Step 2 – Integrating the Web Service(WSDL) with Azure Functions.

  • In the Solution Explorer window, right-click on the “MuhimbiConvertFunction” select Add > Service Reference.
  • Under “Connected Services” click on “Other Services”. 
  • Click on Finish.

Step 3 – Write Microsoft .NET Core code to invoke a SOAP API to convert documents to PDF.

The function uses an HTTP trigger that takes two parameters:

Parameter nameDescription
FileNameThe name of the file.
FileNameThe File content as base64.

The function then converts a document to PDF. Parameters are supplied either in the query string or in the payload of a POST request. Replace the contents of the generated class library code with the following code.

You can download the code from this link.

using System;
using System.IO;
using System.Net;
using System.ServiceModel;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
using ServiceReference1;

namespace MuhimbiConvertFunction
{
    public static class Function1
    {
        static string SERVICE_URL = "http://your_ip_address:41734/Muhimbi.DocumentConverter.WebService/";

        [FunctionName("ConvertToPDF")]
        [OpenApiOperation(operationId: "ConvertToPDF")]
        [OpenApiSecurity("function_key", SecuritySchemeType.ApiKey, Name = "code", In = OpenApiSecurityLocationType.Query)]
        [OpenApiRequestBody("application/json", typeof(RequestBodyModel), Description = "JSON request body containing { FileName, FileContent}")]
        [OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "application/json", bodyType: typeof(ResponseBodyModel), Description = "JSON OK response")]
        [OpenApiResponseWithBody(statusCode: HttpStatusCode.BadRequest, contentType: "application/json", bodyType: typeof(ResponseBodyModel), Description = "JSON BAD response")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            // ** Get request body data.
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            string FileName = data?.fileName;
            string FileContent = data?.fileContent;
            byte[] convFile = null;
            
            DocumentConverterServiceClient client = null;
            try
            {
                byte[] sourceFile = Convert.FromBase64String(FileContent);

                // ** Open the service and configure the bindings
                client = OpenService(SERVICE_URL);

                //** Set the absolute minimum open options
                OpenOptions openOptions = new OpenOptions();
                openOptions.OriginalFileName = FileName;
                openOptions.FileExtension = Path.GetExtension(FileName);

                // ** Set the absolute minimum conversion settings.
                ConversionSettings conversionSettings = new ConversionSettings();
                conversionSettings.Fidelity = ConversionFidelities.Full;
                conversionSettings.Quality = ConversionQuality.OptimizeForPrint;

                convFile = client.ConvertAsync(sourceFile, openOptions, conversionSettings).GetAwaiter().GetResult();
            }
            catch (Exception ex)
            {              
                return new BadRequestObjectResult(new
                {
                    message = ex.ToString()
                }) ;
            }
            finally
            {
                CloseService(client);
            }
            return (ActionResult)new OkObjectResult(new
            {
                processed_file_content = convFile,
            });
        }
        public static DocumentConverterServiceClient OpenService(string address)
        {
            DocumentConverterServiceClient client = null;

            try
            {
                BasicHttpBinding binding = new BasicHttpBinding();

                // ** Use standard Windows Security.
                binding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
                binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;

                // ** Increase the client Timeout to deal with (very) long running requests.
                binding.SendTimeout = TimeSpan.FromMinutes(120);
                binding.ReceiveTimeout = TimeSpan.FromMinutes(120);

                // ** Set the maximum document size to 50MB
                binding.MaxReceivedMessageSize = 50 * 1024 * 1024;
                binding.ReaderQuotas.MaxArrayLength = 50 * 1024 * 1024;
                binding.ReaderQuotas.MaxStringContentLength = 50 * 1024 * 1024;

                // ** Specify an identity (any identity) in order to get it past .net3.5 sp1
                EndpointIdentity epi = new UpnEndpointIdentity("unknown");
                EndpointAddress epa = new EndpointAddress(new Uri(address), epi);

                client = new DocumentConverterServiceClient(binding, epa);
                client.OpenAsync().GetAwaiter().GetResult();

                return client;
            }
            catch (Exception)
            {
                CloseService(client);
                throw;
            }
        }
        public static void CloseService(DocumentConverterServiceClient client)
        {
            if (client != null && client.State == CommunicationState.Opened)
            client.CloseAsync().GetAwaiter().GetResult();
        }
    }
    public class ResponseBodyModel
    {
        public string processed_file_content { get; set; }
        public string message { get; set; }
    }
    public class RequestBodyModel
        {
            public string FileName { get; set; }
            public string FileContent { get; set; }
        }      
}
  • Save and build you solution.

Step 4 – Test function APIs locally using built-in OpenAPI functionality.

When you run the function, the OpenAPI endpoints make it easy to try out the function locally using a generated page. You don’t need to provide function access keys when running locally.

  • Press F5 to start the project. When Functions runtime starts locally, a set of OpenAPI and Swagger endpoints are shown in the output, along with the function endpoint.
  • In your browser, open the RenderSwaggerUI endpoint, which should look like http://localhost:7071/api/swagger/ui. A page is rendered, based on your OpenAPI definitions.
  • Select POST > Try it out, enter values for FileName and FileContent either as the JSON request body(sample below), and select Execute.
{
  "fileName": "c.txt",
  "fileContent": "TXVoaW1iaSByb2Nrcw=="
}
  • You will get a JSON response that looks like the following example:

Now you have a function that determines the cost-effectiveness of emergency repairs. Next, you publish your project  to Azure.

Step 5 – Publish project to a function app in Azure

Visual Studio publishing creates a function app the first time you publish your project. It can also create an API Management instance that integrates with your “MuhimbiConvertFunction” function app to expose the  API. You can find details on how to Publish your Function from this link.

Finally and optionally,

Want to Invoke the Function using Microsoft Flow? If yes, click here for a detailed explanation.

Subscribe to this blog for the latest updates about SharePoint Online, Microsoft Flow, Power Apps and document conversion and manipulation.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s