Consume an AX7 custom web service by SOAP endpoint

Dynamics 365 for Operations (a.k.a. AX7) provides several endpoints for web service. In this blog post, I want to describe consuming a D365O custom web service in a C# application using the SOAP endpoint.

For a detailed description about service endpoints, you can read the official documentation at https://docs.microsoft.com/en-us/dynamics365/operations/dev-itpro/data-entities/services-home-page.

The main advantage of the SOAP protocol is its descriptive functionality through the WSDL language. SOAP endpoints provide detailed description about contracts and parameters to call each service method. Visual Studio has a great functionality that can read the service description and automatically generate proxy classes to access the service methods.

Let’s do an example of consuming a D365O web service in Visual Studio.

  • Run Visual Studio and go to File – New Project, go to Templates – Visual C# – Windows Classic Desktop on the left side of the window and select Console App on the right side. Enter the name of the project and click OK.
Create Visual Studio C# Console App project

Create Visual Studio C# Console App project

Let’s add a new service reference and generate proxy classes.

  • Right click on Visual Studio project and select AddService reference

In opened window, we need to enter URL to WSDL of our service. In this example, I propose to consume the D365O standard financial service so the WSDL URL would be something like…

https://{AOSANAME}.cloudax.dynamics.com/soap/services/financialdimensionservice

Generate proxy classes

Add Service Reference

  • Enter FinancialServices in the Namespace field. Click OK and Visual Studio will generate proxy classes for the financial services endpoint.

You can see the generated classes in Object browser (right click on new node FinancialServices under the Connected services node).

View in Object Browser

View in Object Browser

On this screenshot, you can see automatically generated classes (contracts and client).

Proxy classes and contracts

Proxy classes and contracts

To make web authentification you will need to add some libraries.

  • Right click on Visual Studio project and select Manage NuGet Packages.
Manage NuGet packages

Manage NuGet Packages

  • Type IdentityModel in the search box for searching packages.
Search library

Search library

  • Visual Studio will find the Microsoft library Microsoft.IdentityModel.Clients.ActiveDirectory. Select this library and click Install button on right side of this window. And click Ok to continue.

Let’s try to consume service.

Before consuming the financial service, we need to complete authentication. We will authenticate through web authentication.

Declare some configuration variables:

string aosUri = "https://{AOSNAME}.cloudax.dynamics.com/";
string activeDirectoryTenant = "https://login.windows.net/{COMPANY_DOMAIN}";
string activeDirectoryClientAppId = "11d85570-c352-44e4-b56a-eff677b411f4";
string activeDirectoryClientAppSecret = "hv8zt21Lxq87MwJCFaqP2x/1rzxU1eTQicEMXMBlST6=";
string activeDirectoryResource = "https://{AOSNAME}.cloudax.dynamics.com";

…and here’s the authentication logic:

AuthenticationContext authenticationContext = new AuthenticationContext(activeDirectoryTenant);
string aadClientAppSecret = activeDirectoryClientAppSecret;
ClientCredential creadential = new ClientCredential(activeDirectoryClientAppId, aadClientAppSecret);
AuthenticationResult authenticationResult = authenticationContext.AcquireTokenAsync(activeDirectoryResource, creadential).Result;
string oAuthHeader = authenticationResult.CreateAuthorizationHeader();
string serviceName = "financialdimensionservices";
string soapServiceUriString = GetSoapServiceUriString(serviceName, aosUri);

Now get client binding

EndpointAddress endpointAddress = new EndpointAddress(soapServiceUriString);
Binding binding = GetBinding();

