Receive large texts via AIF

One of our customer’s business partner transmits data as XML file. However, they did not make it to use a structured xml document service, but send a string that (in most cases) contains a valid xml document. Therefore we’ve setup an AIF service, using a class that takes one str parameter called _txt and stores it at a memo field.

public void storeText(str _text)
{
    AifBigData bigData;
    ;
    bigData.Text = _text;
    bigData.insert();
}

However, the business partner recently told us they get an error message while transmitting data to the AIF service telling them the transmitted data is too large. We were able to reproduce the issues by calling the service using a larger random-generated text.

class Program
{
    static void Main(string[] args)
    {
        var client = new BigData.BigDataServiceClient();
        client.ClientCredentials.Windows.ClientCredential = new
         System.Net.NetworkCredential("user","pass123","domain");

        // works fine
        client.storeText("Hello World");

        // fails 😦
        string txt = GenerateBigText();
        client.storeText(txt);
    }

    private static string GenerateBigText()
    {
        var random = new Random(); 
        var sb = new StringBuilder();
        for (int i = 0; i < 61440; i++)
        {
            char c =  (char)random.Next(65, 122);
            sb.Append(c);
        }

        return sb.ToString();
    }
}

The second call results in an exception

The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter http://insideax.at/bigdata:_text. The maximum string content length quota (8192) has been exceeded while reading XML data. This quota may be increased by changing the MaxStringContentLength property on the XmlDictionaryReaderQuotas object used when creating the XML reader.

However, the exception is well explained and contains a solution proposal to set the MaxStringContentLength

Go to Basic > AIF > Services > Button Configure

image

Select Binding > basicHTTPBinding > ReaderQuotas > MaxStringContentLenght and set e.g. 2147483647

image

Save and generate the .NET artifacts. Open windows explorer and navigate to the AIF virtual directory e.g. C:\Program Files\Microsoft Dynamics AX\50\AifWebServices . Select the web.config and open it with internet explorer. Make sure the MaxStringContentLength value is set

image

With this configuration the service call will work and a large text message is stored properly in Dynamics AX

