AX for Retail 2012 R2: Using Multiple Blank Operations and Triggers
If you have done any POS development for AX for Retail you are already pretty familiar with the Blank Operation, as it is one of the primary ways to extend the product (see AX for Retail: The Blank Operation Explained for an introduction to the Blank Operation).
One of the shortcomings of the Blank Operation in previous versions was having to share a single BlankOperation.dll. This is painful enough for multiple developers in the same company and almost impossible to work around in the case of multiple ISVs. AX for Retail 2012 R2 provides a great way around this by allowing you to deploy as many Blank Operations as you wish.
Multiple Blank Operations
Unlike previous versions, Services and Triggers are not loaded based on file name. Any DLL that properly implements a defined interface will get loaded if it is in the right place when the POS starts (see AX for Retail 2012 R2: Using the Extensions folder for POS Customizations). The Blank Operation service is special: the POS will load and execute any such DLL that it finds.
This means if you have the standard Blankoperations.dll in the \Services folder, and two more named BlankOperationDiscounts.dll and BlankOperationsSmartSearch.dll in the \Extensions folder, all three will be loaded at runtime. Furthermore, any time a Blank Operation button is pressed, all will get executed.
On first glance it may seem like this would cause problems. How do you make sure that the code in your Blank Operation is executed instead of code in another Blank Operation? Which order are the Blank Operations executed? The answers: “The same way as you did before” and “It doesn’t matter.”
Remember that the Blank Operation button passes information to the DLL with two parameters: Operation Number and the Blank Operation Param:
This information is passed in as operationInfo.OperationId and operationInfo.Parameter. Previously, you would use a switch statement based on OperationId to determine which branch of code would get executed. With multiple Blank Operation DLLs, the same rule applies: differentiate your logic with the same unique OperationId:
public void BlankOperation(IBlankOperationInfo operationInfo, IPosTransaction posTransaction) { MessageBox.Show("Red blankoperation was started"); if (operationInfo.OperationId == "Red") { MessageBox.Show("Operation ID was Red"); // Set this property to true when your operation is handled operationInfo.OperationHandled = true; } }
In this simple example the first MessageBox shows the incorrect way to add logic; it will get executed whenever any Blank Operation button is executed (with one exception noted below). The second MessageBox shows how the logic is dependent on the OperationId being set up on the button. As long as you deliver your DLL and instruct the person creating the till layouts to use “Red” as the Operation Number, this code will only be executed when you want it to be.
Note the use of the OperationHandled property. This is further insurance that when your code runs, no other Blank Operation will be executed. The execution of the Blank Operations is done in somewhat random order; if all developers use these two rules, all Blank Operations will play together quite nicely.
Load and build the attached MultipleBlankOps.sln to see how all of this plays out. The “Blue” Blank Operation is identical to the “Red” code shown above. Copy the resulting DLLs to the \Extensions folder of the POS and create two Blank Operation buttons: one with operation number “Red” and one with “Blue”:
In my environment it looks like the “Blue” DLL is executed first. When I press the “Blue” button I get these two messages:
When I press the “Red” button there are three messages:
This shows that you shouldn’t rely on the order of the execution. If all code had been properly placed in the if (operationInfo.OperationId == “”) section, the code would have been better isolated.
Note what happens when the OperationHandled is taken out of both DLLs:
Execution falls all the way down to the BlankOperations.dll sample that is shipped with the main product! Don’t let that happen because it is very confusing to the end user.
Multiple Triggers
Just like the Blank Operation, multiple triggers can also now be deployed. There is one minor difference: order of execution is still essentially random, but there is no way to prevent other triggers on the same operation from executing.
In the attached example I created two PreLineDiscountAmount triggers:
using System.Text; using System.Threading.Tasks; using System.ComponentModel.Composition; using Microsoft.Dynamics.Retail.Pos.Contracts.DataEntity; using Microsoft.Dynamics.Retail.Pos.Contracts.Triggers; namespace DiscountTriggersRed { [Export(typeof(IDiscountTrigger))] public class DiscountTriggersRed : IDiscountTrigger { {...} public void PreLineDiscountAmount(IPreTriggerResult preTriggerResult, IPosTransaction transaction, int LineId) { LSRetailPosis.ApplicationLog.Log("Discount Triggers Red", "Pre Line Discount Amount Red.", LSRetailPosis.LogTraceLevel.Error); } {...} } }
When the user presses the “Line Discount Amount” button both triggers will execute:
This makes sense due to the type of logic you would normally be adding to a trigger (either before or after a core operation).
However, note that either trigger (or both!) can use the ContinueOperation to prevent the core operation from executing just as before:
preTriggerResult.ContinueOperation = false;
This can come in handy for Pre triggers to add additional validation or security to operations that the product may not have out of the box.
I’m very excited about these new features that make it easier to deploy and manage your customizations. Take the sample projects for a spin. This is what your Extensions folder should look like after deploying all four DLLs: