Azure Logic Apps: Add several actions inside a Loop (or foreach actions)
Introduction
Following my latest article where I talked about the new ability to call nested Logic Apps directly from Logic Apps Designer and how this feature will allow us to:
- Create reusable pieces
- Overcome some Logic Apps limitations, and what I mean by that is for example:
- The ability to add more than one action inside the condition branch’s (this is actually a false statement);
- or the ability to add more than one action inside the loop;
In this article let’s look how a nested Logic App can help us overcome the current Logic Apps limitation regarding to for each operation, in special, the ability to add more than one action inside the loop;
Using/Implementing Looping inside Logic Apps
If you are used to work with BizTalk Server, we have certain shapes and behaviors inside BizTalk Orchestrations that we would love to have in Logic Apps, at a first glimpse, we may be surprised the lack of features/shapes to perform certain operations…however, that doesn’t mean that they are missing, most of the times we still can do those things… but in a different manner, so we may need to look the “problem” from another angle.
- See also this post Do-until looping in logic apps
Looping is one of those things!
We don’t have any Looping shape, action or operation that we can use in the Logic App Designer, similar to what we have with Conditions.
In the previous designer (v1) we had an option in the connectors, “Repeat over the list“, that allowed us to iterate over an array of items and run an action for each item in that list.
The current design doesn’t have any more this option, but again that doesn’t mean that you can’t. In this new Logic App version or designer, if it detects that the input from the previous action or trigger it’s an array, and if you try to use this input in a subsequent action, it will, almost every time, automatically put you on a for each operation. Which means that you are running this action for each item in that list.
Let’s look, and use, the basic scenario that we are using in the last posts, were:
- The Logic Apps is trigger by a new tweet containing the hashtag we are listening;
- And then create a file;
For now, let’s ignore the dynamic creation of the file name using Azure Functions. So we have this basic Logic App
Because each tweet will generate a new execution of this Logic App, the trigger input will be a simple single message and not an array. If we switch to code view we will see that the Create file action will only be executed once and it’s not inside of any loop operation.
So let’s make some changes!
We will use a similar scenario, but this time we want that our Logic App:
- To be triggered every hour;
- Retrieve the last 20 tweets with the hashtag "#LogicApp"
- And then create a file for each tweet in our Dropbox
For that we will create a new Logic App called “LoopingInsideLogicApps”
- Add a “Recurrence” trigger, and set the:
- “Frequency” property to “Hour”
- And the “Interval” property to “1”
- Select the plus sign, and then choose “Add an action”
- From the search box select “Show Microsoft managed APIs”, type “twitter” and select the “Twitter – Search tweet” action
- Set the “Query text” property with “#LogicApps” hashtag
- You can expand the action to see all the properties if you click “…” in the lower left corner
- And see and define the number of result you want to return
- Select the plus sign, and then choose “Add an action”
- From the search box select “Show Microsoft managed APIs”, type “Dropbox” and select the “Dropbox – Create file” action
- Set the “Folder path” property, let’s say the root by typing: /
- On the “File name” property, set the parameter “Tweet id” from the output of “Search tweet” action as input, following by the string “.txt”
- On the "File Content” property, set the parameter “Tweet text” from the output of “Search tweet” action as input
If we “Save” our Logic Apps we will notice that 20 new files will be created in our Dropbox without us having defined any parameter or configuration inside the actions that would tell it to iterate over a list/array.
This happened, because the designer “was smart” to understand that in input from the “Search Tweet” action is an array and automatically told the “Create file” action to work inside a for each so that it could iterate over all the items in the array.
Trying to add several actions inside a for each
Let’s say now that we want to call our Azure Function to generate the file name and create the file name in our Dropbox with it.
At first glance, we might say that:
- After the “Twitter – Search tweet” action, we need to call our Azure Function: “CreateFileName”, because it doesn’t need inputs we set the input as an empty JSON message
- And reconfigure the “Dropbox – Create file” action in order that the “File name” property is set with the “Filename” parameter from the output of “CreateFileName” function as input
If we once again, “Save” our Logic Apps and this time, execute it manually to for it to run. You will notice that:
- The CreateFileName function is successfully executed;
- But the Create file action failed. If you see the output, you will notice that the first file was created successfully but the following one’s got the following error:
File ‘/NewTweet_6785b86d-4dda-4119-968f-c189416f22bc.txt’ already exists. Use the Update operation to update an existing file.
Maintaining in the run history, but selecting the “CreateFileName” action to see what was the output of the function we will notice that it was a simple output:
{
"statusCode": 200,
"headers": {
"pragma": "no-cache",
"cache-Control": "no-cache",
"date": "Wed, 20 Apr 2016 15:01:18 GMT",
"set-Cookie": "ARRAffinity=9b8e91fd330bc5b8e4bdd3e129b0412d5daca54f6c2ff8a25f773cd80fad03aa;Path=/;Domain=functionssandromsdn.azurewebsites.net",
"server": "Microsoft-IIS/8.0",
"x-AspNet-Version": "4.0.30319",
"x-Powered-By": "ASP.NET"
},
"body": {
"FileName": "NewTweet_6785b86d-4dda-4119-968f-c189416f22bc.txt"
}
}
If we go back to our Logic App designer and switch to “Code View” we will notice why this happened. By setting the input of our function with and empty JSON message were are not telling to the Logic App to iterate over the output array from the “Search Tweet” action and call the function to generate a different file name for each item.
Instead if we analyze the code we will see that the “CreateFileName” function is only call one time, and we will send to the connector Dropbox this same value to all the tweets
Trying to add several actions inside a for each (second shoot)
Ok, that didn’t work well. So what if we try to force the call to the Azure Function to be inside the for each statement?
Let’s them make some changes to our Logic App flow:
- First, delete the “File Name” property from the “Dropbox – Create file” action
- Leave it empty
- Second, let’s send a dummy JSON message to our Azure Function, forcing this action to iterate over the “Search tweet” output array of items by setting the following JSON message:
{ "dummy": ['Tweet text'] }
If we switch to “Code view” you will notice that now this action is under a for each:
"foreach": "@body('Search_tweet')"
The problem of this approach is that
- “CreateFileName” action
- And “Dropbox – Create file” action
Are in two different for each statements and if we try to use both in the same action, for example trying to set the “Body” parameter from the output “CreateFileName” function we will receive the following error:
Basic workaround to solve this problem
So, one of the workaround that you have is to modify your Azure Function, or create a new one to be able to receive one parameter:
- the twitter message;
and return two parameters:
- The file name generated;
- And the twitter message that was passed as an input;
For example:
module.exports = function (context, data) {
var Tweet = data.Tweet;
var d = new Date().getTime();
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (d + Math.random()*16)%16 | 0;
d = Math.floor(d/16);
return (c=='x' ? r : (r&0x3|0x8)).toString(16);
});
// Response of the function to be used later.
context.res = {
body: {
Msg: Tweet.text,
FileName: 'TweetMsg_' + uuid + ".txt"
}
};
context.done();
};
If we are using this strategy, we then need to configure the input of the function as:
{ "Text": ['Tweet text'] }
This will iterate over the list of tweets and will create another list containing all the information that we need to send to the Dropbox connector.
We then need to change all the parameters of the “Dropbox – Create file” action and instead of configuring with values from the “Search tweet” action that will force it to iterate, we will bind it to the output provide by the Azure function output
This will tell to this action to iterate over the function output array to create all the files in Dropbox.
Once again, if we “Save” our Logic App and run it, we will see that now everything will work fine.
However, in this basic workaround we are not adding several actions to a for each statement, instead we are avoiding, going around it and duplicate the content in another list/array… but it’s there another way?
How to add several actions inside a for each
Indeed, there is a better way to accomplish that, and the solution is to call another Logic App (at least while we do not have this functionality in the design).
Still remember our Logic App that we create in my previous posts called “AddFileToDropbox” that:
- Its trigger manually, which means through a HTTP post, allowing to be invoked by other Logic Apps
- Then it will call an Azure Function to dynamically generate a file name;
- Create a Dropbox file
- The file name it’s provide by the Azure Function;
- And the content of the message it’s passed in the HTTP request that triggers this Logic App.
- And finally return an HTTP response based on the status of the file creation
We will reuse this “child” Logic App in this demo now.
So what we need to do now is delete the last two action from our current Logic App:
- “Dropbox – Create file” action
- And call “CreateFileName” function action
And then call our “child” Logic App “AddFileToDropbox”. To accomplish that we need:
- Select the plus sign, and then choose “Add an action”
- When you select “Add an Action”, the search box will be presented were all the available actions. But now, on the search box you can select the option: “Show Logic Apps in the same region”
- And then select “AddFileToDropbox”
- On the “Text” property we need to set the parameter “Tweet text” from the output of “Search tweet” action as input
If we now "Save” our Logic App and run it manually, we instantly see that:
- the “Search tweet” action was successfully executed;
- and the “AddFileToDropbox” action is being executed;
We can go to our “child” Logic App to see its runs (the historic) and you probably see that some of them are already finish and others are still running
Once all the child runs caused by the action of the parent are finished, the parent Logic App will receive the knowledge and will terminate successfully also (if all the runs finish successfully)
How is the behavior of calling a nested Logic App inside a foreach statement?
You may ask yourself how that works, it will execute on iteration one by one, in other words, in other words, will it call the child Logic App synchronously and wait for it to finish returning the response to call another one? Or it will be asynchronous?
Well in BizTalk terms it would be like using a Parallel Actions shape, were all the actions will be executed concurrently but independently but the parent Logic App processing does not continue until all have completed. The parent Logic App in this case will receive an array containing all the responses of the child executions.
References
This article was originally published at Logic Apps: How to add several actions inside a Loop (or foreach actions). But please feel free to improve this article by adding new missing or important content, fixing spelling and so on.
Other languages
See Also
Another important place to find a huge amount of Logic Apps related articles is the TechNet Wiki itself. The best entry point is Microsoft Azure App Service Resources on the TechNet Wiki.