I Wish I Would Have Known :#3

Although you can define multiple quick create forms, only one quick create form can be used by everyone. The form everyone will use is set using the form order. Quick create forms cannot be assigned to security roles and they do not provide the capability for the user to switch forms.

https://docs.microsoft.com/en-us/powerapps/maker/model-driven-apps/create-edit-quick-create-forms#create-a-quick-create-form

Authenticate Dynamics 365 in Azure Functions using MSAL

As you all know, ADAL is deprecated and will be unsupported by June 30th 2022, Microsoft recommends to use the MSAL (Microsoft Authentication Library). MS has also advised to migrate your existing applications to MSAL.

MSAL makes it easy for developers to add identity capabilities to their applications. With just a few lines of code, developers can authenticate users and applications, as well as acquire tokens to access resources. MSAL also enables developers to integrate with the latest capabilities in our platform—like passwordless and Conditional Access.

Microsoft Build 2020

Read the official FAQ for migrating your applications to MSAL here

MSAL is now the recommended official authentication library for use with the Microsoft identity platform.

Photo by Ilargian Faus on Pexels.com

In my pervious post Authenticate Dynamics 365 in Azure Functions Version 3 , I used ADAL for authentication. The below code authenticates D365 using MSAL, and use WEBAPI to fetch the contact record with the email address passed in the request body.

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Microsoft.Identity.Client;
using System.Net.Http.Headers;
using System.Net.Http;

namespace AzureFunctionMSAL
{
    public static class ConnectMSAL
    {
       

        [FunctionName("MSALFunction")]

        // change get or post based on your scenario

        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string contactEmail = req.Query["eamil"];
            string contacts = string.Empty;
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            contactEmail = contactEmail ?? data?.email;


            // MSAL Authentication
            string clientId = "*********583";
            string secret = "zDC******";
            string[] scope = new string[] { "https://******.crm.dynamics.com/.default" };
            string webAPI = " https://***.crm.dynamics.com/api/data/v9.1/";
            string authority = "https://login.microsoftonline.com/69e9641e-4be0-4f4c-9ae4-06fdc1160c34";

            var clientApp = ConfidentialClientApplicationBuilder.Create(clientId: clientId)
            .WithClientSecret(clientSecret: secret)
            .WithAuthority(new Uri(authority))
            .Build();

            try
            {
                AuthenticationResult authResult = await clientApp.AcquireTokenForClient(scope).ExecuteAsync();

    // WEB API
                var httpClient = new HttpClient();
                httpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0");
                httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0");
                httpClient.Timeout = new TimeSpan(0, 2, 0);
                httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
                httpClient.BaseAddress = new Uri(webAPI);
                var queryOptions = "contacts?$select=fullname,mobilephone&$filter=emailaddress1 eq '" + contactEmail + "'";
                var response =  httpClient.GetAsync(queryOptions).Result;
                
                if (response.IsSuccessStatusCode)
                {
                    var contactsresult = response.Content.ReadAsStringAsync().Result;
                    contacts = contactsresult;
                    log.LogInformation("webapi executed");
               
                }

            }
            catch (Exception ex)
            {
                // log error 
                string errorMessage = ex.Message;
                log.LogError(errorMessage);
                //response
                return new BadRequestObjectResult(JsonConvert.SerializeObject("an error occured while retriving contacts:"));
            }



            //
            string responseMessage = "WEB API Response: " + contacts;

            return new OkObjectResult(responseMessage);
        }
    }
}

Test your function from post man.

Request
Response

Please note, the response in the code is not well formatted, you may format the response as a proper json object based on your business need. You might also need to add required exception handling in the code.

A short explanation on the code.

Hope this helps.

Use Dynamics 365 WEB API in Azure Functions Version 3

In the previous post, we saw how to authenticate Dynamics 365 in Azure Functions runtime version 3 (.NET Core). Now let’s see how to use Dynamics 365 WEB API after acquiring the bearer token.

The following sample code accepts email as input in the request body, and uses Web api to return the contacts records with the passed email.

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Net.Http;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Net;
using System.Net.Http.Headers;

namespace SampleFunctionApp
{
   

