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;
}
Friday, June 20, 2014
Save Sales Confirmation Report To PDF w/o any alerts via X++/Code
Update: the controller seems to operate better when using ssrsController.runReport() vs ssrsController.startOperation(). It seems as though if we use runReport then the code afterwards will wait to execute while startOperation doesn't seem to care if the file has been created to move on.
After researching how to run an SSRS report and save it to a PDF via code I found that a lot of them were incomplete and did not always work or required some odd parms or would display the report to the user even though you told it to create a file. Once I was able to get that working default AX will always alert the user of where the file was created. This doesn't make much since if you are executing this through x++ as you will usually need to do something with the file.
The following example will show you how to run a report for the latest sales confirmation and save it to a file without alerting the user.
static void SavePDFJob(Args _args)
{
SrsReportRunController ssrsController = new SrsReportRunController();
SalesConfirmContract salesConfirmContract = new SalesConfirmContract();
SRSPrintDestinationSettings printerSettings;
custConfirmJour custInvoiceJour;
//select the latest record based on create date
select firstOnly custInvoiceJour
order by custInvoiceJour.createdDateTime DESC
where custInvoiceJour.salesid == '1000768';
//tell the controller the report to run (filename, design name)
ssrsController.parmReportName(ssrsReportStr(SalesConfirm, Report));
//define how we want to execute the report (right now or batch style)
ssrsController.parmExecutionMode(SysOperationExecutionMode::Synchronous);
//hide the report dialog
ssrsController.parmShowDialog(false);
//we need to populate the required parms for the current sales order controller
salesConfirmContract.parmRecordId(custInvoiceJour.RecId);
//link the contract to the controller so we know how to run the dp
ssrsController.parmReportContract().parmRdpContract(salesConfirmContract);
//link the printer settings to the controller
printerSettings = ssrsController.parmReportContract().parmPrintSettings();
//print to pdf and always overwrite if the file exists
printerSettings.printMediumType(SRSPrintMediumType::File);
printerSettings.fileFormat(SRSReportFileFormat::PDF);
printerSettings.overwriteFile(true);
printerSettings.fileName(@"\\network\location\test.pdf");
//run & save the report
//ssrsController.startOperation();
ssrsController.runReport() //refer to update at top of page
}
Optional:
The above job will create the file however you will notice you will get an info() message that says "The report has been successfully saved as: \\network\location\test.pdf"
In order to not show this to the user you will need to modify \Classes\SrsReportRunPrinter\toFile()
toward the bottom within the for loop you will see
filename = files.value(i);
info(strFmt("@SYS134644", filename));
You will need to comment out the info so the for loop will look like the following
for (i = 1 ; i <= files.lastIndex() ; i++)
{
// Display success in the infolog for the file(s) created
filename = files.value(i);
//commenting this out so we can call this function thru code without showing the user a msg
//info(strFmt("@SYS134644", filename));
// if print mgmt, then we need to copy over the file to the original file.
// This will ensure a file specified in print mgmt settings is always there on disk. This was the shipped behavior and might keep external systems happy.
if(printMgmtExecutionInfo)
{
// for reports saved in image format, each page is a image and the files are numbered. E.g. foo.gif, foo1.gif, foo2.gif
// so we will preserve that during copying to the original location.
fileNameToCopy = (i == 1) ? printMgmtExecutionInfo.parmOriginalDestinationFileName() : printMgmtExecutionInfo.parmOriginalDestinationFileName() + int2str(i-1);
System.IO.File::Copy(filename, fileNameToCopy, true);
info(strFmt("@SYS134644", filenameToCopy));
}
}
After researching how to run an SSRS report and save it to a PDF via code I found that a lot of them were incomplete and did not always work or required some odd parms or would display the report to the user even though you told it to create a file. Once I was able to get that working default AX will always alert the user of where the file was created. This doesn't make much since if you are executing this through x++ as you will usually need to do something with the file.
The following example will show you how to run a report for the latest sales confirmation and save it to a file without alerting the user.
static void SavePDFJob(Args _args)
{
SrsReportRunController ssrsController = new SrsReportRunController();
SalesConfirmContract salesConfirmContract = new SalesConfirmContract();
SRSPrintDestinationSettings printerSettings;
custConfirmJour custInvoiceJour;
//select the latest record based on create date
select firstOnly custInvoiceJour
order by custInvoiceJour.createdDateTime DESC
where custInvoiceJour.salesid == '1000768';
//tell the controller the report to run (filename, design name)
ssrsController.parmReportName(ssrsReportStr(SalesConfirm, Report));
//define how we want to execute the report (right now or batch style)
ssrsController.parmExecutionMode(SysOperationExecutionMode::Synchronous);
//hide the report dialog
ssrsController.parmShowDialog(false);
//we need to populate the required parms for the current sales order controller
salesConfirmContract.parmRecordId(custInvoiceJour.RecId);
//link the contract to the controller so we know how to run the dp
ssrsController.parmReportContract().parmRdpContract(salesConfirmContract);
//link the printer settings to the controller
printerSettings = ssrsController.parmReportContract().parmPrintSettings();
//print to pdf and always overwrite if the file exists
printerSettings.printMediumType(SRSPrintMediumType::File);
printerSettings.fileFormat(SRSReportFileFormat::PDF);
printerSettings.overwriteFile(true);
printerSettings.fileName(@"\\network\location\test.pdf");
//run & save the report
//ssrsController.startOperation();
ssrsController.runReport() //refer to update at top of page
}
Optional:
The above job will create the file however you will notice you will get an info() message that says "The report has been successfully saved as: \\network\location\test.pdf"
In order to not show this to the user you will need to modify \Classes\SrsReportRunPrinter\toFile()
toward the bottom within the for loop you will see
filename = files.value(i);
info(strFmt("@SYS134644", filename));
You will need to comment out the info so the for loop will look like the following
for (i = 1 ; i <= files.lastIndex() ; i++)
{
// Display success in the infolog for the file(s) created
filename = files.value(i);
//commenting this out so we can call this function thru code without showing the user a msg
//info(strFmt("@SYS134644", filename));
// if print mgmt, then we need to copy over the file to the original file.
// This will ensure a file specified in print mgmt settings is always there on disk. This was the shipped behavior and might keep external systems happy.
if(printMgmtExecutionInfo)
{
// for reports saved in image format, each page is a image and the files are numbered. E.g. foo.gif, foo1.gif, foo2.gif
// so we will preserve that during copying to the original location.
fileNameToCopy = (i == 1) ? printMgmtExecutionInfo.parmOriginalDestinationFileName() : printMgmtExecutionInfo.parmOriginalDestinationFileName() + int2str(i-1);
System.IO.File::Copy(filename, fileNameToCopy, true);
info(strFmt("@SYS134644", filenameToCopy));
}
}
Wednesday, June 18, 2014
Accessing a customers contact list through x++
So I working on something where I need to access a customer contacts list through x++ however I found out that the linkage between CustTable and LogisticsElectronicAddress is far and few between so I figured I would post how I was able to regenerate the contact list that is displayed on the CustTable form
The key is to go from CustTable -> DirPartyTable -> DirPartyLocation. Once you have this you need a combination of DirPartyTable and DirPartyLocation to go to LogisticsElectronicAddress
static void findEmailAckTest(Args _args)
{
CustTable custTable;
DirPartyTable dirParty;
LogisticsElectronicAddress electronicAddress;
DirPartyLocation dirPartyLoc;
//find the customer
custTable = CustTable::find("21356");
//find the party for the customer
dirParty = DirPartyTable::findRec(custTable.Party);
//find all of the contacts for the current customer
while SELECT * FROM electronicAddress
EXISTS JOIN * FROM dirPartyLoc
WHERE electronicAddress.Location == dirPartyLoc.Location && dirParty.RecId==dirPartyLoc.Party
{
info(electronicAddress.Locator);
}
}
The key is to go from CustTable -> DirPartyTable -> DirPartyLocation. Once you have this you need a combination of DirPartyTable and DirPartyLocation to go to LogisticsElectronicAddress
static void findEmailAckTest(Args _args)
{
CustTable custTable;
DirPartyTable dirParty;
LogisticsElectronicAddress electronicAddress;
DirPartyLocation dirPartyLoc;
//find the customer
custTable = CustTable::find("21356");
//find the party for the customer
dirParty = DirPartyTable::findRec(custTable.Party);
//find all of the contacts for the current customer
while SELECT * FROM electronicAddress
EXISTS JOIN * FROM dirPartyLoc
WHERE electronicAddress.Location == dirPartyLoc.Location && dirParty.RecId==dirPartyLoc.Party
{
info(electronicAddress.Locator);
}
}
Wednesday, May 28, 2014
DMF – Stack Trace error @ \Tables\DMFEntity\generateXML_WPF
It appears that whenever you go to Company/Data import
export framework/Setup/Target Entities and then click on an entity then “Modify
target mapping” you may get a stack trace error that is linked to \Tables\DMFEntity\generateXML_WPF
and it will not allow you to open up this window for the specific entity but it
will for others. To fix the error you should first try the following:
1.
Open \Tables\DMFEntity\generateXML_WPF and the
issue is between line 201-208.
a. What happens is the SysDictField object is
called but if the table no longer contains the field in the mapping then it
generates a “null” instance of SysDictField which is called on line 204 which
causes an error to be thrown.
2.
We need to modify the code base for DMF so it
can handle null objects and let us know where the issue is
3.
The following from line 196-209 should be
changed:
Orignal:
// Generate Target Fields
while
select Entity, XMLField,
TargetTable, TargetField from
targetFields
where
targetFields.Entity ==
_entity.EntityName &&
targetFields.XMLField != #blank
{
dictField = new
SysDictField(tableName2id(targetFields.TargetTable),fieldName2id(tableName2id(targetFields.TargetTable),targetFields.TargetField));
xmlElement =
xmlDocument.createElement(#column); //Column
xmlElement.setAttribute(#name,
targetFields.XMLField); //Name
xmlElement.setAttribute(#type, enum2str(dictField.baseType())); //Type
xmlElement.setAttribute(#isActive, enum2str(boolean::true));
xmlElement =
childNode.appendChild(xmlElement);
xmlElement.setAttribute(#Label,
dictField.label());
xmlElement.setAttribute(#Help,
dictField.help());
}
New
Code:
//
Generate Target Fields
while
select Entity, XMLField,
TargetTable, TargetField from
targetFields
where targetFields.Entity ==
_entity.EntityName &&
targetFields.XMLField != #blank
{
dictField = new SysDictField(tableName2id(targetFields.TargetTable),fieldName2id(tableName2id(targetFields.TargetTable),targetFields.TargetField));
// check to see if the dictField is null, if it isn’t then
we can handle the field but if it is then we need to alert the user of the
table and field that should be removed from the mapping
if(dictField
!= null)
{
xmlElement = xmlDocument.createElement(#column); //Column
xmlElement.setAttribute(#name, targetFields.XMLField); //Name
xmlElement.setAttribute(#type, enum2str(dictField.baseType()));
//Type
xmlElement.setAttribute(#isActive, enum2str(boolean::true));
xmlElement = childNode.appendChild(xmlElement);
xmlElement.setAttribute(#Label, dictField.label());
xmlElement.setAttribute(#Help, dictField.help());
}
else
{
// alert the
user of the bad mapping
info("error-table:
" + targetFields.TargetTable + "
field: " + targetFields.TargetField);
}
}
4.
Once you add this bit of code you should compile
the table and rerun the target mapping. This time the window should open but
will give you am info box of the bad field.
5.
Once you have this field we now need to remove
the field from the mapping. However sometimes it does not exists within the
visual mapping so we need manually remove it via the following
static void
FixEmerysDMFProductIssues(Args _args)
{
DMFTargetXML dmftargetxml;
delete_from dmftargetxml
where dmftargetxml.targettable == '<table name from step 3 in info box>'
&& dmftargetxml.targetfield == '<field
name from step 3 in info box>';
}
6.
Whenever you open the target mapping windows for
the specific entity you should no longer get the error or a msg with a table/field
info that we defined in step 3.
Wednesday, May 21, 2014
server 2012 not being able to load custom dll/reference .dll compile issues on AOS
So I ran into this issues a while back and I figured I would share how I fixed it.
So we are using a custom WYSIWYG WinForm editor (C#/.NET 4.0/Targeted for x86/x64) control inside of AX. However I noticed that whenever I went to do a compile locally or tried to even open the form on the AOS it would always get an error and tell me that it couldn't find the reference. Even though it was in all of the .dll folders (manually bin folder copy and dynamic auto deploy temp folder)
What I figured out was all of the local and terminal server clients had Visual Studio installed on it. So I tried installing Visual Studio 2010 on the AOS server itself. Once that it done then you can compile your objects that reference outside .dll's and open the form successfully
I'm not too sure why this fixes the issues, my guess is its one of the many runtime packages that gets installed with Visual Studio fixes the issue.
Would anyone else know why this fixes it?
But if you want a quick fix just install Visual Studio and all of the compile reference issues should go away.
So we are using a custom WYSIWYG WinForm editor (C#/.NET 4.0/Targeted for x86/x64) control inside of AX. However I noticed that whenever I went to do a compile locally or tried to even open the form on the AOS it would always get an error and tell me that it couldn't find the reference. Even though it was in all of the .dll folders (manually bin folder copy and dynamic auto deploy temp folder)
What I figured out was all of the local and terminal server clients had Visual Studio installed on it. So I tried installing Visual Studio 2010 on the AOS server itself. Once that it done then you can compile your objects that reference outside .dll's and open the form successfully
I'm not too sure why this fixes the issues, my guess is its one of the many runtime packages that gets installed with Visual Studio fixes the issue.
Would anyone else know why this fixes it?
But if you want a quick fix just install Visual Studio and all of the compile reference issues should go away.
Sub Reports within AX SSRS Reports
So while trying to add a subreport (no parms)
to an existing report I discovered some goofy things that need to be followed in
order for a sub report to work within AX.
1. Put both reports within the same project (model project within Visual studio)
2. Right Click on the sub report and choose properties. You have to manually type in the report name you want to use instead of being given a dropdown like normal SSRS
3. Include the design name (ReportName.Report, ReportName.PrecisionDesign1, etc...) within the report name mentioned in step 2.
4. Deploy the report to the same folder on the SSRS server (you may need to "move" your report into StaticReports> language> folder)
5. Previewing of subreports does not work within visual studio for AX sub reports. Once you follow steps 1-4 you should be able to add the model to the AOS. Then deploy your modified report and the subreport should show up correctly whenever you call the report from AX.
I sure do hope microsoft has some big enhancements for SSRS in 2015.
1. Put both reports within the same project (model project within Visual studio)
2. Right Click on the sub report and choose properties. You have to manually type in the report name you want to use instead of being given a dropdown like normal SSRS
3. Include the design name (ReportName.Report, ReportName.PrecisionDesign1, etc...) within the report name mentioned in step 2.
4. Deploy the report to the same folder on the SSRS server (you may need to "move" your report into StaticReports> language> folder)
5. Previewing of subreports does not work within visual studio for AX sub reports. Once you follow steps 1-4 you should be able to add the model to the AOS. Then deploy your modified report and the subreport should show up correctly whenever you call the report from AX.
I sure do hope microsoft has some big enhancements for SSRS in 2015.
Subscribe to:
Posts (Atom)