Showing posts with label Service Contract. Show all posts
Showing posts with label Service Contract. Show all posts

Wednesday, May 6, 2015

Microsoft Dynamics AX 2012 – Calling SOAP Web Services from JavaScript

Microsoft Dynamics AX 2012 – Calling SOAP Web Services from JavaScript
 
Purpose: The purpose of this document is to illustrate how to call Microsoft Dynamics AX 2012 SOAP Web Services from Windows 8 application using JavaScript.
 
Challenge: Application Integration Framework (AIF) supports Web services for Windows Communication Foundation (WCF). In AIF, each document is represented by a service that can be exposed from an integration port. To consume services over the Internet, you must host services on Internet Information Services (IIS). AIF uses standard WCF processing to receive and process SOAP requests. Please find more info on AIF and Services here: https://technet.microsoft.com/en-us/library/gg731810.aspx. For the purposes of POC or app development you may need to consume Microsoft Dynamics AX 2012 SOAP Web Services from Windows 8 application using JavaScript
 
Solution: In this walkthrough I'm going to deploy an instance of Microsoft Dynamics AX 2012 R3 in the Cloud on Windows Azure using Microsoft Dynamics AX Lifecycle Management Services (LCS), write my own Custom WCF Web Service exposing business data and call this SOAP-based Web Service from Windows 8 application using JavaScript WinJS.xhr (XML Http Request) object. Please find more info about asynchronous programming, WinJS promises and xhr (XML Http Request) object here: https://msdn.microsoft.com/en-us/library/windows/apps/br229787.aspx
 
Walkthrough
 
First of all I'll deploy an instance of Microsoft Dynamics AX 2012 R3 using LCS. Please visit http://lcs.dynamics.com to access LCS Cloud service
After successful deployment I'll be able to access my Demo VM on Azure portal
What I'll need to do for my walkthrough is to enable HTTP/HTTPS endpoints for my Demo VM so I can reach out to my Web Service over internet
 
Endpoints
 
 
Please note that enabling HTTP/HTTPS endpoints for Demo VM is suitable for the purposes of POC, but it is not secure and not suitable for production applications accessing Microsoft Dynamics AX 2012. In order to build production application securely accessing Microsoft Dynamics AX 2012 please utilize Windows Azure Service Bus. Please find detailed guidance on how to leverage Windows Azure Service Bus to establish secure cross-domain connection to Microsoft Dynamics AX 2012 here: http://www.microsoft.com/en-us/download/details.aspx?id=38413
 
Continuing in this walkthrough I'll focus on mechanics of what's going on with Web Service request behind the scenes. But before we get there I'll do some necessary plumbing  
 
My plan is to expose custom business data based on newly created table. Please see the structure of my project below
 
Code
 
 
By using my simple WCF Custom Services Class Wizard (http://ax2012aifintegration.blogspot.com/2014/11/microsoft-dynamics-ax-2012-custom-web.html) I quickly created Data contract and Service contract classes to expose necessary business data
 
Data Contract
 
[DataContractAttribute]
public class AlexTableContract
{
    AlexName    Name;
    AlexID      ID;
}
[DataMemberAttribute]
public AlexID ID(AlexID _ID = ID)
{
         ID = _ID;
         return ID;
}
[DataMemberAttribute]
public AlexName Name(AlexName _Name = Name)
{
         Name = _Name;
         return Name;
}
 
Service Contract
 
public class AlexServiceContract
{
}
[AifCollectionTypeAttribute('return', Types::Class, classStr(AlexTableContract)), SysEntryPointAttribute(true)]
public List getList()
{
    AlexTable           alexTable;
 
    AlexTableContract   alexTableContract;
 
    List                list;
 
    list = new List(Types::Class);
 
    while select alexTable
    {
        alexTableContract = new AlexTableContract();
 
        alexTableContract.ID(alexTable.ID);
        alexTableContract.Name(alexTable.Name);
 
        list.addEnd(alexTableContract);
    }
 
    return list;
}
 
Then I will wrap up my classes with Microsoft Dynamics AX Service (AlexService) and execute right click > Add-ins > Register service command for AlexService

At this point I'll populate my table with a few records using Table Browser

Table browser
 
 
Next I'll create Inbound port for newly created Web Service using HTTP adapter
 
Inbound port
 
 
Then I will add AlexService.getList operation to the Web Service
 
Select service operations
 
 
As the next step I'll write a simple Windows Console client application to better understand how my Web Service request looks like behind the scenes when I call my Web Service. As I create new Windows Console application I will need to add Web Service Reference as shown below 
 
Add Service Reference
 
 
The code for the client looks like below
 
Code
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ConsoleApplication1.ServiceReference1;
 
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
 
            AlexServiceClient client = new AlexServiceClient();
 
            CallContext context = new CallContext();
            context.Company = "usmf";
 
            client.ClientCredentials.Windows.ClientCredential.UserName = "Admin";
            client.ClientCredentials.Windows.ClientCredential.Password = "pass@word1";
 
            AlexTableContract[] list = client.getList(context);
 
            foreach (AlexTableContract item in list)
            {
                Console.WriteLine(item.ID + ": " + item.Name);
            }
 
            Console.ReadLine();
        }
    }
}
 
