Wednesday, July 30, 2014

Send email within AX/X++ via .NET objects or using built in SMTP objects

How to send email within AX/X++ while using the admin defined settings within AX via  .NET objects or using built in SMTP objects. Personally I prefer the system.net method.


System.Net.Mail method


static void SendEmailDotNet(Args _args)
{
    System.Net.Mail.MailMessage mailMessage;
    System.Net.Mail.SmtpClient smtpClient;
    System.Net.Mail.MailAddress mailFrom;
    System.Net.Mail.MailAddress mailTo;
    System.Net.Mail.MailAddressCollection       addressCollection;
    System.Net.Mail.Attachment                  attachment;
    System.Net.Mail.AttachmentCollection        attachementCollection;
    str    smtpServer;

    mailFrom = new System.Net.Mail.MailAddress('from@address.com',"Sender name");
    mailTo  = new System.Net.Mail.MailAddress('to@address.com',"Receipt Name");
    smtpServer = SysEmaiLParameters::find(false).SMTPRelayServerName;// using the SMTP server ip setup in Email Parameters
    mailMessage = new System.Net.Mail.MailMessage(mailFrom,mailTo);
    mailMessage.set_IsBodyHtml(true);
    mailmessage.set_Subject('This is email subject');
    mailmessage.set_Body('<html><body>This is email bodyamc</body></html>');
    addressCollection = mailMessage.get_CC();
    addressCollection.Add("cc1@address.com");
    addressCollection.Add("cc1@address.com");
   
   attachementCollection = mailMessage.get_Attachments();
   attachment = new System.Net.Mail.Attachment("C:\\test.txt"); //file to attach
   attachment.set_Name("adam.txt"); //name to display file as in email
   attachementCollection.Add(attachment);
    smtpClient = new System.Net.Mail.SmtpClient(smtpServer);
    smtpClient.Send(mailmessage);
    mailMessage.Dispose();
    info("sent");
}


AX SMTP method

static void TestEmailJob(Args _args)
{
SysEmailParameters parameters = SysEmailParameters::find();
SMTPRelayServerName relayServer;
SMTPPortNumber portNumber;
SMTPUserName userName;
SMTPPassword password;
Str subject,body;
InteropPermission interopPermission;
SysMailer mailer;
relayServer = parameters.SMTPServerIPAddress;
portNumber = parameters.SMTPPortNumber;
userName = parameters.SMTPUserName;
password = SysEmailParameters::password();
subject = "Subject line for the email";
body = "<B>Body of the email</B>";
CodeAccessPermission::revertAssert();
interopPermission = new InteropPermission(InteropKind::ComInterop);
interopPermission.assert();
mailer = new SysMailer();
mailer.SMTPRelayServer(relayServer,portNumber,userName,password, parameters.NTLM);
mailer.fromAddress("from@address.com");
mailer.tos().appendAddress("to@address.com");
mailer.subject(subject);
mailer.htmlBody(body);
mailer.attachments().add("C:\\test.txt");
mailer.sendMail();
CodeAccessPermission::revertAssert();
info("sent");
}

Catching errors vs CLR errors via try...catch

When utilizing try catch's within CLR objects (.NET extensions) I noticed that if you do not specifiy the type of catch it is then they will always go to the same catch. So the following occurs

try
{
 //do something in X++
 throw error("exit out of code because of missing data");
}
catch
{
 //the throw error lands the code logic here which is correct
}


However if I want to catch a clr error and display it, but if I throw an AX error I want some logic to occur or just ignore everything and stop the issue is they both end up at the same CLR error location. The problem is when you run the throw error it also ends up being handled like a CLR error which it is not because nothing was defined for the catch. Which is shown below


try
{
  System.Exception errorMsg;
 //do something in X++ that references a .net object (CLR)
 throw error("exit out of code because of missing data");
}
catch(Exception::CLRError)
{
 //a clr error ends here
 //but the throw error also lands here thus displaying an error message to the user that is not true
       
 errorMsg = CLRInterop::getLastException();
 info(netExcepn.ToString());
}


So in order to handle CLR errors vs AX errors cleanly you would need to do the following


try
{
  System.Exception errorMsg;
 //do something in X++ that references a .net object (CLR)
 throw error("exit out of code because of missing data");
}
catch (Exception::Error)
{
 //ax thrown errors get handled here
 return false;
}
catch(Exception::CLRError)
{
 //a clr error ends here
       
 errorMsg = CLRInterop::getLastException();
 info(netExcepn.ToString());
}



You can also do the following and it works just the same


try
{
  System.Exception errorMsg;
 //do something in X++ that references a .net object (CLR)
 throw error("exit out of code because of missing data");
}
catch(Exception::CLRError)
{
 //a clr error ends here
       
 errorMsg = CLRInterop::getLastException();
 info(netExcepn.ToString());
}
catch
{
 //ax thrown errors get handled here
 return false;
}

