Building Web APIs for Power Platform

Power Automate empowers everyone to build automated processes with flows. Power Automate uses low-code, drag-and-drop tools, as well as hundreds of prebuilt connectors that automate repetitive, mundane, tasks.

As the IT sphere has becomes more complex, with a mix of on-premise and online solutions working together, it has pushed the use of Fusion development which allows businesses to build better applications, faster, by bringing together professional developers.

In this post we’ll consume the Muhimbi SOAP service in an Azure Microsoft .NET Core App Service (as it can easily handle those SOAP webservices through WCF by creating service references) that can further be invoked using a Custom Connector in Power Automate/ PowerApps.

In this three part series for ASP.NET Core Web APIs, we’ve covered the following –

Please note, Muhimbi also offers The PDF Converter Online, which has an excellent online REST based API and is available at a very reasonable cost. The aim of this post is to show  the flexibility of The Muhimbi PDF Converter and the numerous, perhaps not obvious, ways it can be used. 

The end-to-end application architecture looks like:

  1. Install and configure a Windows Server or VM with a public facing IP address. For High availability, Install and Configure the Load balancer and the Virtual Machines to the Load balancer Pool – For details Azure Load Balance see link.
  2. Download the fully functional free trial of The Muhimbi PDF Converter Services here.
  3. Please make sure The Muhimbi PDF Converter Services  is installed on the Virtual Machines exactly as described in chapter 2 of the Administration Guide.
  4. You’ll also need an Azure subscription and appropriate privileges to create Azure APP service.
  5. Basic working knowledge of ASP.net Core and Visual Studio IDE.

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 related post of mine: https://clavinfernandes.wordpress.com/2019/12/02/azure-functions-for-muhimbi-powerusers-and-itpros/

Azure App Service or Azure Functions, what to choose?

Azure App Service and Azure Functions are primarily classified as “Platform as a Service” and “Serverless / Task Processing” tools respectively.

Azure Functions in our use case might work just fine, however we need to remember that Azure Functions is a different beast than an App Service.

An Azure function is triggered by an external event. When hosted on a consumption plan this execution is allowed to run for 5 or 10 minutes max. From the scaling point of view, if traffic is heavy an Azure Function consumption plan might be more costly than having a dedicated App Service plan. That depends of course on a lot of factors (CPU usage, request duration etc.).

If you are confused, please refer to the guide to choose a computing service, it includes both Azure Functions and Azure App Service you can review the discussion in this link.

Regardless of the function app timeout setting, 230 seconds is the maximum amount of time that an HTTP triggered function can take to respond to a request. This is because of the default idle timeout of Azure Load Balancer.

If you think Azure Functions will suffice for your requirements, have a look at this blog post of mine: https://clavinfernandes.wordpress.com/2021/08/25/creating-power-automate-openapi-extensions-for-azure-functions-fusion-development/

In this tutorial, you will learn how to:

  • Step 1 – Create a web API project in Visual Studio.
  • Step 2 – Integrating the Web Service(WSDL) with web API project.
  • Step 3 – Configure the Startup.Configure to support Swagger JSON in the 2.0 format instead. Note – This 2.0 format is important for integrations such as Microsoft Power Apps and Microsoft Flow that currently support OpenAPI version 2.0.
  • Step  4 – Add Model Classes and Model Validations.
  • Step  5 – Write a .Microsoft .NET Core code to invoke a Muhimbi SOAP API in the controller.
  • Step  6 – Test APIs locally using built-in OpenAPI functionality.

You can download the code from this link.

Step 1 – Create a web API project in Visual Studio.

  • From the File menu, select New > Project.
  • Select the ASP.NET Core Web API template and click Next.
  • Name the project MuhimbiAPI and click Create.
  • In the Create a new ASP.NET Core Web Application dialog, confirm that .NET Core and ASP.NET Core 5.0 are selected. Select the API template and click Create.

Step 2 – Integrating the Web Service(WSDL) witweb API project .

In the Solution Explorer window, right-click on the “Connected Service” and click on “Manage Connected Services”.

  • Under “Connected Services” click on “Other Services”.
  • Click on the ‘Finish’ button.