    public static class D365
    {
        [FunctionName("ConnecttoD365")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string contactEmail = req.Query["email"];
            string responseMessage = string.Empty;
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            contactEmail = contactEmail ?? data?.email;
            #region Auth Code
            //
            string cloud = "https://login.microsoftonline.com";

            //This is the Domain!

            string tenantId = "*******";

            string authority = $"{cloud}/{tenantId}";

            // ApplicationID in the new UI

            string clientId = "************";

            //Created from scratch in Keys

            string clientsecret = "*************";

            ClientCredential clientcred = new ClientCredential(clientId, clientsecret);

            // Application ID of the Resource (could also be the Resource URI)

            string resource = "https://*****.crm.dynamics.com/";

            AuthenticationContext ac = new AuthenticationContext(authority);
            AuthenticationResult result = null;
            var bearerToken = string.Empty;
            string ErrorMessege = string.Empty;
            try

            {
                //already having token
                result = await ac.AcquireTokenSilentAsync(resource, clientId);
                if (result != null)
                {
                    bearerToken = result.AccessToken;
                    log.LogInformation("Token Acquired:" );
                }
            }

            catch (AdalException adalException)

            {//Acquire token
                if (adalException.ErrorCode == AdalError.FailedToAcquireTokenSilently

                || adalException.ErrorCode == AdalError.InteractionRequired)

                {

                    result = await ac.AcquireTokenAsync(resource, clientcred);
                    if (result != null)
                    {
                        bearerToken = result.AccessToken;
                        log.LogInformation("Token Acquired");

                    }
                }
                else
                {
                    log.LogWarning("Failed to acquire Bearer Token :-" + adalException.Message);
                    var AdalException = new { adalexception = "Failed to acquire Bearer Token :-" + adalException.Message };
                    return new BadRequestObjectResult(JsonConvert.SerializeObject(AdalException));
                    throw adalException;

                }

            }

            #endregion

            #region getContact
           


            string outputString = string.Empty;
            //Next use a HttpClient object to connect to specified CRM Web service.
            var httpClient = new HttpClient();
            //Define the Web API base address, the max period of execute time, the 
            // default OData version, and the default response payload format.
            httpClient.BaseAddress = new Uri("https://*********.crm.dynamics.com/api/data/v9.1/");
            httpClient.Timeout = new TimeSpan(0, 2, 0);
            httpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0");
            httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0");
            httpClient.DefaultRequestHeaders.Add("Prefer", "odata.include-annotations=\"*\"");
            httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", (bearerToken));

            //Query to retrieve contacts
            var queryOptions = "contacts?$select=fullname,mobilephone&$filter=emailaddress1 eq '" + contactEmail + "'";
            HttpResponseMessage retrieveResponse1 = httpClient.GetAsync(queryOptions).Result;


            if (retrieveResponse1.StatusCode == HttpStatusCode.OK)
            {


              

                    responseMessage = retrieveResponse1.Content.ReadAsStringAsync().Result.ToString();
               

            }
            else
            {

                return new BadRequestObjectResult(JsonConvert.SerializeObject("an error occured while retriving contacts"));
            }



            #endregion

            if (string.IsNullOrEmpty(contactEmail) && responseMessage== String.Empty)
            {
                responseMessage = "Pass an email in the query string or in the request body";
            }        

            return new OkObjectResult(responseMessage);
        }
    }
}

Test your function from post man.

Request
Response

Please note, the response in the code is not well formatted, you may format the response as a proper json object based on your business need.

Hope this helps.

Authenticate Dynamics 365 in Azure Functions Version 3

I consider Azure functions as a powerful weapon in the armory in numerous scenarios, but not limited to the following:

  • Expose Dynamics 365 APIs to third-part apps in a well-wrapped manner.
  • Delegate some of the computation load from plugins to outside D365.
  • Create scheduled custom code to run on specific intervals.
  • Insufficient infrastructure to host custom services or APIs for integration.

Read more about Azure Functions here.

One challenge we face when creating a new azure function with Dynamics 365 is that the current runtime version which is 3, it uses .Net core(runtime version 2 also). It was much easier with version 1 as it uses the .Net framework and we can directly use D365 SDK for authentication and consuming 365 services. The power apps .Net core SDK is still under the alpha version and cannot be utilized for production purposes. One easy option here is to use ADAL and WEB API. In this post, let’s see how we can authenticate an application user in Azure functions V3.

Before we start, if you are not familiar with the following, please have a glance at the links provided,

  1. Create Application users in Dynamics
  2. Creating an Azure function
  3. Creating Azure functions from visual Studio / VS Code

Create your Azure function with the help of links added in point 3, and add following NuGet packages if they are not there in your function app.

Now use the following code to authenticate your Dynamics 365 application user.

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Net.Http;
using Microsoft.IdentityModel.Clients.ActiveDirectory;

