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");
}
Wednesday, July 30, 2014
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;
}
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/
Microsoft Dynamics AX 2012 X++ Editor Extensions
http://ax2012editorext.codeplex.com/
Brace Matching Extension
Highlight Words Extension
Outlining Extension
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();
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;
}
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;
}
Subscribe to:
Posts (Atom)