Custom Service Integration with D365 F&O
Using customs Services is the most flexible and customizable way to integrate with D365FO. Custom services are created with standard D365FO X++ code and can be used for both data-based, and operation-based integrations.
In this blog, I have covered the steps to retrieve customers from D365 FO to the third-party applications. There are three parts specified below to execute the required:
- Create Custom Service on D365 FO
- Register the application on Azure Portal and add the same in D365 FO
- Access the service in third-party application
1. Create Custom Service on D365 FO
Below are the steps to create the custom service in D365 FO.
Create Dynamics Model and Project in visual studio
Create Service class
Add class “IntegrationServiceClass” with method below:
Method returning static Value:
public str getTest()
{
return "Custom Service has been called Successfully.";
}
Method returning customer Detail:
public str getCustomer(str strDataAreaId, str strAccountNum)
{
CustTable custTable;
changecompany(strDataAreaId)
{
select * from custTable where custTable.AccountNum == strAccountNum;
}
return custTable.AccountNum + " - " + custTable.name();
}
Create Service
Right click on solution explorer and add a service named “Integration Services”. Set Properties as shown in the screen below:
Right click on Service Operations and add New Service Operations for the methods created in Service class.
Set Name and Method for the created service operations.
Create Service Group
Add the Service Group named “Integration Services Group” and add a new service named “Integration Services”. Set Auto Deploy to “Yes.”
Build the project.
If you get an error such as the one specified in the screen below, update the model and add the “Directory” package. to solve the error of “Dynamics.AX.Directory”.
Update your model and add “ContactPerson” Package to solve the error of “Dynamics.AX.ContactPerson”.
Build the project. On building the project, service will be automatically deployed.
2. Verify service on D365 FO URL
Service URL Format: https://<d365fourl>/api/services/<servicegroupname>/<servicename>/<operation_name>
In my case, below will be the service URL
3. Register the application on Azure Portal and add the same in D365 FO
Here are the steps to register the application on Azure Portal and add that application on D365 FO.
New Registration
Login on https://portal.azure.com/ using your credentials. Select Azure Active Directory >> App Registration.
Click on “New Registration”. Enter details as shown in the screen below and register the application. Select Redirect URL to Web to authorize the application using client Id and secret. In case of selecting Native, the application will be authorized with the user credentials.
Generate Secret Key
To create a secret key, select certificates & secrets. Click on New client secret.
Enter details as shown in the screen below and Add the client secret.
Copy the client secret value
Secret key value:
This value will not be visible again so do not miss to copy this.
API Permissions
To add permissions, click on API Permissions >> Add a permission
Add a permission >> Microsoft APIs >> Dynamics ERP.
Permission will be added as shown in the screen below.
Add Application in Dynamics AX FO
System Administration >> Azure Active Directory applications >> New
Enter Azure application client id in Application Id, application name and user id.
4. Access the service in third-party application
We will access the developed service in the .net application. Before developing the .net application, we will have to create the Authentication application for the security purpose.
AuthenticationUtility Application
Create a .net application for AuthenticationUtility. Add two classes “ClientConfiguration.cs” and “OAuthHelper.cs”.
You can copy the code in these two classes from Microsoft Dynamics AX Integration GitHub repository.
Below is the code of clientConfiguration, I have updated for my Integration Services.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AuthenticationUtility
{
public partial class ClientConfiguration
{
public static ClientConfiguration Default { get { return ClientConfiguration.OneBox; } }
public static ClientConfiguration OneBox = new ClientConfiguration()
{
// You only need to populate this section if you are logging on via a native app. For Service to Service scenarios in which you e.g. use a service principal you don't need that.
UriString = "https://usnconeboxax1aos.cloud.onebox.dynamics.com/",
UserName = "abc@domain.co.in",
// Insert the correct password here for the actual test.
Password = "",
// You need this only if you logon via service principal using a client secret. See: https://docs.microsoft.com/en-us/dynamics365/unified-operations/dev-itpro/data-entities/services-home-page to get more data on how to populate those fields.
// You can find that under AAD in the azure portal
ActiveDirectoryResource = "https://usnconeboxax1aos.cloud.onebox.dynamics.com", // Don't have a trailing "/". Note: Some of the sample code handles that issue.
// You can find the TenantID from the application registered on Azure Portal
ActiveDirectoryTenant = "https://login.microsoftonline.com/<TenantId>", // Some samples: https://login.windows.net/yourtenant.onmicrosoft.com, https://login.windows.net/microsoft.com
ActiveDirectoryClientAppId = "<clientid>",
// Insert here the application secret when authenticate with AAD by the application
ActiveDirectoryClientAppSecret = "<secretkey>",
// Change TLS version of HTTP request from the client here
// Ex: TLSVersion = "1.2"
// Leave it empty if want to use the default version
TLSVersion = "",
};
public string TLSVersion { get; set; }
public string UriString { 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 ActiveDirectoryClientAppSecret { get; set; }
}
}
Add “OAuthHelper.cs” class.
Before adding this class add directory for Microsoft.IdentityModel.Clients.ActiveDirectory from Tools >> NuGet Package Manager >> Package Manager Console with below command.
Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory -Version 5.2.8
Add OAuthHelper code as below and build the application.
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AuthenticationUtility
{
public class OAuthHelper
{
/// <summary>
/// The header to use for OAuth authentication.
/// </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 string GetAuthenticationHeader(bool useWebAppAuthentication = false)
{
string aadTenant = ClientConfiguration.Default.ActiveDirectoryTenant;
string aadClientAppId = ClientConfiguration.Default.ActiveDirectoryClientAppId;
string aadClientAppSecret = ClientConfiguration.Default.ActiveDirectoryClientAppSecret;
string aadResource = ClientConfiguration.Default.ActiveDirectoryResource;
AuthenticationContext authenticationContext = new AuthenticationContext(aadTenant, false);
AuthenticationResult authenticationResult;
if (useWebAppAuthentication)
{
if (string.IsNullOrEmpty(aadClientAppSecret))
{
Console.WriteLine("Please fill AAD application secret in ClientConfiguration if you choose authentication by the application.");
throw new Exception("Failed OAuth by empty application secret.");
}
try
{
// OAuth through application by application id and application secret.
var creadential = new ClientCredential(aadClientAppId, aadClientAppSecret);
authenticationResult = authenticationContext.AcquireTokenAsync(aadResource, creadential).Result;
}
catch (Exception ex)
{
Console.WriteLine(string.Format("Failed to authenticate with AAD by application with exception {0} and the stack trace {1}", ex.ToString(), ex.StackTrace));
throw new Exception("Failed to authenticate with AAD by application.");
}
}
else
{
// OAuth through username and password.
string username = ClientConfiguration.Default.UserName;
string password = ClientConfiguration.Default.Password;
if (string.IsNullOrEmpty(password))
{
Console.WriteLine("Please fill user password in ClientConfiguration if you choose authentication by the credential.");
throw new Exception("Failed OAuth by empty password.");
}
try
{
// Get token object
var userCredential = new UserPasswordCredential(username, password); ;
authenticationResult = authenticationContext.AcquireTokenAsync(aadResource, aadClientAppId, userCredential).Result;
}
catch (Exception ex)
{
Console.WriteLine(string.Format("Failed to authenticate with AAD by the credential with exception {0} and the stack trace {1}", ex.ToString(), ex.StackTrace));
throw new Exception("Failed to authenticate with AAD by the credential.");
}
}
// Create and get JWT token
return authenticationResult.CreateAuthorizationHeader();
}
}
}
Third Party Application
Create a new .net application “dev_ConsumeIntegrationServices”. Add reference to AuthenticationUtility.dll in this application.
Add Web Form with TextBox and Button to access service of AX as shown below:
Before applying the code, add references shown below:
Microsoft.VisualStudio.QualityTools.UnitTestFramework
To use Newtonsoft.Json library, install package Install-Package Newtonsoft.Json -Version 12.0.3 from Tools >> NuGet Package Manager >> Package Manager Console
Below is the code to access services of AX in the .net application:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using AuthenticationUtility;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
//using SoapUtility.UserSessionServiceReference;
using System;
using System.Text;
using System.IO;
using System.Net;
namespace dev_ConsumeIntegrationServices
{
public partial class IntegrationForm : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
#region Method - without Parameters
protected void btnGetTest_Click(object sender, EventArgs e)
{
string GetTest = ClientConfiguration.Default.UriString + "api/services/IntegrationServicesGroup/IntegrationServices/getTest";
var request = HttpWebRequest.Create(GetTest);
request.Headers[OAuthHelper.OAuthHeader] = OAuthHelper.GetAuthenticationHeader(true);
request.Method = "POST";
request.ContentLength = 0;
using (var response = (HttpWebResponse)request.GetResponse())
{
using (Stream responseStream = response.GetResponseStream())
{
using (StreamReader streamReader = new StreamReader(responseStream))
{
string responseString = streamReader.ReadToEnd();
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
Assert.IsFalse(string.IsNullOrEmpty(responseString));
//Console.WriteLine(responseString);
TextBox1.Text = responseString;
}
}
}
}
#endregion
#region Get Customer - Method with Parameters
protected void btnGetCustomer_Click(object sender, EventArgs e)
{
string getCustomer = ClientConfiguration.Default.UriString + "api/services/IntegrationServicesGroup/IntegrationServices/getCustomer";
var request = HttpWebRequest.Create(getCustomer);
request.Headers[OAuthHelper.OAuthHeader] = OAuthHelper.GetAuthenticationHeader(true);
request.Method = "POST";
// Pass parameters in JsonSerialize Object start
var requestContract = new
{
strDataAreaId = "USMF",
strAccountNum = "DE-001"
};
var requestContractString = JsonConvert.SerializeObject(requestContract);
using (var stream = request.GetRequestStream())
{
using (var writer = new StreamWriter(stream))
{
writer.Write(requestContractString);
}
}
// Pass parameters in JsonSerialize Object end
using (var response = (HttpWebResponse)request.GetResponse())
{
using (Stream responseStream = response.GetResponseStream())
{
using (StreamReader streamReader = new StreamReader(responseStream))
{
string responseString = streamReader.ReadToEnd();
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
Assert.IsFalse(string.IsNullOrEmpty(responseString));
TextBox2.Text = responseString;
//Console.WriteLine(responseString);
}
}
}
}
}
#endregion
}
Wrapping Up
What do you think? The Microsoft dynamics 365 f&o integration was quite simple, right? With a bit of an understanding and a planned approach, we could ace any technical concept easily. Implement this and meanwhile, wait for our next resourceful tutorials on dynamics 365 finance and operations.