We want to get a list of financial attributes and them values. Create the financial service request.

            EndpointAddress endpointAddress = new EndpointAddress(soapServiceUriString);
            Binding binding = GetBinding();
 
            DimensionServiceClient dimServiceClient = new DimensionServiceClient(binding, endpointAddress);
 
            IClientChannel dimServiceChannel = dimServiceClient.InnerChannel;
 
            using (OperationContextScope dimServiceOperContext = new OperationContextScope(dimServiceChannel))
            {
                HttpRequestMessageProperty requestMessage = new HttpRequestMessageProperty();
                requestMessage.Headers[OAuthHeader] = oAuthHeader;
                OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = requestMessage;
 
                CallContext callContext = new CallContext {Company = "USMF"};
 
                // Call getDimensionAll method of dimension service
                IList dimensionContracts = ((DimensionService) dimServiceChannel).getDimensionsAll(new getDimensionsAll(callContext)).result;
 
                foreach (DimensionContract dimensionContract in dimensionContracts)
                {
                    Console.WriteLine(string.Format("Dimension: {0}", dimensionContract.parmDimensionName));
 
                    DimensionValueServiceClient valueServiceClient = new DimensionValueServiceClient(binding, endpointAddress);
                    IClientChannel dimValServiceChannel = valueServiceClient.InnerChannel;
 
                    using (OperationContextScope dimValServiceOperContext = new OperationContextScope(dimValServiceChannel))
                    {
                        HttpRequestMessageProperty dimValRequestMessage = new HttpRequestMessageProperty();
                        dimValRequestMessage.Headers[OAuthHeader] = oAuthHeader;
                        OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = dimValRequestMessage;
 
                        CallContext dimValCallContext = new CallContext {Company = "USMF"};
 
                        // Call getDimensionValues method which get list of value by specific attribute
                        IList<DimensionValueContract> dimValues = ((DimensionValueService)dimValServiceChannel).getDimensionValues(new getDimensionValues(dimValCallContext, dimensionContract)).result;
 
                        Console.WriteLine("Values: ");
                        foreach (DimensionValueContract dimValue in dimValues)
                        {
                            Console.Write(dimValue.parmValue + ", ");
                        }
                    }
                }
            }

The CallContext class is used to the pass the company, language, user id and partitional key to the service.

Source code for the method GetBinding

        public static Binding GetBinding()
        {
            BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
 
            // Set binding timeout and other configuration settings
            binding.ReaderQuotas.MaxStringContentLength = int.MaxValue;
            binding.ReaderQuotas.MaxArrayLength = int.MaxValue;
            binding.ReaderQuotas.MaxNameTableCharCount = int.MaxValue;
 
            binding.ReceiveTimeout = TimeSpan.MaxValue;
            binding.SendTimeout = TimeSpan.MaxValue;
            binding.MaxReceivedMessageSize = int.MaxValue;
 
            HttpsTransportBindingElement httpsTransportBindingElement = binding.CreateBindingElements().OfType().FirstOrDefault();
            if (httpsTransportBindingElement != null)
            {
                httpsTransportBindingElement.MaxPendingAccepts = 10000; // Largest posible is 100000, otherwise throws
            }
 
            HttpTransportBindingElement httpTransportBindingElement = binding.CreateBindingElements().OfType().FirstOrDefault();
            if (httpTransportBindingElement != null)
            {
                httpTransportBindingElement.MaxPendingAccepts = 10000; // Largest posible is 100000, otherwise throws
            }
 
            return binding;
        }

Source code for method GetSoapServiceUriString

        public static string GetSoapServiceUriString(string serviceName, string aosUriString)
        {
            string soapServiceUriStringTemplate = "{0}/soap/services/{1}";
            string soapServiceUriString = string.Format(soapServiceUriStringTemplate, aosUriString.TrimEnd('/'), serviceName);
            return soapServiceUriString;
        }

Full source code: D365O_SOAPEndpointExample
NOTE: Once you download this source code, you must change placeholders {AOSNAME} and {COMPANY_DOMAIN} in the source code.
The example of changing placeholders:

{AOSNAME} = usnconeboxax1aos
{COMPANY_DOMAIN} = contoso.com

Also, you will need to configure Azure Active Directory applications. Go in D365 menu System Administration -> Setup -> Azure Active Directory applications.
How to do it, you can read in blog https://community.dynamics.com/ax/b/axforretail/archive/2016/09/26/service-to-service-authentication-in-ax7

Let’s build and run the project.

When you run this project you will see Dimensions and their values.

Dimensions and them values

Dimensions and their values

 

4 thoughts on “Consume an AX7 custom web service by SOAP endpoint

  1. I get an exception with the message ‘Forbidden’ on this line.

    IList dimensionContracts = ((DimensionService) dimServiceChannel).getDimensionsAll(new getDimensionsAll(callContext)).result;

    What could this mean?

    • Jaffer, that means that the authenticated user does not have the security privileges to access the service. Try setting the ‘System administrator’ role on the user and try again.

  2. Hi,
    I am trying to create sales order by consuming ax service, but I don’t find any new records created in salestable. Does updating ax tables through entities works same as getting data from ax?
    p.s. I am using SOAP endpoint

Leave a Reply

Your email address will not be published. Required fields are marked *