Showing posts with label dynamics ax. Show all posts
Showing posts with label dynamics ax. Show all posts

Thursday, July 28, 2016

AX 2009/2012 FTP operations

Here is a class that I wrote to perform operations via FTP file transfer

There is an interface to:
  • Upload a file
  • Upload text as a file
  • Rename file
  • Delete file
  • Get a list of files from an FTP server
  • Download file
The class is tested to work on the client and on the server (batch)

Everything is based on the .NET class FtpWebRequest




HOW TO USE


Lud_FtpClient ftpClient = Lud_FtpClient::construct("FTP URL", "user", "pwd");
;
ftpClient.uploadText("text you want to upload", "FC/test.txt");

ftpClient.uploadFile(@"c:\IFRToolLog.txt", "FC/IFRToolLog.txt");

ftpClient.getFileList("", "xml");

ftpClient.downloadFile("/assets/ISB_PAGE 36.pdf", @"\\192.168.100.44\temp\test.pdf");

ftpClient.deleteFile("/images.jpg");

Code sample:

void downloadFile(str fileUrl, str destination)
{
    System.Object               ftpo;
    System.Object               ftpResponse;
    System.Net.FtpWebRequest    request;
    System.IO.Stream            responseStream;
    System.IO.StreamReader      reader;
    System.IO.FileStream        writeStream;

    System.String               xmlContent;
    System.Net.FtpWebResponse   response;

    System.Byte[]               buffer;
    int                         bufferLenght = 2048;
    int                         bytesRead;
    ;

    new InteropPermission(InteropKind::ClrInterop).assert();

    try
    {
        ftpo = System.Net.WebRequest::Create("ftp://" + ftpUrl + (strEndsWith(ftpUrl, "/") ? fileUrl : "/" + fileUrl));
        request = ftpo;

        // se vengono fatte chiamate consecutive ricreando sempre le credential ogni tanto va in errore (Bad request)
        // per evitare il problema keepAlive = false, ma così va più lento. Con questo hack se la classe non viene ricreata
        // ogni volta dovrebbe andare meglio
        if(!credential)
        {
            credential = new System.Net.NetworkCredential(username, password);
            request.set_Credentials(credential);
            request.set_KeepAlive(false);
        }
        else
        {
            request.set_Credentials(credential);
            request.set_KeepAlive(true);
        }

        request.set_Method("RETR");
        // "Bypass" a HTTP Proxy (FTP transfer through a proxy causes an exception)
        request.set_Proxy( System.Net.GlobalProxySelection::GetEmptyWebProxy() );
        ftpResponse = request.GetResponse();
        response = ftpResponse;
        responseStream = response.GetResponseStream();

        buffer = new System.Byte[bufferLenght]();
        writeStream = new System.IO.FileStream(destination, System.IO.FileMode::Create);
        bytesRead = responseStream.Read(buffer, 0, bufferLenght);

        while (bytesRead > 0)
        {
            writeStream.Write(buffer, 0, bytesRead);
            bytesRead = responseStream.Read(buffer, 0, bufferLenght);
        }
        writeStream.Close();

        response.Close();

    }
    catch(Exception::CLRError)
    {
        throw error(this.getClrErrorMessage());
    }

    CodeAccessPermission::revertAssert();
}

Friday, November 20, 2015

Dynamics AX, How to post an invoice / packing slip / confirm for multiple orders

Here is the code to post multiple orders invoices or packing slip at once.