image

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

    Query NoYes fields in AIF 4.0

    found an interesting behavior in AX 4.0 AIF. Querying a document with a NoYes field e.g. InventLocation.Manual requires to use the value “Yes” to become TRUE but value “1” doesn’t work.

    var client = new InventLocationServiceSoapClient();
    client.ClientCredentials.Windows.ClientCredential.Domain = "insideax";
    client.ClientCredentials.Windows.ClientCredential.UserName = "AifUser";
    client.ClientCredentials.Windows.ClientCredential.Password = "********";

    var context = new DocumentContext();
    context.MessageId = Guid.NewGuid().ToString();
    context.SourceEndpoint = "EXTAPP";
    context.DestinationEndpoint = "APP";

    var query = new QueryCriteria();
    query.CriteriaElement = new CriteriaElement[1];
    query.CriteriaElement[0] = new CriteriaElement();
    query.CriteriaElement[0].DataSourceName = "InventLocation";
    query.CriteriaElement[0].FieldName = "Manual";
    query.CriteriaElement[0].Operator = Operator.Equal;
    query.CriteriaElement[0].Value1 = "1";

    var axdInventLocation = client.findListInventLocation(context, query);
    Console.WriteLine(axdInventLocation.InventLocation == null);

    // new guid
    context.MessageId = Guid.NewGuid().ToString();
    // change value
    query.CriteriaElement[0].Value1 = "Yes";

    axdInventLocation = client.findListInventLocation(context, query);
    Console.WriteLine(axdInventLocation.InventLocation == null);
    Console.ReadKey();

    Result is shown here:

    image

    AX 4.0 Send GUID in AIF document

    I’ve noticed a a bug in AX 4.0 (K/A: 4.0.2501.116) AIF implementation when using a table field type GUID. Unfortunately the generated XSD schema referred to the field type as Int64 instead of GUID. The sent XML of course contained an GUID String {00000000-1234-5678-9101-000000000000}. My C# .NET client reported an error because the received value was not a valid Int64.
    I solved the problem by editing AxdBaseGenerateXSD.addGuid() method
    protected void addGuid(
    SysDictType dt,
    XmlSchemaSimpleTypeRestriction restriction
    )
    {;
    /*  restriction.baseTypeName(
    XmlQualifiedName::construct(
    this.addInt64Type(),
    targetNameSpace)); */
    restriction.baseTypeName (XmlQualifiedName::construct(
    this.addGuidType(),
    targetNameSpace));
    }
    As result the generated XSD schema contained the correct GUID definition
    <xs:simpleType name=“AxdExtType_MyGUID„>
    <xs:annotation>
    <xs:documentation xml:lang=“DE-AT„>A Guid field</xs:documentation>
    </xs:annotation>
    <xs:restriction base=“AxdType_GUID“ />
    </xs:simpleType>

    <xs:simpleType name=“AxdType_GUID„>
    <xs:restriction base=“xs:string„>
    <xs:minLength value=“38“ />
    <xs:maxLength value=“38“ />
    <xs:pattern
    value=“({[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}})“ />
    </xs:restriction>
    </xs:simpleType>

    Connect a Windows Mobile Device with Dynamics AX

    It’s easy to connect a mobile device like a smartphone with Dynamics AX using web services. This tutorial requires Microsoft Dynamics AX 2009 with AIF Webservices installed and Visual Studio for Smart Device development.

    Deploy AIF Service

    1. Go to Basic → Setup → Application Integration Framework → Services
    2. Select Refresh button to update service list
    3. Enable CustCustomerService
    4. Generate the webservice

      Aif Services

      Aif Service

    5. To verify your service is working open the IIS manager
    6. Go to → Sites → Default Web Site  → MicrosoftDynamicsAxAif50  → Content View

      IIS 7 Manager on Windows Server 2008

      IIS 7 Manager on Windows Server 2008

    7. Right Click on CustomerSerivce.svc and Browser
    8. You should see a web page with a link to a WSDL file
    9. Follow the link an you should see an XML document containing the service contract

    Implement Mobile Device Project

    1. Open Visual Studio and Create a new Solution
    2. Add a new Smart Device Project
    3. Go to References and add a new service reference
    4. Provide the URL of your Dynamics AX CustomerService
    5. Open Form1.cs file
    6. Add a text field (Name: textBoxAccountNum),
      a button (Name: buttonLookup)
      and a label (Name: labelName) to your form
    7. Double Click on the button and implement the service call:
    private void buttonLookup_Click(object sender, EventArgs e)
    {
        System.Net.NetworkCredential credentials = new System.Net.NetworkCredential();
        credentials.Domain = "YOUR_DOMAIN_NAME";
        credentials.UserName = "YOUR_USER_NAME";
        credentials.Password = "YOUR_USER_PASSWORD";
    
        QueryCriteria query = new QueryCriteria();
        query.CriteriaElement = new CriteriaElement[1];
        query.CriteriaElement[0] = new CriteriaElement();
        query.CriteriaElement[0].DataSourceName = "CustTable";
        query.CriteriaElement[0].FieldName = "AccountNum";
        query.CriteriaElement[0].Operator = Operator.Equal;
        query.CriteriaElement[0].Value1 = textBoxAccountNum.Text;
    
        CustomerService service = new CustomerService();
        service.Credentials = credentials;
        try
        {
            AxdCustomer customer = service.find(query);
            if (customer != null &&
                customer.CustTable != null &&
                customer.CustTable.Length > 0)
            {
                labelName.Text = customer.CustTable[0].Name;
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show("Service Call failed",
                            "Error",
                            MessageBoxButtons.OK,
                            MessageBoxIcon.Hand,
                            MessageBoxDefaultButton.Button1);               
        }           
    }

    You application should look like that:

    Windows Mobile 5 Emulator

    Windows Mobile 5 Emulator