Skip to main content

Azure Functions - Retrieve data from Dynamics 365 CRM/CE Online using CRM OData Web API

Introduction:

This blog describes how you can retrieve data from Dynamics 365 CRM/CE Online in an Azure Function, using CRM OData Web API.

The CRM Web API has a couple of advantages over the traditional SOAP based approach to interact with CRM. These are rest based services, its easy to use and transport over http protocol , and there is no need to use additional web resources (XrmServiceToolkit ) of SDK (SDK.REST).

Here is a detailed documentation on CRM OData Wbeb API.

I will be using an Azure Function Application in Visual Studio 2017 to retrieve records from Dynamics 365 CRM/CE, by making use of OData API Endpoint provided by CRM.

Prerequisites:

  • Dynamics 365 CRM Online Trial
https://trials.dynamics.com/

Concepts:

  • There are various ways by which you can authenticate to CRM Web API. 2 of the most frequently used ways are:
    • Using Crm User name and Password
    • Using Client ID and Client Secret
  • In order to make OData Web API calls from Azure Function, we need to register an app in Azure Active Directory in same tenant where CRM is hosted. 
  • This Active Directory app will be used to fetch Bearer token Azure Active Directory. This token needs to be passed in Authorization Header of the HTTP Request that we will be making to CRM OData Endpoint.
  • If your result set has more than 5000 records, you will be returned with first 5000 records ONLY in one OData call. I have created another blog post on retrieving more than 5000 records form CRM. You can find it here,

Use Case:

We will take a simple use case of retrieving top 10 account names from CRM using OData Web API in an HTTP Triggered Azure Function. The benefit of HTTP Triggered Azure Functions is that they are extremely light weight, and works just an API. We can test them locally using Postman as well.

In Odata syntax, our Accounts query that will return us the desired results:

https://skssr.api.crm8.dynamics.com/api/data/v9.1/accounts?$select=name&$top=10
If we will post this url in a browser (I've used Firefox), we will get a response from CRM just like one in below screenshot:


Authenticating CRM:


As mentioned earlier, there are two ways to authenticate to CRM for making ODAta Web API calls. We will go over both of them now.

  • Authenticate Using Client Id and Secret

  1. In this, along with creating a new Azure Active Directory app, we also need create an application user in CRM and assign roles to it.
  2. An Application User needs to be created in CRM, which will be used to provide user context to the Azure Active Directory application when we will try to interact with CRM from Azure Function.
  3. Navigate to https://portal.azure.com/ 
  4. Go To Azure Active Directory
  5. Click on App Registrations
  6. Create a new Application
  7. Select Web app / API, fill other fields and click on Create
  8. Copy the Application ID, this will be needed later.
  9. Go to Required Permissions
  10. Click on Add
  11. Select API
  12. Select Dynamics CRM Online, and click on Select below
  13. Select Permissions
  14. Check Access Dynamics 365 as Organization users checkbox, and click on Select
  15. Click on Done
  16. Now click on Keys, and give a Key name, and select Never Expired for duration field, and click on Save. Once saved, you will be shown Value of the Key, copy it as this will be required later. Also once you close this window, you wont be able to see this later.
  17. Now navigate to Dynamics 365 CRM/CE Online instance
  18. Go to Settings -> Security
  19. Change the View to Application Users
  20. Click on New to create a new Application User
  21. Fill in the Full Name, Primary Email, and the Application ID that you copied earlier from the Azure AD app that you just created.
  22. Click on Save
  23. You will notice that Application ID URI and Azure AD Object ID fields are automatically populated as the form is saved.
  24. Now Click on Manage Roles, and provide necessary roles to the user so that he is able to access the desired entity records. I have given System Administrator and System Customizer role for my CRM trial instance.
  25. After doing this, you need to provide the copied Application ID and Key, and other details in the below method to generate the AD token.
  26. That's it, we are all set up now to Authenticate to CRM using client id and secret. 

public virtual string GetTokenUsingClientIdSecret()
        {
            var authority = "https://login.windows.net/YourOrganizationName.onmicrosoft.com";
            var clientId = "Your-Client-ID";
            var secret = "Your-Secret/Key";
            var authContext = new AuthenticationContext(authority, false);
            var credentials = new ClientCredential(clientId, secret);
            var tokenResult = authContext.AcquireTokenAsync(this.CrmUri, credentials).Result;
            return tokenResult.AccessToken;
        }























  • Authenticate Using User Name and Password

  1. For this, we need to create a new app in the same Azure Active Directory as CRM Online.
  2. Navigate to https://portal.azure.com/ 
  3. Go To Azure Active Directory
  4. Click on App Registrations
  5. Create a new Application
  6. Select Native, fill other fields and click on Create
  7. Copy the Application ID, this will be needed later.
  8. Go to Required Permissions
  9. Click on Add
  10. Select API
  11. Select Dynamics CRM Online, and click on Select below
  12. Select Permissions
  13. Check Access Dynamics 365 as Organization users checkbox, and click on Select
  14. Click on Done
  15. Click on Grant Permissions button under Required Permission section, and click on Yes, if there is any popup.
  16. After doing this, you need to provide the copied Application ID, and other details in constructor of CrmOdataHelper class.
  17. That's it, we are all set up now to Authenticate to CRM using username and password. This blog uses the this same mechanism to get AD token from Azure.














Code:


I have made use of two classes here:

  • CrmOdataHelper.cs

    • Contains methods for retrieving Authentication Token for making OData calls, and the actual helper method that makes the HTTP GET call for retrieve records from OData endpoint.

  • Function1.cs

    • This is our HTTP Triggered Azure Function created in Visual Studio 2017, and can be tested locally using Postman.
    • This Azure Function contains the Run method that will make use of CrmOdataHelper.cs to make call to CRM, and then parse the response from CRM and retrieve account names from it.

Here is the main Function1.cs file:




using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs.Host;
using Newtonsoft.Json;
using CrmOdataFetchLargeDataset;
using System.Collections.Generic;
using System.Linq;

namespace FunctionApp1
{
    public static class Function1
    {
        [FunctionName("Function1")]
        public static IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get", Route = null)]HttpRequest req, TraceWriter log)
        {
            log.Info("C# HTTP trigger function processed a request.");

            var crmHelper = new CrmOdataHelper();
            var query = "/api/data/v9.1/accounts?$select=name&$top=10";
            var response = crmHelper.CrmWebApiFormattedGetRequest(query);
            var responseString = crmHelper.GetResponseString(response);
            var jsonObject = JsonConvert.DeserializeObject<dynamic>(responseString);
            var list = new List<string>();
            foreach (var item in jsonObject.value)
            {
                var name = item.name;
                list.Add(name.ToString());
            }

            return (ActionResult)new OkObjectResult($"Account Names = , {list.Aggregate((i, j) => i + " , " + j)}");
        }
    }
}

Here is the CrmOdataHelper.cs file:

This is kind of a HTTP helper class that is responsible for making HTTP requests and returning the response to the caller.

The method CrmWebApiFormattedGetRequest takes input a query, and makes call to CRM and returns the response to the caller.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
namespace CrmOdataFetchLargeDataset
{
    using System;
    using System.Net.Http;
    using System.Net.Http.Headers;

    using Microsoft.IdentityModel.Clients.ActiveDirectory;

    public class CrmOdataHelper
    {
        private readonly string clientId;
        private readonly string endpoint;
        private readonly string crmPassword;
        private readonly string crmUsername;
        private string _authToken = String.Empty;

        private string Token
        {
            get => GetTokenUsingUserNamePassword();
            set => _authToken = value;
        }

        public string CrmUri = string.Empty;

        public CrmOdataHelper()
        {
            this.crmPassword = "Password";
            this.crmUsername = "Your-CRM-UserName";
            this.endpoint = "https://login.windows.net/common";
            this.clientId = "Your-Client-Id";
            this.CrmUri = "https://YourCompany.api.crm9.dynamics.com";
        }

        private string GetTokenUsingUserNamePassword()

        {
            if (string.IsNullOrWhiteSpace(_authToken))
            {
                var authContext = new AuthenticationContext(this.endpoint, false);
                var credentials = new UserCredential(this.crmUsername, this.crmPassword);
                var tokenResult = authContext.AcquireToken(this.CrmUri, this.clientId, credentials);
                _authToken = tokenResult.AccessToken;
            }
            return _authToken;
        }

        public HttpResponseMessage CrmWebApiFormattedGetRequest(string apiRequest)
        {
            using (var httpClient = new HttpClient())
            {
                httpClient.BaseAddress = new Uri(this.CrmUri);
                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",
                    this.Token);
                return httpClient.GetAsync(this.CrmUri + apiRequest).Result;
            }
        }

        public string GetResponseString(HttpResponseMessage responseMessage)
        {
            string responseString = null;

            if (responseMessage.IsSuccessStatusCode)
            {
                responseString = responseMessage.Content.ReadAsStringAsync().Result;
            }

            return responseString;
        }
    }
}