Step 3 – Configure the Startup.Configure.

  • In the Startup.Configure method add the following code, to serve the Swagger UI at the app’s root (http://localhost:<port>/), set the RoutePrefix property to an empty string.
	app.UseSwaggerUI(c =>
	{
	    c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
	    c.RoutePrefix = string.Empty;
    });
  • By default, Swashbuckle generates and exposes Swagger JSON in version 3.0 of the specification (officially called The OpenAPI Specification).
  • To support backwards compatibility, you can opt into exposing JSON in the 2.0 format instead.
  • This 2.0 format is important for integrations such as Microsoft Power Apps and Microsoft Flow that currently support OpenAPI version 2.0.
  • To opt into the 2.0 format, set the SerializeAsV2 property in Startup.Configure:
	app.UseSwagger
	(
	   c => { c.SerializeAsV2 = true; 
    });

The Startup.cs should look like the code below –

	using Microsoft.AspNetCore.Builder;
	using Microsoft.AspNetCore.Hosting;
	using Microsoft.Extensions.Configuration;
	using Microsoft.Extensions.DependencyInjection;
	using Microsoft.Extensions.Hosting;
	using Microsoft.OpenApi.Models;
	
	namespace MuhimbiAPI
	{
	    public class Startup
	    {
	        public Startup(IConfiguration configuration)
	        {
	            Configuration = configuration;
	        }
	        public IConfiguration Configuration { get; }
	
	        // This method gets called by the runtime. Use this method to add services to the container.
	        public void ConfigureServices(IServiceCollection services)
	        {
	            services.AddApiVersioning(o =>
	            {
	                o.AssumeDefaultVersionWhenUnspecified = true;
	                o.DefaultApiVersion = new Microsoft.AspNetCore.Mvc.ApiVersion(1, 0);
	            });
	            services.AddControllers();
	            services.AddSwaggerGen(c =>
	            {
	                c.SwaggerDoc("v1", new OpenApiInfo { Title = "MuhimbiAPI", Version = "v1" });
	            });
	        }
	
	        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
	        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
	        {
	            if (env.IsDevelopment())
	            {
	                app.UseDeveloperExceptionPage();
	                app.UseSwagger(c =>
	                {
	                    c.SerializeAsV2 = true;
	                });
	                app.UseSwaggerUI(c => 
	                    { 
			c.SwaggerEndpoint("/swagger/v1/swagger.json", "MuhimbiAPI v1");
	                      c.RoutePrefix = string.Empty;
	                    }); 
	            }         
	            app.UseHttpsRedirection();
	            app.UseRouting();
	            app.UseAuthorization();
	            app.UseEndpoints(endpoints =>
	            {
	                endpoints.MapControllers();
	            });
	        }
	    }
}

Step  4 – Add Model Classes and Model Validations.

  • Add the ‘Models’ folder and add the three class files.
    • Convert_Request.cs
    • Output.cs
    • Watermarking_Request.cs

Mark the model with attributes (found in the System.ComponentModel.DataAnnotations namespace) to help drive the Swagger UI components. Using this namespace we can make properties [Required] in our Model.

Code for the Convert_Request

using System.ComponentModel.DataAnnotations;

namespace MuhimbiAPI.Models
{
    public class Convert_Request
    {
        [Required]
        public string Source_file_name { get; set; }
        [Required]
        public string Source_file_content { get; set; }
    }
}

Code for Output.cs

namespace MuhimbiAPI.Models
{
    public class Output
    {
        public string processed_file_content { get; set; }
        public string base_file_name { get; set; }
        public int result_code { get; set; }
        public string result_details { get; set; }
    }
}

Code for Watermarking_Request.cs

using System.ComponentModel.DataAnnotations;
namespace MuhimbiAPI.Models
{
    public class Watermarking_Request
    {
        [Required]
        public string Source_file_name { get; set; }
        [Required]
        public string Source_file_content { get; set; }
        [Required]
        public string Content { get; set; }
        [Required]
        public string FillColor { get; set; }
        [Required]
        public string LineColor { get; set; }
        [Required]
        public string FontFamilyName { get; set; }
        [Required]
        public string FontSize { get; set; }
        [Required]
        public int ZOrder { get; set; }
        [Required]
        public string Rotation { get; set; }
        [Required]
        public string Width { get; set; }
        [Required]
        public string Height { get; set; }
        [Required]
        public string HPosition { get; set; }
        [Required]
        public string VPosition { get; set; }
    }
}

Step  5 – Write a .Microsoft .NET Core code to invoke a Muhimbi SOAP API in the controller.

  • Add a new Controller ‘MVC Controller – Empty’.
  •  Add an ‘MVC Controller – Empty’ and Name it ‘MuhimbiController.cs’.
  • Copy Paste the Code below.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using MuhimbiAPI.Models;
using ServiceReference1;
using System;
using System.Collections.Generic;
using System.IO;
using System.ServiceModel;
using System.Threading.Tasks;

namespace MuhimbiAPI.Controllers
{
    [Produces("application/json")]
    [Route("api/[controller]")]
    [ApiController]
    public class MuhimbiController : ControllerBase
    {
        // ** The URL where the Web Service is located. Amend host name if needed.
        static string SERVICE_URL = "http://<your_ip_address>.azure.com:41734/Muhimbi.DocumentConverter.WebService/";
            
        [HttpPost("Convert", Name = nameof(ConverttoPDF))]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status500InternalServerError)]
        [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Output))]
        public async Task<IActionResult> ConverttoPDF([FromBody] Convert_Request convertRequest)
        {
            byte[] convFile = null;
            string returnValue;
            //Output output = new Output();

            DocumentConverterServiceClient client = null;         
            try
            {
                byte[] sourceFile = Convert.FromBase64String(convertRequest.Source_file_content);

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

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

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

                convFile = client.ConvertAsync(sourceFile, openOptions, conversionSettings);
                returnValue = Convert.ToBase64String(convFile);
            }
            catch (Exception ex)
            {
                return StatusCode(StatusCodes.Status500InternalServerError, ex);
            }
            finally
            {
                CloseService(client);
            }
            //return convFile.ToString();
            var responseobject = new Output {
            processed_file_content = returnValue,
            base_file_name = Path.GetFileNameWithoutExtension(convertRequest.Source_file_name),
            result_code = StatusCodes.Status200OK,
            result_details = "Conversion completed succesfully"
            };

            return Ok(responseobject);
        }

        [HttpPost("text_watermark", Name = nameof(Text_Watermark))]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status500InternalServerError)]
        [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Output))]
        public async Task<IActionResult> Text_Watermark([FromBody] Watermarking_Request watermarking_Request)
        {
            byte[] convFile = null;
            string returnValue;
 
            DocumentConverterServiceClient client = null;
            try
            {
                byte[] sourceFile = Convert.FromBase64String(watermarking_Request.Source_file_content);

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

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

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

                conversionSettings.Watermarks = CreateWatermarks(watermarking_Request);
                convFile = client.ConvertAsync(sourceFile, openOptions, conversionSettings);
                returnValue = Convert.ToBase64String(convFile);
            }
            catch (Exception ex)
            {
                return StatusCode(StatusCodes.Status500InternalServerError, ex);
            }
            finally
            {
                CloseService(client);
            }
            var responseobject = new Output
            {
                processed_file_content = returnValue,
                base_file_name = Path.GetFileNameWithoutExtension(watermarking_Request.Source_file_name),
                result_code = StatusCodes.Status200OK,
                result_details = "Conversion completed succesfully"
            };
            return Ok(responseobject);
        }        
        public static Watermark[] CreateWatermarks(Watermarking_Request watermarking_Request)
        {
            List<Watermark> watermarks = new List<Watermark>();

            // ** Specify the default settings for properties
            Defaults wmDefaults = new Defaults();
            wmDefaults.FillColor = "#000000";
            wmDefaults.LineColor = "#000000";
            wmDefaults.FontFamilyName = "Arial";
            wmDefaults.FontSize = "10";

            // **************** 'Confidential' Text ***************

            // ** 'Confidential' watermark for front page
            Watermark confidential = new Watermark();
            confidential.Defaults = wmDefaults;
            confidential.StartPage = 1;
            confidential.EndPage = 1;
            confidential.Rotation = "-45";
            confidential.Width = "500";
            confidential.Height = "500";
            confidential.HPosition = HPosition.Center;
            confidential.VPosition = VPosition.Middle;
            confidential.ZOrder = -1;

            // ** Create a new Text element that goes inside the watermark
            Text cfText = new Text();
            cfText.Content = watermarking_Request.Content;
            cfText.FontSize = "40";
            cfText.FontStyle = FontStyle.Bold | FontStyle.Italic;
            cfText.Width = "500";
            cfText.Height = "500";
            cfText.Transparency = "0.50";

            // ** And add it to the list of watermark elements.
            confidential.Elements = new Element[] { cfText };

            // ** And add the watermark to the list of watermarks
            watermarks.Add(confidential);

            return watermarks.ToArray();
        }
        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.None;

                // ** 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();
        }
    }
}