namespace SampleFunctionApp
{
    public static class D365
    {
        [FunctionName("ConnecttoD365")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string name = req.Query["name"];

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;
            #region Auth Code
            //
            string cloud = "https://login.microsoftonline.com";

            //This is the Domain!

            string tenantId = "**********";

            string authority = $"{cloud}/{tenantId}";

            // ApplicationID in the new UI

            string clientId = "*********";

            //Azure App secret Key

            string clientsecret = "******";

            ClientCredential clientcred = new ClientCredential(clientId, clientsecret);

            // Application ID of the Resource (could also be the Resource URI)

            string resource = "https://******.crm.dynamics.com/";

            AuthenticationContext ac = new AuthenticationContext(authority);
            AuthenticationResult result = null;
            var bearerToken = string.Empty;
            string ErrorMessege = string.Empty;
            try

            {
                //already having token
                result = await ac.AcquireTokenSilentAsync(resource, clientId);
                if (result != null)
                {
                    bearerToken = result.AccessToken;
                    log.LogInformation("Token Acquired:"+ bearerToken);
                }
            }

            catch (AdalException adalException)

            {//Acquire token
                if (adalException.ErrorCode == AdalError.FailedToAcquireTokenSilently

                || adalException.ErrorCode == AdalError.InteractionRequired)

                {

                    result = await ac.AcquireTokenAsync(resource, clientcred);
                    if (result != null)
                    {
                        bearerToken = result.AccessToken;
                        log.LogInformation("Token Acquired="+ bearerToken);

                    }
                }
                else
                {
                    log.LogWarning("Failed to acquire Bearer Token :-" + adalException.Message);
                    var AdalException = new { adalexception = "Failed to acquire Bearer Token :-" + adalException.Message };
                    return new BadRequestObjectResult(JsonConvert.SerializeObject(AdalException));
                    throw adalException;

                }

            }

            #endregion

            string responseMessage = string.IsNullOrEmpty(name)
                ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a better response."
                : $"Hello, {name}. This HTTP triggered function executed successfully.";

            return new OkObjectResult(responseMessage);
        }
    }
}

The above function simply authenticates and write bearer to the log. Ensure you do not log this in your actual code.

In the next post, we will see how we can use this token to call D365 Web API from Azure Function App.

Hope this helps.

Shift your focus; Upgrade yourself

Upgrade Yourself

Be it any profession, upgrading your skills is unquestionably significant. Not only does it open the door to new career opportunities, but it also helps you enhance the quality of your life. To keep myself technically updated, I have bookmarked a list of blogs and YouTube channels.. I also frequently check Feedspot’s Top 35 Microsoft Dynamics 365/CRM Blogs, which is regularly updated by the moderators. But is it enough?

For a Business Application Consultant , critical thinking is as important as software skills , and I must say that reading ‘Farnam Street’ enhances your decision-making skills. Shane Parrish who is an ex-spy, investor, and writer, improves your critical thinking ability with every post in his blog “Farnam Street”.

Why Farnam Street?

MR Shane focuses on the importance of cultivating mental models and frameworks to tackle complex issues. The blog ‘guides to decision making’ especially when you’re in a challenging situation. I must say that reading a 3-minute post on Farnam Street equals watching a week of TED or simialr videos. It simply means that you are served with the most appropriate information that you need to boost your decision-making skills regardless of your professional or skillset.

Shane Parrish urges visitors to ‘Upgrade Yourself’ and that’s one of the biggest reasons he is promoting self-betterment over classic self-help fare. He believes that being responsible to yourself is imperative and learning is one of the best investments that you can make in your life.

Photo by Pixabay on Pexels.com

Here are some other ‘Be Better’ blogs that I would recommend:

For me, learning is like entering a world of opportunities. What about you?

Do You Want to Prove Your Productivity While Working from Home?

How productive are you?

When we work from home, we have enough flexibility to do our job but, are we productive enough? The luxury can turn into a missed opportunity if not done precisely.

It’s been almost two months since I wrote a blog or participated in some community activities. I was too busy with my work and passive learning. I was under the impression that I am doing great because, I am delivering more tasks, learning new things, and extending my working hours by staying awake at night. I was being extremely productive. Right?

NO, I was just killing my productivity. HOW? It’s not all about delivering more tasks, but also about completing them in budgeted time without compromising on quality.

When I looked into my work, the activity which I used to complete in an hour from office is now taking more than an hour. Initially, I thought it could be the frequent breaks, however, I gradually realized, it was not just the breaks, it was simply because my productivity was going down the scale.

Oh no!!

This is not always noticeable mainly due to relaxed timelines during the current situation, as well as increased family time and close to zero daily commute. Do not let this comfort zone control your productivity, instead, put efforts to learn new things or educate yourself during the extra hours you earned by working from home. I bet this is the right time to sharpen your actual time management skills.

Shh!!

So what it takes to improve productivity while working from home? Though I am not an expert but can surely share my experience as of what helped me…take a look:

  • Be accountable for your timelines as more open-ended schedule may lead to bad results.
  • Set up a workspace and stick to your work hours.
  • Create a to-do-list and try to stick to it.
  • Take breaks and minimize distractions to put your best effort.
  • Avoid late-night work as socializing and exercising is also important.
  • Track your working hours to understand when you are more productive.
  • Start maintaining work-life balance to make sure you are doing great in both departments.

So now, over to you! let me know if they helped…