So, yesterday I started writing about the Business Operation Framework (BOF) - aka the SysOperation framework. We walked through a full example of taking a basic RunBase class and turning into a Business Operation Framework MVC pattern. The example turned out fairly straightforward once you get the concepts down. The two things we were missing though, was the ability to add a query to the Operation, as well as manipulating the auto-generated dialog of the BOF, which is based on the Data Contract and its data member data types. This article builds further on that code, so if you haven't read it yet, please check the previous article first.
Let's start with the query. Since all inputs that are being passed down to your operation are on the data contract, we can safely assume the query will need to go onto the data contract as well. Now, the data contract has accessor methods for every member you wish to expose, and those members are base AX types (string, int, real, etc). So how will we pass a query? Well, traditionally queries are "packed" (aka serialized) into a container. Containers however are not part of the CLR and so we need a more basic type to serialized our query to. So, BOF basically serializes (encodes) the query to a string. Let's look at the easy way to do first, which is basically all automatic.
Assuming we have a query in the AOT called "CustBaseData" (this query is available in standard AX for your convenience). All we need to do add that query to our Data Contract is add a variable in the classDeclaration for the encoded query string, and create a data member on the data contract class of type string, and add a special attribute for the query (on top of the datamember attribute we have to add anyway):
The AifQueryTypeAttribute specifies the name of the argument variable that the encoded query will go into, and you specify what query in the AOT it is based on (CustBaseData in this case). This is already enough to make the query show up on your dialog, as it would with RunBase (a dialog with select button, etc).
Now of course the question is, how do we retrieve (decode) this encoded query string in our operation "CoolOperation"? Well, basically we're not encoding the query itself necessarily, we're encoding and decoding a container (=packed query).
For this, AX has two methods available:
So, to retrieve the packed query, we just need to decode the parmQuery() string of the data contract to a container, then unpack that container to a query... So, let's add some code to our previous CoolOperation method:
Here we decode the parmQuery() string to container and immediately pass that to the new Query() method to unpack that there. Further down, we create a new QueryRun object base on that query and just iterate the result set and output the customer account number. Before we can run this, we need to run the Incremental CIL Generation! Then, go ahead and try it! To limit the amount of output, I basically clicked the "Select" button and selected the first customer account as a filter.
Ok, so that worked beautifully! Now you know how to add a query to your Business Operation. So how about customizing the dialog? In a RunBase class, we can add groups to the dialog, and order dialog controls by changing the order of adding of fields to the dialog. This can also be achieved with BOF, by adding some attributes to the data contract.
So, the original dialog showed the two integers and then the title. Let's add a group for the numbers, and a group for the title. And let's sort it so the title group is comes before the numbers. All this information is added as metadata on the contract. First, you "declare" the group in the classDeclaration of the data contract:
The attribute takes a name for the group (control), a label (I used a hard coded string here, but you should use a label such as @SYS9999), and a third parameter specifying the order. Now, for each data member, we can say what group it belongs to, and give it a sorting number within the group:
The order in which you add the attributes doesn't matter. The query doesn't take a group since it's on the side with the filter fields and the select button (ok, I had to try this to see what would happen... nothing. Adding a group attribute on the query doesn't do anything. You read it here first!).
Anyway, now we have groups, but our two integer fields are still called "Integer" and "Integer". So how do we set label and helptext like we used to do on RunBase dialog fields? Well, more attributes! (Again, please use labels in real life code!)
Here's what the dialog now looks like:
Admittedly, I haven't figured out how to get rid of the "Parameters" top-level group. I'll be debugging this some time to figure out if there's an easy way to get rid of it. So anyway, this is all great. But is there *ANY* way to build a dialog from scratch, like, the old-fashioned way using dialog.addField() or something?
Well yes ladies and gentlemen, there is. You can create your own "UIBuilder" class by extending the SysOperationUIBuilder class. By default, the SysOperationServiceController class uses the SysOperationAutomaticUIBuilder class, which examines your data contract(s), the attributes used (for groups etc), and builds the dialog from that automatically. But, you can create your own builder. To make the BOF use your UI builder, you guessed it, we can attach the builder to your data contract using... an attribute:
Unfortunately, again, the post is running a little long, so I'll owe you an example of this some time. Feel free to test out the UI builder class, if you search the AOT for the SysOperationContractProcessingAttribute string in the classes, you will find some examples of this in standard AX. Happy coding!