AX2012: SysOperation part 2: SysOperationServiceController

August 23, 2011 at 12:20
filed under Dynamics AX
Tagged , , ,

Hi again.

In part 1, we created a nice batch using data contracts, service operation and the SysOperationServiceController class.

Today, we will place our menu item on the Customers form, so we can process the customer that is selected in the grid. It will look like this:

A thing that is still missing though, is a way to work with the Args that are passed when a menu item is executed. We want to be able to set the query on our data contract before showing the batch dialog. We might also want to have a default value in some fields, for example, the date should be today’s date every time we launch the dialog.
We can do all of that by extending the SysOperationServiceController class.

So let’s do that!

Create a new class that extends SysOperationServiceController

class KlForCustTesterServiceController extends SysOperationServiceController
{
}

Now let’s create a method that constructs an instance of this class based on the Args object

public static KlForCustTesterServiceController newFromArgs(Args _args)
{
    KlForCustTesterServiceController    klForCustTesterServiceController;
    ;

    // create a new instance of the controller
    klForCustTesterServiceController = new KlForCustTesterServiceController();
    // initialize from args
    // one of the things this will do is read the "parameters" property from the menu item
    klForCustTesterServiceController.initializeFromArgs(_args);

    // return a new instance of this controller
    return klForCustTesterServiceController;
}

Above is the basic code that should be in your construct method. However, this doesn’t do anything more than the SysOperationServiceController already does, so let’s add the logic that sets the date to today’s date.

public static KlForCustTesterServiceController newFromArgs(Args _args)
{
    KlForCustTesterServiceController    klForCustTesterServiceController;
    klForCustTesterDataContract     klForCustTesterDataContract
    ;

    // create a new instance of the controller
    klForCustTesterServiceController = new KlForCustTesterServiceController();
    // initialize from args
    // one of the things this will do is read the "parameters" property from the menu item
    klForCustTesterServiceController.initializeFromArgs(_args);
   
    // get datacontract
    // the string should be the same as the parameter name!
    klForCustTesterDataContract = klForCustTesterServiceController.getDataContractObject('_klForCustTesterDataContract');

    // default current date
    klForCustTesterDataContract.parmTransDate(systemDateGet());

    // return a new instance of this controller
    return klForCustTesterServiceController;
}

As you can see, the Data Contract is fetched using the getDataContractObject() method. This will also unpack any values that were packed before, just like a normal RunBase would.
Setting the date is a simple as setting the parm method on the data contract.

Now let’s add the logic that creates the correct query by adding a range based on the record in the Args variable.

public static KlForCustTesterServiceController newFromArgs(Args _args)
{
    KlForCustTesterServiceController    klForCustTesterServiceController;
    klForCustTesterDataContract         klForCustTesterDataContract;
   
    CustTable   custTable;
    Query       query;
    ;

    // create a new instance of the controller
    klForCustTesterServiceController = new KlForCustTesterServiceController();
    // initialize from args
    // one of the things this will do is read the "parameters" property from the menu item
    klForCustTesterServiceController.initializeFromArgs(_args);
   
    // get datacontract
    // the string should be the same as the parameter name!
    klForCustTesterDataContract = klForCustTesterServiceController.getDataContractObject('_klForCustTesterDataContract');

    // default current date
    klForCustTesterDataContract.parmTransDate(systemDateGet());
   
    // check if the record is of type CustTable
    if(_args && _args.dataset() == tableNum(CustTable))
    {
        // cast record
        custTable = _args.record();

        // create new query
        query = new query(queryStr(KlForCustomers));
        // add range
        query.dataSourceTable(tableNum(CustTable)).addRange(fieldNum(CustTable, AccountNum)).value(queryValue(custTable.AccountNum));

        // set query on datacontract
        klForCustTesterDataContract.setQuery(query);

    }

    // return a new instance of this controller
    return klForCustTesterServiceController;
}

There you go. Now we need to make a main method that uses this construct method, and starts the operation:

public static void main(Args _args)
{
    KlForCustTesterServiceController klForCustTesterServiceController;
    ;

    klForCustTesterServiceController = KlForCustTesterServiceController::newFromArgs(_args);
    klForCustTesterServiceController.startOperation();
}

We also need to create a new menu item. Right click you project – New – Menu Item.
Enter the following properties:

ObjectType: Class
Object: KlForCustTesterServiceController
Parameters: KlForCustTesterDataService.testCustomer

Next, add the menu item to the CustTable form:

Finally before testing, click the Generate Incremental CIL button to generate CIL.

Right click the CustTable form and click Open. This will open the Customers form. Switch to the grid view (Ctrl + Shift + G), and select a record. Then click the button we just added.

Note: a bug (or something I do wrong?) I found is that the query values that are displayed on the dialog seem to be “lagging”. The wrong values are shown, but when you click ok, the correct values are used, unless you click the select button.
This can be fixed by adding this line before returning the controller object:

KlForCustTesterServiceController.queryChanged("_klForCustTesterDataContract.parmQuery", query);

This is probably not a nice way to do this, so I’ll look into it, and let you know.

