Dynamics AIF : Query Update Stacktrace

May 31st, 2010 No comments

Today, when updating some records through AIF I got the following error :

AIF Stacktrace

This is definitely not a clear error :-) since the only thing was wrong is that the Update property was set to No instead of Yes on the Query’s datasource.

Update Property on the query's datasource

Thanks to a collegue of mine (from artofcreation) to point this one out !

Dynamics AIF : Null vs Empty string

May 31st, 2010 No comments

Today I experienced that when using AIF through web services, you have to pay attention to the difference between an empty string and a null value.

We created a service for inventory locations and we had the InventLocationId filled in by the AIF. But when calling the service we received an error everytime telling us that the InventLocationId was mandatory but missing. (Although it was in the exemption list and there was code to fill it in)

Then we discovered that the calling .NET application specified the “” value on the inventLocationId instead of the NULL value. This results in a different XML file and the AIF thinks that the InventLocationId is filled in but with an empty value.

XML file when using null value :

AIF NULL Value

XML file when using empty string value :

AIF Empty String Value

Dynamics AIF : Working Times service

May 31st, 2010 18 comments

Today I created an AIF web service to expose the Working times functionality in Ax 2009. The service itself is quite simple but there was the issue with the workTimeId. The application on the other side doesn’t care about the Id of the working times and wants Dynamics to use a number sequence for the workTimeId field. This post will not deal with the creation of a number sequence reference but shows how you can fill in the WorkTimeId automatically and especially how you can link the WorkTimeLine to the WorkTimeTable via the WorkTimeId.

First things first, we create a query which contain the two tables.

AxdWorkTime Query Object

Since we are planning on updating / deleting through the service, we must not forget to set the properties on the datasources. Properties Delete and Update must be set to Yes.

Then we create the AIF service by using the Wizard available in AX 2009. Afterwards we have the following objects available :

  • AIF Service object
  • AIF Service class
  • AxWorkTimeTable entity class
  • AxWorkTimeLine entity class
  • AxdWorkTime document classWorkTimeDocument / WorkTimeDocument_WorkTimeTable / WorkTimeDocument_WorkTimeLine Classes
  • Some datacontainertype macros stuff

Created project

Now we are going to make sure the WorkTimeId is fetched from a number sequence and inserted automatically. For this we need to do 2 things :

  • Set the WorkTimeId as not mandatory (Both on the axWorkTimeTable and axWorkTimeLine entities)
  • Modify the setWorkTimeId method on the axWorkTimeTable entity to use the number sequence
protected void initMandatoryFieldsExemptionList()
{
    super();
    this.setParmMethodAsNotMandatory(methodstr(AxWorkTimeTable,parmWorkTimeId));
}
protected void setWorkTimeId()
{
    NumberSequenceReference numberSequenceReference;
    ;
 
    if (this.isMethodExecuted(funcName(), fieldNum(WorkTimeTable, WorkTimeId)))
    {
        return;
    }
 
    if (this.isSetMethodsCalledFromSave())
    {
        if (this.isFieldSetExternally(fieldnum(WorkTimeTable, WorkTimeId)))
        {
            if (!this.workTimeTable())
            {
                numberSequenceReference = NumberSeqReference::findReference(typeId2ExtendedTypeId(typeid(WorkTimeId)));
                this.checkNumber(numberSequenceReference.numberSequenceTable(),fieldNum(WorkTimeTable, WorkTimeId),this.parmWorkTimeId());
                if (numberSequenceReference.NumberSequence && numberSequenceReference.numberSequenceTable().Continuous)
                {
                    NumberSeq::newReserveNum(numberSequenceReference).reserve(this.parmWorkTimeId());
                }
            }
        }
        else
        {
            if (this.isFieldSet(fieldnum(WorkTimeTable, WorkTimeId)))
            {
                return;
            }
 
            if (!this.parmWorkTimeId())
            {
                this.parmWorkTimeId(NumberSeq::newGetNum(NumberSeqReference::findReference(typeId2ExtendedTypeId(typeid(WorkTimeId)))).num());
            }
        }
    }
}

And last but not least we need to link the AxWorkTimeLine to the AxWorkTimeTable via the WorkTimeId. The wizard already did some action to achieve this by putting the prepareForSave method on the AxdWorkTime document class.