Now we can use Postman to test our HTTP Triggered Azure Function. Since our Azure Function is created to support GET requests only, at the moment, we need to make a GET request from postman to the function URL.

Once we do that, this is the response string that we receive when we make OData Web API call to CRM:

{"@odata.context":"https://skssr.api.crm8.dynamics.com/api/data/v9.1/$metadata#accounts(name)","@Microsoft.Dynamics.CRM.totalrecordcount":-1,"@Microsoft.Dynamics.CRM.totalrecordcountlimitexceeded":false,"value":[{"@odata.etag":"W/\"5881751\"","name":"Thea Mueller","accountid":"6d5f341a-c1f0-e811-a974-000d3af246e1"},{"@odata.etag":"W/\"5881752\"","name":"Ms. Alberta Sauer","accountid":"6f5f341a-c1f0-e811-a974-000d3af246e1"},{"@odata.etag":"W/\"5881753\"","name":"Nikko Kunde","accountid":"715f341a-c1f0-e811-a974-000d3af246e1"},{"@odata.etag":"W/\"5881754\"","name":"Mossie Fadel","accountid":"735f341a-c1f0-e811-a974-000d3af246e1"},{"@odata.etag":"W/\"5881755\"","name":"Graciela Kerluke","accountid":"755f341a-c1f0-e811-a974-000d3af246e1"},{"@odata.etag":"W/\"5881756\"","name":"Bulah D'Amore DDS","accountid":"775f341a-c1f0-e811-a974-000d3af246e1"},{"@odata.etag":"W/\"5881757\"","name":"Ona Franecki","accountid":"795f341a-c1f0-e811-a974-000d3af246e1"},{"@odata.etag":"W/\"5881758\"","name":"Toy Klein","accountid":"7b5f341a-c1f0-e811-a974-000d3af246e1"},{"@odata.etag":"W/\"5881759\"","name":"Lesly Fisher","accountid":"7d5f341a-c1f0-e811-a974-000d3af246e1"},{"@odata.etag":"W/\"5881760\"","name":"Dr. Courtney Doyle","accountid":"7f5f341a-c1f0-e811-a974-000d3af246e1"}]}

Now we can deserialize it using Newtonsoft or any other deserializers, and retrieve the account names from the response. I am just deserializing it into dynamic object and then fetching the names from the object.

Hope this is helpful. Happy Coding 😊😊

Comments

  1. Great Article! Thanks for saving the Day! Keep Blogging :)

    ReplyDelete
  2. It has been simply incredibly generous with you to provide openly what exactly many individuals would’ve marketed for an eBook to end up making some cash for their end, primarily given that you could have tried it in the event you wanted.
    Expence Reports

    ReplyDelete

Post a Comment

Popular posts from this blog

Handling Concurrency in Azure Functions (HTTP Triggered)

Concurrency & Isolation Concurrency is one of the most common requirements when we don't want the resource to be accessed by multiple requests at the same time. Lets say we have a Database that holds products stock information, then it is very important that any read/write operation that happens on the Database must hold the property of Isolation . The isolation property ensures that the concurrent execution of transactions results in a system state that would be obtained if transactions were executed sequentially, i.e., one after the other. Providing isolation is the main goal of concurrency control. Depending on the concurrency control method (i.e., if it uses strict – as opposed to relaxed – serializability), the effects of an incomplete transaction might not even be visible to another transaction. Options Available in Azure Functions Recently while working on Azure Functions, I discovered a really simple way of handling Concurrency via code. There are ways to con

Creating Azure Function from Visual Studio 2017 triggered from Azure Service Bus Topic

Recently, I was building an Azure Function from Visual Studio and encountered an issue with the auto generated Function.cs class file. My requirement was to create an Azure Function that would get triggered as soon as we receive a message on an Azure Service Bus Topic. For this I tried to use the Project template available in Visual Studio 2017. Unfortunately, when I tried to provide all the required values, and run the function, I got following error: First you get a warning which says: Warning: Cannot find value named 'Endpoint=sb:......' in local.settings.json that matches 'connection' property set on 'serviceBusTrigger' in '\Source\repos\FunctionApp1\MyFunction\bin\Debug\net461\Function1\function.json'. You can run 'func azure functionapp fetch-app-settings <functionAppName>' or specify a connection string in local.settings.json. This warning was followed by an error: [2/12/2018 9:48:05 AM] A ScriptHost error has occurre