Using an Azure Logic app is often a quick fix for small computing tasks. So my scenario is as follows: I want to use a form on a website to offer text fields and file upload. The submit button is intended to send the data to a Logic App. The Logic App receives the data and writes it to a SharePoint Online list and sends a notification if necessary. See the implementation here.
The special feature here is that the form should not only process text data, but also file uploads. Even if this example sounds very simple, the devil is in the details. I want to thank my colleague John Liu 劉 #DifinityConf (see also #FlowNinja) for helping me out with the tip for getting and converting the uploaded file correctly! Let´s start.
Create an HTML web form with a file upload button
I use Hugo as static website generator. In one of the generated HTML pages, I embed a simple web form as here. Not pretty, but functional.
<p>Please fill out the form and submit the data.</p>
<form action=https://[some-logic-app-URL] method="POST" enctype="multipart/form-data">
name: <input type="text" name="name"><br>
email: <input type="email" name="email"><br>
file1: <input type="file" name="file1"><br>
If you need to upload files, the enctype attribute with the value multipart/form-data must be added. This means, that no characters are encoded and a file upload control can be used. See more at HTML <form> enctype Attribute. Adapt the form as needed and modify the form action URL to the one of your Azure Logic App. In my scenario, I tested locally and then deployed the web site to an Azure App Service. You need to have an Azure subscription to work with Azure Logic Apps.
The web form
When rendered in a browser, this beautiful web form looks as here. The user can fill it out and submit the form.
Sure, there is no Anti-Spam method implemented, I just want to focus on the Logic App here. If you need to implement that, check out Google recaptcha.
Create the Azure Logic App to process the web form data
I create a new Azure Logic App named Webform1 and start with an HTTP trigger. In the designer, I save the HTTP trigger to get the HTTP POST URL as here. This URL must be used in the web form action above. In my sample, the Logic App is in North Europe and starts with https://prod-62.northeurope.logic.azure.com:443/workflows/... See more about working with the HTTP object at Send outgoing calls to HTTP or HTTPS endpoints by using Azure Logic Apps.
Send some data from the web form
Now we need to get some data from the web form to see the payload. So, let´s fill it out and submit the data. Our Logic App will be triggered and we see the data as JSON object for every call.
The HTTP trigger is the first action in the Webform Logic App. Let´s add more later.
Write data to a custom SharePoint list
In a SharePoint Online site, here in the "Business Hub" site, I create a custom list named "Webform". The Title shall contain the name and I add a column for the EMail address. Also, I change the view to show the fields ID, Created and Attachments, sorted by ID descending. The latest records shall be on top.
This list will store the data from the web form. File1 shall be uploaded as attachment to the Attachments of that record. We just need the site URL for the Logic App and can continue. Of course, we can write the data to an Azure Storage Account in a BLOB object or to other storage locations as well, there are many connectors we can use...
Send data to SharePoint
In the Logic App, we can now add the connector for SharePoint Online. First, I authenticate the connector. Then I create a new Sharepoint action "Create item" as below.
To access the data, we can use the expression triggerMultipartBody([index]) - see triggerMultipartBody. The first item has index 0, the second 1, etc.
When I look at the data in the HTTP trigger, I see the structure of the multipart/form-data request: The first item is field name, email is the second item and file1 the third item as we see in the data sent to the Logic App. In notepad, this looks as here, I added the red comments to line out the three items from the web form.
So, we use triggerMultipartBody(0) for the name and triggerMultipartBody(1) for the email. Guess, what we will use for file1... triggerMultipartBody(2). Sure, the order matches to the data sent.
Test the data flow
When I submit the form again, I should see the result with name and email in the SPO list.
Nice, the flow works for the text.
Add the file to the SPO list as attachment
Ok, let´s add file1. This must be done in an extra action for an already existing item. I add a SharePoint action "Add attachment". We need to provide the list item ID we get from the last action. Below, I add a dummy file name file1.png and add triggerMultipartBody(2) as File Content.
The filled out action looks as here.
I save the Logic App and submit the webform again. I now see a new record with an attachment in the list.
I click on the name to see the panel with the fields.
Here, I open the file1.png attachment. As result, the file is displayed and can be downloaded. The Logic App works.
The cool part is that the triggerMultipartBody() expression also delivers the correct Content-Type of the file. So, the connector can store the file with the correct type, as we see in the run of one of the flows here.
Are we done? Well, basically yes. There´s only one thing missing: That´s the file name.
Get the file name
When I look in the data that is sent from the web form, we get the body content. I can work with that.
The process is a bit tricky, but I found a helpful article (as so often on stackoverflow.com): How do I parse out the file name in a form-data trigger by Joey Eng. Joey is using a For each control and running through the items to extract and modify the file name. Thanks Joey!
Use a For each to run through the fields
To get the data, I use triggerOutputs().body['$multipart'] in the For each (see all expressions listed below).
Before the loop: Initialize a variable for the filename
To work with the filename, I add an action "Initialize variable" and name the variable filename1 of type String before the loop. Actually, it´s a good idea to initialize the string with a valid file name (as file1.png), just in case.
Back in the loop...
In the loop, I add an action "Set variable" and assign a value to filename1 as here:
replace(replace(item().headers['Content-Disposition'], 'form-data; name="file1"; filename="', ''), '"', '')
So, what´s happening here? Well, here two replace()-functions are used to extract the string between the filename-quotes. The text...
Content-Disposition: form-data; name="file1"; filename="atwork-logo-small.png"
...is turned into "atwork-logo-small.png" by replacing unneeded text and the last quote. The following screenshot shows that flow actions in a first step.
Well, now this would happen for every item, and the last item wins.
Extract only the file name
To fix that mechanism for more data that can be sent, let´s add a condition only if the current item is file1. I add this condition control in the loop, delete the Set variable action before, and add it in the True-condition. So, here are the logical expressions for the For each and the If-Condition:
For each: triggerOutputs().body['$multipart']
Condition: If item().headers['Content-Disposition'] starts with form-data; name="file1";
If True: Set variable filename1 = replace(replace(item().headers['Content-Disposition'], 'form-data; name="file1"; filename="', ''), '"', '')
Hint: To get the body of an item, use item().body
The full For each construct looks as here.
In the end, I want to use that extracted filename1. So, I replace the static filename with the variable filename1.
That´s it. I save the Logic App and test it.
The complete Logic App
This screenshot shows an overview of the actions in the Webform1 Logic App. triggerMultipartBody() is used to get the content from the HTTP request, a For each loop using triggerOutputs().body['$multipart'] and item().headers['name'] or item().body for getting data from the current item.
Note: For working with multiple files, the actions should be in the loop as well. Adapt the flow as needed, that´s the idea. Also, sending notifications or processing of the files could be added.
The last run when submitting the web form shows the provided file name in the SharePoint Online list as attachment.
Since the Content-Type and the filename are set, the client (browser) can handle all file types such as PDF, Images, Office documents and more file types automatically and show the file directly from the list item.
This article shows how to automate scenarios for uploading text and files from a web form and storing the content in a SharePoint list for further processing with an Azure Logic app. Since I haven't found a fully documented example and had to do a lot of research and testing, I hope that this step-by-step guide will help administrators and power users to automate processes with Azure and Microsoft 365 without developing them.
Happy automating with Azure Logic Apps!