The Muhimbi PDF Converter comes with a comprehensive, but friendly, web services interface that can be accessed from any modern web services based development environment including JavaPHPRuby, SAP, SharePoint.NET (C#, VB) and Documentum. The full Developer Guide can be found here. Further information can be found in the Knowledge Base.

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

  • Press Ctrl+F5 to run without the debugger.
  • Visual Studio displays the following dialog when a project is not yet configured to use SSL:
  • Select ‘Yes’, if you trust the IIS Express SSL certificate.
  • Select ‘Yes’, if you agree to trust the development certificate.

Visual Studio launches:

  • Select POST > Try it out, enter values for “source_file_name” and “source_file_content” either as the JSON request body(sample below), and select Execute.
	{
	  "source_file_name": "string.txt",
	  "source_file_content": "TXVoaW1iaSByb2Nrcw=="
}
  • You will get a JSON response that looks like the following example:

Now you have a Web Application that works locally on your computer.

In the next blog –

  • Secure ASP.NET Core Web API using API Key Authentication.
  • Document API Key authentication using Swashbuckle.AspNetCore v5.0.0.
  • Publish project to a Azure APP Service.
  • Create a custom connector from an OpenAPI definition.
  • Accessing Custom Connector in Power Automate to Convert Documents to PDF using our Customer connector.

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 )

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