blog.atwork.at

news and know-how about microsoft, technology, cloud and more.

Azure Logic Apps Toolbox 2-Send text and files (multipart/form-data) to SharePoint Online

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>
   <button type="submit">Submit</button>
</form>                 

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.

image

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.

image

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.

image

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.

image

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.

image

To access the data, we can use the expression triggerMultipartBody([index]) - see triggerMultipartBody. The first item has index 0, the second 1, etc.

triggerMultipartBody(0)

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.

image

So, we use triggerMultipartBody(0) for the name and triggerMultipartBody(1) for the email. Guess, what we will use for file1triggerMultipartBody(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.

image

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.

triggerMultipartBody(2)

The filled out action looks as here.

image

I save the Logic App and submit the webform again. I now see a new record with an attachment in the list.

image

I click on the name to see the panel with the fields.

image

Here, I open the file1.png attachment. As result, the file is displayed and can be downloaded. The Logic App works.

image

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.

image

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.

image

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).

image

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.

image

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.

image

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.

image

In the end, I want to use that extracted filename1. So, I replace the static filename with the variable filename1.

image

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.

image

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 result

The last run when submitting the web form shows the provided file name in the SharePoint Online list as attachment.

image

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.

image

Quick summary

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!

Comments (2) -

  • Richard Carrigan

    12/29/2020 2:55:55 PM |

    Great tutorial, Toni! I have been struggling, and in fact gave up, on trying to get an Azure Function App to parse my multipart/form-data form. However, this looks like a much better approach.

    The only concern I have with this solution is that you are parsing through the form using the array indices, so wouldn't that break if the order of form fields changed? The form I am working with never stays in the same order, and many fields are added and removed on a regular basis, so I wouldn't want to have to change my API every couple weeks.

    I will start testing this out and will update you if I come up with a solution to this problem.

    Thank you for providing this helpful information!

  • Richard Carrigan

    1/4/2021 7:55:26 PM |

    UPDATE: I was able to refactor the For each loop to create an object with the form data, instead of an array. This allowed me to map each field by name instead of by index (ex. "variables('jsonFormData').LastName" versus "variables('formData')[1]").

    TIP: By default, Azure Logic Apps For each loops run concurrently, so the original ordering will not be honored. To fix this, go into the settings for the For each loop and turn on "Concurrency Control" and set the "Degree of Parallelism" to 1.

    However, now my issue is that the For each loop is very slow (~9s for a single POST request). At this slow speed, the Logic App would not be viable for my application. Any suggestions on how I could make it more efficient?

Loading