If you want to post only a subset of the quantities of each row just change the deliverNow/invoiceNow field of the salesId and set the proper specQty enum value

   SalesFormLetter salesFormLetter;  
   QueryRun queryRun;  
   Query query;  
   ;  
   ttsbegin;  

   salesFormLetter = SalesFormLetter::construct(DocumentStatus::PackingSlip);  
   salesFormLetter.transDate(systemdateget());  
   salesFormLetter.specQty(SalesUpdate::All);  
   salesFormLetter.printFormLetter(false);  
   salesFormLetter.createParmUpdate(false);  
   salesFormLetter.sumBy(AccountOrder::Account);  

   query = new Query(QueryStr(SalesUpdate));  
   query.dataSourceTable(tablenum(SalesTable)).addRange(fieldnum(SalesTable, SalesId));  
   queryRun = new QueryRun(query);  
   salesFormLetter.chooseLinesQuery(queryRun);  

   queryRun.query().dataSourceTable(tablenum(SalesTable)).findRange(fieldnum(SalesTable, SalesId)).value('A15000438');  
   salesFormLetter.chooseLines(null, true);  

   queryRun.query().dataSourceTable(tablenum(SalesTable)).findRange(fieldnum(SalesTable, SalesId)).value('A15000439');  
   salesFormLetter.chooseLines(null, true);  

   // insert here other orders  
   // ...  

   salesFormLetter.reArrangeNow(true);  
   salesFormLetter.run();  

   ttscommit;  

Friday, August 29, 2014

Read an XML file in AX with namespaces

If you have to read an XML file containing namespaces you have to use the class XmlNamespaceManager.

So, to read this sample XML, that contain 4 namespaces:

<?xml version="1.0" encoding="utf-8"?>
<ns1:Structure xmlns:ns1="http://www.influe.com/xns/2000/xmlfile/deffile/definition" 
               xmlns:gr="http://www.influe.com/xns/2000/xmlfile/deffile/groupe" 
               xmlns:li="http://www.influe.com/xns/2000/xmlfile/deffile/line" 
               xmlns:zn="http://www.influe.com/xns/2000/xmlfile/deffile/zone">
  <gr:MESSAGE>
    <li:TESTATA>
      <zn:TIPO_RECORD_EN>EN</zn:TIPO_RECORD_EN>
      <zn:MITTENTE_MESSAGGIO>4324234</zn:MITTENTE_MESSAGGIO>
 </li:TESTATA>
 <gr:group1>
      <li:GERARCHIA>
  <zn:NUMERO_DESADV_HE>DDT1</zn:NUMERO_DESADV_HE>
   </li:GERARCHIA>
 </gr:group1>
 <gr:group1>
      <li:GERARCHIA>
  <zn:NUMERO_DESADV_HE>DDT2</zn:NUMERO_DESADV_HE>
   </li:GERARCHIA>
 </gr:group1>
 <gr:group1>
      <li:GERARCHIA>
  <zn:NUMERO_DESADV_HE>DDT3</zn:NUMERO_DESADV_HE>
   </li:GERARCHIA>
 </gr:group1> 
  </gr:MESSAGE>
</ns1:Structure>

You can proceed like that:

static void Job92(Args _args)
{
    XmlDocument xmlDoc = XmlDocument::newFile(@"YOURPATH\file.xml");
    XmlNamespaceManager nmgr = new XmlNamespaceManager(new XmlNameTable());
    XmlNode node;
    XmlNodeList nodeList;
    ;
    
    nmgr.addNamespace("ns1", "http://www.influe.com/xns/2000/xmlfile/deffile/definition");
    nmgr.addNamespace("li", "http://www.influe.com/xns/2000/xmlfile/deffile/line");
    nmgr.addNamespace("gr", "http://www.influe.com/xns/2000/xmlfile/deffile/groupe");
    nmgr.addNamespace("zn", "http://www.influe.com/xns/2000/xmlfile/deffile/zone");
    
    // nodo TESTATA
    node = xmldoc.selectSingleNode("//li:TESTATA", nmgr);
    
    infO(node.xml());
    
    // lista group1
    nodeList = xmldoc.selectNodes("//gr:group1");
    
    while(true)
    {
        node = nodeList.nextNode();
        if(node == null)
            break;
            
        // esempio lettura elemento all"interno di group1
        node = node.selectSingleNode("li:GERARCHIA/zn:NUMERO_DESADV_HE", nmgr);
        info(node.text());
    }
}

Tuesday, July 01, 2014

AX separated instances on Windows taskbar

This one is a little trick that I find very useful.

If you have to work with multiple AX instances, you will surely get annoyed by the way windows group the icons on the taskbar.

Multiple instances of the same program are not grouped separately, thus it get very hard to switch between a window or another.


Having separated AX instances on the taskbar is much better, imho