As the result I'll output the list of values from my table
 
Result
 
 
Now the most interesting part. Before I ran my client program to fetch the data from Microsoft Dynamics AX 2012, I launches Fiddler to listen to HTTP/HTTPS traffic and see HTTP/HTTPS requests and responses taking place. You can download Fiddler from here: http://www.telerik.com/download/fiddler
 
Fiddler
 
 
In Fiddler I can clearly see how HTTP/HTTPS request and response looked like when I execute my client application and called Microsoft Dynamics AX 2012 Web Service
 
Request
 
 
Content-Type: text/xml; charset=utf-8
VsDebuggerCausalityData: uIDPo9P2U4sA1ENFu3GK474gpEsAAAAA7d1xyqrazUe5zAsDszpG8Js/kfbqE3NPrANxMKaGnFUACQAA
Accept-Encoding: gzip, deflate
Authorization: Negotiate TlRMTVNTUAADAAAAGAAYAHQAAABWAVYBjAAAAAAAAABYAAAACgAKAFgAAAASABIAYgAAABAAEADiAQAAFYKY4gYDgCUAAAAP4smxKQqDAj833urXTwSs6kEAZABtAGkAbgBBAFgAMgAwADEAMgBSADIAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK1LD71uLVVMkxHvfFgRwwAQEAAAAAAABOi3kxVIjQAcS5+RmOrRhQAAAAAAIADgBDAE8ATgBUAE8AUwBPAAEAEgBBAFgAMgAwADEAMgBSADIAQQAEABYAYwBvAG4AdABvAHMAbwAuAGMAbwBtAAMAKgBBAFgAMgAwADEAMgBSADIAQQAuAGMAbwBuAHQAbwBzAG8ALgBjAG8AbQAFABYAYwBvAG4AdABvAHMAbwAuAGMAbwBtAAcACABOi3kxVIjQAQYABAACAAAACAAwADAAAAAAAAAAAQAAAAAgAADeJ7ljj7kKuEdzr8/a6bURMeu7wAp6EgJ5THCXwCzf3QoAEAAAAAAAAAAAAAAAAAAAAAAACQA0AEgAVABUAFAALwBhAHgAMgAwADEAMgByADIAYQAuAGMAbwBuAHQAbwBzAG8ALgBjAG8AbQAAAAAAAAAAAAAAAACX0gZZ9yPRi0zt4hbarMLO
Host: ax2012r2a.contoso.com
Content-Length: 595
Expect: 100-continue
 
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Header><h:CallContext xmlns:h="http://schemas.microsoft.com/dynamics/2010/01/datacontracts" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><h:Company>usmf</h:Company><h:Language i:nil="true"/><h:LogonAsUser i:nil="true"/><h:MessageId i:nil="true"/><h:PartitionKey i:nil="true"/><h:PropertyBag i:nil="true" xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays"/></h:CallContext></s:Header><s:Body><AlexServiceGetListRequest xmlns="http://schemas.microsoft.com/dynamics/2008/01/services"/></s:Body></s:Envelope>
 
 
Please note that POST method was used to call Web Service. Also appropriate envelope was added to the request
 
The response is returned in form of structured XML also wrapped with envelope

Response
 
 
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><AlexServiceGetListResponse xmlns="http://schemas.microsoft.com/dynamics/2008/01/services"><response xmlns:b="http://schemas.datacontract.org/2004/07/Dynamics.Ax.Application" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><b:AlexTableContract><b:ID>1</b:ID><b:Name>Alex</b:Name> </b:AlexTableContract><b:AlexTableContract><b:ID>2</b:ID><b:Name>Test><b:Name> </b:AlexTableContract></response></AlexServiceGetListResponse></s:Body></s:Envelope>
 
At this point I have enough knowledge to call my Web Service from Windows 8 application using JavaScript WinJS.xhr object. WinJS is a Windows library for JavaScript which implements Windows look and feel for your apps as well as a lot of useful objects such as xhr (XML HTTP request). Please find more info about WinJS here: https://dev.windows.com/en-us/develop/winjs
 
In order to create my Windows 8 app I'll use JavaScript Hub App template as shown below
 
Hub App (Windows)
 
 
For my app I'll implement a simple HTML page List.html to display the list of records from Microsoft Dynamics AX 2012 after calling Web Service. Here's how related List.js file looks like which implements business logic calling Web Service using WinJS.xhr object 
 
List.js
 