public boolean prepareForSave(AxdStack _axdStack,  str _dataSourceName)
{
    AxWorkTimeTable   AxWorkTimeTable;
    AxWorkTimeLine    AxWorkTimeLine;
    ;
    switch (classidget(_axdStack.top()))
    {
        case classnum(AxWorkTimeTable) :
            AxWorkTimeTable = _axdStack.top();
            return true;
 
        case classnum(AxWorkTimeLine) :
            AxWorkTimeLine  = _axdStack.top();
            AxWorkTimeTable = AxWorkTimeLine.parentAxBC();
            AxWorkTimeLine.parmWorkTimeId(AxWorkTimeTable.parmWorkTimeId());
            return true;
 
        default :
            error(strfmt("@SYS88979",classId2Name(classidget(_axdStack.top()))));
            return false;
    }
    return false;
}

Since the parm method will call the setWorkTimeId method once, we still need to place some code there :
The code will call the parent entity’s parmWorkTimeId method.

protected void setWorkTimeId()
{
    if (this.isMethodExecuted(funcName(), fieldNum(WorkTimeLine, WorkTimeId)))
    {
        return;
    }
 
    if (!this.parmWorkTimeId())
    {
        this.parmWorkTimeId(this.axWorkTimeTable().parmWorkTimeId());
    }
}

The only thing still needed now is pointing out who is the parent entity. Therefore I’ve created a method that creates an axWorkTimeTable entity.

public AxWorkTimeTable axWorkTimeTable(AxWorkTimeTable _axWorkTimeTable = null)
{
    AxWorkTimeTable axWorkTimeTable;
 
    if (!prmisdefault(_axWorkTimeTable))
    {
        axWorkTimeTable = _axWorkTimeTable;
    }
    else
    {
        this.setWorkTimeId();
        axWorkTimeTable = WorkTimeTable::find(this.parmWorkTimeId()).axWorkTimeTable();
    }
 
    return axWorkTimeTable;
}
public AxWorkTimeTable axWorkTimeTable()
{
    AxWorkTimeTable axWorkTimeTable = AxWorkTimeTable::newWorkTimeTable(this);
    return AxWorkTimeTable;
}

If someone wants to add something, be my guest… comments are welcome !

Dynamics AIF : Tracing Web Service calls

May 31st, 2010 No comments

When using AIF through web services, you can be in the dark when you want to know what’s really going on in there. Additionally you can have trouble debugging the thing when you are running a Windows 2008 box. (Contacted Microsoft last week and the response was that debuggin AIF webservices does not work on a Win2k8 box but they are developing a hotfix as we speak)

There is a possibility to have some tracing of what is happening there.  I found this on MSDN and will show an example here of what the result of the tracing shows. 

First, head over to the folder where the Dynamics Ax web services are generated. There you should find a web.config file. To enable tracing you need to make sure this tag is present 

 <system.diagnostics>
    <!-- This is used for enabling tracing in retail and debug builds.
           AIF Service processing stack outputs and information
           messages used for debugging.
           Possible values for the switch are Off, Warning,
             Information, Critical, Verbose.
           Change the switchValue to Information to enable tracing.
      -->
    <sources >
      <source name="AifServiceProcessing" switchValue="Information">
        <listeners >
          <add name="TextWriterListener"
               type="System.Diagnostics.TextWriterTraceListener"
               initializeData="AifServiceTrace.webinfo" >
          </add>
          <remove name="Default" />
        </listeners>
      </source>
    </sources>
  </system.diagnostics>  

After invoking the service, there should be a file named AifServiceTrace.Webinfo present with the tracing information : 

The following information can be viewed :

  • UserId’s
  • Message ID
  • Source and destination endpoint
  • Key Data XML
  • Return Data from Ax

 

AIF WCF Tracing

Dynamics AIF : Invalid child element in namespace

April 27th, 2010 No comments

A couple of days ago, I encountered this problem while calling the InventInventLocation AIF web service’s create operation.