When you click Ok on the dialog, you should see that only the customer you’ve selected will be processed. You will also see that the date has been set to today’s date, just like we wanted.

Download the XPO for part 2 here.

18 comments

RSS / trackback

  1. pradeep

    Hi Klass
    I get data not initialised error, when it reaches below line

    / default current date
    klForCustTesterDataContract.parmTransDate(systemDateGet());

    i suspect
    below line of code is not initializing the object for me
    klForCustTesterDataContract = klForCustTesterServiceController.getDataContractObject(‘_klForCustTesterDataContract’);

    ia ma not sure what identifiers name does here.

  2. pradeep

    Solved!!!

  3. Maroweh

    Hi Pradeep,

    I have the same error.
    How did you correct that?

    Thx!

    Maroweh

  4. Ravindar

    Hi Pradeep , i am getting same error I get data not initialised error, can you please help how you solved this issue, its urgent
    If possible can you please email me , it will be great.
    my email id : ravi_d2029@yahoo.co.in
    Thanks in adv

  5. Klaas Deforche

    Hi Ravindar,

    The problem is probably here (in your code it is where you get the data contract object for your controller):
    klForCustTesterDataContract = klForCustTesterServiceController.getDataContractObject(‘_klForCustTesterDataContract’);

    The parameter ‘_klForCustTesterDataContract’ is the name of the data contract in the service operation.
    Go to your service class and on the service operation, check what the name is and use that name there.

    Hope this helps.

  6. Ashish

    My dialog box size after clicking on menuitembutton is very small unable to maximize or minimize

  7. plix

    Hi Klaas,
    this is my first approach with sysoperationframework
    I did exactly what you explained, but running the menuitem
    of the controller class and run in Interactive, I see a new batch
    job added to the batch jobs in waiting status

    How can I debug in X++ the Service class?
    Tks

  8. Manas

    Hi Let me know how I can select multiple records with this framework.

  9. Anudeep Kannegandla

    Hi Team,

    How can we get the contract object ‘klForCustTesterDataContract’ here. It’s clear am getting error because of this.

    klForCustTesterDataContract = klForCustTesterServiceController.getDataContractObject(‘_klForCustTesterDataContract’);

    Any pointers on this please.

    Regards,
    Anudeep

  10. HUBER

    klForCustTesterDataContract = klForCustTesterServiceController.getDataContractObject();

  11. Klaas Deforche

    Anudeep ,
    The string ‘_klForCustTesterDataContract’ is the variable name of the parameter in your service operation.

  12. Vinodh

    Microsoft.Dynamics.Ax.Xpp.ErrorException: Exception of type ‘Microsoft.Dynamics.Ax.Xpp.ErrorException’ was thrown.

    at Microsoft.Dynamics.Ax.MSIL.Interop.throwException(Int32 ExceptionValue)

    at Microsoft.Dynamics.Ax.MSIL.cqlClassIL.callReturn(Int32 rc, interpret* ip)

    at Microsoft.Dynamics.Ax.MSIL.cqlClassIL.Call(IntPtr c, String methodName, Object[] parameters, Type[] types, Object[] varargs, Type[] varargsTypes)

    at Microsoft.Dynamics.Ax.Xpp.XppObjectBase.Call(String methodName, Object[] parameters, Type[] types, Object[] varargs)

    at Dynamics.Ax.Application.QueryRun.Next()

    at Dynamics.Ax.Application.KlForCustTesterDataService.Testcustomer(KlForCustTesterDataContract _klForCustTesterDataContract) in KlForCustTesterDataService.testCustomer.xpp:line 15

    at KlForCustTesterDataService::testCustomer(Object , Object[] )

    at Microsoft.Dynamics.Ax.Xpp.ReflectionCallHelper.MakeInstanceCall(Object instance, String MethodName, Object[] parameters)

    at Dynamics.Ax.Application.SysOperationServiceController.Runoperation(Boolean _async) in SysOperationServiceController.runOperation.xpp:line 88

    at Dynamics.Ax.Application.SysOperationServiceController.Run() in SysOperationServiceController.run.xpp:line 27

    at Dynamics.Ax.Application.BatchRun.runJobStaticCode(Int64 batchId) in BatchRun.runJobStaticCode.xpp:line 54

    at Dynamics.Ax.Application.BatchRun.runJobStatic(Int64 batchId) in BatchRun.runJobStatic.xpp:line 13

    at BatchRun::runJobStatic(Object[] )

    at Microsoft.Dynamics.Ax.Xpp.ReflectionCallHelper.MakeStaticCall(Type type, String MethodName, Object[] parameters)

    at BatchIL.taskThreadEntry(Object threadArg)

  13. Vinodh

    Not sure how to fix that error above.

    The data source is not embedded within a (parent) data source.

    Got this error also.. however, there is only DS that is custTable.

    Klaas Deforche: Can you please help me with this problem?

  14. Klaas Deforche

    Hi Vinodh,
    You are running the code from the xpo attached, right? Without modifications? And it’s running in batch?
    I will check it out and let you know.
    Br
    Klaas.

  15. Trackbacks

respond