Make Motorola/Symbol 9090 Scanner scan

I was recently working on a Motorola 9090 scanner device, loaded with Windows Mobile 6.5, developing a .NET Compact Framework application. Most scanner devices automatically trigger the scanner when pressing the scan button and write the decoded barcode text in any text field or document. This device didn’t and there was no preinstalled scanner tool to configure the devices behavior. However, there is a nice application called DataWegde provided by Motorola to make the scanner scan without using any obscure APIs.

Dynamics AX 2009: FTP Adapter for AIF

The Application Integration Framework in Dynamics AX is an extensible framework for data transportation and reception. It support the separation of transport technology (e.g. MSMQ, XML/SOAP Webservices, Filesystem Share) security aspects (services, permissions, transported data) and data manipulation. Creating new adapters to support other transport technologies is simple. Here is an example to support FTP.

Develop AIF FTP Adapter

  • First, create a new parameter table AifFTPParameters to store FTP server name, user, password and directory (4 strings). Next create a setup form for the AifFTPParameters table and a display menu item. Add an OK command button. Add this code to the buttons clicked event:

    void clicked()
    {;
        super();
        // use the server name as selected value 
        element.closeSelect(Setup_ServerName.text());
    }

    FTP Parameters setup

  • Create a new AifFTPSendAdapter class that implementes the AifSendAdapter interface. You may create empty implementations for begin(), commit(), initialize(), rollback() and terminate(). But you really need to implement the sendMessage() method. I’m using the System.Net Classes to implement the FTP transfer.
     

    public void sendMessage(AifGatewayMessage gatewayMessage)
    {
        System.Text.Encoding encoding;
        System.Byte[] bytes;
        str requestStr = "";
        object ftpo;
        System.Net.FtpWebRequest ftpRequest;
        System.Net.NetworkCredential credential;
        str ftpUser;
        str ftpPass;
        System.IO.Stream requestStream;
        InteropPermission clrPermission =
         new InteropPermission(InteropKind::ClrInterop);
        ;

        clrPermission.assert();

        switch(gatewayMessage.parmEncoding())
        {
            case ‘UTF-8’:
                encoding = System.Text.Encoding::get_UTF8();
                break;
            default:
                throw error("Unsupported Encoding");
        }
        bytes = encoding.GetBytes(gatewayMessage.parmMessageXml());

        requestStr = strfmt(‘ftp://%1′,AifFTPParameters::find().ServerName);
        if(AifFTPParameters::find().Directory)
        {
            requestStr = strfmt(‘%1/%2’,
                                 requestStr,
                                 AifFTPParameters::find().Directory);
        }

        requestStr = strfmt(‘%1/%2%3’,
                              requestStr,
                              gatewayMessage.parmMessageId(),
                              ’.xml’);

        ftpo =  System.Net.WebRequest::Create(requestStr);
        ftpRequest = ftpo;

        ftpUser = AifFTPParameters::find().UserName;
        ftpPass = AifFTPParameters::find().Password;
        //BP deviation documented
        credential = new System.Net.NetworkCredential(ftpUser,ftpPass);
        ftpRequest.set_Credentials(credential);
        ftpRequest.set_ContentLength(bytes.get_Length());
        ftpRequest.set_Method(‘STOR’);

        requestStream = ftpRequest.GetRequestStream();
        requestStream.Write(bytes,0,bytes.get_Length());
        requestStream.Close();

        CodeAccessPermission::revertAssert();
    }

  • Create a new AifFTPAdapter class that implements AifIntegrationAdapter. This class is used by the AIF configuration interface in Dynamics AX to identify an outbound adapter and its configuration form.

    public AifIntegrationAdapterType getAdapterType()
    {;
        return AifIntegrationAdapterType::SendOnly;
    }

    public MenuItemNameDisplay getAddressDisplayMenuItem()
    {;
        // the AifFTPParameter forms display menu items name
        return ‘AifFTPParameters’;
    }

    public AifTransportAddress getCanonicalTransportAddress(AifTransportAddress transportAddress)
    {;
        return transportAddress;
    }

    public MenuItemNameDisplay getConfigurationDisplayMenuItem()
    {;
        return ”;
    }

    public LabelString getLabel()
    {;
        return "AIF FTP Adapter";
    }

    public AifReceiveAdapter getReceiveAdapter()
    {;
        return null;
    }

    public AifSendAdapter getSendAdapter()
    {;
        return new AifFTPSendAdapter();
    }

    public boolean isHosted()
    {;
        return true;
    }

    public void validateConfiguration(AifTransportAddress transportAddress,
                                      AifChannelDirection channelDirection)
    {;
        //TODO:Check or throw error
    }

      1. Register FTP Adapter and configure AIF

    1. Go to Basics > Setup > Application Integration Framework.
    2. In Local Endpoints make sure you have an endpoint configured
    3. In Transport Adapters create a new record, select AifFTPAdapter and mark it as active.
    4. In Channels, create a new outbound FTP Channel, provide server name and credentials.
      Mark the channel as active.
      Configure AIF FTP Adapter 
    5. In Services activate the SalesSalesInvoiceService
      Activate SalesSalesInvoiceService 
    6. In Endpoints create a new endpoint. Set the outbound channel to your FTP channel and set the local endpoint. Go to the Constraints tab and set “No Constraints” flag. Mark the endpoint as active.
      Create an endpoint with outbound FTP

      Click the Action Policies button and add the SalesSalesInvoiceService.read method
      Activate SalesSalesInvoiceService.read

      Click the Data Policies button and use the set button to enable all data fields for transportation.
      Set data policies for invoice

    7. Go to Basic > Inquiries > Batch jobs. Make sure to run a batch job that processes the AIF message send and receive tasks: AIFInboundProcessingService, AIFOutboundProcessingService, AIFGatewaySendService and AIFGatewayReceiveService.
      Setup AIF processing batch jobs
    8. Go to Accounts Receivable > Inquiries > Journals > Invoice. Select an invoice and use the Send Electronically button to put it in the transportation queue.
      Send invoice electronically using AIF
    9. Wait a few miniutes and take a look at your FTP space. There you should see the transmitted invoices as xml file.
      Invoice successfully uploaded

    How to create a custom ribbon Addin for Excel 2010

    Creating a custom ribbon addin for Excel can be done in a few steps

    1. Develop your VBA code in a new Excel sheet, save it as Excel addin .xlam
    2. Open the addin with Custom UI Editor to create Buttons, Groups, etc. and link it with your VBA code
    3. Add the addin directory to the secure folders in excel
    4. Configure Excel to load your addin at startup

        Here are the required steps in detail

      Start Excel and develop your VBA code as you did before. If you do not see a development tab in excel, activate it via the options menu. File > Options > Ribbon > On the right side check “Development Tools”

      Activate the VBA Development Tab in Excel 2010
      You need to create an event handler to make your functions callable from the UI. Here is an example for a function and an event handler. The event handler foo_eventhandler calls the function foo which displays a message box with “Hello World from Excel”.

    Sub foo_eventhandler(control As IRibbonControl)
        foo
    End Sub

    Sub foo()
        MsgBox ("Hello World from Excel")
    End Sub

    Save your Excel as Excel Addin (.xlam) to your C:\Users\YOURNAME\AppData\Roaming\Microsoft\Addins directory. You can save it wherever you want, but the predefined addins directory might be good idea.

    Yet the addin does not have any UI elements. You have to define these elements by yourself. Download and install the Custom UI Editor from http://openxmldeveloper.org/blog/b/openxmldeveloper/archive/2009/08/07/7293.aspx . Start the editor and open your previous saved .xlam file. The editor shows a blank text field. From the Menu > Insert > Sample XML choose Excel Custom Tab. Use the onAction property at the button to call an eventhandler. Here is an example code to call the foo_eventhandler

    <customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui">
        <ribbon>
            <tabs>
                <tab id="customTab" label="MyTab" insertAfterMso="TabHome">
                    <group id="customGroup" label="MyGroup">
                        <button id="customButton1" label="Say Hello" size="large"
                         onAction="foo_eventhandler" imageMso="HappyFace" />
                    </group>
                </tab>
            </tabs>
        </ribbon>
    </customUI>

    Start Excel from the program menu. Go to File > Options > Security Center > Settings > Secure Locations > Add Location, and add your addins directory e.g. C:\Users\YOURNAME\AppData\Roaming\Microsoft\Addins as secure directory to load stuff from. Close Excel.

    Add the Addins directory to the secure locations in Excel 2010

    When you open the .xlam file using Excel, you can already use your addin. However, it is not available when you start Excel from the programs menu. To load the addin when excel starts, go to the development tab  > Add-Ins and check your addin. (The form displays addins from the Appdata\Roaming\Microsoft\Addins directory)

    Load your Addin when Excel starts

    Now the Addin is loaded when Excel starts. Whenever you change something and the Addin does not appear anymore, go and check your xml UI code. Make sure that all IDs are unique and do not have blanks
    (e.g. id=”My Button2” is a bad idea)

    Custom Hello World Addin for Excel 2010

    More Icons

    There are many icons available you can use for your addins. For example download the icon gallery addin from http://www.accessribbon.de/index.php?Downloads:24 . To change the icon on button modify the imageMso property in your xml file.

    <button id="customButton1" label="Say Hello" size="large"
     onAction="foo_eventhandler" imageMso="HappyFace" />

    Have fun!

    Many icons in Excel 2010

    MCTS: 70-573 SharePoint 2010 Application Development

    image

    On Friday 13th Smiley I was certified for SharePoint 2010 Application Development. Since every Dynamics AX Silver+ Partner need at least on SharePoint guy, I’m the one for InsideAx. I’m already experienced in maintaining SharePoint 2012 (mainly Foundation) for those of our Customers running Rolecenter in AX 2009. Furthermore I’ve upgraded and developed Enterprise Portal applications in the last two years. However, EP development and classic SharePoint development does diverge. Everybody who ever tried to modify EP using SharePoint designer knows what I’m taking about.

    I don’t want to say that all the cool SharePoint features cannot be used to extend Dynamics AX (e.g. I’ve built a web based appointment app using SharePoint and its lists and calendars to visualize the schedules). All those who are now forced to get in touch with SharePoint may experience that it can be used in may cases to provide cool solutions for their customers. But I’d like Microsoft to strengthen the web / enterprise portal development aspect of Dynamics AX by extending the development course materials and provide an Enterprise Portal Certification.

    SSRS: Error when opening design of duplicated report

    When you duplicate an SSRS Dynamics AX 2012 report in Visual Studio, and you try to open the design node you may get an error message like “object instance not set to an object”. The reason is that the data set parameters contain the name of the report e.g. SalesQuotation. Now that you have duplicated the report its name is CopyOfSalesQuotation and the report is broken.

    Broken parameter in duplicated SSRS report

    Compiling the report project brings up a list of errors.

    Errors when compiling a duplicated report

    Navigate to each data set parameter and change the name of the report to match the new name e.g. CopyOfSalesQuotation instead fo SalesQuotation

    Broken parameter in duplicated report

    The fixed parameter should look like this

    Dynamics AX 2012 duplicated report data set parameter

    Finally the report designer is working

    Dynamics AX 2012 duplicated report in report designer

    Use Fulltext Index in AX 2012 to search in RSS feeds

    Here is a simple example how to use the fulltext index capability in Dynamics AX 2012. This example downloads RSS postings into a table with fulltext index and runs a query to identify those with specific words.

    Create the RssFeedTable

    1. Create a new String Extended Datatype called RssFeedId
    2. Create a new table called RssFeedTable and add the RssFeedId, Name and Uri
    3. Create a primary index based on the RssFeedId
    4. Set the RssFeedTable as Reference at the RssFeedId Extended
    5. Populate the table with data e.g.
      FeedId = technet
      Name = “Technet Austria”
      Uri = http://feeds.feedburner.com/TechnetTeamBlogAustria?format=xml

    Create the RssContentTable

    1. Create a new Table called RssContentTable
    2. Add the RssFeedId, Description255 and rename it as Title and a Memo field
    3. Create a fulltext index for the memo field

    image image

      Create the RSS Reader Class in C#

      1. Create a new Visual C# class library project for .NET 4.0 in Visual Studio
      2. Add the project to AOT
      3. Copy this code

      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      using System.ServiceModel.Syndication;
      using System.Xml;
      using System.Text.RegularExpressions;

      namespace ErpCoder.RssFeed
      {
          public class Reader
          {
              private string xmlAddress = String.Empty;
              private IEnumerator<SyndicationItem> enumerator;

              public Reader(string address)
              {
                  xmlAddress = address;
              }

              public void Load()
              {
                  var reader = new XmlTextReader(xmlAddress);
                  var feed = SyndicationFeed.Load(reader);
                  enumerator = feed.Items.GetEnumerator();
              }

              public bool Read()
              {
                  return enumerator.MoveNext();
              }

              public string GetTitle()
              {
                  return enumerator.Current.Title.Text;
              }

              public string GetDescription()
              {
                  string text = Regex.Replace(enumerator.Current.Summary.Text,
                                               "<(.|n)*?>", String.Empty);
                  return text;
              }       
          }
      }

      1. Set the Deployment Target at the project to Client: Yes and deploy

      Populate the RssContentTable

      1. Create a find method on the RssFeedTable
      2. Copy this code and substitute the parameter in the find method with one of yours

      static void populateFeedTable(Args _args)
      {
          System.Exception ex;
          RssFeedTable feedTable = RssFeedTable::find("techat");
          RssFeedContent content; 
          ErpCoder.RssFeed.Reader reader;
          InteropPermission permission;
       
          permission = new InteropPermission(InteropKind::ClrInterop);    
          permission.assert();

          try
          {
              reader = new ErpCoder.RssFeed.Reader(feedTable.Uri);
              reader.Load();

              while(reader.Read())
              {
                  content.clear();
                  content.Title = reader.GetTitle();
                  content.Text = reader.GetDescription();
                  content.FeedId = feedTable.FeedId;
                  content.insert();
              }
          }
          catch(Exception::CLRError)
          {
              ex = CLRInterop::getLastException();
              error(ex.get_Message());
          }

          CodeAccessPermission::revertAssert();
      }

      Create a job to test the fulltext index

      1. Create a new job and copy this code

        static void queryFullText(Args _args)
        {
            RssFeedContent feedContent;
            Query query = new Query();

            QueryBuildDataSource qbdsContent =
             query.addDataSource(tableNum(RssFeedContent));

            QueryBuildRange rangeText =
             qbdsContent.addRange(fieldNum(RssFeedContent,Text),
                                  1,QueryRangeType::FullText);
            QueryRun queryRun;
            rangeText.value("Office Hyper-V");

            queryRun = new QueryRun(query);
            while(queryRun.next())
            {
                feedContent = queryRun.get(tableNum(RssFeedContent));
                info(feedContent.Text);
            }
        }

      2. Subsitute the value of the fulltext range value with yours
      3. Test the query

      image

      @Dynamics AX Technical Conference

      You can find me at the Dynamics AX Technical Conference in Nice

      Extend SalesTable2Line Framework

      Dynamics AX provides a Framework for updating changes made on SalesTable fields to SalesLine fields. The update method can be configured within Dynamics AX at Accounts Receivable > Setup > Parameters > Updates > “Update order Line”. This form is used to configure if and how changes made to the SalesTable are written to the lines. This framework can be extended to update your own fields, like a “Notes” field in the example below.

      Dynamics AX 2009 Update Order Lines

      1. Create a new field called "SalesNotes” in the SalesTable and SalesLine
      2. Add the SalesTable.SalesNotes field to the Field Group HeaderToLineUpdate at the SalesTable
        Put Sales Notes field HeaderToLineUpdate group in the SalesTable
      3. Display the new fields in the SalesTable form e.g. in the General tab
      4. Open the SalesTable2LineParameters table in the table browser and remove all records. Don’t worry, these data will be populated automatically and is the basis for the “Update order line” form.
      5. Add the following line to the SalesTable2LineField.lineUpdateDescription method

        case fieldnum(SalesTable, SalesNote):
            return fieldid2pname(tableNum(SalesLine), fieldNum(SalesLine, SalesNote));

        Modify the SalesTable2LineField.lineUpdateDescription method

         

      6. Add a parameter method for the SalesNote field to the AxSalesTable class

        public SalesNote parmSalesNote(SalesNote _salesNote = ”)
        {
            if (!prmisdefault(_salesNote))
            {
                this.setField(fieldnum(SalesTable, SalesNote), _salesNote);
            }

            return salesTable.SalesNote;
        }

      7. Add a parameter method for the salesNote field to the AxSalesLine class

        public SalesNote parmSalesNote(SalesNote _salesNote = ”)
        {
            if (!prmisdefault(_salesNote))
            {
                this.setField(fieldnum(SalesLine, SalesNote), _salesNote);
            }

            return salesLine.SalesNote;
        }

      8. Create a setSalesNote method on the AxSalesLine class

        protected void setSalesNote()
        {
            if (this.isMethodExecuted(funcname(), fieldnum(SalesLine, SalesNote)))
            {
                return;
            }

            this.setAxSalesTableFields();

            if (this.isAxSalesTableFieldsSet() || this.axSalesTable().isFieldModified(fieldnum(SalesTable, SalesNote)))
            {
                this.parmSalesNote(this.axSalesTable().parmSalesNote());
            }
        }

      9. Modify the setTableFields method to call the setSalesNote method
        Call the setSalesNote method
        Test your modification. Open the “Update order line” form and set the update method for Sales Notes to “Prompt”. Open the sales order details form, go to your new field, modify the text and save. A dialog will appear and ask your to update the Note fields. Click OK, and review the Sales Notes in the SalesLines.

      Modify the SalesNote value in a sales order

      Review the update on the sales line

      Add Sound to Infolog

      I’ve been asked by a colleague if it is possible to let the Infolog make a noise depending on the log type, e.g. a *beep* for an info, a *pling* for a warning and an evil noise for an error. Fortunately we can use .NET in AX 2009. The System.Media.SystemSound class already provides access to the typical system sounds. I’ve modified the Info class in AX this way:

      Exception add(
          Exception _exception,
          str _txt,
          str _helpUrl = ”,
          SysInfoAction _sysInfoAction = null,
          boolean buildprefix = true)
      {

          int numOfLines,i;
          int actionClassId;
          container packedAction;
          xSession session;   

          System.Media.SystemSound sound;
          InteropPermission permission;

          ;

      // default infolog code here …

          permission = new InteropPermission(Interopkind::ClrInterop);
          permission.assert();
         
      switch(_exception)
          {
              case(Exception::Info):
              sound = System.Media.SystemSounds::get_Asterisk();
              break;
              case(Exception::Warning):
              sound = System.Media.SystemSounds::get_Hand();
              break;
              case(Exception::Error):
              sound = System.Media.SystemSounds::get_Exclamation();
              break;
          }    
          if(sound!=null)
              sound.Play();

          CodeAccessPermission::revertAssert();

          return super(_exception, (buildprefix?getprefix():”)+_txt);
      }

      Enterprise Portal Custom Filter Error after a short time

      Again I experienced a strange behavior within Dynamics AX 2009 Enterprise Portal. I’ve created a AxGridView using an AxDataSource connected to a DataSet in Dynamics AX. The DataSet holds a setFilter method to set values on some QueryBuildRanges. Moreover I’ve create a button in my AxUserControl Webpart that invokes the setFilter method with some values.

      protected void SearchButton_Click(object sender, EventArgs e)
      {
         string value1 = TextBox1.Text;
         string value2 = TextBox2.Text;
         AxDataSourceMyTable.GetDataSet().DataSetRun.AxaptaObjectAdapter.Call  
          (“setFilter”,value1,value2);
      }

      public void setFilter(str _value1, str value2)
      {;
         qbrSomeField.value(_value1);
         qbrAnotherField.value(_value2);
         MyTable_DS.executeQuery();
      }

      This implementation worked fine the first time using the webpart. However, after a very short time I got an error telling me that no Business Connector Session was found.

      Microsoft.Dynamics.Framework.BusinessConnector.Session.Exceptions.NoKernelSessionException

      First I thought of some kind of timeout and played with IIS settings. But Google found this discussion where it is explained that Dynamics AX deletes the QueryBuildRange objects after a very short time, and therefore the call fails. The solution is to use SysQuery::findOrCreateRange .

      public void setFilter(str _value1, str value2)
      {
         QueryBuildDataSource qbds;
         ; 
         qbds = MyTable_DS.query().dataSourceTable(tableNum(MyTable))
         SysQuery::findOrCreateRange(qbds,fieldNum(MyTable,Field1)).value(_value1);
         SysQuery::findOrCreateRange(qbds,fieldNum(MyTable,Field2)).value(_value2);
         MyTable_DS.executeQuery();
      }