Access Dataverse Web API from .NET Core or Azure Functions with client credentials

For applications targeting the full .NET framework the process of connecting to the Dataverse Web API is usually handled by the XrmTooling SDK and the use of connection strings.

For .NET Core applications the security handshake needs to be implemented with the help of the Microsoft Authentication Library (MSAL).

Update: There is finally an official SDK library targeting .NET Core. You can find a walkthrough of the setup in Accessing Dataverse Web API from .NET Core with Microsoft.PowerPlatform.Dataverse.Client.

In the upcoming sample we will build an application with authentication via client credentials to access the Dataverse Web API.

Application registration in Azure Directory

We assume that you did create an app registration upfront and configured Dynamics impersonation permissions with clientId and client secret. The process is fully documented in docs.microsoft.com.

Create .NET Core Console Application (with Visual Studio Code)

Next step is to create a .NET Core Console App with the required dependency on Microsoft Authentication Library (MSAL).

mkdir "connect-to-dataverse-webapi" cd "connect-to-dataverse-webapi" dotnet new console dotnet add package Microsoft.Identity.Client --version 4.30.1 code .

Replace the code in Program.cs with the code below.

Edit lines 15-18 by setting clientId, clientSecret and tenantId.

In line 43 the Dataverse Web API is queried for 10 accounts along with their name.

using Microsoft.Identity.Client; using System; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Text.Json; using System.Threading.Tasks; namespace connect_to_dataverse_webapi { class Program { static async Task Main(string[] args) { const string clientId = "<clientId>", clientSecret = "<clientSecret>", tenantId = "<tenantId>", environment = "<yourEnvironment>.crm4", apiVersion = "9.2"; var scope = new[] { $"https://{environment}.dynamics.com/.default" }; var webAPI = $"https://{environment}.dynamics.com/api/data/v{apiVersion}/"; var authority = $"https://login.microsoftonline.com/{tenantId}"; var clientApp = ConfidentialClientApplicationBuilder.Create(clientId) .WithClientSecret(clientSecret) .WithAuthority(new Uri(authority)) .Build(); var authResult = await clientApp.AcquireTokenForClient(scope).ExecuteAsync(); using var httpClient = new HttpClient { BaseAddress = new Uri(webAPI), Timeout = new TimeSpan(0, 0, 10) }; httpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0"); httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0"); httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken); var response = await httpClient.GetAsync("accounts?$top=10&$select=name"); var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; if (response.IsSuccessStatusCode) { var accountsResponse = await response.Content.ReadAsStringAsync(); var accounts = JsonSerializer.Deserialize<Response<Account>>(accountsResponse, options).Value; Console.WriteLine(string.Join("\n", accounts.Select(x => $"{x.Name}, {x.AccountId}"))); } else { var errorResponse = await response.Content.ReadAsStringAsync(); var error = JsonSerializer.Deserialize<ErrorResponse>(errorResponse, options); Console.WriteLine("HTTP: {0} ReasonPhrase: {1}\nError: {2}", (int)response.StatusCode, response.ReasonPhrase, error.Error.Message); } } } class Response<T> { public T[] Value { get; set; } } class Account { public string Name { get; set; } public Guid AccountId { get; set; } } class ErrorResponse { public ErrorMessage Error { get; set; } public class ErrorMessage { public string Message { get; set; } } } }

Room for improvement

  • Reuse of bearer token and automatic renewal before its expiration. See authResult.ExpiresOn
  • Move the auth constants to app.config (console) or local.settings.json (Azure Function) file and load the values as environment variables
  • Better error handling

Blog post written by

Marius Pothmann

Marius Pothmann is a Microsoft certified solution architect focusing on the Power Platform and cloud-native application development on Azure. Working on the Microsoft stack for over a decade gave him the opportunity to deep dive into many building blocks of successful enterprise application architectures like Dynamics 365, SQL Server, ADFS, ASP.NET (Core), SPA-based Web Apps, WPF/WinForms. If you think he can assist in solving your challenges drop us an e-mail and we will get back to you right away.