To accomplish that you have to create an hard link to the AX32 executable on the filesystem, like that:
  1. Open command prompt (CMD.exe)
  2. Go to the AX installation folder (usally C:\Program Files (x86)\Microsoft Dynamics AX\50\Client\Bin)
  3. Create N hard link for every different instance of AX:

    mklink /h ax32_DEV.exe ax32.exe
    mklink /h ax32_PROD.exe ax32.exe



  4. Create a shortcut to the newly created file (ax32_DEV.exe) wherever you want, and edit the shortcut to open the specific AXC file pointing to the desired AX instance.


  5. Open AX from that shortcut
That should work with Windows 7, 8, Vista and XP

Have fun

Tuesday, May 20, 2014

AX 2009 - Reduce file size of PDF generated from report

AX 4.0 and 2009 doesn't have a good PDF generation engine. The problems are basically 2:

  1. Duplicated images
  2. Fonts included on the PDF

Duplicated images

If you include an image on the report and this image is repeated across pages, the image will be included N times (depending on the number of the pages)

Company logos are usually included in every report, and that will cause a drastic increase on the file size of the generated PDF.

To solve this problem, some changes has to be made to the class PDFViewer.

Add this method to the class:


 int FindOrAddToCache(str data, int imageObjectNo)  
 {  
   int hashCode;  
   System.String netStr = data;  
   ;  
   hashCode = netStr.GetHashCode();  
     
   if (resourceIdImageMap.exists(hashCode))  
     imageObjectNo = resourceIdImageMap.lookup(hashCode);  
   else  
   {  
     resourceIdImageMap.insert(hashCode, imageObjectNo);  
   }  
   return imageObjectNo;  
 }  

That method will build an hash out of the image, and thus will be possible to reuse the latter without increasing the file size.

