Lab 07 – Azure Functions

Scenario

A regional building department issues and tracks permits for new buildings and updates for remodeling of existing buildings. Throughout this course you will build applications and automation to enable the regional building department to manage the permitting process. This will be an end-to-end solution which will help you understand the overall process flow.

In this lab you will create an Azure Function that will handle routing inspections. Every morning people call in to request inspections on their permits. They must call before 9:30 AM and once that period ends all the inspections for the day must be assigned and sequenced for the inspectors. To accomplish this, you will build an Azure Function that runs on a schedule, queries pending inspections and assigns them to inspectors. Given the limited time to complete the lab, we’ve simplified the routing and sequencing decisions.

High-level lab steps

As part of building the Azure Function, you will complete the following:

  • Configure an application user for the app along with a security role

  • Build the function logic to route the requests

  • Deploy the Azure Function

Things to consider before you begin

  • Could we have used Dynamics 365 Universal Resource Scheduling instead of custom code?

  • Could we have used Power Automate instead of custom code?

  • Remember to continue working in your DEVELOPMENT environment. We’ll move everything to production soon.

Exercise #1: Configure an application user

Objective: In this exercise, you will configure an application user that will be used to connect the Azure Function back to Microsoft Dataverse.

Task #1: Register Azure AD Application

  1. Navigate to Azure Active Directory

Note: You must be logged in with an organization account in the same tenant as your Microsoft Dataverse Environment. This does NOT have to be the account that has your Azure subscription.

  • Select Show portal menu.

Show portal menu - screenshot

  • Select Azure Active Directory.

Azure active directory - screenshot

  • Select + Add and then select App registration.

New registration - screenshot

  • Enter Inspection Router for Name, select Accounts in this Organizational Directory Only for Supported Account Types, and then select Register.

Register application - screenshot

  1. Add Client Secret.

Note: In this lab we are using a secret, however, you can also use a certificate. With either of these options you need to have a plan in place to handle rollover when they expire to ensure that the app keeps running.

  • Select Certificates & Secrets.

  • Select + New Client Secret.

New client secret - screenshot

  • Enter Inspection Routing for Description, select 12 months for Expires, and then select Add.

Add client secret - screenshot

  • Copy the Value and save it on a notepad. You will need this value in future tasks.

Copy value - screenshot

Task #2: Create Application User and Security Role

In this task, you will create the application user and associate it with the Azure AD app that you just registered. You will also create a security role needed to run the routing logic.

  1. Create New Security Role
  • Navigate to https://make.powerapps.com/ and make sure you have your Dev environment selected.

  • Select Solutions and open the Permit Management solution.

  • Select + New and then select Security > Security role

New security role - screenshot

  • Enter Inspection Router for Role Name and then select Save.

  • Select the Business Management tab.

  • Locate the User table and set Read and Append To privileges to Organization.

User table privileges - screenshot

  • Select the Custom Entities tab.

  • Locate the Inspection table and set Read, Write, Append, and Assign privileges to Organization.

User table privileges - screenshot

  • Select Save and Close.

  • Select Done.

  1. Add Application User.
  • Select Settings and then select Admin center.

    Admin settings - screenshot

  • Select your Dev environment and then select Settings.

  • Expand Users + permissions.

  • Select Users.

    Users - screenshot

  • Select App users list.

    App users list button - screenshot

  • Select + New app user.

  • Select + Add an app.

    New application user - screenshot

  • Select the Inspection Router you created and then select Add.

    Switch form - screenshot

  • Select your Business Unit and then select edit Security roles.

    Edit security roles- screenshot

  • Select the Inspection Router security role and then select Save.

 Add security role - screenshot

  • Select Create.

Create app user - screenshot

Exercise #2: Create Azure Function for Inspection Routing

Objective: In this exercise, you will create the Azure function that will route the inspections.

Task #1: Create the Function

  1. Create Azure Function project
  • Start Visual Studio.

  • Select Create a New Project.

New visual studio project - screenshot

  • Select Azure Functions and then select Next.

Azure functions - screenshot

  • Enter InspectionManagementApp for Name and then select Create.

Create project - screenshot

  • Select NET Core3 and select Timer Trigger.

  • Change the Schedule to 0 0 0 * * * (Midnight Every Day) and select Create.

Create Azure function application - screenshot

  1. Rename and run the Function
  • Rename the function file InspectionRouter.

Rename function file - screenshot

  • Select Yes to rename references.

  • Change the function FunctionName to InspectionRouter.

Change function name - screenshot

  • Select Run.

Run function - screenshot

  • Azure functions tools should start.