(function () {
    "use strict";
 
    WinJS.UI.Pages.define("/pages/list/list.html", {
        // This function is called whenever a user navigates to this page. It
        // populates the page elements with the app's data.
        ready: function (element, options) {
        }
    });
 
    function doClickSync() {
 
        var data = '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Header><h:CallContext xmlns:h="http://schemas.microsoft.com/dynamics/2010/01/datacontracts" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><h:Company>usmf</h:Company><h:Language i:nil="true"/><h:LogonAsUser i:nil="true"/><h:MessageId i:nil="true"/><h:PartitionKey i:nil="true"/><h:PropertyBag i:nil="true" xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays"/></h:CallContext></s:Header><s:Body><AlexServiceGetListRequest xmlns="http://schemas.microsoft.com/dynamics/2008/01/services"/></s:Body></s:Envelope>';
        var options = {
            url: "http://ax2012r3-demo.cloudapp.net/MicrosoftDynamicsAXAif60/AlexWebServices/xppservice.svc",
            type: "post",
            headers: {
                "Content-Type": "text/xml; charset=utf-8",
                "SOAPAction": "http://schemas.microsoft.com/dynamics/2008/01/services/AlexService/getList"
            },
            user: 'Admin',
            password: 'pass@word1',
            data: data
        };
 
        WinJS.xhr(options)
            .done(
            function (request) {
                var dataItems = [];
                var xmlResponse = request.responseXML.documentElement;
 
                var fullNodeList = xmlResponse.getElementsByTagName("b:AlexTableContract");
 
                for (var i = 0; i < fullNodeList.length; i++) {
                    var dataItem = { id: fullNodeList[i].childNodes[0].textContent, name: fullNodeList[i].childNodes[1].textContent };
                    dataItems.push(dataItem);
                }
 
                var dataList = new WinJS.Binding.List(dataItems);
                var alexView = document.getElementById('alexList').winControl;
                alexView.itemDataSource = dataList.dataSource;
            },
            function (error) {
            },
            function (progress) {
            });
    }
 
})();
 
Please note that I included envelope and passed it as data into WinJS.xhr. Also I used post method to call Web Service, explicitly defined SOAP Action, provided authentication details as a part of request (please take into account security concerns when developing your apps, for the purposes of POC and simplicity I just hard-coded authentication details into the request). Finally when I receive asynchronous response I'll need to parse it out using XML DOM model and re-assign the data source for List element in HTML
 
As the result I successfully display the list of records from Microsoft Dynamics AX 2012 in my app
 
Result
 
 
Similarly you can implement not only data retrieval scenarios but also data manipulation scenarios calling Microsoft Dynamics AX 2012 Web Services. For example, creation of Sales order or creation/posting of General journal or Inventory journal
 
Summary: In this walkthrough I illustrated how to call Microsoft Dynamics AX 2012 SOAP Web Services from Windows 8 application using JavaScript and shed some light into what's happening behind the scenes when you call Web Service, how request and response look like and what you have to do to successfully call Microsoft Dynamics AX 2012 SOAP-based Web Services.
 
Tags: Microsoft Dynamics AX 2012, Custom Web Services, Data Contract, Service Contract, X++, Windows 8 App, JavaScript, WinJS, XHR, XML HTTP Request/Response, Fiddler.
 
Note: This document is intended for information purposes only, presented as it is with no warranties from the author. This document may be updated with more content to better outline the issues and describe the solutions.
 
Author: Alex Anikiev, PhD, MCP

Monday, April 6, 2015

Microsoft Dynamics AX 2012 – Product Configurator as a Service

Microsoft Dynamics AX 2012 –
Product Configurator as a Service
 
Purpose: The purpose of this document is to illustrate how to leverage Microsoft Dynamics AX 2012 Product Configurator engine from external application.
 
Challenge: For Configure-to-Order businesses Product Configurator is an essential component of the solution. Product Configurator in Microsoft Dynamics AX 2012 is a constraint-based configurator which leverages the power of Microsoft Solver Foundation and OML (Optimization Modeling Language). Product Configurator can be used to address both sales and production configurator needs. Here's the core pieces of information you get as the result of configuration process: Sales price (cost + profit margin), Delivery date, BOM structure, Route structure. In some industries companies tend to utilize specialized configurator solutions which will have to integrate with Microsoft Dynamics AX 2012 and/or will/may be ultimately absorbed/replaced with the capabilities Microsoft Dynamics AX 2012 standard Product Configurator     
 
Solution: In this scenario I have a custom web-based configurator deployed as Cloud App in Azure which implements some specific business logic around rendering 2D/3D images of configured products. In fact I want to leverage Microsoft Dynamics AX 2012 Product Configurator capabilities as a part of Microsoft Dynamics AX 2012 which is my ERP solution of choice. Specifically I'm going to utilize a custom web-based configurator as a front-end when having Microsoft Dynamics AX 2012 Product Configurator on the back-end (nicely linking Quote/Sales Configuration CSR experience with Manufacturing processes). For these purposes I'll turn Product Configurator into a Web Services for external consumption. This may also be a part of a larger migration plans switching towards using Product Configurator to enable the entire Configure-to-Order experience. 
 
Walkthrough
 
We'll start off with a very simple configuration model called "Alex"
 
New product configuration model
 
 
In the model I'm going to use 2 variables: A and B of type integer with value range [0, 1]
 
Attribute types
 
 
Constraint-based product configuration model
 
 
Now I'll define 2 attributes for A and B
 
Attributes
 
 
Please note that not all combinations of attributes will be allowed. In fact for my model I will define expression constraint stating that "IF A == 1 THEN B == 0] and vice versa
 
Constraints
 
 
This is how my model looks like at runtime
 
