Outbound web service (AIF)


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:


Comments (36)

  1. Dilip says:

    Hi! David,

    It works. Thanks for writing this article. I have tried this example and it works. Now, I will extend this example to our real-time implementation, where the outbound webservice is a Biztalk orchestration webservice. Will give it a shot and let you know the results.

    Thanks mate for your help. Have a great day!

    Regards,

    Dilip

    dilip.nair@keaneaustralia.com

  2. dpokluda says:

    Thank you Dilip. I’m glad it was useful. If there is something else about AIF (or other areas of Dynamics AX) where you think I can help then please let me know.

    David.

  3. David,

    Very well done and put together! I am glad that you have taken the time to put such a great example out on this. I myself was going to, but I am have been super busy. Instead I created a post on my blog that points to this one!

    Great job!

    -J. Brandon George

  4. Justin Biggs says:

    David,

    This is great!  I’m having a bit of trouble however, and I don’t know if you’ve seen this or not.  For some reason I only have the Endpoints Form under Basic > Setup > Application Integration Framework.  I tried launching the AifLocalEndpoints form from the AOT, and got a "not enough rights" message, even though I’m logged in under the Admin user.  Any ideas?

    -Justin

  5. dpokluda says:

    Hi Justin,

    the reason might be that you don’t have AIF license. All tables except Endpoint* tables require AIF configuration key. Endpoint* tables require LogisticsBasic configuration key. Because these tables are used as data sources in AIF forms that’s why you don’t see them. Endpoint is different because there is a business scenario where customers without AIF might still use this form/tables.

    You need to have that to be able to use AIF forms.

    I hope it helps. Let me know if you need more info.

    David.

  6. Justin Biggs says:

    David,

    DOH!  You have no idea how happy and upset you made me.  Happy that I now know the answer, and upset with myself for not recognizing it sooner.  I thought we had the license but it was the EP license instead of AIF.  Many thanks, David.

    -Justin

  7. Phil says:

    David,

    Great written…

    Can you make one for inbound as well!

  8. dpokluda says:

    Hi,

    can you be more specific? What exactly would you like me to do for the inbound case? I can try to write a similar post but I would like to understand your problem correctly.

    Thanks,

    David.

  9. Phil says:

    I need some instructions how to use the web-services that are generated by the AIF-framwork. For exampel, how to call the web-service to create an salesorder in Ax. I have som problem to make it work…

  10. Somitra says:

    Hi David , did you find any solution to create send electonically button for custom Schema . For Example : if one wants to generate schema for customer master and want to send the same electronically , how will it be. Any help on this would be amazing

  11. dpokluda says:

    Hi Somitra, can’t you just call getSchema on the corresponding AXD class?

    David.

  12. FNS says:

    But as I make to generate a WebService leaving from a WSDL of an external service ?

  13. Johan says:

    It kind of works: my webservice is called without errors, but it adds just an empty message to the cache. I suppose it should contain the info from the Purchase order?

    What am I missing?

    Any help would be greatly appreciated.

    Thanks,

    Johan

  14. Jan Van Roey says:

    I have the same problem as Johan. The webservice receives a null-string to put in the cache.

    On one side, if I call the webservice from internet explorer, enter a textmessage and ‘invoke’ the method, it works correctly. So I guess the IIS-Webservice is correctly set up.

    On the other side, in AX I drilled down to the method-call AIFWebserviceSendAdapter.sendmessage, where the instruction webserviceHelper.sendmessage correctly sends the xml-message out.

    So, I guess the problem is somewhere in between.

    Why would the com-component ‘Microsoft.Dynamics.IntegrationFramework.Adapter.Webservice’ loose the message ??

    Thanks, Jan

  15. Jan Van Roey says:

    I have found the problem for the empty string.

    The string parameter name in the webservice method MUST BE NAMED ‘message’. I had given it a more appropriate name ‘_AXDocument’.

    But apparently the [webmethod] attribute uses this exact name to construct the webservice.

  16. nice boobs says:

    bouncing boobs

    <a href="http://boobs.shoutpost.com/ ">

    bouncing boobs

    </a>

    [url=http://boobs.shoutpost.com/ ]

    huge boobs

    [/url]

    http://boobs.shoutpost.com/

  17. ... says:

    Stupore! Amo questo luogo!:)))))))

  18. Yunfeng says:

    Hi David,

    It is a good article. Thanks for your efforts!

    I have the following problems:

    1) When using "localhost" to connect the web service (AIF->channels…), the error is:

    "…404…Please review the following URL and make sure that it is spelled correctly.

                           <b> Requested Url: </b>/AifWebServiceTest/MyWebService.asmx<br>…". Why the leading "http://localhost/&quot; was truncated?

    2) When using real ip address, I got:

    "You are not authorized to view this page</h1>

    You do not have permission to view this directory or page using the credentials that you supplied."

    Please help solve at your convenience.

    Thanks again!

  19. Safin says:

    Hi,

    I went through the above process and configured webservice as well as Ax for Aif outbound webservice through adapters.

    When i am trying to run the batch after doing the configuration for the outbound web service i get this error message:

    Method ‘sendMessage’ in COM object of class

    ‘Microsoft.Dynamics.IntegrationFramework.Adapter.WebService’ returned error

    code 0x80131600 (<unknown>) which means: An error occurred while the

    Application Integration Framework Web service method was being invoked.

    (Response code: 500; Response text: <?xml version="1.0"

    encoding="utf-8"?><soap:Envelope

    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/&quot;

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance&quot;

    xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><soap:Fault><faultcode>soap:Client</faultcode><faultstring>System.Web.Services.Protocols.SoapException:

    Server did not recognize the value of HTTP Header SOAPAction:

    http://localhost/AifWebServiceTest/MyWebService.asmx/readPurchaseRequisition.

      at System.Web.Services.Protocols.Soap11ServerProtocolHelper.RouteRequest()

      at

    System.Web.Services.Protocols.SoapServerProtocol.RouteRequest(SoapServerMessage message)

      at System.Web.Services.Protocols.SoapServerProtocol.Initialize()

      at System.Web.Services.Protocols.ServerProtocolFactory.Create(Type type,

    HttpContext context, HttpRequest request, HttpResponse response, Boolean&

    abortProcessing)</faultstring><detail

    /></soap:Fault></soap:Body></soap:Envelope>).

    Can anyone let me know where it is failing and the reason for it.

  20. JJ says:

    Hi  david,

    Nice article !

    Is it the only way to call Ax outbound web services ? We do have Querycriteria class and if the web service is already hosted on machine cant we write .NET code to consume that web service using the normal .NET code and passing the required parameters ?

    Is it not the way ? I am trying to do some thing similar let me know your thought.

  21. JJ says:

    Hi  david,

    Nice article !

    Is it the only way to call Ax outbound web services ? We do have Querycriteria class and if the web service is already hosted on machine cant we write .NET code to consume that web service using the normal .NET code and passing the required parameters ?

    Is it not the way ? I am trying to do some thing similar let me know your thought.

  22. santosh.r says:

    Whenever exception is thrown in Web-service.

    Does it do it through AifFault class in Ax.

    I have read that AifFault only sends the "generic message" to the end user & complete log can be viewed in Exception table.

  23. santosh.r says:

    Can u kindly give a detail description as to "How exception is dealt in Webservice". Is there any relation with "AifFault" class to throw error

  24. Gundwale says:

    Hi David, the result I get back is <string xsi:nil="true" /> and thats that? Doing something wrong, I would expect to receive the full xml document as a string?

  25. Ellen says:

    Hi!

    In connection to AIF, I use readPurchaseRequisition and need to add print information to the existing XML document.

    Do you know how to do this? Some tips?

    I can’t find the class where DAX actually write to the XML document…

    Thanks

  26. I’m gonna talk about a new feature in Dynamics AX 2009 (Dynamics AX "5.0") which is the really powerful

  27. Error : You can not recognized user of Microsoft Dynamics

  28. Jimmy Glass says:

    Hi David,

    I’m desperately searching for documentation on the handshake between BizTalk and AX. I have reviewed ConfigureAIFBizTalkAdpaterForDataExchange.PDF and found it very informative… However, there was little to no discussion on how to automate error handling.

    Using an asynchronous call, how do you retrieve error messages from AX? If I were to use a Synchronous methodology as you have described above… It appears AX will only return an Entity Key, but… No error message.

    Do you know where I can find this type of information?

    Thanks,

    Jimmy G

  29. Jorge says:

    hi,

    some questions.

    my visual studio 2005 haven’t got asp.net web services template and my dinamics ax 4 haven’t got "basic" or I can’t find it.

    any solution?

  30. Hcbcomm says:

    Generally call centers provide this highly-successful outbound call center services to provide customary services. This helps in<a href="http://www.hcbcommunications.com/">lead generation services</a>that in turn helps in boosting sales.

  31. developer says:

    i can download the dll to upgrade to AX 4.01

  32. I am really grateful to have the information from this blog.I liked the blog as it has been written,the information  i got from here.