System.ServiceModel.FaultException : Invalid document schema. The following error is reported : The element ‘InventLocation’ in namespace ‘http://schemas.microsoft.com/dynamics/2008/01/documents/InventLocation’ has invalid child element ‘InventLocationIdQuarantine’ in namespace ‘http://schemas.microsoft.com/dynamics/2008/01/documents/InventLocation’. List of possible elements expected: ‘InventLocationId’ in namespace ‘http://schemas.microsoft.com/dynamics/2008/01/documents/InventLocation’. 

The error itself may actually be confusing because in this case, the InventLocationIdQuarantine field is not actually the cause of the problem. In most cases this error will appear when the order of fields is different from the one expected by the document schema. So either the field ordering is different of there are some fields missing. In this case, I was using AIF webservices (WCF services) and you can view the actual XML that was communicated to the AIF by going to the document history.

Basic – Periodic – Application Integration Framework -  Document History 

AIF Document History

 There you can view the XML by clicking the XML button.   

XML InventLocation Create Operation

Here we can see that the inventlocation field is not present in the XML.
Do note that in this Ax application we have create custom code to initiate the InventLocationId field from a numbersequence when the record is created.   

To solve the error here, we need to tell the AIF engine that the InventLocationId field is not mandatory in our XML document and make sure the value gets defaulted.
This will be done here by adding defaulting logic in the Entity class but this could also be done by putting the defaulting “behavior” on the table using a strategy pattern. 

Setting the field as not mandatory :   

protected void initMandatoryFieldsExemptionList()
{
    super();
    this.setParmMethodAsNotMandatory(methodstr(AxInventLocation,parmInventLocationId));
}

Adding the defaulting logic

protected void setInventLocationId()
{
    NumberSequenceReference numberSequenceReference;
    ;
 
    if (this.isMethodExecuted(funcname(), fieldnum(InventLocation, InventLocationId)))
    {
        return;
    }
 
    if (this.isSetMethodsCalledFromSave())
    {
        if (this.isFieldSetExternally(fieldnum(InventLocation, InventLocationId)))
        {
            if (!this.inventLocation())
            {
                numberSequenceReference = InventParameters::numRefInventLocationId();
                this.checkNumber(numberSequenceReference.numberSequenceTable(),fieldnum(InventLocation, InventLocationId),this.parmInventLocationId());
                if (numberSequenceReference.NumberSequence &amp;&amp; numberSequenceReference.numberSequenceTable().Continuous)
                {
                    NumberSeq::newReserveNum(numberSequenceReference).reserve(this.parmInventLocationId());
                }
            }
        }
        else
        {
            if (this.isFieldSet(fieldnum(InventLocation, InventLocationId)))
            {
                return;
            }
 
            if (!this.parmInventLocationId())
            {
                this.parmInventLocationId(NumberSeq::newGetNum(InventParameters::numRefInventLocationId()).num());
            }
        }
    }
}

Now we do not need to specify the inventLocationId and the field will be defaulted to a new number from the number sequence reference.

Dynamics Ax Alerting multiple users (By email)

April 21st, 2010 3 comments

First things first… the original post that I found about this topic can be found here : http://dynamic-ax.co.uk/DynamicsAXAlertsToMultipleUsers.aspx

But since the link to the xpo was broken , I created this post to put this online and make some minor changes. (Don’t mind some best practices stuff like labels, security keys, … because you’ll probably create your own to use )

First we create a table and form to contain the recipient list.

Event Rule Recipient List

 

Then an adjustment is needed in the EventCreateRule class, method Run. Here the event rule will be updated so we need to maintain the link between the EventRuleId and the recipients table.

