Dynamics 365 Roadmap: Dynamics 365 for Operations – How to Access Dynamics 365 for Operations Data Entities using OData Protocol and .NET

Open Data Protocol (OData) is a standard protocol for consuming data exposed by Dynamics 365 for Operations. OData is a new Representational State Transfer (REST) based protocol for CRUD operations – C-Create, R-Read, U-Update and D-Delete – that allows for integrating with Dynamics 365 for Operations. It is applied to all types of web technologies, such as HTTP and JavaScript Object Notation (JSON).

Dynamics 365 for Operations is a cloud-based ERP application that can require integration with one or many third-party applications (either desktop- or web-based). Common integrations can include recurring integrations for inventory, purchase orders, sales orders, transfer orders, etc.

Dynamics 365 for Operations has many data entities exposed to third-party applications, which helps create a robust solution for businesses. For example, a franchise management portal can call the Dynamics 365 for Operations data entity SalesOrdersHeader and SalesOrderLines, respectively. Some other console application can request a list of all legal entities and all customers listed for the company ‘USMF’.

In this article, I will explain how to access list of legal entities and all ‘USMF’ customers using OData protocol.

Purpose

This blog is a proof of concept of technical behavior to call Dynamics 365 for Operations data entities using .NET framework based on the Console application. For this purpose, it is required to download the solution code from GitHub (Courtesy Microsoft). The requirement is to get a list of all the legal entities, and a list of all customers (customer account numbers and names) of the ‘USMF’ company.

Configuration and Code

Once the solution files have been download from GitHub, there are other steps required as well to run this code successfully: 

Azure Active Directory – An account is required where I created an application.

ODataUtility.DLL – This assembly has all the metadata information about Dynamics 365 for Operations data entities that are exposed and ready to be consumed. Files can be download from here. (Courtesy Microsoft).

Visual Studio .NET Console Application (ODataConsoleApplication) – The code is not straightforward. I did download the code from GitHub and then modified it as per my need, which I am going to explain.

Step 1. Azure Active Directory

Assuming you have an Azure subscription, and you have an item of type ‘Azure Active Directory’ (AAD); inside this AAD, you have several applications of the type ‘Native client application’ and ‘Web application’. My requirement is to have a ‘Web Application’. So I created and registered a new ‘Web Application’ with the following settings:

  • Name – IntegrationDevWebApp (as I was creating this POC on a DEV version of Dynamics 365).
  • Sign-On URL – This could be anything. I used the actual Dynamics 365 for Operations URL for the DEV machine.
  • Client ID – This is required and important. It is a global unique identifier (GUID). Unique to the application and Azure Tenant.
  • User Assignment Required to Access App – Yes (this is the default).
  • Keys Section – This does not generate automatically, so I selected ‘1 Year’ as the duration from the drop-down, and then the Secret Key (GUID) generates after saving the application. It is important to copy this Secret Key at this time (after saving); otherwise, it won’t be accessible again in the future.
  • App ID URI – Required and Important. It is GUID. I kept it the same as the Dynamics 365 DEV URL.
  • Reply URL – Required only if the application type is ‘Native client application’. I kept a value in there as http://localhost123.
  • Permissions to other applications
    • Microsoft Dynamics ERP – Application Permission: 1 (Only 1 is available so select this one), Delegated Permissions: 3 (Only 3 are available, so select all of them)
    • Windows Azure Active Directory – Application Permission: 0 (there are lot of them, but you don’t need to select any), Delegated Permissions: 1 (there are a lot of them, but click to select the one that says ‘Sign in and read user profiles’).Azure active directory based application settings – Now save the application settings. Once saved, please make sure that you copy and preserve the generated Secret key. This key won’t be accessible again once you close this application.

Step 2: OData Application

Now for the OData application, you need these three property values from the Azure application that you have created: Client ID, App ID URI, and Secret Key ID.Assuming that you have downloaded the solution file from the GitHub URL as I did, I then modified OAuthHelper.cs file exist in AuthenticationUtility folder in the solution:

       // Class variables

       private static string _authorizationHeader;

       private static AuthenticationResult _authResult { get; set; }

       /// <summary>

       /// The header to use for OAuth.

       /// </summary>

       public const string OAuthHeader = “Authorization”;

       /// <summary>

       /// Retrieves an authentication header from the service.

       /// </summary>

       /// <returns>The authentication header for the Web API call.</returns>

       public static async Task<string> AuthorizationHeader()

       {

            string aadTenant = ClientConfiguration.Default.ActiveDirectoryTenant;

           string aadClientAppId = ClientConfiguration.Default.ActiveDirectoryClientAppId;

           string aadResource = ClientConfiguration.Default.ActiveDirectoryResource;

            // Added following two

           string azureEndPoint = ClientConfiguration.Default.AzureAuthEndPoint;

           string aadClientSecret = ClientConfiguration.Default.AADClientSecret;

           try

           {

               if (!string.IsNullOrEmpty(_authorizationHeader) &&

               DateTime.UtcNow.AddSeconds(180) < _authResult.ExpiresOn) return _authorizationHeader;

               var uri = new UriBuilder(azureEndPoint) { Path = aadTenant };

               var authContext = new AuthenticationContext(uri.ToString());

               var credentials = new ClientCredential(aadClientAppId, aadClientSecret);

               bool validateAuthority = authContext.ValidateAuthority;

               _authResult = await authContext.AcquireTokenAsync(aadResource, credentials);

               _authorizationHeader = _authResult.CreateAuthorizationHeader();

               return _authorizationHeader;

           }

           catch (Exception ex)

           {

              Console.WriteLine(ex.Message);

               throw ex;

           }                     

       }