Running azure function - screenshot

  1. Trigger the function with Postman

Paste URL - screenshot

  • Select the Headers tab.

  • Add Content-Type and set it to application/json.

Add content type - screenshot

  • Select the Body tab.

  • Select Raw and set it to empty json {}.

Select body - screenshot

  • Select Send.

  • You should get 202 Accepted Status.

Status - screenshot

  • Go to the output console.

  • The function should get triggered.

Triggered function - screenshot

  • Go back to Visual Studio and stop debugging.
  1. Add NuGet packages
  • Right Click on the project and select Manage NuGet Packages.

  • Select the Browse tab and search for Microsoft.IdentityModel.Clients.ActiveDirectory.

  • Select the latest stable version and then select Install.

Install package - screenshot

  • Search for Xrm.Tools.CrmWebAPI. Note: This is a community library designed to work with the Microsoft Dataverse Web API. When you are building this type of extension you can use any oData V4 library you prefer. Make sure you select the one developed by DavidYack.

  • Select the latest stable version and then select Install.

Install package - screenshot

  • Close the NuGet Package Manager.
  1. Edit the local settings file
  • Open the local.settings.json file

Local settings file - screenshot

  • Add the Values below to local.settings

      "cdsurl": "",
    
      "cdsclientid": "",
    
      "cdsclientsecret": ""
    

Add values - screenshot

  • Find the client secret you saved in the notepad and paste as the cdsclientsecret.

Client secret - screenshot

  1. Copy the App ID
  • Go back to your Azure portal.

  • Select Azure Active Directory.

  • Select App Registrations.

  • Open the registration you created.

Open app registration - screenshot

  • Copy the Application (Client ID).

Copy app id - screenshot

  • Go back to Visual Studio and paste the Application ID as the cdsclientid.
  1. Find the your Microsoft Dataverse URL

Open environment - screenshot

  • Copy the Environment URL.

Copy environment URL - screenshot

  • Go back to Visual Studio and paste the URL you copied as the cdsurl.

Paste URL - screenshot

  • Save and close the file.
  1. Add using statements to the function class.
  • Open the InspectionRouter.cs file

  • Add the using statements below

      using System.Threading.Tasks;
      using Xrm.Tools.WebAPI;
      using Microsoft.IdentityModel.Clients.ActiveDirectory;
      using Xrm.Tools.WebAPI.Results;
      using System.Dynamic;
      using Xrm.Tools.WebAPI.Requests;
      using System.Collections.Generic;
    
  1. Create a method that will create the web API.
  • Add the method below inside the class.

      private static async Task<CRMWebAPI> GetCRMWebAPI(ILogger log)
      {
      return null;
      }
    
  • Add the local variables below before the return line on the GetCRMWebAPI method.

      var clientID = Environment.GetEnvironmentVariable("cdsclientid", EnvironmentVariableTarget.Process);
      var clientSecret = Environment.GetEnvironmentVariable("cdsclientsecret", EnvironmentVariableTarget.Process);
      var crmBaseUrl = Environment.GetEnvironmentVariable("cdsurl", EnvironmentVariableTarget.Process);
      var crmurl = crmBaseUrl + "/api/data/v9.0/";
    
  • Create Authentication Parameters.

      AuthenticationParameters ap = await AuthenticationParameters.CreateFromUrlAsync(new Uri(crmurl));
    
  • Create Client Credential passing your Client Id and Client Secret.

      var clientcred = new ClientCredential(clientID, clientSecret);
    
  • Get Authentication Context.

      // CreateFromUrlAsync returns endpoint while AuthenticationContext expects authority
      // workaround is to downgrade adal to v3.19 or to strip the tail
      var auth = ap.Authority.Replace("/oauth2/authorize", "");
      var authContext = new AuthenticationContext(auth);
    
  • Get Token.

      var authenticationResult = await authContext.AcquireTokenAsync(crmBaseUrl, clientcred);
    
  • Return the web API. Replace the return line with the code below.

      return new CRMWebAPI(crmurl, authenticationResult.AccessToken);
    

Get CRM web API method - screenshot

  1. Test the web API you created
  • Call the GetCRMWebAPI method. Add the code below to the Run method.

      CRMWebAPI api = GetCRMWebAPI(log).Result;
    
  • Execute WhoAmI function and log the User Id.

      dynamic whoami = api.ExecuteFunction("WhoAmI").Result;
      log.LogInformation($"UserID: {whoami.UserId}");
    

Run method - screenshot

  1. Debug
  • Select Run.

  • Go back to Postman and select Send.

