In This blog post we are going to look at how to expose and consume data entities in the new OData v4 endpoint in Dynamics 365 for Operations.
We will mainly focus on three task
· Customize/Use a data entity provided by Microsoft.
· Register a new application in Azure Active Directory.
· Implement an OData client to consume OData services, including creating a proxy library for the Microsoft AX OData endpoint.
We are going to create simple Inventory movement journal as an example Using out of the box available entities InventoryMovementJournalHeader and InventoryMovementJournalEntry.
Customize Microsoft provided data entity
To make data entity available on Odata endpoint its isPublic property must be set to true. So for any standard entity you must change this property if it’s not already set to yes to make it available on endpoint. When you create any new entity make sure you set isPublic = true so that its available on oDATA endpoint. In my scenario I am going to use above mentioned entities.
To validate that configurations are now exposed in OData visit following URL. Here I am using ReleasedProduct entity just to verify URL is working.
<Dynamics AX URL>/data/ ReleasedProducts?cross-company=true
Create a new application in AAD
1. To create new application you have to log in to Microsoft Azure portal. Once you log in click on Active Directory and then click Applications along the top.
2. Near the bottom of the browser window, click Add, enter a Name for your application and select the radio button for Native Client Application. Then click the next arrow.
3. Enter a Redirect URL of “http://MYOData” (don’t specify quotes) and click the tick to complete the wizard.
4. On the top toolbar, click Configure. Scroll down until you see permissions to other applications. Click Add application.
5. Select Microsoft Dynamics ERP in the list and click the tick to complete the wizard. The application will be added to the list of applications.
6. In the Delegate Permissions list, select all the check boxes.
7. Click the save icon in the toolbar at the bottom of the page, and the application details will be saved.
For more information visit this link
Create ODATA Client
As Visual Studio is unable to directly consume OData v4 services via service references, you will need to use a T4 template which will generate the C# proxy library. More information on this can be found here
Use the OData v4 Client Code Generator in your Visual Studio application to build the OData entity proxy classes. This can be downloaded and installed from with Visual Studio. Once it is installed, you can add the OData client to the application. In the following screenshots, I added it to my C# Console application project:
This will create a file with an extension of tt which stands for Text Template. Update the MetadataDocumentUri string value in the tt file so it contains your OData metadata endpoint.
https://******************.dynamics.com/data/$metadata
Once you update URI save this file ,This will read the metadata and build a large class file.It may take several minutes to complete. The generated proxy classes let you instantiate the data entity objects, set properties, and call methods to interact with the OData endpoint.
Implementing Authentication helper
Create a new C# project and add class classConfiguration.cs.
using System; namespace AuthenticationUtility { public partial class ClientConfiguration { public static ClientConfiguration Default { get { return ClientConfiguration.OneBox; } } public static ClientConfiguration OneBox = new ClientConfiguration() { ODataEndpointUri = "https://********dev6358751fc5bd9ac4aos.cloudax.dynamics.com/data", ActiveDirectoryResource = "https://**********dev6358751fc5bd9ac4aos.cloudax.dynamics.com", ActiveDirectoryTenant = "https://login.windows.net/TenatId ActiveDirectoryClientAppId = "Your Client Id", key = "Your Client Secrete", }; public string ODataEndpointUri { get; set; } public string UserName { get; set; } public string Password { get; set; } public string ActiveDirectoryResource { get; set; } public String ActiveDirectoryTenant { get; set; } public String ActiveDirectoryClientAppId { get; set; } public string key { get; set; } } }
- ODataEndpointUri -The web address for your deployment’s OData endpoint, as per LCS.
- Azure Active directory client ID - The client ID is obtained in step 13 in "Create a web service application in Active Directory".
- Azure Active directory client secret - The client secret is obtained in step 13 in "Create a web service application in Active Directory".
- Azure Active directory resource - The Azure AD directory resource depicts the Finance and Operations root URL. Note: Do not end this field with a forward slash character (/).
- Azure Active directory tenant - The Azure AD directory tenant used with the Finance and Operations server: https://login.windows.net/your-AD-tenant-ID.
Create a Dynamics AX OData client
Add another class and name it as OAuthHelper. This class contains whole logic to deal with AAD Authentication.
using Microsoft.IdentityModel.Clients.ActiveDirectory; using System; using System.Net; namespace AuthenticationUtility { public class OAuthHelper { /// <returns>The authentication header for the Web API call.</returns> public static string GetAuthenticationHeader() { var request = HttpWebRequest.Create(ClientConfiguration.Default.ODataEndpointUri); AuthenticationContext authenticationContext = new AuthenticationContext(ClientConfiguration.Default.ActiveDirectoryTenant); // Get token object var ar = authenticationContext.AcquireToken(ClientConfiguration.Default.ActiveDirectoryResource, new ClientCredential(ClientConfiguration.Default.ActiveDirectoryClientAppId, ClientConfiguration.Default.key)); // Create and get JWT token return ar.CreateAuthorizationHeader(); } } }
More information about authentication can be found here. Authentication utility project is available on GitHub at this location
Now we will create console application, where in we will use entities InventoryMovementJournalHeader and InventoryMovementJournalEntry to create Movement journal.
public class ODataAXConnector { private LogMessage logMessageHandler; private AXODataContext context; public ODataAXConnector(LogMessage logMessageHandler, bool enableCrossCompany) { this.logMessageHandler = logMessageHandler; this.context = new AXODataContext(enableCrossCompany); } public void CreateJournal(string JournalId, string targetAXLegalEntity) { // Check if journal exists if(!IsJournalExists(JournalId)) { InventoryMovementJournalHeader jourHeader = new InventoryMovementJournalHeader(); DataServiceCollection<InventoryMovementJournalHeader> JournalCollection = new DataServiceCollection<InventoryMovementJournalHeader>(context); JournalCollection.Add(jourHeader); jourHeader.JournalNumber = JournalId; jourHeader.JournalNameId = "IMov"; InventoryMovementJournalEntry jourLines = new InventoryMovementJournalEntry(); DataServiceCollection<InventoryMovementJournalEntry> JournalneCollection = new DataServiceCollection<InventoryMovementJournalEntry>(context); JournalneCollection.Add(jourLines); jourLines.LineNumber = number; jourLines.JournalNumber = JournalId; jourLines.JournalNameId = "IMov"; jourLines.ItemNumber = "1000"; jourLines.InventoryQuantity = 1; jourLines.InventorySiteId = "1"; jourLines.InventoryWarehouseId = "13"; jourLines.OffsetMainAccountIdDisplayValue = "600150"; context.SaveChanges(SaveChangesOptions.PostOnlySetProperties | SaveChangesOptions.BatchWithSingleChangeset); } logMessageHandler(string.Format("Created Journal '{0}' in company '{1}'.", JournalId, targetAXLegalEntity)); } private bool IsJournalExists(string JournalId) { bool exist = false; var query = from entity in context.InventoryMovementJournalHeaders where entity.JournalNumber == JournalId select entity; if (query.Count() > 0) exist = true; else exist = false; return exist; } }
In the above code objects are created for each entity and passed to different datacollection for journal header and journal lines. I used the SaveChangesOptions.BatchWithSingleChangeset enum in the SaveChanges method this ensures that if any of the object fails validations during saving data none of the record is created ,so it’s kind of running transaction with in a loop.
Note the context object used in isJournalExist method (an instance of AXODataContext) provides collections for each data entity which is exposed in the Dynamics AX OData endpoint. You can use regular LINQ style queries on these collections, and the underlying DataServiceContext object handles building the OData JOSN request which in the above example will simply return a response with the resulting count. I.e. because Count() is invoked on the query, the resulting generated JOSN request is an aggregate query; no need to fetch all the data just to count it!
In screenshot below you can see output of the above code
Journal Header:
Journal Lines:
Hope this article helps you in right direction to create integration with Dynamics 365 for operations using data entities.
*This post is locked for comments