Configure line
 
 
Please note that I have to select values for 2 variables A and B on the screen as a part of configuration process. In fact I'll be selecting those values sequentially and in case at some point after my next variable value selection my model is in contradiction the system will let me know about it immediately without waiting until I select all variables values 
 
Now let's take some time and review how configuration process looks like in more detail
 
Diagram

<![if !vml]><![endif]>
 
We start with conceptual design of the model (Design model defining elements of the model, their types, rules, etc.), after we're done with it we can launch configuration experience. Once you start configuration process you will be dealing with specific values of variables which may be correct or incorrect (Runtime model). As you change the values of variables the system invokes Configuration backend after each change to validate that your model is not in contradiction against the constraints. In case contradiction is detected and one or more constraints are not satisfied the system will immediately notify you so appropriate correction can be made to variable values before you proceed further in configuration process. Once certain state of the model is validated appropriate values of variables will be "fixed" which will be provide you a starting point for the next step/change. Once you specify all necessary variables values and you are ready to see the how much will configuration cost (Sales price) and when it can be delivered (Delivery date) the system will solve the model which will propagate depending values, validate used calculations and make sure that the solution found is feasible. On the last step when model is solved you will save configuration in order to generate associated with source document BOM and Route. Then hand off to Manufacturing will happen manually (Create Production order from Quote/Sales order) or automatically (MRP run)       
 
Let's see what data structures we can use to invoke Product Configurator engine programmatically
 
First we'll examine Design model by using Export function which allows to move Product Configurations from one environment to another very easily. This will also provide an insight about how Product Configuration model is structured
 
Export (Design model)
 
<Export Version="1">
  <AttributeTypes>
    <EcoResAttributeType Name="AlexInt" DataType="4" IsEnumeration="0" HasBounds="1">
      <EcoResBoundedAttributeTypeValue Lower="0" Upper="1" />
    </EcoResAttributeType>
  </AttributeTypes>
  <TableConstraintDefintions />
  <Components>
    <PCClass Name="Alex" ReuseEnabled="0">
      <EcoResCategoryTranslation FriendlyName="Alex" Description="" LanguageId="en-us" />
      <EcoResAttribute Name="A" SolverName="A" AttributeType="AlexInt" IncludeInReuse="0">
        <EcoResAttributeTranslation FriendlyName="A" Description="A" Language="en-us" />
      </EcoResAttribute>
      <EcoResAttribute Name="B" SolverName="B" AttributeType="AlexInt" IncludeInReuse="0">
        <EcoResAttributeTranslation FriendlyName="B" Description="B" Language="en-us" />
      </EcoResAttribute>
      <PCComponentConstraint Name="AB" Description="AB">
        <PCExpressionConstraint Expression="Implies[ A == 1, B == 0]" />
      </PCComponentConstraint>
    </PCClass>
  </Components>
  <PCProductConfigurationModel Name="Alex" RootComponentClass="Alex" SolverStrategy="0">
    <PCProductConfigurationModelTranslation Name="Alex" Description="Alex" Language="en-us" />
    <PCConfigurationControl>
      <PCComponentControl UIOrder="1" />
    </PCConfigurationControl>
  </PCProductConfigurationModel>
</Export>
 
When I launch configuration Runtime model will be composed which looks like the following
 
Runtime model
 
<Model name="Alex"><Component name="Alex" componentId="52565427706" instanceId="1"><Attribute name="A" displayName="A" uniqueId="11290926482" instanceId="2" type="integer"><IntegerDomain from="0" to="1" /></Attribute><Attribute name="B" displayName="B" uniqueId="11290926483" instanceId="3" type="integer"><IntegerDomain from="0" to="1" /></Attribute><Constraint constraintId="AB" uniqueId="11290924754" constraintText="Implies[ A == 1, B == 0]" /></Component></Model>
 
As I go ahead and start changing variables values I'll be doing work within my configuration session.
 
Please note that I'll select some of variables values (UserSelected), but some of variables values will be set by the system automatically based on constraints in place (isUserSelected = 0/1)
 
UserSelected
 
<Session><Component name="Alex" uniqueId=""><Attribute name="A" uniqueId="11290926482" isUserSelected="1" type="integer" value="0" /></Component></Session>
 
As I solve the model the system will provide a list of bound values corresponding to a feasible solution for then model
 
Bound
 
<Session><Component name="Alex" uniqueId=""><Attribute name="A" uniqueId="11290926482" isUserSelected="1" type="integer" value="1" /><Attribute name="B" uniqueId="11290926483" isUserSelected="0" type="integer" value="0" /></Component></Session>
 
Once background work is done on the backend the system will update user interface properly in Microsoft Dynamics AX 2012. Depending on whether you use Rich Client Product Configurator or Enterprise Portal Product Configurator the user experience may slightly differ
For example, in Rich Client invalid values will be highlighted in the drop-down for Integer with Range variable as shown below
 
Configure line
 
 
Infolog
 
 
In case contradiction is detected for the model appropriate infolog will be provided
 
Infolog
 
 
In fact if you use Enterprise Portal configuration experience the appropriate message will provided along with inline data range hint
 
Configure selected item
 
 
Configure selected item - Error
 
 
In case contradiction is detected for the model the appropriate message will be provided
 