Postman send - screenshot

  • Go to the output console.

  • You should see the User ID.

Run result - screenshot

Go back Visual Studio and stop debugging.

Task #2: Get Inspections and Users and Assign Inspections

  1. Get the New Request and Pending option values for the Inspection table Status Reason column.
    • Navigate to https://make.powerapps.com/ and make sure you have your Dev environment selected.
  • Select Solutions and open the Permit Management solution.

  • Select Tables and open the Inspection table.

  • Select Switch to classic.

  • Select Fields.

  • Locate and open the Status Reason field,

  • Scroll down and double open the New Request option.

    New request option - screenshot

  • Copy the Value and keep it in a notepad.

  • Select OK.

  • double click to open the Pending option.

  • Copy the Value and keep it in a notepad.

    Pending option - screenshot

  • Select OK.

  • Close the field editor.

  • Close the classic table editor.

  1. Create a method that will get all active inspections that are New Request or Pending, and schedule them for today
  • Add the method below inside the class.

      private static Task<CRMGetListResult<ExpandoObject>> GetInspections(CRMWebAPI api)
    
      {
    
      return null;
    
      }
    
  • Create Fetch XML. Add the code below before the return line of the GetInspections method. replace [New Request] and [Pending] with the option values you copied without the commas.

      var fetchxml = @"<fetch version=""1.0"" mapping=""logical"" >
      <entity name=""contoso_inspection"" >
      <attribute name=""contoso_inspectionid"" />
      <attribute name=""contoso_name"" />
      <attribute name=""ownerid"" />
      <attribute name=""contoso_inspectiontype"" />
      <attribute name=""contoso_sequence"" />
      <attribute name=""contoso_scheduleddate"" />
      <filter type=""and"" >
      <condition value=""0"" operator=""eq"" attribute=""statecode"" />
      <condition attribute=""contoso_scheduleddate"" operator=""today"" />
      <condition attribute=""statuscode"" operator=""in"" >
      <value>[New Request]</value>
      <value>[Pending]</value>
      </condition>
      </filter>
      </entity>
      </fetch>";
    
  • Get the list of Inspections.

      var inspections = api.GetList<ExpandoObject>("contoso_inspections", QueryOptions: new CRMGetListOptions()
      {
      FetchXml = fetchxml
      });
    
  • Return the Inspections. Replace the return line with the code below.

      return inspections;
    

Get inspections method - screenshot

  1. Call the GetInspections method from the Run method.
  • Go back to the Run method.

  • Call the GetInspections method.

      var inspections = GetInspections(api).Result;
    

Call get inspections method - screenshot

  1. Create a method that will get all users.
  • Add the method below inside the class.

      private static Task<CRMGetListResult<ExpandoObject>> GetUsers(CRMWebAPI api)
      {
      var users = api.GetList<ExpandoObject>("systemusers");
      return users;
      }
    
  • Call the GetUsers method from the Run method.

      var users = GetUsers(api).Result;
    

Call get users method - screenshot

  1. Create a method that will assign inspections to users
  • Add the method below to the class.

      private static async Task<CRMUpdateResult> RouteInspection(CRMWebAPI api, dynamic inspection, string userId, int sequenceNumber)
      {
      dynamic updateObject = new ExpandoObject();
      ((IDictionary<string, object>)updateObject).Add
      ("ownerid@odata.bind", "/systemusers(" + userId + ")");
      updateObject.contoso_sequence = sequenceNumber.ToString();
      return await api.Update("contoso_inspections", new Guid(inspection.contoso_inspectionid), updateObject);
      }
    
  1. Create two-digit random number.
  • Add the code below to the Run method.

      Random rnd = new Random();
      int sequenceNumber = rnd.Next(10, 99);
    
  1. Assign Inspections
  • Go through the Inspections and call the RouteInspection method.

      int currentUserIndex = 0;
      foreach (dynamic inspection in inspections.List)
      {
      log.LogInformation($"Routing inspection {inspection.contoso_name}");
      var inspectionResult = new CRMUpdateResult();
      //Your record assignment would look like this. We will not assign records to different users in this lab
      // if (users.List.Count > (currentUserIndex))
      //{
      // dynamic currentUser = users.List[currentUserIndex];
      // inspectionResult = RouteInspection(api, inspection, currentUser.systemuserid.ToString(), sequenceNumber).Result;
      //currentUserIndex++;
      //}
      }
    
  • We will not assign inspection records to other users in this lab.

Comment out code - screenshot

  • Assign inspections to the Inspection Router. Add the code below inside foreach.

      //We will instead assign inspections to the user you are currently logged in as
      inspectionResult = RouteInspection(api, inspection, whoami.UserId.ToString(), sequenceNumber).Result;
    