Thursday, July 17, 2014

Editor Extensions / Brace Matching , Highlight Words, Outlining within code

I've been using this editor extensions plugin for morphx for a couple months now on one of the PC's I develop on. Well the other day I tried to switch back to a machine that did not have it and I got so accustom to these features it didn't feel like morphx without it. So I figured I would post a link and give some credit where credit is due as I'm not sure why Microsoft didn't include these simple features within MorphX


Microsoft Dynamics AX 2012 X++ Editor Extensions
http://ax2012editorext.codeplex.com/

Brace Matching Extension

ax-ext-bracematching.png

Highlight Words Extension

ax-ext-highlightword.png

Outlining Extension

ax-ext-outlining-v2.png

Monday, July 14, 2014

Multi-Table Lookups (SysMultiTableLookup)

When creating an override lookup method that needs to return data from multiple tables you will need to be sure to use the SysMultiTableLookup class instead of SysTableLookup class. If you do not then whenever you run the fieldnum() function it will try to use the field id on the initial table instead of the one you are referring to. Below is an example on how to run a lookup to get the UOM symbols and descriptions for a special unit class uom.

 Query                   query;
    QueryBuildDataSource    queryBuildDataSourceTable;
    QueryBuildDataSource    queryBuildDataSourceTableDescriptions;
    QueryBuildRange         queryBuildRange;
    //you need to define the multitable, but initalize it AFTER you have defined the query
    //SysTableLookup          sysTableLookup  = SysTableLookup::newParameters(tableNum(UnitOfMeasure), ctrl);
    SysMultiTableLookup sysTableLookup;
    query = new Query();

    queryBuildDataSourceTable = query.addDataSource(tableNum(UnitOfMeasure));
    //join the translation table so we can get a description of the UOM
    queryBuildDataSourceTableDescriptions = queryBuildDataSourceTable.addDataSource(tableNum(UnitOfMeasureTranslation));
    queryBuildDataSourceTableDescriptions.joinMode(JoinMode::InnerJoin);
    queryBuildDataSourceTableDescriptions.relations(false);
    queryBuildDataSourceTableDescriptions.addLink(fieldNum(UnitOfMeasure,RecId),fieldNum(UnitOfMeasureTranslation,UnitOfMeasure));

    //filter by the unit class being passed
    queryBuildRange = queryBuildDataSourceTable.addRange(fieldNum(UnitOfMeasure, UnitOfMeasureClass));
    queryBuildRange.value(queryValue(unitClass));
    //define multiple table lookup query
    sysTableLookup  = SysMultiTableLookup::newParameters(ctrl, query);
    //add which fields will be displayed to the user (symbol + desc.)
    sysTableLookup.addLookupfield(fieldNum(UnitOfMeasure, Symbol), true);
    sysTableLookup.addLookupfield(fieldNum(UnitOfMeasureTranslation, Description), 2);
    //do not use for multi table
    //sysTableLookup.parmQuery(query);
    sysTableLookup.performFormLookup();

Thursday, July 3, 2014

Validate single & multiple email addresses using regular expression

One of the things I've noticed is within AX they don't do much validation of email addresses so I can enter test@123 and it would recognize it as a valid email. So if you do any sort of custom notifications the code samples below will show you have to make sure you don't try to send anything to an invalid email. You can also use the following website to check any regular expression http://rubular.com/ syntax

Check single email address

public boolean isValidEmail(str emailAddress)
{
    boolean isValidInput;
    System.Text.RegularExpressions.Match myMatch;
    str validEmailPattern = @"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"; //pattern check for a valid email address
    try
    {
        //call function to see if the input matches the reg ex pattern
        myMatch = System.Text.RegularExpressions.Regex::Match(emailAddress, validEmailPattern);
        //check to see what the status of matching the pattern was
        isValidInput = myMatch.get_Success();
        //return to the call if the value follows the email pattern
        return isValidInput;
    }
    catch
    {
        //there was an error parsing this email so return false
        return false;
    }
}



How to check multiple emails:
(pass in a container of email addresses and return the invalid email addresses. If the container being returned has a conlen() of 0 then all of the emails are valid. You could also easily change this so it just returns a boolean if you like.


public static container checkEmailValidity(container emailAddresses)
{
    str currentEmail;
    container  invalidEmailAddresses;
    Counter emailCounter = 1;
    //check all of the email addresses against the email regular expression
    while (emailCounter <= conLen(emailAddresses))
    {
        //read the current email address from the container
        currentEmail = conPeek(emailAddresses, emailCounter);
        //check to see if the email is invalid and if it is then add it to the list
        if(!this.isValidEmail(currentEmail) && currentEmail !="")
        {
           invalidEmailAddresses += currentEmail;
        }
        ++emailCounter;
    }
    return invalidEmailAddresses;
}