web
You’re offline. This is a read only version of the page.
close
Skip to main content
Community site session details

Community site session details

Session Id :

Using the Microsoft OData Connected Service in your .NET solutions for D365FO

Chris Roehrich Profile Picture Chris Roehrich 289

This post is for the D365FO and .NET Developers out there that are interested in creating solutions that interact with the OData end point in D365FO. In the past, some of the samples that Microsoft provided were to use the OData v4 Client Code Generator which creates a proxy class that can be used in your VS project. The generated class allows you to perform CRUD operations with the data entities using convenient .NET objects that you can use. One drawback with using this tool is the generated class file is very large and programming with it in Visual Studio can be an unpleasant experience if it starts becoming unresponsive and crashes. There are ways to trim it down but they are extra steps. So I was interested in seeing what this other option of using the OData Connected Service would be like. I found it by viewing some .NET samples by the Microsoft D365FO FastTrack team. I thought others might be interested in it also. Let's take a look at the steps below for using this OData Connected Service.

Installing the Microsoft OData Connected Service into Visual Studio 2017

I will use Visual Studio 2017 since I am using a Tier 1 cloud hosted dev VM for D365FO. This will be a C# Console application that will be able to get a authorization token for my desired D365FO instance using a registered Azure Application and then interact with the OData entities. 

In your console application project, start by installing the OData Connect Service from the Tools - Extensions and Updates menu option. After you click Download a message will appear that the modifications will being when Visual Studio is closed. 

Search-extensions.png

After you restart Visual Studio 2017, the VSIX Installer will prompt you to install the OData Connected Service.

3264.VSIX-Installer.png

Adding a Connected Service into the project

Now we can add the Connected service and select the exact data entities from D365FO that we are interested in work with. Right click the project and select Add - Connected Service

add-service.png

add-service-2.png