Assign inspections - screenshot

Build the project and make sure that the build succeeds.

Exercise #3: Publish and Test

Objective: In this exercise, you will publish the Azure function to Azure, update the app settings, and test the function.

Task #1: Publish to Azure

  1. Publish the function
  • Right click on the project and select Publish.

Publish project - screenshot

  • Select Azure and then select Next.

  • Select Azure Function App (Windows) and then select Next.

  • Sign in with user that has an Azure subscription.

  • Select your Azure subscription and then select + create new Azure function app.

Create Azure function app - screenshot

  • Provide a unique name for App Name, select your subscription name, create new resource group, create new Azure storage, select Consumption for Plan type, select Location, and then select Create.

Create profile - screenshot

  • Wait for the app service to be created.

  • Select Finish.

  • Select Publish.

  • Wait for the function application to be configured and published.

  1. Open the function application settings
  • Go back to you Azure portal.

  • Select All Resources, search for InspectionManagement, and open the function you published.

Open azure fi=unction application - screenshot

  • Scroll down to Settings and select Configuration.

Configuration - screenshot

  1. Update App Settings
  • Select Advanced Edit.

Advanced edit - screenshot

  • Paste the json below at the top of the settings.

      {
    
      "name": "cdsclientid",
    
      "value": "[clientid]",
    
      "slotSetting": false
    
      },
    
      {
    
      "name": "cdsclientsecret",
    
      "value": "[clientsecret]",
    
      "slotSetting": false
    
      },
    
      {
    
      "name": "cdsurl",
    
      "value": "[cdsurl]",
    
      "slotSetting": false
    
      },
    

Edit settings - screenshot

  • Go back to Visual Studio and open the local.settings.json file.

  • You will copy the cdsurl, cdsclientid, and cdsclientsecret. Copy the cdsurl value.

Copy URL - screenshot

  • Go back to Azure and replace [cdsurl] with the URL you copied.

Paste URL - screenshot

  • Copy the cdsclientid and cdsclientsecret values from the local.settings.json file and replace [cdsclientid] and [cdsclientsecret].

Paste id and secret - screenshot

  • Select OK.

  • Select Save.

Save changes - screenshot

  • Select Continue.

  • Select Functions and open the function you published.

Open function - screenshot

  • Select Code + Test.

Code + Test - screenshot

  1. Prepare test record

Current environment - screenshot

  • Select Apps and open the Permit Management application.

Start application - screenshot

  • Select Settings and then select Personalization and Settings.

Personal settings - screenshot

  • Change the Time Zone to (GMT-11:00) Coordinated Universal Time-11 and then select OK. This will ensure the query results will produce the same results regardless of your time zone.

Change time zone - screenshot

  • Select Inspections and open one of the Inspection records or create a new record.

Open inspection record - screenshot

  • Set the Status Reason to New Request or Pending, change the Scheduled Date to today’s date, and make a note of the current Owner of the record.

Update record - screenshot

  • Select Save.
  1. Run the function
  • Go to your Azure portal.

  • Select Test/Run.

Run function - screenshot

  • Select Run.

  • The function should run and succeed.

Run result - screenshot

  1. Confirm record assignment
  • Go back to the Permit Management application.

  • Select Refresh.

Refresh record - screenshot

  • The record Owner should now be the Inspection Router.

Routed record - screenshot

Exercise #4: Promote to production

Objective: In this exercise, you will export the Permit Management solution form your Dev environment and import it into your Production environment. In this lab, you have added a security role to the solution that must be promoted.

Task #1: Export Solution

  1. Export Permit Management managed solution
  • Sign in to Power Apps maker portal and make sure you are in the Dev environment.

  • Select Solution.

  • Select the Permit Management solution and then select Export.

Export solution - screenshot

  • Select Publish and wait for the publishing to complete.

Publish solution - screenshot

  • Select Next.

  • Select Managed and then select Export.

Export solution - screenshot

  • Save the Exported solution on your machine.
  1. Export Permit Management unmanaged solution
  • Select Solution again.
  • Select the Permit Management solution and then select Export.
  • Select Next.
  • Select Unmanaged, edit the version number to match the Managed solution and then select Export.

  • Save the Exported solution in your machine.

Task #2: Import Solution

  1. Import Permit Management managed solution

Import solution - screenshot

  • Select Choose file.

  • Select the Managed solution you exported and then select Open.

  • Select Next.

  • Select Import.

  • Wait for the import to complete and then select Close.

  • Review and test your production environment.