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://signup.microsoft.com/Signup?OfferId=bd569279-37f5-4f5c-99d0-425873bb9a4b&dl=DYN365_ENTERPRISE_PLAN1&Culture=en-us&Country=us&ali=1
- Azure Subscription related to CRM Online instance
- Visual Studio 2017 with Cloud Components
- ADAL Nuget Package
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:
In Odata syntax, our Accounts query that will return us the desired results:
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
- 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.
- 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.
- Navigate to https://portal.azure.com/
- Go To Azure Active Directory
- Click on App Registrations
- Create a new Application
- Select Web app / API, fill other fields and click on Create
- Copy the Application ID, this will be needed later.
- Go to Required Permissions
- Click on Add
- Select API
- Select Dynamics CRM Online, and click on Select below
- Select Permissions
- Check Access Dynamics 365 as Organization users checkbox, and click on Select
- Click on Done
- 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.
- Now navigate to Dynamics 365 CRM/CE Online instance
- Go to Settings -> Security
- Change the View to Application Users
- Click on New to create a new Application User
- Fill in the Full Name, Primary Email, and the Application ID that you copied earlier from the Azure AD app that you just created.
- Click on Save
- You will notice that Application ID URI and Azure AD Object ID fields are automatically populated as the form is saved.
- 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.
- 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.
- 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;
}
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
- For this, we need to create a new app in the same Azure Active Directory as CRM Online.
- Navigate to https://portal.azure.com/
- Go To Azure Active Directory
- Click on App Registrations
- Create a new Application
- Select Native, fill other fields and click on Create
- Copy the Application ID, this will be needed later.
- Go to Required Permissions
- Click on Add
- Select API
- Select Dynamics CRM Online, and click on Select below
- Select Permissions
- Check Access Dynamics 365 as Organization users checkbox, and click on Select
- Click on Done
- Click on Grant Permissions button under Required Permission section, and click on Yes, if there is any popup.
- After doing this, you need to provide the copied Application ID, and other details in constructor of CrmOdataHelper class.
- 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.
- For this, we need to create a new app in the same Azure Active Directory as CRM Online.
- Navigate to https://portal.azure.com/
- Go To Azure Active Directory
- Click on App Registrations
- Create a new Application
- Select Native, fill other fields and click on Create
- Copy the Application ID, this will be needed later.
- Go to Required Permissions
- Click on Add
- Select API
- Select Dynamics CRM Online, and click on Select below
- Select Permissions
- Check Access Dynamics 365 as Organization users checkbox, and click on Select
- Click on Done
- Click on Grant Permissions button under Required Permission section, and click on Yes, if there is any popup.
- After doing this, you need to provide the copied Application ID, and other details in constructor of CrmOdataHelper class.
- 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 😊😊
Great Article! Thanks for saving the Day! Keep Blogging :)
ReplyDeleteIt 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.
ReplyDeleteExpence Reports