Configure selected item - Error
 
 
For Enterprise Portal Product Configurator most of X++ interface part is implemented in PCEnterprisePortalMain class (\Classes\PCEnterprisePortalMain) and EPPCConfigurator Web Control. In Rich Client PCRuntimeConfigurator X++ form implements user interface part supported by number of classes. In Enterprise Portal all the backend heavy-lifting is done inside of Microsoft.Dynamics.Ax.Frameworks.ProductConfigurationServer.dll assembly
 
As a next step I'll consume X++ Product Configurator interface and implement X++ job to invoke Product Configurator backend in order to solve a predefined model
 
X++ job
 
static void PCSolveModelJob(Args _args)
{
    InteropPermission interopPermission;
    Microsoft.Dynamics.Ax.Frameworks.Controls.ProductConfiguration.ProductConfigurationServer serverSolver;
   
    str        modelXml = "<Model name=\"Alex\"><Component name=\"Alex\" componentId=\"52565427706\" instanceId=\"1\"><Attribute name=\"A\" displayName=\"A\" uniqueId=\"11290926482\" instanceId=\"2\" type=\"integer\"><IntegerDomain from=\"0\" to=\"1\" /></Attribute><Attribute name=\"B\" displayName=\"B\" uniqueId=\"11290926483\" instanceId=\"3\" type=\"integer\"><IntegerDomain from=\"0\" to=\"1\" /></Attribute><Constraint constraintId=\"AB\" uniqueId=\"11290924754\" constraintText=\"Implies[ A == 1, B == 0]\" /></Component></Model>";
    boolean    isFinished;
   
    str        boundValues;
    str        userSelectedValues_good = "<Session><Component name=\"Alex\" uniqueId=\"\"><Attribute name=\"A\" uniqueId=\"11290926482\" isUserSelected=\"1\" type=\"integer\" value=\"1\" /></Component></Session>";
    str        userSelectedValues_bad = "<Session><Component name=\"Alex\" uniqueId=\"\"><Attribute name=\"A\" uniqueId=\"11290926482\" isUserSelected=\"1\" type=\"integer\" value=\"1\" /><Attribute name=\"B\" uniqueId=\"11290926483\" isUserSelected=\"1\" type=\"integer\" value=\"1\" /></Component></Session>";
    str        userSelectedValues_okay = "<Session><Component name=\"Alex\" uniqueId=\"\"><Attribute name=\"A\" uniqueId=\"11290926482\" isUserSelected=\"1\" type=\"integer\" value=\"0\" /></Component></Session>";
 
    PCExecuteVariantConfiguration   executeVariantConfiguration = PCExecuteVariantConfiguration::construct();
    PCConfigurationState            configurationState = PCConfigurationState::construct();
 
    interopPermission = new InteropPermission(InteropKind::ClrInterop);
    interopPermission.assert();
 
   //bp deviation documented
    serverSolver = new Microsoft.Dynamics.Ax.Frameworks.Controls.ProductConfiguration.ProductConfigurationServer(modelXml);
    serverSolver.set_UserSelectedValues(userSelectedValues_good);
  
    isFinished = serverSolver.Solve();
    if (isFinished)
    {
        info("Feasible solution");
        boundValues = serverSolver.get_BoundValues();
        info(boundValues);
    }
    else
    {
        info("Infeasible solution");
    }
 
    CodeAccessPermission::revertAssert();
}
 
In the code above I used all the data structures described at the beginning. Please note that I also supplied 3 types of UserSelectedValues to simulate various outcomes after solving: feasible complete model, feasible incomplete model and infeasible model
 
Let's review these intermediary results
 
Example 1 – Good/Feasible/Complete: A = 1
 
Input
 
<Session><Component name="Alex" uniqueId=""><Attribute name="A" uniqueId="11290926482" isUserSelected="1" type="integer" value="1" /></Component></Session>
 
 
Result
 
<Session><Component name="Alex" uniqueId=""><Attribute name="A" uniqueId="11290926482" isUserSelected="1" type="integer" value="1" /><Attribute name="B" uniqueId="11290926483" isUserSelected="0" type="integer" value="0" /></Component></Session>
 
Example 2 – Bad/Infeasible: A = 1 && B = 1
 
Input
 
<Session><Component name="Alex" uniqueId=""><Attribute name="A" uniqueId="11290926482" isUserSelected="1" type="integer" value="1" /><Attribute name="B" uniqueId="11290926483" isUserSelected="1" type="integer" value="1" /></Component></Session>
 
 
Result
 
 
 
Example 3 – Okay/Feasible/Incomplete: A = 0
 
Input
 
<Session><Component name="Alex" uniqueId=""><Attribute name="A" uniqueId="11290926482" isUserSelected="1" type="integer" value="0" /></Component></Session>
 
 
Result
 
<Session><Component name="Alex" uniqueId=""><Attribute name="A" uniqueId="11290926482" isUserSelected="1" type="integer" value="0" /></Component></Session>
 
Please note that I can parse returned results to find out whether my solution is feasible or not based on selected variables values 
 
When I introduce my changes to variables values the system does appropriate checks for solution feasibility, thus after each step variables values I selected will be "fixed" for the next configuration step
 
