In this post I'll try to demonstrate how to use AIF outbound web service adapter. I'll begin with building a sample web service implementing AIF SendMessage "interface". We will validate the web service by building a test.

For the end-to-end test we will need to deploy the web service to a real IIS server. When IIS is configured and web serviced deployed we will configure AIF in Dynamics AX 4.0.

At the end we will run our end-to-end test by sending purchase requisition to our web service.

Web Service

Let's begin with building our new web service. I assume you have Microsoft VisualStudio 2005 and NUnit (you can use Team System test framework or any other unit test framework but I picked NUnit because I believe that everybody has access to this one) ready on your box.

Generate project templates

  • Create a new blank solution named AifOutboundWebService in Visual Studio 2005.
  • Add new ASP.NET Web Service project named AifOutboundWebService by right-clicking on your solution and selecting Add > New Web Site.... Select ASP.NET Web Service template and set name to AifOutboundWebService. (I'm also selecting File System and C# as the target language.)
  • Delete generated web service Service.asmx and code file App_Code/Service.cs.
  • Create new Web Service named MyWebService by right-clicking on your web site project and selecting Add New Item.... Select Web Service and set name to MyWebService.
  • Rename web method HelloWorld to GetTestMessage.
  • Update web service namespace to:
 [WebService(Namespace = "http://localhost/AifWebServiceTest/MyWebService.asmx/")]
  • Create new class library project by right-clicking on your solution and selecting Add > New Project.... Select Class Library and set name to AifWebServiceTest.
  • Rename Class1.cs to MyWebServiceTest.cs in Solution Explorer.
  • Add reference to NUnit.Framework.dll.
  • Add NUnit.Framework namespace to your test class. using NUnit.Framework;
  • Build the solution.
  • Start web service by right-clicking on the web service project and selecting View in Browser... menu item.
  • Add web reference to our new web service by right-clicking your NUnit test project and selecting Add Web Reference.... Copy URL from IE running your web service (on my box that is http://localhost:1234/AifOutboundWebService/) and click Go button. Click on MyWebService.asmx, change Web reference name from localhost to MyWebService and click Add Reference.
  • Add simple test to your newly created class AifWebServiceTest:
 using System;
 using System.Collections.Generic;
 using System.Text;
 using NUnit.Framework;
 
 namespace AifWebServiceTest
 {
    [TestFixture]
    public class MyWebServiceTest
    {
        private MyWebService.MyWebService service;

        [Test]
        public void TestHelloMessage()
        {
            Assert.AreEqual("Hello World", service.GetTestMessage());
        }

        [SetUp]
        public void SetUp()
        {
            service = new MyWebService.MyWebService();
        }
        [TearDown]
        public void TearDown()
        {
            service = null;
        }
    }
 }
  • Start the web service (by right-clicking on the web service project and selecting View in Browser...)
  • Start NUnit and run the test.

Requirements for MyWebService

Let's update the unit test to capture requirements for our web service. Our web service will store all messages sent in a cache. Later you can ask the web service to retrieve all messages sent to it.

Add the following tests to your unit test (notice that Array2String is a helper method converting array of string to a string representation of that array):

ClearMessages

This method clears the cache:

  • when called then GetMessages always returns an empty array (until a new message is added)
        [Test]
        public void TestClearMessages()
        {
            string message = service.GetTestMessage();
            Assert.AreEqual(0, service.GetMessages().Length);
            service.SendMessage(message);
            Assert.AreEqual(1, service.GetMessages().Length);

            //ClearMessages: clears the cache
            service.ClearMessages();
            Assert.AreEqual(0, service.GetMessages().Length);
        }

GetMessages

This method returns all messages from the cache:

  • when called then it returns an array of strings
        [Test]
        public void TestGetMessages()
        {
            string message = service.GetTestMessage();
            List< string > emptyList = new List< string >();
            Assert.AreEqual(Array2String(emptyList.ToArray()), Array2String(service.GetMessages()));

            service.SendMessage(message);
            emptyList.Add(message);
            service.SendMessage(message);
            emptyList.Add(message);

            //GetMessages: retrieves an array of messages from the cache
            Assert.AreEqual(Array2String(emptyList.ToArray()), Array2String(service.GetMessages()));
        }

SendMessage

This method sends a message to the cache:

  • when called the message passed to it is added to the cache
  • when another instance of the same message is sent then both of them appear in the cache
        [Test]
        public void TestSendMessage()
        {
            string message = service.GetTestMessage();
            List< string > emptyList = new List< string >();
            Assert.AreEqual(Array2String(emptyList.ToArray()), Array2String(service.GetMessages()));

            //SendMessage: add string ( = input ) to the cache
            service.SendMessage(message);
            emptyList.Add(message);
            Assert.AreEqual(Array2String(emptyList.ToArray()), Array2String(service.GetMessages()));

            service.SendMessage(message);
            emptyList.Add(message);
            Assert.AreEqual(Array2String(emptyList.ToArray()), Array2String(service.GetMessages()));
        }

readPurchaseRequisition

We will also need this method for our AIF end-to-end scenario:

  • this will be exactly same as our SendMessage
        [Test]
        public void TestReadPurchaseRequisition()
        {
            string message = service.GetTestMessage();
            List< string > emptyList = new List< string >();
            Assert.AreEqual(Array2String(emptyList.ToArray()), Array2String(service.GetMessages()));

            //SendMessage: add string ( = input ) to the cache
            service.readPurchaseRequisition(message);
            emptyList.Add(message);
            Assert.AreEqual(Array2String(emptyList.ToArray()), Array2String(service.GetMessages()));

            service.readPurchaseRequisition(message);
            emptyList.Add(message);
            Assert.AreEqual(Array2String(emptyList.ToArray()), Array2String(service.GetMessages()));
        }

Helper method

Let's code the helper method:

        private string Array2String(string[] array)
        {
            StringBuilder content = new StringBuilder();
            content.Append("<");
            foreach (string value in array)
            {
                if (content.Length > 1)
                    content.Append(", ");
                content.AppendFormat("\"{0}\"", value);
            }
            content.Append(">");

            return content.ToString();
        }

Coding our cache

So now we know exactly how our web service should behave. Let's begin with our cache for storing messages.

  • Add new class named AifMessageCache.cs to our web service project (confirm message asking to put the class into App_Code folder):
 using System;
 using System.Collections.Generic;

 internal class AifMessageCache
 {
    private static List< string > messages = new List< string >();

    public static void Clear()
    {
        messages = new List< string >();
    }
    public static List< string > Messages
    {
        get { return messages; }
    }
    public static void Add(string message)
    {
        messages.Add(message);
    }
 }

Coding MyWebService

Let's code our web methods ClearMessages, GetMessages, SendMessage and readPurchaseRequisition. It will be extremely simple because all we need to do is to delegate the call to our message cache.

    [WebMethod]
    public void ClearMessages()
    {
        AifMessageCache.Clear();
    }
    [WebMethod]
    public void SendMessage(string message)
    {
        AifMessageCache.Add(message);
    }
    [WebMethod]
    public void readPurchaseRequisition(string message)
    {
        AifMessageCache.Add(message);
    }
    [WebMethod]
    public List< string > GetMessages()
    {
        return AifMessageCache.Messages;
    }

For this code to compile we have to add support for generic collections:

 using System.Collections.Generic;

Build the solution

  • Build the web service project by right-clicking on it and selecting Build Web Site.... Now we need to update web service reference in our NUnit project. Right-click on the web reference to MyWebService and select Update Web Reference. Now let's rebuild the whole solution by right-clicking on the solution and selecting Rebuild Solution menu item.
  • Start the web service by right-clicking the web service project and selecting View in Browser.... Run all the unit tests to validate the basic functionality.

We get one failure in TestSendMessage. This failure is caused by a wrong assumption. Our tests assume that the cache is empty when the test begins. That's not true and thus we have to call ClearMessages before every test. To do that update SetUp method in your test class:

        [SetUp]
        public void SetUp()
        {
            service = new MyWebService.MyWebService();
            service.ClearMessages();
        }

Rebuild the solution and rerun the tests again. You should see all the tests passing.

IIS

I assume you have IIS configured on your box (localhost).
  • Create new folder AifWebServiceTest and copy there the content of your AifOutboundWebService folder (including App_Code and App_Data subfolders).
  • Open Internet Information Services (IIS) Manager from Administrative Tools. Go to your Default Web Site and display properties for AifWebServiceTest folder (right-click on the AifWebServiceTest folder and select Properties menu item).
  • Check Directory browsing, click Create button (to create web application). Go to Directory Security tab and check Integrated Windows authentication and uncheck Enable anonymous access. Close the dialog by clicking OK.
  • Check ASP.NET settings by going back to Properties, navigate to ASP.NET tab and verify that version 2.0.50727 is selected as ASP.NET version.
  • Validate the application by right-clicking on it and selecting Browse. You should see a listing of files. Click on MyWebService.asmx and you should see the web service methods listed.
  • Try the same using your browser -- open the browser and navigate to http://localhost/AifWebServiceTest/MyWebService.asmx.

Note:

  1. Make sure your default web site and your new web application are running ASP.NET 2.0.
  2. Your NUnit test project is not running against this new IIS web application. If you want that then you have to update the web reference to point to your new web application -- delete the current web reference and add new using the IIS URL: http://localhost/AifWebServiceTest/MyWebService.asmx.

Aif configuration

  • Open Microsoft Dynamics AX 4.0.
  • Navigate to Basic > Setup > Application Integration Framework.
  • Add new local endpoint with Local endpoint = MyLocalEndpoint.
  • Add transport adapter: Class = AifWebServiceAdapter and don't forget to check Active.
  • Add channel: ID = MyChannel, Name = MyChannel, Adapter = Web Service Adapter, Direction = Outbound, Address = http://localhost/AifWebServiceTest/MyWebService.asmx and don't forget to check Active.
  • Adding actions begins by clicking Scan and register button in Actions form. When the scan is done, navigate to readPurchaseRequisition and check Enabled.
  • Add endpoint: ID = MyEndpoint, Name = MyEndpoint, Local endpoint ID = MyLocalEndpoint. Go to General tab and select Outbound channel ID = MyChannel. Go to Constraints tab and check No constraints. Go to Users tab and add Admin user to Endpoint users section. Click Action policies to open endpoint policies form. Add readPurchaseRequisition action and set Status = Enabled, Loggins Mode = Log All. After saving the policies, close that form. Activate the endpoint by checking Active checkbox on the General tab page.

Final test

  • Create and post a purchase order (Accounts Payable > Purchase Order, create a purchase order, Posting > Purchase Order)
  • Send the purchase order electronically (Inquiries > Purchase, Send electronically > Original)
  • Execute job RunAifServices (you should configure batch process but since this is not the main focus of this post I will cheat a little bit here).
 static void RunAifServices(Args _args)
 {
    AifGatewaySendService gtwySend = new AifGatewaySendService();
    AifOutboundProcessingService outSvc = new AifOutboundProcessingService();
    ;
    print 'Running Outbound Processing Service...';
    outSvc.run();

    print 'Running Gateway Send Service...';
    gtwySend.run();

    print "AIF Services complete";
    pause;
 }
  • No error message should be displayed
  • Verify the send operation
    • Navigate to http://localhost/AifWebServiceTest/MyWebService.asmx.
    • Click on GetMessages and click on Invoke.
    • Verify the message is displayed

External identifier override

What if our web service doesn't have readPurchaseRequisition but only SendMessage. Can we still configure AIF so that it calls this method instead of readPurchaseRequisition? Yes we can. Go to Endpoint configuration form, select your endpoint and go to endpoint action policies form by clicking Action policies. Navigate to readPurchaseRequisition row and enter SendMessage to External Identifier Override. This will force AIF to call SendMessage whenever there is a message for readPurchaseRequisition action.

Rerun the test and verify that the message was sent to the web service.

FAQs, Tips and Tricks

Error messages

Problem: In case of a problem with Dynamics AX 4.0 you don't receive a valid error message but instead an error message saying:

 Could not find any resources appropriate for the specified culture or the neutral culture.

Solution: This is a known error that was already fixed. The fix will be part of 4.01 release. If you want you can download updated DLL from here.

  • Exit all Dynamics AX instances (clients)
  • Restart AOS
  • Copy the DLL to your Dynamics AX 4.0 Server\{CompanyName}\Bin folder

Now whenever there is an error with Outbound web service adapter in AIF you should receive a better error message containing the real cause of an error.

Can't call web method

Problem: You keep on getting a SOAP error that it's not possible to call your web method. You are sure that everything is fine but you keep on getting this error message.

Solution: Try to verify that the namespace defined in your web service corresponds to the web service URL. If your web service URL is http://localhost/AifWebServiceTest/MyWebService.asmx then your web service namespace should be set that way:

 [WebService(Namespace = "http://localhost/AifWebServiceTest/MyWebService.asmx/")]

Also, please note the trailing forward slash character. This all is very important!


You can download the complete solution at: AifOutboundWebService


Other resources

For more information about AIF I suggest: