[Azure] Using an Azure Function to create custom Flow actions
If you’re working with Microsoft Flow, chances are that on some point in time you’ll run into a situation where the action you need simply doesn’t exist. If you’re a developer with skills to write C#, PowerShell, Node or even batch code, you’re in luck! Cause why not create that action yourself in the form of an Azure Function? Here’s how to do it.
Let’s set some context first. At mStack we have a SharePoint list to keep track of leave requests. It’s simple, just a calendar with approval enabled. You create a new appointment, your manager approves and your free to take your leave. But of course it would make sense to use this information to keep track of how many of your vacation time is left for the remainder of the year, right? So I wanted to create a Flow which calculates the number of hours of leave requested, by looking at the start time and end time of the request.
Azure Function
First, we’ll create the Function. This will take the two dates from the querystring and calculate the number of hours needed to fulfill this request. The code is not that difficult:
using System.Net; using System; public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log) { log.Info("C# HTTP trigger function processed a request."); // parse query parameter string startTimeStr = req.GetQueryNameValuePairs() .FirstOrDefault(q => string.Compare(q.Key, "startTime", true) == 0) .Value; string endTimeStr = req.GetQueryNameValuePairs() .FirstOrDefault(q => string.Compare(q.Key, "endTime", true) == 0) .Value; DateTime startTime = DateTime.Parse(startTimeStr); DateTime endTime = DateTime.Parse(endTimeStr); int hours = 0; if (startTime.Date != endTime.Date) { // when the dates are not equal, we need to calculate the number of working days in the period DateTime tmpDate = startTime.Date; int numberOfWorkingDays = 0; do { if (tmpDate.DayOfWeek != DayOfWeek.Saturday && tmpDate.DayOfWeek != DayOfWeek.Sunday) numberOfWorkingDays++; tmpDate = tmpDate.AddDays(1); } while (tmpDate <= endTime.Date); hours = numberOfWorkingDays * 8; } else { hours = (endTime - startTime).Hours; if (hours > 8) hours = 8; } // Get request body dynamic data = await req.Content.ReadAsAsync<object>(); return req.CreateResponse(HttpStatusCode.OK, hours); }
Disclaimer: this does not take into account holidays and it assumes everyone works 8 hours a day. There’s room for improvement, I know 🙂
Swagger
With your function created, we need a way to connect it to Flow. You can use the generic action to perform an HTTP request, but there’s a nicer way to do it. By creating a swagger file, Flow will know about your function, it’s parameters and the output. Basically, swagger provides a format to describe API’s. Flow will then, based on the definition, surface your Function as a real action which is a lot nicer and easier to use, especially when you want power users to also use it. This way you could create an entire catalog of self-made actions powered by Functions.
Creating a swagger file for a simple function is not too complicated, I simply copy/pasted an example and adjusted it a bit. So feel free to do the same with the file below 🙂
{ "swagger": "2.0", "info": { "version": "1.0.0", "title": "sharepoint-leaveDurationInHours" }, "host": "mstack-functionapps.azurewebsites.net", "paths": { "/api/sharepoint-leaveDurationInHours": { "post": { "description": "Calls my azure function over https", "operationId": "Calculate duration in hours", "parameters": [ { "name": "code", "in": "query", "description": "code", "default": "<< FUNCTION CODE >>", "type": "string" }, { "name": "startTime", "in": "query", "description": "Begin of leave period", "required": true, "type": "string" }, { "name": "endTime", "in": "query", "description": "End of leave period", "required": true, "type": "string" } ], "responses": { "200": { "description": "Successful response", "schema": { "title": "The response of the api.", "type": "number" } } } } } } }
Note that “<< FUNCTION CODE >>” can be replaced with the authentication code of your function. Whether your function requires such a code depends on how you set it up, by default a code is required. You can also choose to leave it blank or at this default value which means you’ll need to specify the correct code on design time.
PowerApps
Wait a minute, where does PowerApps come in? Well this is a bit odd, but you’ll need to add your connection to your custom service via PowerApps. Flow and PowerApps share the same Connections infrastructure, but for some reason Flow does not offer a way to add custom connections (yet, probably). So instead, you head over to powerapps.microsoft.com and sign in using the same account you’re using for Flow.
In the Connections section, click “Add Connection” and then choose the “custom” tab. In the top right corner you’ll find the option to add a “New custom API“.
Now you’ll need to provide a name for your API and that swagger file you’ve created. Note that you can include multiple functions in the same file as long as they belong to the same function app in Azure.
With your connection created, do not forget to click the plus (+) sign behind that custom API so it is added as an actual connection.
Back to Flow
If you now check out your connections page in flow, it should list your custom API:
Cool! And now that this connection has been created, your functions will start up showing as real actions in Flow:
And even better, it also knows about the parameters and return values:
This makes it very easy to use the action and use values from other steps in your Flow as input.
July 20, 2017 at 11:11 pm |
Hello Jasper,
I have been able to successfully create a “Hello World” flow action using an azure function, but am running into issues with my Swagger API definition when I have more than one parameter involved (swagger is new to me). Can you provide some additional insight into your swagger is accounting for “startTime” and end Time”?
July 20, 2017 at 11:32 pm |
Never mind Jasper – it looks like I cried wolf! After updating my Parameters object to ensure each parameter had an “in” property of “query”, I didn’t have any issue. Thanks!
July 21, 2017 at 6:55 am |
That’s great Patrick! Good to hear and thanks for the update! 🙂