public void run()
{
    #OCCRetryCount
    EventRule           eventRuleDB;
 
    // KeSae : 21/04/2010 : Alerting multiple mail recipients
    EventRuleId        oldRuleId;
    ;
 
    try
    {
        ttsbegin;
 
        select firstonly eventRuleTmp;
 
        eventRuleDB.data(eventRuleTmp);
        eventRuleDB.setDeleteRefRecId(callerRecord);
 
        // KeSae : 21/04/2010 : Alerting multiple mail recipients
        oldRuleId = eventRuleDB.RuleId;
 
        eventRuleDB.RuleId = this.parmCreatedRuleId();
        eventRuleDB.insert();
        eventRuleDB.filterQuery(this.queryrun().query());
        eventRuleDB.alertField([tmpEventAlertField]);
        eventRuleDB.typeValue(packedEventType);
        eventRuleDB.contextInfo(this.packContextInfo(eventRuleDB));
 
        this.parmCreatedRuleId(eventRuleDB.RuleId);
 
        // KeSae : 21/04/2010 : Alerting multiple mail recipients
        // Since the RuleId changes during this process, update the link in the recipient table
        NVMPEventRuleRecipients::updateRuleIdLink(oldRuleId, eventRuleDB.RuleId);
 
        ttscommit;
 
        this.checkForeignKey();
    }
    catch (Exception::Deadlock)
    {
        retry;
    }
    catch (Exception::UpdateConflict)
    {
        if (appl.ttsLevel() == 0)
        {
            if (xSession::currentRetryCount() &gt;= #RetryNum)
            {
                throw Exception::UpdateConflictNotRecovered;
            }
            else
            {
                retry;
            }
        }
        else
        {
            throw Exception::UpdateConflict;
        }
    }
 
}

 

The following change is adding the recipients when mailing. Class EventActionEmail, method Execute :

mappings = this.createEmailParameterMap(alertInbox,eventType,eventRule);
 
        xmlParameters = EventActionEmail::createEmailParameterXml(mappings);
 
        if (eventParameters.AlertTemplateId)
        {
            SysEmailTable::sendMail(eventParameters.AlertTemplateId
                                ,   userInfo.Language
                                ,   sysUserInfo.Email
                                ,   mappings
                                ,   ''
                                ,   xmlParameters
                                ,   true
                                ,   eventRule.UserId
                                ,   true);
 
            // KeSae : 21/04/2010 : Alerting multiple mail recipients
            NVMPEventRuleRecipients::sendMail(eventRule.RuleId
                                            , eventParameters.AlertTemplateId
                                            , mappings
                                            , xmlParameters);
        }
        else
            throw error("@SYS94918");

 

When the email for this alert is sent, every user in the list will receive a link to the alert by email.

Download the xpo here

Debug Alerts in Dynamics Ax 2009

April 20th, 2010 No comments

Today I was creating some alert rules and needed to make some modifications to the alerting functionality. Something didn’t go quite as expected, so I wanted to debug the execution of an alert action.

Here is how you can do it :

  1. Make sure the debug mode is set to ‘when breakpoint’ for the user linked to the alert
  2. Put a breakpoint in the EventJobCud class, method Run
  3. Change the EventJobCus class, method Run so that it does not use the runAs command :

( Revert these changes when the debugging is done, but for now you need to make this change otherwise you will not be able to step into the EventAction classes for debugging)

void run()
{
    EventCUD             event;
    container             params;
    RunAsPermission    runAsPermission;
    ;
 
    this.expandCudRecords();
 
    while select event
          group by UserId
          where (event.Status == BatchStatus::Waiting) &amp;&amp;
                (event.CompanyId == curext())
    {
              params = connull();
              runAsPermission = new RunAsPermission(event.UserId);
              runAsPermission.assert();
 
              //BP Deviation Documented
              //runas(event.UserId, classnum(EventJobCUD), staticmethodstr(EventJobCUD, runCudEventsForUser),params,
              //      curext(), EventJobCUD::getLanguageId(event.UserId));
 
              EventJobCUD::runCudEventsForUser(params);
 
              CodeAccessPermission::revertAssert();
    }
 
    this.cleanupExecuting();
}

X++ run a process with user credentials

April 6th, 2010 6 comments

When you want to run for example a command prompt from within the X++ language, you can use the following code :
Let’s assume that the following parameters were passed to the method :

  • FileName : “C:\windows\system32\cmd.exe”
  • Arguments : “”
  • RunAsAdmin : true
  • UseShellExecute : true
  • WaitForExit : false

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
static void runWindowsProcess(  FileName    _fileName
                            ,   str         _arguments
                            ,   boolean     _runAsAdmin         = false
                            ,   boolean     _useShellExecute    = false
                            ,   boolean     _waitForExit        = false)
{
    System.Diagnostics.ProcessStartInfo processStartInfo;
    System.Diagnostics.Process          process;
    System.OperatingSystem              operatingSystem;
    System.Version                      operatingSystemVersion;
    System.Exception                    ex;
    int                                 major;
    Boolean                             start;
    ;
 
    try
    {
        // Assert CLRInterop permission
        new InteropPermission(InteropKind::ClrInterop).assert();
 
        // BP deviation documented
        processStartInfo    = new System.Diagnostics.ProcessStartInfo(_fileName, _arguments);
 
        // BP deviation documented
        process             = new System.Diagnostics.Process();
 
        // Get an instance of the System.OperatingSystem class
        operatingSystem         = System.Environment::get_OSVersion();
 
        // Get the OS version number
        operatingSystemVersion  = operatingSystem.get_Version();
 
        // If admin rights asked
        if(_runAsAdmin)
        {
            major = operatingSystemVersion.get_Major();
 
            // Get the major part of the version number (Starting from Windows Vista)
            if(major >= 6)
            {
                // Start using the runAs property which will prompt for administrator credentials
                processStartInfo.set_Verb('runas');
            }
        }
 
        // Use the system shell to execute the process
        processStartInfo.set_UseShellExecute(_useShellExecute);
 
        // Attach the starting information to the process
        process.set_StartInfo(processStartInfo);
 
        // Start the process and wait for it to complete
        start = process.Start();
 
        // Wait for the process to finish
        if(_waitForExit)
        {
            process.WaitForExit();
        }
 
        // Revert permission
        CodeAccessPermission::revertAssert();
    }
    catch(Exception::CLRError)
    {
        // BP Deviation documented
        ex = ClrInterop::getLastException();
 
        while (ex != null)
        {
            error(ex.ToString());
 
            ex = ex.get_InnerException();
        }
 
        throw error(strFmt("@HEL270", _fileName));
    }
}

Dynamics Ax AIF Web Service reference credential problem

March 26th, 2010 2 comments
 

At a customer’s site, I was creating some extra AIF wcf services so that some extra tables would be externally available. For creating the WCF service, I was using the service wizard in Ax 2009 which is described here : http://msdn.microsoft.com/en-us/library/aa609947.aspx

The newly created service was deployed to the Internet Information Server but when the .Net application tried to reference the webservice, the following happened : The service was prompting for credentials and did not work properly.

WCF Service Reference Credential Prompt

After a certain amount of searching time and nearly giving up,  I was browsing the configuration file of the web services directory and discovered that the BindingMethod of the deployed services was configured to use basicHttpBinding.

Endpoint binding method basicHttpBinding

This was the actual problem because the wsdl looked like this : (negotiate authentication tags were added, …)

 

Wrong WSDL with negotiateAuthentication tag

Changing the binding method to wsHttpBinding creates the service without the negotiateAuthentication and takes care of our annoying credential problem.

Endpoint binding method wsHttpBinding

Dynamics AX 6.0 – SQL AOD

October 10th, 2009 No comments

Owkay, have a seat and hold on to whatever you can hold on to…

As of the beginning of AX (Axapta) we have always been used to working with AOD files. But it seems that the days of the AOD fileformat are numbered !

There are a couple of reasons why the use of AOD files has become slightly obsolete :

  • The MorphX environment has been decreasing in performance because of the increasing number of objects in the AOT
  • Searching for text in the AOT nodes takes a huge amount of time

So if AOD files don’t cut it anymore, what is there to be used then? Well Dynamics Ax 6.0 will be the first version ever to have the AOT metadata stored in the SQL Server backend !!
For the users using MorphX there is no difference except for the fact that it will be much more responsive, faster, … (fe searching for a text in all the methods of all forms completes in 2 seconds ! )

The next question will probably be : How are we going to transport between different Ax environments…? Well the answer is the new fileformat : *.axModel
This format is also a binary fileformat as the AOD file but they are much smaller then AOD files (Half the size) and there will be a new tool to export/import them into a SQL Database.

The client will be configurable to point to a certain model file to use in the client session. So it will be possible to fire up an Ax client and choose a certain model file to work in. Then you can create your solution and when you’re done all the changes will be available in that .axModel file and will be easily transported to other environments.

This will be a benefit in several situations, but if you want to read the full explanation, you can find a lot of info here : http://blogs.msdn.com/mfp/