Let's review how my session details look like as I move along changing variables values
 
"Fixed" values
 
Scenario
Before
After
A = 0
<Session><Component name="Alex" uniqueId="" /></Session>
<Session><Component name="Alex" uniqueId=""><Attribute name="A" uniqueId="11290926482" isUserSelected="1" type="integer" value="0" /></Component></Session>
A = 1
<Session><Component name="Alex" uniqueId="" /></Session>
<Session><Component name="Alex" uniqueId=""><Attribute name="A" uniqueId="11290926482" isUserSelected="1" type="integer" value="1" /></Component></Session>
B = 0
<Session><Component name="Alex" uniqueId="" /></Session>
<Session><Component name="Alex" uniqueId=""><Attribute name="B" uniqueId="11290926483" isUserSelected="1" type="integer" value="0" /></Component></Session>
B = 1
<Session><Component name="Alex" uniqueId="" /></Session>
<Session><Component name="Alex" uniqueId=""><Attribute name="B" uniqueId="11290926483" isUserSelected="1" type="integer" value="1" /></Component></Session>
A = 0, B = 0
<Session><Component name="Alex" uniqueId=""><Attribute name="A" uniqueId="11290926482" isUserSelected="1" type="integer" value="0" /></Component></Session>
<Session><Component name="Alex" uniqueId=""><Attribute name="A" uniqueId="11290926482" isUserSelected="1" type="integer" value="0" /><Attribute name="B" uniqueId="11290926483" isUserSelected="1" type="integer" value="0" /></Component></Session>
A = 0, B = 1
<Session><Component name="Alex" uniqueId=""><Attribute name="A" uniqueId="11290926482" isUserSelected="1" type="integer" value="0" /></Component></Session>
<Session><Component name="Alex" uniqueId=""><Attribute name="A" uniqueId="11290926482" isUserSelected="1" type="integer" value="0" /><Attribute name="B" uniqueId="11290926483" isUserSelected="1" type="integer" value="1" /></Component></Session>
A = 1, B = 0
 
 
A = 1, B = 1
<Session><Component name="Alex" uniqueId=""><Attribute name="A" uniqueId="11290926482" isUserSelected="1" type="integer" value="1" /></Component></Session>
The value entered is not valid; it is either outside the defined domain range or it would contradict model constraints
B = 0, A = 0
<Session><Component name="Alex" uniqueId=""><Attribute name="B" uniqueId="11290926483" isUserSelected="1" type="integer" value="0" /></Component></Session>
<Session><Component name="Alex" uniqueId=""><Attribute name="A" uniqueId="11290926482" isUserSelected="1" type="integer" value="0" /><Attribute name="B" uniqueId="11290926483" isUserSelected="1" type="integer" value="0" /></Component></Session>
B = 0, A = 1
<Session><Component name="Alex" uniqueId=""><Attribute name="B" uniqueId="11290926483" isUserSelected="1" type="integer" value="0" /></Component></Session>
<Session><Component name="Alex" uniqueId=""><Attribute name="A" uniqueId="11290926482" isUserSelected="1" type="integer" value="1" /><Attribute name="B" uniqueId="11290926483" isUserSelected="1" type="integer" value="0" /></Component></Session>
B = 1, A = 0
 
 
B = 1, A = 1
<Session><Component name="Alex" uniqueId=""><Attribute name="B" uniqueId="11290926483" isUserSelected="1" type="integer" value="1" /></Component></Session>
The value entered is not valid; it is either outside the defined domain range or it would contradict model constraints
 
As you can see I will not be able to proceed forward with infeasible solution (model in contradiction) in case I select variable value which doesn't satisfy at least one constraint
 
At this point I am able to solve the model, identify whether my solution is feasible or not for the selected variables values, thus consequently I'm going to turn this business logic into a Web Service for external consumption
 
But before I do that let me quickly implement my custom web-based configurator hosted in Azure
The idea of this configurator will be to implement a specific rendering for 2D/3D images required in my business and at the same time leverage Microsoft Dynamics AX 2012 Product Configurator back-end
 
I'll use ASP.NET to create a web site project
 
New Project
 
 
I'll use MVC (Model-View-Controller) template
 
New ASP.NET Project
 
 
I'll change it authentication settings to "No authentication" for the sake of simplicity
 
Change authentication
 
 
Next I'll implement a Controller
 
Controllers > Add new > Controller
 
 
Add Controller
 
 
Controller
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
 
namespace AlexConfigurator.Controllers
{
    public class ABController : Controller
    {
        // GET: AB
        public ActionResult Index()
        {
            return View();
        }
    }
}
 
Then I'll implement View
 
Views > AB > Add View
 
 
View code will be provided below (once I fully implement it)
 
And finally Model
 
Models > Add new > Class
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
 
namespace AlexConfigurator.Models
{
    public class AB
    {
        public int A { get; set; }
        public int B { get; set; }
    }
}
 
This is how my project looks like in Solution Explorer
 
Solution Explorer
 
 
Controllers
 
 
Models
 
 
Views
 
 
Now it is a time to come back and implement View. In this implementation I added a few controls to manipulate with configuration variables, image of rendered object (2D/3D) and a few buttons to kick off rendering and invoke Microsoft Dynamics AX 2012 Product Configurator
 
Index.cshtml
 
 
@{
    ViewBag.Title = "Index";
 
    var a = Request.Params["A"];
    var b = Request.Params["B"];
 
    var output = "";                     
 
    var service = new AlexConfigurator.ServiceReference1.AlexConfiguratorServiceClient();
 
    service.ClientCredentials.Windows.ClientCredential.Domain = "contoso";
    service.ClientCredentials.Windows.ClientCredential.UserName = "Admin";
    service.ClientCredentials.Windows.ClientCredential.Password = "pass@word1";
 
    var context = new AlexConfigurator.ServiceReference1.CallContext();
 
    context.Company = "usmf";
 
    var ret = service.solve(context, a.AsInt(), b.AsInt());
 
    if (ret == true)
    {
        output = "Feasible";
    }
    else
    {
        output = "Infeasible";
    }
 
    ViewBag.Title = output;
}
 
<script type="text/javascript">
    function Render() {
        var textA = document.getElementById('TextA');
        var textB = document.getElementById('TextB');
 
        var img = document.getElementById('Img');
        var textAreaOutput = document.getElementById('TextAreaOutput');
 
        if (textA.value == "" || textB.value == "" || (textA.value == "0" && textB.value == "0")) {
            img.setAttribute("src", "/Images/NANB.png");
            textAreaOutput.textContent = "NANB";
        }
        else if (textA.value == "0" && textB.value == "1") {
            img.setAttribute("src", "/Images/NAB.png");
            textAreaOutput.textContent = "NAB";
        }
        else if (textA.value == "1" && textB.value == "0") {
            img.setAttribute("src", "/Images/ANB.png");
            textAreaOutput.textContent = "ANB";
        }
        else if (textA.value == "1" && textB.value == "1") {
            img.setAttribute("src", "/Images/AB.png");
            textAreaOutput.textContent = "AB";
        }
    }
 
    function Configure() {
        var textA = document.getElementById('TextA');
        var textB = document.getElementById('TextB');
 
        var url = "Index?" + "A=" + textA.value.toString() + "&" + "B=" + textB.value.toString();
       
        return url;
    }
</script>
 
<h2>AlexConfigurator</h2>
 
A:<input id="TextA" type="text" value="@a" />
</br>
B:<input id="TextB" type="text" value="@b" />
</br></br>
<img id="Img" src="~/Images/NANB.png" alt="" />
</br></br>
<input id="ButtonRender" type="button" value="Render" onclick="Render()" />
&nbsp;
<input id="ButtonConfigure" type="button" value="Configure" onclick="document.location.href(Configure())" />
</br></br>
<textarea id="TextAreaOutput" rows="2" cols="20">@output</textarea>
 
Please note that I use URL parameters to pass A and B values to the web site. As I parse out the values of A and B I invoke Microsoft Dynamics AX 2012 Product Configurator business logic
 
Now let's review how I implemented Microsoft Dynamics AX 2012 Custom Web Service to wrap up Product Configurator business logic and make it available for external consumption
 
For this walkthrough I used instance of Microsoft Dynamics AX 2012 R3 deployed in the Cloud using LCS
 
I enabled HTTP/HTTPS endpoint on the VM in order access the Web Service 
 
Azure Portal
 
 
Then I implemented Data Contract and Service Contract for the new Web Service
 
Data Contract
 
[DataContractAttribute]
public class AlexConfiguratorContract
{
    int a;
    int b;
}
[DataMemberAttribute]
public int A(int _a = a)
{
    a = _a;
 
    return a;
}
[DataMemberAttribute]
public int B(int _b = b)
{
    b = _b;
 
    return b;
}
 
Service Contract
 
class AlexConfiguratorService
{
}
[SysEntryPointAttribute(true)]
public boolean solve(int _a, int _b)
{
    InteropPermission interopPermission;
    Microsoft.Dynamics.Ax.Frameworks.Controls.ProductConfiguration.ProductConfigurationServer serverSolver;
 
    str        modelXml = "<Model name=\"Alex\"><Component name=\"Alex\" componentId=\"52565427706\" instanceId=\"1\"><Attribute name=\"A\" displayName=\"A\" uniqueId=\"11290926482\" instanceId=\"2\" type=\"integer\"><IntegerDomain from=\"0\" to=\"1\" /></Attribute><Attribute name=\"B\" displayName=\"B\" uniqueId=\"11290926483\" instanceId=\"3\" type=\"integer\"><IntegerDomain from=\"0\" to=\"1\" /></Attribute><Constraint constraintId=\"AB\" uniqueId=\"11290924754\" constraintText=\"Implies[ A == 1, B == 0]\" /></Component></Model>";
    boolean    isFinished;
 
    str        boundValues;
 
    str        userSelectedValues = strFmt("<Session><Component name=\"Alex\" uniqueId=\"\"><Attribute name=\"A\" uniqueId=\"11290926482\" isUserSelected=\"1\" type=\"integer\" value=\"%1\" /><Attribute name=\"B\" uniqueId=\"11290926483\" isUserSelected=\"1\" type=\"integer\" value=\"%2\" /></Component></Session>", _a, _b);
 
    PCExecuteVariantConfiguration   executeVariantConfiguration = PCExecuteVariantConfiguration::construct();
    PCConfigurationState            configurationState = PCConfigurationState::construct();
 
    boolean ret = false;
 
    interopPermission = new InteropPermission(InteropKind::ClrInterop);
    interopPermission.assert();
 
    //bp deviation documented
    serverSolver = new Microsoft.Dynamics.Ax.Frameworks.Controls.ProductConfiguration.ProductConfigurationServer(modelXml);
    serverSolver.set_UserSelectedValues(userSelectedValues);
 
    info(userSelectedValues);
 
    isFinished = serverSolver.Solve();
    if (isFinished)
    {
        boundValues = serverSolver.get_BoundValues();
        ret = true;
    }
    else
    {
        ret = false;
    }
 
    CodeAccessPermission::revertAssert();
 
    return ret;
}
 
