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 = "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 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 localhost/AifWebServiceTest/MyWebService.asmx.
Note:
- Make sure your default web site and your new web application are running ASP.NET 2.0.
- 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: 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 = 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 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 localhost/AifWebServiceTest/MyWebService.asmx then your web service namespace should be set that way:
[WebService(Namespace = "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:
- Help AxITPro.chm (located in Microsoft Dynamics AX 4.0 Client\bin\Help subfolder), navigate to Managing external connections > Application Integration Framework
- Arthur Greef, Michael Fruergaard Pontoppidan, Lars Dragheim Olsen, Inside Microsoft Dynamics AX 4.0, Chapter 9 (Pontoppidan's blogs, Agermark's blog and Amazon)
- David Bowles's blog at <daxdave.blogspot.com/2006/08/aif-configuration-and-additional.html>
Comments
Anonymous
October 05, 2006
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.comAnonymous
October 06, 2006
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.Anonymous
October 10, 2006
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 GeorgeAnonymous
October 17, 2006
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? -JustinAnonymous
October 17, 2006
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.Anonymous
October 18, 2006
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. -JustinAnonymous
October 28, 2006
David, Great written... Can you make one for inbound as well!Anonymous
October 30, 2006
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.Anonymous
October 30, 2006
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...Anonymous
November 14, 2006
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 amazingAnonymous
November 30, 2006
Hi Somitra, can't you just call getSchema on the corresponding AXD class? David.Anonymous
January 17, 2007
But as I make to generate a WebService leaving from a WSDL of an external service ?Anonymous
January 24, 2007
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, JohanAnonymous
January 29, 2007
The comment has been removedAnonymous
January 30, 2007
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.Anonymous
April 06, 2007
boob <a href="http://www.spin3000.com/forum/images/avatars/big/nice-boobs.htm "> bouncing boobs </a> [url=http://www.spin3000.com/forum/images/avatars/big/nice-boobs.htm ] bouncing boobs [/url] http://www.spin3000.com/forum/images/avatars/big/nice-boobs.htmAnonymous
April 06, 2007
bouncing boobs <a href="http://boobs.shoutpost.com/ "> bouncing boobs </a> [url=http://boobs.shoutpost.com/ ] huge boobs [/url] http://boobs.shoutpost.com/Anonymous
April 11, 2007
baby got boobs <a href="http://www.leal-alfa.upc.edu/mensajes.php?id=830 "> big boobs </a> [url=http://www.leal-alfa.upc.edu/mensajes.php?id=830 ] boob [/url] http://www.leal-alfa.upc.edu/mensajes.php?id=830Anonymous
April 16, 2007
Stupore! Amo questo luogo!:)))))))Anonymous
April 18, 2007
huge boobs <a href="http://belfastcresting.com/pear/tmp/conf/boobs9.html "> bouncing boobs </a> [url=http://belfastcresting.com/pear/tmp/conf/boobs9.html ] small boobs [/url] http://belfastcresting.com/pear/tmp/conf/boobs9.htmlAnonymous
April 19, 2007
bouncy boobs <a href="http://belfastcresting.com/pear/tmp/var/boobs9.html "> bouncing boobs </a> [url=http://belfastcresting.com/pear/tmp/var/boobs9.html ] boobs [/url] http://belfastcresting.com/pear/tmp/var/boobs9.htmlAnonymous
June 04, 2007
Hi David, It is a good article. Thanks for your efforts! I have the following problems:
- 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/" was truncated?
- 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!
Anonymous
October 01, 2007
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/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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.Anonymous
October 16, 2007
The comment has been removedAnonymous
October 16, 2007
The comment has been removedAnonymous
October 24, 2007
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.Anonymous
October 24, 2007
Can u kindly give a detail description as to "How exception is dealt in Webservice". Is there any relation with "AifFault" class to throw errorAnonymous
December 13, 2007
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?Anonymous
January 03, 2008
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... ThanksAnonymous
April 21, 2008
I'm gonna talk about a new feature in Dynamics AX 2009 (Dynamics AX "5.0") which is the really powerfulAnonymous
May 12, 2008
Error : You can not recognized user of Microsoft DynamicsAnonymous
October 07, 2008
The comment has been removedAnonymous
March 16, 2009
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?Anonymous
October 19, 2009
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.Anonymous
November 09, 2009
i can download the dll to upgrade to AX 4.01Anonymous
August 08, 2010
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.