Archive

Archive for April, 2010

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 && 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() >= #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) &&
                (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));
    }
}