PowerApps and Flow are awesome, but at some point you might need to extend them with some custom code. When integrating with other systems, you can do a lot with Flow (and the HTTP action in particular), but a lot more doors are open to you if you can plug in an Azure Function to your app or process. Perhaps you have a particular system you need to talk to, or some custom logic that is easier to express in code rather than in Flow. Or perhaps you need to call the Graph, and do it from your custom code rather than another way. Over the next two articles I want to talk about how to create a custom connector so that you can talk to your function from different places in Office 365:
- Using a custom Azure Function in Flow [this article]
- Using a custom Azure Function in PowerApps
We’ll do most of the work in this article. If you haven’t already gone through the process of working with an Open API/Swagger definition for a custom API, the key point is that your methods get individual parameters which are strongly-typed, and this becomes easy to work with in PowerApps/Flow. You have to do some work for this, but it’s nice considering underneath it’s just a big ugly string of JSON which is passed between PowerApps/Flow and the function. In the example I’m using in these articles, I have a function which updates the Office 365/AAD record for the current user (using the Graph) – having done the OpenAPI work and defining a custom connector, in Flow the action for my API exposes individual parameters which makes it simple to call:
It’s a similar story in PowerApps. So these steps are pretty much mandatory if you want to plug in some custom code to these technologies.
Let’s walk through the process needed to piece these things together.
Defining the Azure Function
First off, we need an Azure Function which is going to do the work. I won’t walk through every aspect of this since our focus here is on use of Swagger to define the API and make it callable from PowerApps/Flow. But the setup is:
- A HTTP trigger function which receives some data passed as JSON in the request body (from Flow/PowerApps/some other client)
- For function authentication, we just use the “key” option
- An AAD app registration which has permissions to call the appropriate Graph endpoints
- OPTIONAL - Use of Managed Service Identity + Azure Key Vault to obtain the Client ID/secret for the AAD app registration (N.B. this is what you should do and is fairly trivial, but I’ll keep this example simple by omitting it – see How to use Azure Managed Service Identity in App Service and Azure Functions for the MSFT docs on this)
- Code in the Function to take the passed data and call the /v1.0/users/endpoint with the PATCH method to update the user profile
Here’s the code – some notable points:
- There’s quite a lot of logging – useful as you’re getting things working, but you might dial it down slightly once you’re happy
- You should use a static class level HttpClient object, rather than instantiating a new one during each execution (this is a best practice with Azure Functions which make HTTP calls with this object)
- The data passed from the client is basically in exactly the same format that the Graph endpoint needs to – this simplifies our function code since we just need to serialize it again
- ADAL.Net is used to obtain the token for AAD app registration
Our Azure Function also needs CORS to be allowed so that it can be called from PowerApps/Flow - either with a wildcard (*) entry, or the the domains used by PowerApps/Flow.
Defining the API using OpenAPI (previously known as Swagger)
Azure tries to be useful by providing a default API definition for your functions, but typically we need to extend this before your function can be used with PowerApps/Flow. In this step we’re going to expand the default API definition, so that the lower level detail of how JSON is passed and returned is also defined. There are a number of ways you can do this – options include:
- Directly in the Azure editor
- Using an online Swagger editor which provides a bit more support (e.g. http://editor.swagger.io/)
- Using a tool to generate the definition from your code. Mikael Svenson has a Swagger definition generator - I ran into some issues with it, but I’m sure it was just user error (missing annotations?) and to be honest I didn’t spend too much time on it. Looks useful though.
- Defining your API from scratch in the PowerApps/Flow/Logic Apps “create custom connector from scratch” process.
Options 1-3 have you working with the OpenAPI definition file in some form. Option 4 allows you to use a designer which builds the OpenAPI file for you (which we’ll look at later) – this sounds good, but for some reason I find I prefer the quick “copy/paste of some JSON” experience of options 1 or 2. Unless your functions have lots and lots of parameters, you’ll probably find these options get the job done fine, but you can choose.
When working with the OpenAPI definition for a function, we update the definition and then point PowerApps and Flow at it:
Process
- Go to the “API definition” area of your Function app:
- Notice the default API definition that is provided for you – this is what we’re going to update:
- Using one of the above approaches, ensure that:
- Each function in your API has an entry in “paths”
- The “parameters” and “responses” section for each method properly describes how JSON is passed in and out of the function – you need to provide a “schema” element which describes this
- The “produces” and “consumes” elements are set to “application/json”
- Save the updated definition.
The best way to illustrate this is with a before and after – in the definitions below, you can see that before the editing the “parameters” and “responses” sections are empty or minimal. I added the detail of the JSON objects which are passed into these sections (along with a couple of other bits of added info):
Before
After
Once the definition of your API is looking good, it’s time to make it available to where it will be used.
Export your API to PowerApps and Flow
The next step is to define a custom connector in PowerApps/Flow/Logic Apps from your API – the OpenAPI definition is the foundation of this. You can start this process from either the Azure Function side or the PowerApps/Flow side. In PowerApps/Flow, you would do:
Or, you can start from your Azure Function. Either approach gives the same result. On the Azure side, within the “API definition” area you’ll find this button:
Let’s use this approach. When you do this, Azure asks you for some details to link your OpenAPI definition with PowerApps/Flow – be careful because you can’t rename it later (without affecting any PowerApps/Flows which use it):
Process:
- Provide a name for your API, and set other initial details:
NOTE – in the “API Key Name” field above, I’m providing a hint to users of my connector about how to get the key (which must be passed to my function in the back-end). This appears whenever someone uses the connector for the first time.
- Your API will now show up as a “custom connector” in PowerApps or Flow:
NOTE – the connector is only shared with you at this point. To enable it to be used by other people creating PowerApps/Flows in your organisation, it will need to be shared with them (or Everyone) also..
[OPTIONAL] Extending your API definition for PowerApps/Flow/Logic Apps
Now that your API is known to PowerApps/Flow, you can optionally play with the definition some more on the PowerApps/Flow/Logic Apps side. This is known as “extending an OpenAPI” definition. Microsoft have some specific extensions to the OpenAPI spec, and you can do some nifty things with them such as marking parameters as “important” or “advanced” in the x-ms-visibility tag. When you use “advanced” for example, the setting is hidden away behind an “advanced” or “more” option, which might make more sense for some of your parameters:
The process of extending an OpenAPI definition looks like this – and although it’s technically optional if your OpenAPI definition is perfect, you’ll often find that this process is great for validating your definition and helping you find missing attributes etc. In my case, I found my methods were missing a summary which I needed to add:
On the “Definition” tab you may have some work to do - in my case, here’s where I found my methods had a warning initially because they had no value for the summary. You’ll see an “Action” for each function you’re exposing in your API – you can see my fetch and update operations for example:
Take each operation one by one. After adding any missing details and ensuring any warnings have disappeared, you should drill into the “Body”:
Here you should check that the individual bits of data in the JSON that would be passed to your API is reflected. In my case, I’m returning user profile information and it looks like this:
You should also check the parameter validates OK:
Once you’ve done this for all your methods, save the changes:
You’re now ready to use your API in PowerApps/Flow/Logic Apps.
Using your connector in a Flow
When in the Flow designer, you can now add an action which calls your API. If you create or edit a Flow and search for your connector, it will show up with the actions that are defined (in my case fetch or update the user profile):
Of course, I now get the benefit of individually-defined fields that are easy to work with (shown here for my update operation):
If you test the Flow by performing the trigger action (I’m using Postman to make a HTTP POST call), then you should see things work beautifully:
..and of course, my Office 365 profile is indeed updated with the values:
Summary
Being able to plug custom code in to PowerApps/Flow/Logic Apps is an important tool in the toolbox for the modern Office 365 architect or developer. You create the OpenAPI definition for your function or API, and then create a custom connector for it, ensuring that all operations get validated and the parameters passed in the POST body are individually-defined. Once you’re at this point, you can pretty much build anything into PowerApps and Flow – which is awesome! In my example, I used a custom connector which calls an Azure Function which calls the Graph – which can probably be the foundation for many different scenarios.
Waldek has some great information on this too, especially around async connectors which can be used for long-running operations. There’s a model where your API returns “location” and “retry-after” headers to Flow if the operation is still in progress, and this tells Flow to check back again in X seconds (however many you specified in “retry-after”). This could be very useful in certain scenarios, so I recommend reading his article What you should know about building Microsoft Flow connectors. Other good reading:
No comments:
Post a Comment