Thursday, August 18, 2011

From RunBase to SysOperation : Business Operation Framework (Cont'd)

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:

SysOperationHelper::base64Encode()
SysOperationHelper::base64Decode()

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:


LastNumber
Title

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!)


Last Number

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:

SysOperationContractProcessingAttribute(classStr(YOURUIBuilderClassName))

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!

17 comments:

  1. Hi ,

    I am having a requirement , where i need to process one table ,i am using this datacontract attribute for handling Table , I am passing this table in Parm() method.

    The Problem I am facing ,is that I am not able to process the table passed in Parm method , can you please give me some insight/example.

    ReplyDelete
    Replies
    1. When you say "i am not able to process the table", what does that mean? Do you get an error message, does it crash, etc, etc.

      Delete
    2. I mean to say there is no data in table

      Delete
  2. Hi,

    is there any way to pass parameters (batch schedule parameters + recurrence parmeters + parameters needed to process business logic in batch) to the sys operation framework to create a new batch without getting prompt dialog box (Sys operation dialog box).

    If there is a way, that would be very helpful.

    Regards,
    Arvind

    ReplyDelete
  3. Hi

    Do you know if it is possible to put two groups side by side rather than on top of each other?

    Joe

    ReplyDelete
    Replies
    1. By default it will just scale and move things into a second column based on available width/height. Either way you're looking at a custom UI as opposed to the generated design.

      Delete
  4. This comment has been removed by the author.

    ReplyDelete
  5. How would I browse for a folder? By using a member with type FilenameOpen in my DataContract, I can browse for a specific file, but is there a way to browse for a folder instead?

    Thanks.

    ReplyDelete
    Replies
    1. You can try using FilePath

      Delete
    2. I've added FilenameOpen in my DataContract but there is nothing showing up to browse for a file. Is there something extra I'm supposed to do??

      Delete
  6. Joris,

    Were you ever able to get rid of the 'Parameters' title? Just curious!

    ReplyDelete
    Replies
    1. I haven't looked into it, so I guess that's a no :-)

      Delete
  7. Thanks for sharing this post. I was just wondering that we call Service operation from Controller, but how does it know that it needs to take which Data contract? Is it through passing it as an argument in operation?

    ReplyDelete
    Replies
    1. Yes, the argument to the operation should be the data contract. You can also return a data contract.

      Delete
  8. Hi Joris, i was wondering how you can modify the datacontract in the service layer and then use the values AFTER the .startOperation in the controller. This to give some feedback to the controller.

    I've tried this but i suspect the datacontrect gets passed by value (serialized). and the values dont appear in the controller.

    Would be nice to have some feedback from service to controller without having to resort to dirty methods :D

    ReplyDelete
    Replies
    1. Depends on the specifics of what you're doing of course. The service can return a value (i.e. an "output" data contract)?

      Delete