To do that the following modification will be needed on the method writeBitmap (I will not include all the method since it's too long. So, declare an int variable named newImageObjectNo, add the following code starting approximately at line 140 and close the else bracket at the end) 



       // revert previous assertion  
       CodeAccessPermission::revertAssert();  
       // assert read permissions  
       readPermission = new FileIOPermission(fn, #io_read);  
       readPermission.assert();  
       // BP deviation documented (note that the file io assert IS included above)  
       bin.loadFile(fn);  
       data = bin.getData();  
       // Get rid of the temporary file.  
       WinAPI::deleteFile(fn);  
       if (bitmapEncode85)  
         s = bin.ascii85Encode();  
       else  
         s = BinData::dataToString(data);  
       // Begin modify  
       newImageObjectNo = this.ABM_FindOrAddToCache(s, imageObjectNo);  
       if(newImageObjectNo != imageObjectNo)  
       {  
         imageObjectNo = newImageObjectNo;  
         nextObjectNumber -= 1;  
       }  
       else  
       {  
       // End modify  

That would do the trick for the duplicated images!

Fonts included on the PDF

AX lacks the possibility to only include a subset of the characters used in the report.

This cause the whole font to be included in the generated PDF, causing a huge increase of the file size.

To solve this problem you can avoid to embed fonts on generated PDF (but who does not have the font installed on his system will not be able to view it)

Or you can use one of these fonts (please note that if you use those on a report, the characters are case sensitive):

Courier
Courier-Bold
Courier-Oblique
Courier-BoldOblique
Helvetica
Helvetica-Bold
Helvetica-Oblique
Helvetica-BoldOblique
Times
Times-Roman
Times-Bold
Times-Italic
Times-BoldItalic
ZapfDingbats

Those fonts are default fonts that should be readed in every PDF reader, even if someone does not have the font installed on the system.

When you consider to use a predefined font then you have to do the following code change in the endReport method of PDFViewer class:
...
// Delete the fonts stored in the fontdescrs map
mapEnumerator = fontDescrs.getEnumerator();
while (mapEnumerator.moveNext())
{
    font = mapEnumerator.currentValue();
//->Begin
    if(font.isTrueType())
//<-End
        font.deleteFont();
}

The above piece of code is taken from here

Please note that if you plan to use one of the default fonts, you can only use ASCII characters on your reports! This should be usually enough, but there are some character like "€" that are not available.

Some unsafe characters are:

€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ

You can force the PDF generation engine to substitute those characters with something else, like € with EUR, if you change the method \Classes\PDFViewer\writeStringField like that:


   fn = this.getFontName(currentPage, _section, _field);  
   if (fn != fontNameLast)  
   {  
     fontNameLast = fn;  
     fontIdLast = this.addFont(fn);  
     fontDescrLast = fontDescrs.lookup(fontIdLast);  
   }  
   fontId = fontIdLast;  
   fontDescr = fontDescrLast;  
   fontsOnPage.add(fontId);  
   // Begin modify  
   if(this.isPredefinedFont(fn))  
     _fieldValue = strReplace(_fieldValue, "€", "EUR");  
   // End modify  


Following these tricks the generated PDF file should be viewed on windows, mac, linux and android, and without images the file size will be less than 10Kb

Thursday, November 14, 2013

Dynamics AX 2012 FAST synchronization

Database synchronization on AX 2012 is usually pretty slow.

This is due to the Ax database becoming bigger and bigger. Actually an Ax 2012 R2 installation has something like 5000+ tables.

Slowliness on the synchronization process can be a problem when it come to update a production environment, since it will take the system unavailable for 20-30 minutes.

Deploying changes via model store using the AXUTIL temporary schema technique is very useful, but then you still have to waste a lot of time synchronizing the database.

However, to speed things up, you can synchronize only tables that are actually changed since the last update.

You really don't need to synchronize all those 5000 tables every time you update the system.

So here is a tool to come to rescue.

Ax keep track of changes you made to the aot on the Model* tables. With the query below you can find the last date on which a table or table field has been changed.


 SysModelElementData modelElementData;  
   SysModelElement   modelElement;  
   SysModelElementData modelElementDataRoot;  
   SysModelElement   modelElementRoot;  
   delete_from tmpFrmVirtual;  

   while select modelElement  
     group by modelElement.RootModelElement, modelElementRoot.Name, modelElementRoot.AxId  
     join maxOf(ModifiedDateTime) from modelElementData  
     where modelElementData.ModelElement == modelElement.RecId  
      && (modelElement.ElementType == UtilElementType::TableField || modelElement.ElementType == UtilElementType::TableIndex || modelElement.ElementType == UtilElementType::Table)  
      //&& modelElementData.Layer == UtilEntryLevel::cus + 1  
      && modelElementData.modifiedDateTime > DateTimeUtil::newDateTime(transDate, 0)  
     join modelElementRoot  
       where modelElementRoot.RecId == modelElement.RootModelElement  
   {  

So with that query you can filter down tables modified within the last X days, and synchronize only those tables.

I've built a tool that does exactly this, you got a screenshot below:


In the date field you can filter tables modified since this date. Pressing OK the synchronization of the selected tables will occour.

That way, I can usually synchronize the system in about... 10 seconds?

XPO project download link HERE


P.S.: please note that this is not a complete replacement for the DB synchronization, but a procedure that synchronize only a small subset of the database!
There are cases in wich a modification of a table is not tracked in the SysModel* tables, for example if you change a string extended data type lenght, or completely delete a table layer. So use at your own risk.

Friday, November 30, 2012

AX 2012 Financial dimension update

Here are 2 ways to update a financial dimension, while keeping the other dims. The result should be the same, just use whatever you prefer

To use those two method proceed like so:

  purchTable = purchTable::find('PURCHID', true);  
  purchTable.DefaultDimension = getNewDefaultDimension(purchTable.DefaultDimension, "COSTCENTER", "YOURVALUE");  
  purchTable.update();  

METHOD NUMBER 1:

 static RecId getNewDefaultDimension(RecId defaultDimension, Name dimName, str 255 dimValue)  
 {  
   DimensionAttributeValueSetStorage  dimStorage;  
   Counter               i;  
   DimensionAttribute         dimAttributeCostCenter;  
   DimensionAttributeValue       dimAttributeValue;  
   dimStorage = DimensionAttributeValueSetStorage::find(defaultDimension);  
   dimAttributeCostCenter = DimensionAttribute::findByName(dimName);  
   if(dimValue)  
   {  
     dimAttributeValue = DimensionAttributeValue::findByDimensionAttributeAndValue(dimAttributeCostCenter, dimValue, true, true);  
     dimStorage.addItem(dimAttributeValue);  
   }  
   else  
     dimStorage.removeDimensionAttribute(DimensionAttribute::findByName(dimName).RecId);  
   return dimStorage.save();  
 }  


METHOD NUMBER 2:

 static RecId getNewDefaultDimension(RecId defaultDimension, Name dimName, str 255 dimValue)  
 {  
   container c;  
   RecId   newdefaultDimension;  
   int    i;  
   c = AxdDimensionUtil::getDimensionAttributeValueSetValue(defaultDimension);  
   i = conFind(c, dimName);      
   if(!i && !dimValue)  
     return defaultDimension;  
   if(i)  
   {      
     c = conDel(c, i+1, 1);   
     c = conDel(c, i, 1);    
   }  
   if(dimValue)  
   {  
     c += dimName;  
     c += dimValue;  
   }  
   c = conDel(c, 1, 1);  
   c = conIns(c, 1, conLen(c) / 2);  
   newdefaultDimension = AxdDimensionUtil::getDimensionAttributeValueSetId(c);  
   return newdefaultDimension;  
 }  

To get the current value of a financial dimension:

 static str 255 getDimensionValue(RecId defaultDimension, Name dimName)  
 {  
   DimensionAttributeValueSetStorage  dimStorage;  
   Counter               i;  
   DimensionAttribute         dimAttributeCostCenter;  
   DimensionAttributeValue       dimAttributeValue;  
    dimStorage = DimensionAttributeValueSetStorage::find(defaultDimension);  
   return dimStorage.getDisplayValueByDimensionAttribute(DimensionAttribute::findByName(dimName).RecId);  
 }  

Monday, October 29, 2012

Printing footer on report last page without reserving white space in all pages (AX 2009)

AX 2009 (and prior versions) always had a problem in printing report's footer.

If you want a footer to be printed only in the last page of the report, in the bottom part of the page, you have to condition the last page print by code.

This works pretty well, except that the footer space is reserved in all of the pages, leaving a white space hole in all the pages!

That's a problem in report like Invoices, where you have a big footer with totals and so on.

Normally you would make the footer appear in all pages, so the white space hole will be filled, but that's not a proper solution for some customers.

I found a way to bypass this problem, and is also easy to implement.

The key is to programmatically compute where you are in the page, and how much space will need the footer, so you can move it down or eventually create a new page.

So, here the steps to accomplish that:
  1. Create a programmable section in the report to act as your page footer. Place in this programmable section all the controls that you want to render as the last page footer
  2. Create an empty footer section below the body that fetch your report data, and place an empy "Static text control" inside this footer (otherwise nothing will be rendered)
  3.  Override the method executeSection of the footer with the code below

 public void executeSection()  
 {  
   int pageHeight, currentPos, i;  
   super();  
   if(!CustTable_1)  
   {    
     pageHeight = element.mm100PageHeight();  
     currentPos = element.currentYmm100();  
     if(pageHeight < currentPos + ProgrammableSection.height100mm())  
     {  
       element.newPage();  
     }  
     element.gotoYmm100(pageHeight - ProgrammableSection.height100mm());  
     element.execute(1);  
   }  
 }  

What I'm doing here is, if I ended up printing report data (in this case CustTable is empty) I compute the page height, where I am in the page, and how much space will need the footer.

Then you can print the footer in the proper position, or previously go to a new page if the space needed is not sufficient.



HERE  attached you find an XPO with a sample report


P.S.: My colleagues says that this problem belong also to SSRS reporting on AX 2012. I haven't seen it yet there, if you find a solution feel free to drop a line!



Friday, October 12, 2012

Enhanced table browser

If you find it hard to find fields on the Ax Table Browser, this tool may help you.

It will simply show the visible fields on the right, and you can check/uncheck those that you want to see.

If you experience some problem after installation, clear Usage data for the form



XPO download HERE

Thursday, October 11, 2012

Create custom Alert for AX with Go to Source support

To create an Alert in AX, you can use the class EventNotification and its derivates

I got some problems using these standard classes, particularly in making the button "Go to source" work.

To avoid those problem, here is some code that may help:

 public static void CreateAlert(str message,  
               str subject,  
               UserId userId = curUserId(),  
               NoYes showPopup = NoYes::Yes,  
               NoYes sendEmail = NoYes::No,  
               Common record = null,  
               str dataSourcename = '',  
               MenuFunction menuFunction = null)  
 {  
   EventInbox inbox;  
   DictTable table;  
   EventContextInformation eci;  
   EventInboxData inboxData;  
   Args args = new Args();  
   List list;  
   EventInboxId inboxId = EventInbox::nextEventId();  
   FormRun formRun;  
   WorkflowRecordCaptionGenerator recordCaptionGenerator;  
   UserInfo userInfo;  
   inboxId = EventInbox::nextEventId();  
   inbox.initValue();  
   inbox.ShowPopup      = showPopup;  
   inbox.Subject       = subject;  
   inbox.Message       = message;  
   inbox.SendEmail      = sendEmail;  
   inbox.EmailRecipient    = SysUserInfo::find().Email;  
   inbox.UserId        = userId;  
   inbox.InboxId       = inboxId;  
   inbox.AlertCreatedDateTime = DateTimeUtil::getSystemDateTime();  
   if (record)  
   {  
     table = new DictTable(record.TableId);  
     eci = new EventContextInformation();  
     if (!menuFunction)  
     {  
       menuFunction = new MenuFunction(table.formRef(),MenuItemType::Display);  
       if (!menuFunction)  
         throw error(strFmt("@SYS104114",table.formRef()));  
     }  
     //Build the data to drill down to from the notification  
     args.menuItemName(menuFunction.name());  
     args.menuItemType(MenuItemType::Display);  
     args.name(menuFunction.object());  
     eci.parmPackedArgs(args);  
     eci.parmAlertBuffer(record);  
     eci.parmAlertFormDsName(dataSourceName);  
     //eci.parmDontUseFormRunFromMenuItem(true);  
     inboxData.InboxId = inboxId;  
     inboxData.DataType = EventInboxDataType::Context;  
     inboxData.Data = eci.pack();  
     inboxData.insert();  
     inbox.AlertTableId = table.id();  
     inbox.ParentTableId = table.id();  
     recordCaptionGenerator = WorkflowRecordCaptionGenerator::construct(record);  
     inbox.AlertedFor = recordCaptionGenerator.caption();  
     list = SysDictTable::getUniqueIndexFields(table.id());  
     if (list)  
     {  
       inbox.keyFieldList(list.pack());  
       inbox.keyFieldData(SysDictTable::mapFieldIds2Values(list,record).pack());  
     }  
     inbox.CompanyId = record.company();  
   }  
   inbox.insert();  
 }  

To use this code
 static void Job155(Args _args)  
 {  
   InventTable inventTable;  
   select firstOnly inventTable;  
   DEVUtils::CreateAlert("message", "subject", curUserId(), true, false, inventTable, "InventTable", new MenuFunction(menuitemDisplayStr(EcoResProductDetailsExtended), MenuItemType::Display));  
 }  

Sending PEC (Certified E-mail) with AX

A PEC (Certified E-mail) is not very different from a usual e-mail.

You just have to use SSL to secure the connection while sending the mail.

Usually I use the class System.Net.Mail to send mails with Ax, and this one has a useful property named EnableSsl that could work. But with a PEC, no, this isn't working.

That's because, to send a PEC you need an Implicit SSL, while System.Net.Mail only supports “Explicit SSL”.

 Reference: HERE

 And, as stated in the MSDN:

The EnableSsl property specifies whether SSL is used to access the specified SMTP mail server.
The default value for this property can also be set in a machine or application configuration file. Any changes made to the EnableSsl property override the configuration file settings.
The SmtpClient class only supports the SMTP Service Extension for Secure SMTP over Transport Layer Security as defined in RFC 3207. In this mode, the SMTP session begins on an unencrypted channel, then a STARTTLS command is issued by the client to the server to switch to secure communication using SSL. See RFC 3207 published by the Internet Engineering Task Force (IETF) for more information.
An alternate connection method is where an SSL session is established up front before any protocol commands are sent. This connection method is sometimes called SMTP/SSL, SMTP over SSL, or SMTPS and by default uses port 465. This alternate connection method using SSL is not currently supported.

So, if you want to send a mail with Implicit SSL, you have to use a deprecated class from the old 2.0 .NET Framework, that is System.Web.Mail

Using this class is not difficult, and you can find a working sample down here:


 static void SendPECMail(Args _args)  
 {  
   System.Web.Mail.MailMessage  newMail;  
   System.Collections.IDictionary fields;  
   System.Collections.IList    attachementCollection;  
   System.Web.Mail.MailAttachment attachment;  
   try  
   {  
     new InteropPermission(InteropKind::ClrInterop).assert();  
     newMail = new System.Web.Mail.MailMessage();  
     fields = newMail.get_Fields();  
     fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpserver", "smtps.pec.aruba.it");  
     fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpserverport", "465");  
     fields.Add("http://schemas.microsoft.com/cdo/configuration/sendusing", "2");  
     fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate", "1");  
     fields.Add("http://schemas.microsoft.com/cdo/configuration/sendusername", "lmattiuzzo@pec.it");  
     fields.Add("http://schemas.microsoft.com/cdo/configuration/sendpassword", "password");  
     fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpusessl", "true");  
     newMail.set_From("lmattiuzzo@pec.it");  
     newMail.set_To("santa@klaus.com");  
     newMail.set_Subject("subject");  
     newMail.set_BodyFormat(System.Web.Mail.MailFormat::Html);  
     newMail.set_Body("body of your message");  
     newMail.set_Priority(System.Web.Mail.MailPriority::High);  
     attachementCollection = newMail.get_Attachments();  
     attachment = new System.Web.Mail.MailAttachment(@"c:\file.pdf");  
     attachementCollection.Add(attachment);  
     System.Web.Mail.SmtpMail::set_SmtpServer("smtps.pec.aruba.it:465");  
     System.Web.Mail.SmtpMail::Send(newMail);  
     CodeAccessPermission::revertAssert();  
   }  
   catch( Exception::CLRError )  
   {  
     throw error(AifUtil::DEVgetClrErrorMessage());  
   }  
 }  

Thursday, July 26, 2012

AX Enterprise Portal 2012 - Modify AxGridView lookup

If you want to filter or modify visualized fields on a Lookup inside an Axgridview, here is how you can do it:

Add this method to the field you want to change your lookup, in your ax Dataset. (the method is not listed in the override methods)

 void void dataSetLookup(SysDataSetLookup sysDataSetLookup)
{
    Query q = new Query();
    List list = new List(Types::String);
 
    list.addEnd(fieldStr(InventTable, ItemId));
    list.addEnd(fieldStr(InventTable , NameAlias));
 
    q.addDataSource(tableNum( InventTable ));
    q.dataSourceNo(1).addRange(fieldNum( InventTable , ItemId)).value('000001');
     
    sysDataSetLookup.parmQuery(q);
 
    sysDataSetLookup.parmLookupFields(list);        
}


Friday, May 18, 2012

AxUtil GUI

If you find that the AXUtil tool for importing models is not so pratical, here's the tool for you.


I've extended the functionality of the standard "Installed models" form inside Tools -> Models Management to let you Import, Export, Move or Delete models directly from AX. How it works: With a little of reverse engineering i found that AxUtil.exe simply uses a DLL named AXUtil.dll. This DLL is already referenced in AX, so you can use it directly from X++.

Here's a sample to export a model. The code is pretty straightforward, I just pass in the name of the DB, the path where to export the file and some other params, and then I get my model saved/imported/moved/deleted.

Here you can find the project for the tool, this will work on every client (you don't need to be on the AOS to export/import models)



 private void exportModel(SysModelManifest _SysModelManifest, filePath path)  
 {  
   Microsoft.Dynamics.AX.Framework.Tools.ModelManagement.AxUtil        axutil      = new Microsoft.Dynamics.AX.Framework.Tools.ModelManagement.AxUtil();  
   Microsoft.Dynamics.AX.Framework.Tools.ModelManagement.AxUtilContext     context     = new Microsoft.Dynamics.AX.Framework.Tools.ModelManagement.AxUtilContext();  
   Microsoft.Dynamics.AX.Framework.Tools.ModelManagement.AxUtilConfiguration  config     = new Microsoft.Dynamics.AX.Framework.Tools.ModelManagement.AxUtilConfiguration();  
   Microsoft.Dynamics.AX.Framework.Tools.ModelManagement.ModelArgument     modelArgument;  
   int     modelId = _SysModelManifest.Model;  
   int     i;  
   Object ret;  
   ClrObject o; // = new ClrObject("System.Collections.Generic.List`1[System.String]");  
   ClrObject enumerator;  
   boolean hasErrors;  
   try  
   {  
     modelArgument = new Microsoft.Dynamics.AX.Framework.Tools.ModelManagement.ModelArgument(modelId);  
     config.set_ExportFile(path + _SysModelManifest.Name);  
     config.set_ModelArgument(modelArgument);  
     config.set_Database(SysSQLSystemInfo::construct().getloginDatabase());  
     config.set_Server(SysSQLSystemInfo::construct().getLoginServer());  
     axutil.Export(context, config);  
     o = context.get_Errors();  
     enumerator = o.GetEnumerator();  
     while (enumerator.MoveNext())  
     {  
       error(enumerator.get_Current());  
     }  
     o = context.get_Warnings();  
     enumerator = o.GetEnumerator();  
     while (enumerator.MoveNext())  
     {  
       warning(enumerator.get_Current());  
     }  
     hasErrors = context.get_HasError();  
     if(!hasErrors)  
       info(strFmt("Esportato modello %1 nel file %2" , _SysModelManifest.Name, path + _SysModelManifest.Name));  
   }  
   catch ( Exception::CLRError )  
   {  
     error(AifUtil::getClrErrorMessage());  
     throw Exception::Error;  
   }  
 }  

Saturday, May 12, 2012

AX Project Finder Tool

Here's a small and handy tools that let you search for projects inside AX.

Just start typing something on the search bar and you will get a list of the projects found.


With a double click you can open the selected project

How it works:

with the call of:


projectListNode = SysTreeNode::getSharedProject(); 


you get the AOT node for the shared project. Then all the child of this node are looped, and the name is checked against the input text


treeNodeIterator = projectListNode.AOTiterator();  
   treeNode = treeNodeIterator.next();  
   while(treeNode)  
   { ... }  


 void FindProject()  
 {  
   ProjectSharedPrivate  projectType;  
   TreeNode treeNode;  
   ProjectListNode projectListNode;  
   TreeNodeIterator treeNodeIterator;  
   str aotName;  
   ;  
   ListboxResult.clear();  
   projectListNode = SysTreeNode::getSharedProject();  
   treeNodeIterator = projectListNode.AOTiterator();  
   treeNode = treeNodeIterator.next();  
   while(treeNode)  
   {  
     aotName = treeNode.AOTname();  
     //return;  
     if(DEVString::contains(aotName, projectName.text()))  
     {  
       ListboxResult.add(aotName);  
     }  
     treeNode = treeNodeIterator.next();  
   }  
 }  



Download link below

Friday, May 04, 2012

AX 2012 Create new project tool

Here's a port of the tool to create a new project template found on Axaptapedia.

This one will work on AX 2012, and has been entirely rewritten.

It support all the new nodes of the AOT made available with AX 2012


As usual, link to download is below

Friday, April 20, 2012

Static void main()

Hi guys, I'm an italian programmer working on Microsoft Dynamics AX and the .NET world for years.

In this blog I will give you tools and hints on the development world, with a special regard on Microsoft Dynamics AX.
You'll find also useful tips on the .NET world, Web Development, Mobile, Interfaces and maybe some tips on Android Java Games.
I hope to give something back to the community!

Ludovico Mattiuzzo