Please note that in order to avoid "CIL generation: Error: .NET Cast Type Name not found" error associated with interopPermission object you have to copy Microsoft.Dynamics.AX.Xpp.Support.dll from Server Bin folder into Client Bin folder and then regenerate Full CIL
 
Then I created Web Service pointing to Service Contract class created earlier
 
Service
 
 
And then I created appropriate Inbound port
 
Inbound ports
 
 
Select service operations
 
 
 
RoutingService Service
 
 
This WSDL you can use to add to Service Reference to your ASP.NET project in order to consume SOAP service exposed from Microsoft Dynamics AX 2012
 
Add Service Reference
 
 
Please note that in order to avoid errors when adding Service reference to the project I copied the entire project to Microsoft Dynamics AX 2012 Demo VM, added Service reference there (on the same machine where Web Service was exposed from) and then copied the entire project back to my local machine to continue working on it. The only thing which was left to do was to substitute "ax2012r2a.contoso.com" to "XYZ.cloudapp.net" in Web.config file for Web Service to be called properly from external application
 
It is important to mention that some classes being called in PCExecuteVariantConfiguration::construct() are set up to be executed on the Client. In fact because CIL Web Service code is executed on the Server you will face with runtime exception. In order to avoid runtime exception as a workaround I commented out a few lines of code related to UI processing
 
\Classes\PCExecuteVariantConfiguration\new
 
protected void new()
{
    executeSupplyLocation               = PCExecuteSupplyLocation::construct();
    sourceDocumentLineUtility           = PCSourceDocumentLineUtility::construct();
    validationNumberSequence            = PCValidationNumberSequence::construct();
    backEndConfiguration                = PCBackEndConfiguration::construct();
    xmlParseConfigurationInstance       = PCXmlParseConfigurationInstance::construct();
    configurationProductVariantFactory  = PCConfigurationProductVariantFactory::construct();
    configurationProductVariantDelete   = PCConfigurationProductVariantDelete::construct();
    releaseLegalEntities                = new List(Types::Record);
    //alex:>>
    //runtimeConfiguratorFormFactory      = PCRuntimeConfiguratorFormFactory::construct();
    //priceBreakdownUpdater               = PCRuntimePriceBreakdownUpdater::construct();
    //alex:<<
}
 

The following classes have RunOn property set to "Client": PCRuntimeConfiguratorFormFactory and PCRuntimePriceBreakdownUpdater. After introducing any changes you have to regenerate CIL (I regenerated Incremental CIL in this case)
 
Once Web Service is ready to go and my custom web-based configurator code is code I can deploy web site to Azure by clicking Project > Publish in Solution Explorer. Then I can review newly deployed web site on Azure Portal
 
Azure Portal
 
 
At this point I can launch my custom configuration experience at  http://alexconfigurator.azurewebsites.net/AB/Index
 
Because I allowed passing URL parameters for A and B variables values I can now test 2 scenarios for having feasible solution and infeasible solution. Please note that I leverage Microsoft Dynamics AX 2012 Product Configurator engine on the backend and consume results after solving the model in my custom web site  
 
Infeasible solution
 
 

 
Feasible
 
 

 
Summary: In this document I explained how invoke Microsoft Dynamics AX 2012 Product Configurator business logic from external application. This scenario is useful in the case you have your own custom configurator front-end you would like to keep in place for number of reasons (as a part of migration strategy or to support specific business workloads), but still be able to leverage Microsoft Dynamics AX 2012 Product Configurator back-end. In particular we explored how to solve the model with selected variables values. Similarly you can leverage Product Configurator X++ interface to on demand calculate Sales Price, calculate Delivery Date to be displayed in custom configurator front-end, "Fix" selected variables values in process of configuration, and still have all business data and product models in Microsoft Dynamics AX 2012. Please consider reviewing then following methods in ProductConfigurationServer class (Microsoft.Dynamics.Ax.Frameworks.Controls.ProductConfiguration.ProductConfigurationServer) for more details:  solve, fixValue and navigateToComponent.
 
Author: Alex Anikiev, PhD, MCP

Tags: Product Configurator, PC, Custom Web Services, Data Contract, Microsoft Dynamics AX 2012, Service Contract, X++