Modified Code for ClientConfiguration.cs as well as follows –

//Following to be used for DEV AOS Environment

       public static ClientConfiguration OneBox = new ClientConfiguration()

       {

           UriString = “https://xxxxxxdevaos.sandbox.ax.dynamics.com/”,

           UserName = “”,

           Password = “”,

           ActiveDirectoryResource = “https://xxxxxdevaos.sandbox.ax.dynamics.com”,

           ActiveDirectoryTenant = “<Tenant ID or Name>”,

           ActiveDirectoryClientAppId = “<Client App ID eg.,12345678-xxxx-1212-yyyy-0055001zzzzz>”,

           AADClientSecret = “<Generated Secret Key ID>”,

           AzureAuthEndPoint = “https://login.windows.net”,

       };

This client configuration is very important here. Notice that UriString URL ends with “/” (forward slash) but ActiveDirectoryResource URL is not having trailing slash (or not ending with “/”). If you add trailing slash “/” in ActiveDirectoryResource URL, authentication will fail in the code (I will explain later).

Now when it comes to the main code, ODataConsoleApplication folder, there is a Program.cs file. I had to modify this code as well; here is the code piece:

public static string ODataEntityPath = ClientConfiguration.Default.UriString + “data”;

       static void Main(string[] args)

       {

           Uri oDataUri = new Uri(ODataEntityPath, UriKind.Absolute);

           var context = new Resources(oDataUri);

           string header = string.Empty;

           context.SendingRequest2 += new EventHandler<SendingRequest2EventArgs>(delegate (object sender, SendingRequest2EventArgs e)

           {

               var response = Task.Run(async () =>

               {

                   return await OAuthHelper.AuthorizationHeader();

               }

               );

                response.Wait();

               e.RequestMessage.SetHeader(OAuthHelper.OAuthHeader, response.Result.ToString());

           });

           Console.WriteLine(“Here are the list of legal entities. “);

           Console.WriteLine(“——————————————————————“);

           foreach (var legaglEntity in context.LegalEntities.AsEnumerable())

           {

               Console.WriteLine(“Name: {0}”, legaglEntity.Name);

           }

           Console.WriteLine(“Press any key to continue…”);

           Console.ReadLine();

           Console.WriteLine(“Here are the list of all USMF Customers.”);

           Console.WriteLine(“——————————————————————“);

           DataServiceQuery<Customer> usmfCustomer = context.Customers.AddQueryOption(“$filter”, “dataAreaId eq ‘USMF'”).AddQueryOption(“cross-company”, “true”);

           foreach (Customer cus in usmfCustomer)

           {

               Console.WriteLine(string.Format(“Customer ID: {0}, Name: {1}, Company: {2}”, cus.CustomerAccount, cus.Name, cus.DataAreaId));

           }

           Console.WriteLine(“End of result. Press any key to close this window….”);

           Console.ReadLine();

       }

Once you have all of this code together, Program.cs will generate an output in a console window appropriately.

Now comes the error part.

As I said earlier, if you have “/” (forward slash) at the end of an ActiveDirectoryResource URL, you will get the following error –

Microsoft.OData.Client.DataServiceQueryException was unhandled

HResult=-2146233079

Message=An error occurred while processing this request.

Source=Microsoft.OData.Client

InnerException:

HResult=-2146233079

Message=Unauthorized

Source=Microsoft.OData.Client

StatusCode=401

At this piece of code –

           foreach (var legaglEntity in context.LegalEntities.AsEnumerable())

Apparently I figured out that if a trailing slash exists, then the base assemblies fail to authenticate and authorize the user request. In a Microsoft article it says not to add this trailing slash at the end, in the section ‘Client sample code’. The comment lines says: 

//request token for the resource – which is the URL for your organization. NOTE: Important to not add a trailing slash at the end of the URL.

And when I run my code, I see this result:

For the sake of displaying output, I am limiting the result output to only display five legal entities and ten USMF customers.

Conclusion

This article is part of our Dynamics 365 Roadmap series that helps companies stay up to date on the latest Dynamics 365 releases. To see our previous posts, click here to view our roadmap.

In this article, I have explained how to consume Dynamics 365 for Operations data entities (LegalEntities and Customers) using enumeration and DataServiceQuery class, and I also explained how to use filters to access different sets of data. But if you have further questions on this process, contact Hitachi Solutions today.

References