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++