Enter in the OData endpoint metadata URL (https://XXXXXdevaos.cloudax.dynamics.com/data/$metadata for example) for the D365FO instance in the Configure endpoint step and click Next

7382.service3.png

I had created a custom entity named CustomerCreditRating which allows updates to the CreditRating field in the CustTable. The Schema Types step allows you to Deselect all and select your desired entity types. So here I can select the exact data entity I want to use in my solution.

4643.add-service-4.png

On the Settings step you can enter a name for the file that will be created in the project and click Finish.

2273.add-service-5.png

The Visual Studio 2017 project will now have a Connected Services folder that contains the generated class and related files. Note the name space in the class file is Microsoft.Dynamics.DataEntities. Once you add the namespace you are now able to work with the OData objects in a manner that gives you access to strongly-typed C# and serialization\deserialization convenience. 

class-file-in-project.png

Sample C# code to authenticate and read\update an entity in D365FO

using System;
using System.Linq;
using System.Threading.Tasks;

// added manually
using Microsoft.Dynamics.DataEntities; //Resources
using Microsoft.Identity.Client; //Nuget Package: IConfidentialClientApplication, ConfidentialClientApplicationBuilder, AuthenticationResult
using Microsoft.OData.Client; //DataServiceQueryException, DataServiceClientException
using System.Web; //Manually browsed and added reference to System.Web: HttpUtility
using System.Text.Json; //JsonSerializer

namespace D365_BlogOData_ConsoleApp
{
    class Program
    {

        static string aadClientAppId = "9e9999af-de0c-999d-a1d6-b4ea7f99a8e9"; // Azure App Client ID
        static string aadClientAppSecret = "Abc7FvitABC93oLQEiK0tR1b3mXXXX5sLC1mABCieS8="; // Azure App Secret
        static string baseURL = "https://xxxxxxxxxxxdevaos.cloudax.dynamics.com"; // D365FO instance no slash at end
        static string aadTenant = "https://login.windows.net/domain.com"; //replace domain.com with your azure AD domain
        private static async Task GetAuthenticationHeader()
        {
            IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(aadClientAppId)
                    .WithClientSecret(aadClientAppSecret)
                    .WithAuthority(new Uri(aadTenant))
                    .Build();
            string[] scopes = new string[] { $"{baseURL}/.default" };
            AuthenticationResult result = await app.AcquireTokenForClient(scopes)
                .ExecuteAsync();
            return result.CreateAuthorizationHeader();
        }

        private static void ReportODataError(DataServiceQueryException ex)
        {
            //Client level Exception message
            Console.WriteLine(ex.Message);

            //The InnerException of DataServiceQueryException contains DataServiceClientException
            DataServiceClientException dataServiceClientException = ex.InnerException as DataServiceClientException;

            // You can get ODataErrorException from dataServiceClientException.InnerException
            // This object holds Exception as thrown from the service
            // ODataErrorException contains odataErrorException.Message contains a message string that conforms to dotnet
            // Exception.Message standards
            var odataErrorException = dataServiceClientException.InnerException as Microsoft.OData.ODataErrorException;
            if (odataErrorException != null)
            {
                Console.WriteLine(odataErrorException.Message);
            }

            Console.WriteLine(dataServiceClientException.Message);
        }
        static void Main(string[] args)
        {
            try
            {
                MainAsync().Wait();                
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex);                
            }

            Console.ReadLine();
        }


        private static async Task MainAsync()
        {
            Console.WriteLine("Authenticating with AAD...");
            //AuthenticationConfig config = AuthenticationConfig.ReadFromJsonFile("appsettings.json");
            string bearerToken = await GetAuthenticationHeader();

            var context = new Resources(new Uri($"{baseURL}/data/"));

            //Example to make all the OData requests cross-company, otherwise you will only reference records in the default company
            context.BuildingRequest  = (sender, eventArgs) =>
            {
                var uriBuilder = new UriBuilder(eventArgs.RequestUri);
                var paramValues = HttpUtility.ParseQueryString(uriBuilder.Query);
                if (paramValues.Get("cross-company") != null)
                {
                    //Console.WriteLine("Note: cross-company parameter already present - removing");
                    paramValues.Remove("cross-company");
                }
                paramValues.Add("cross-company", "true");
                uriBuilder.Query = paramValues.ToString();
                eventArgs.RequestUri = uriBuilder.Uri;
            };

            //Add authorization token. This should be requested from AAD programatically, expiry managed, etc.
            context.SendingRequest2  = (sender, eventArgs) =>
            {
                eventArgs.RequestMessage.SetHeader("Authorization", bearerToken);
            };


            //Read and update a customer credit rating using DE-001 customer for example
            Console.WriteLine("Reading the customer rating...");
            try
            {
                var custCreditRating = context.CustomerCreditRatings.Where(x => x.DataAreaId == "usmf" && x.CustomerAccount == "DE-001").First();
                Console.WriteLine(JsonSerializer.Serialize(custCreditRating)); //Should be the same as the response Json after creation

                Console.WriteLine("Updating Customer Credit Rating...");
                custCreditRating.CreditRating = "Good";
                context.UpdateObject(custCreditRating);
                DataServiceResponse dsr = context.SaveChanges();
                var changeResponse = (ChangeOperationResponse)dsr.First();
                Console.WriteLine("HTTP status = {0}", changeResponse.StatusCode); //Success is 204
                var entityDescriptor = (EntityDescriptor)changeResponse.Descriptor;
                var custCreditRatingUpdated = (CustomerCreditRating)entityDescriptor.Entity; 
                Console.WriteLine(JsonSerializer.Serialize(custCreditRatingUpdated));
            }
            catch (DataServiceQueryException queryException)
            {
                ReportODataError(queryException);
            }

            catch (DataServiceClientException clientException)
            {
                Console.WriteLine(clientException.InnerException);
            }

            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.InnerException);
            }
        }
    }
}

1016.console-app-result.png

The sample above sends a PATCH request to the service where the entire entity and its unchanged properties are sent to it. You can use a DataServiceCollection to send in entity changes where only the desired properties are sent in. More information on these key concepts for working with the OData Client is linked below.

Tips - I recommend for certain integration scenarios to create new Data entities instead of using the default ones from Microsoft.  For example, in this case it would have been overkill to use the CustomersV3 data entity. It is more lightweight and easier to use a stripped down version if possible since CustomersV3 is a complex entity. Lastly, if you make changes to your data entity by adding fields for example, you can run the process of adding the Connected service again. This will create a new class file that will have the updated schema.

That is all for now, I hope this post will be helpful to be able to use .NET with the the OData entities for D365FO by giving you one more option to consider.

Related Resources:

Microsoft repo for D365FO integration sample solutions is at GitHub.

Microsoft D365 FastTrack repo for implementation assets is here.

Getting started page for the Microsoft OData Connected Service.

Getting started page for the OData Client.

Comments

*This post is locked for comments