I have created a C# class library that uses document services for SalesSalesOrderService to create a sales order (just the header) and then a separate class method that updates a salesorder by adding a sales line to an existing sales order. The class library is added to the AOT in AX, it deploys fine, the intellisense in AX works fine, etc. I get no errors.
The problem, however, is that nothing happens when I execute the code in my test job and the debugging doesn't make it to the infolog statement. I also tried debugging with VS by attaching to the AX process but it never goes into VS. I am perplexed as this seems to be pretty simple. If you need more information just let me know. Thanks in advance for any help.
Below is the code from the class library and the job code that I am executing. I am using .NET framework 4.5.1
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using ALTAIFIntegration.DEVERP7SalesOrders; namespace ALTAIFIntegration { public class Deverp7SalesOrders { public string createSOHeader(string company, string custAccount, string invAccount, string delName, string poFormNum, string city, string county, string state, string street, string zip, string countryRegionId) { var salesTableAddressRec = new AxdEntity_TableDlvAddr() { City = city, County = county, State = state, Street = street, ZipCode = zip, CountryRegionId = countryRegionId }; var salesTable = new AxdEntity_SalesTable() { CustAccount = custAccount, InvoiceAccount = invAccount, DeliveryName = delName, PurchOrderFormNum = poFormNum, ReceiptDateRequested = DateTime.Now.Date, SalesType = AxdEnum_SalesType.Sales, TableDlvAddr = new AxdEntity_TableDlvAddr[] { salesTableAddressRec } }; AxdSalesOrder newSalesOrder = new AxdSalesOrder() { SalesTable = new AxdEntity_SalesTable[] { salesTable } }; var callContext = new CallContext { Company = company }; var client = new SalesOrderServiceClient(); try { client.create(callContext, newSalesOrder); client.Close(); return "Created"; } catch { client.Abort(); return "Failed"; throw; } } private static EntityKey[] EntityKeyForSalesId(string salesId) { KeyField field = new KeyField() { Field = "SalesId", Value = salesId }; EntityKey key = new EntityKey() { KeyData = new[] { field } }; return new[] { key }; } public string addSOLine(string soId, string soItemId, decimal qty, string invColorId) { using (SalesOrderServiceClient client = new SalesOrderServiceClient()) { EntityKey[] entityKeyList = EntityKeyForSalesId(soId); // Retrieve the order to modify var order = client.read(new CallContext(), entityKeyList); var inventDim = new AxdEntity_InventDim() { InventColorId = invColorId }; var salesLineAddressRec = new AxdEntity_TableDlvAddr() { City = order.SalesTable[0].TableDlvAddr[0].City, County = order.SalesTable[0].TableDlvAddr[0].County, State = order.SalesTable[0].TableDlvAddr[0].State, Street = order.SalesTable[0].TableDlvAddr[0].Street, ZipCode = order.SalesTable[0].TableDlvAddr[0].ZipCode, CountryRegionId = order.SalesTable[0].TableDlvAddr[0].CountryRegionId }; var salesLine = new AxdEntity_SalesLine() { ItemId = soItemId, QtyOrdered = qty, action = AxdEnum_AxdEntityAction.create, actionSpecified = true, InventDim = new AxdEntity_InventDim[] { inventDim } }; var salesTable = new AxdEntity_SalesTable() { _DocumentHash = order.SalesTable[0]._DocumentHash, PurchOrderFormNum = order.SalesTable[0].PurchOrderFormNum, ReceiptDateRequested = order.SalesTable[0].ReceiptDateRequested, action = AxdEnum_AxdEntityAction.update, actionSpecified = true, SalesLine = new[] { salesLine } }; AxdSalesOrder newOrder = new AxdSalesOrder() { SalesTable = new[] { salesTable } }; try { // Update the order client.update(new CallContext(), entityKeyList, newOrder); return "Updated"; } catch (Exception) { return "Failed"; throw; } } } } }
*This post is locked for comments
I was able to get this class's RunOn property to function when set to Client so AX will handle the security by who's running the code, problem solved. Thanks again Martin. If you have any tips on documentation or reference sites or books to get, that would be helpful.
In the end my problem was running the code in a job. I executed the code in a class method where the class was either set to run on the server or client and that worked. The other issues were related to where I had the class set to run on the server but when the AIF call attempted to create the sales order in the context of the "server" which is the account the aos server is running as, that AD account did not have an actual DAX 2012 R3 user account so it failed. I created a user account for the service (which Martin pointed out is not a good idea), and that made it work. Then I set the class to run on the Client, using my user's context and it works fine and I confirmed that I had created the order by looking at the createdBy field in the SalesTable.
I finally got the entity key string value working! What a crazy bunch of meandering code.
// Declaration code
ALTDocSvcs.QA01SORef.EntityKey[] entityKeys;
ALTDocSvcs.QA01SORef.EntityKey entityKey;
ALTDocSvcs.QA01SORef.KeyField[] keyFieldList;
ALTDocSvcs.QA01SORef.KeyField keyField;
// Working code
entityKeys = new ALTDocSvcs.QA01SORef.EntityKey[1]();
entityKeys = aifServiceClient.create(testCallContext, testSO);
entityKey = entityKeys.GetValue(0);
keyFieldList = entityKey.get_KeyData();
keyField = keyFieldList.GetValue(0);
createdSalesId = keyField.get_Value();
info("Sales Id: " + createdSalesId);
Anyway, I know we're off of the original problem I posted so we can close this but I do want to ask about documentation/reference and how to handle the security if I take the approach to do this inside of AX. I know there's a way to set the username and password but how can I make that so it is not clear text in my code?
Thanks again for taking the time out of your busy day! Anything you would like to add as far as approach, that would be helpful also.
Thanks again for your reply Martin. I have seen this example walkthrough several times but it is by no means exhaustive. Does Microsoft have a white paper or a series of documents that explain all of the objects, how to reference/call them, etc?
The basic requirement stems from this. We will have a couple warehouses in a couple different countries that fulfill most of our intracompany orders (company entity ordering from another company entity) and we want to be able to generate a sales order on the fulfilling instance from inside the existing PO, passing the PO information and the original shipping address of the original customer and the automatically generated SO on the fulfilling instance will drop ship to the original customer's address but will use the intracompany name as the invoice account. Then we want to update the Product Receipt on the original instance once the SO is invoiced and then update the Packing Slip on the original instance to delivered status. This is only for intracompany transactions.
I know what you mean about C# being better for object construction, etc, AX can be mind numbing on that. However if the overhead is not too bad and it simplifies everything, I don't mind to put myself through it as long as I know what I'm doing.
For example, I have the interop code working and creating the SO on the other instance but the entitykey stuff is kicking my butt (see code below).
// Declaration section
ALTDocSvcs.QA01SORef.EntityKey[] entityKeys;
ALTDocSvcs.QA01SORef.EntityKey entityKey;
ALTDocSvcs.QA01SORef.KeyField keyField;
// Code that doesn't work
entityKeys = aifServiceClient.create(testCallContext, testSO);
entityKey = entityKeys.GetValue(0);
keyField = entityKey.get_KeyData();
keyField.get_Field();
info("Sales Id: " + keyField.ToString());
Should I be using a response object or something? If so, how does that work? I don't mind working with X++ as long as there is a reference on how to refer or instantiate/use these objects.
An example of using AifUtil::createServiceClient() can be seen in Walkthrough: Calling an External Web Service from X++. You can also open the method to see a bit more about what it does, although the actual work is done in a .NET assembly and not in X++.
Note that my preference is writing a custom C# library rather then using too much of .NET Interop. It makes code much easier to write and read, because you don't have to repeat namespaces all the time, you can utilize generic types, simpler syntax for object construction, foreach, LINQ and all the goodness of C#.
The AOS service account shouldn't be a user in AX. That's opening a security hole, not closing it, in my opinion.
Can you explain your actual business problem, please? You said you want to replicate intercompany functionality, but that's done inside a single environment, while here we're talking about integration.
Martin, I sort of figured out the security piece and I am creating the sales order and bypassing the C# library like you mentioned but my skills at this inside AX are limited as I have no reference. I found out that the account that the aos service is running under doesn't actually have an account in AX. I created the account with admin rights in our non production environment and this got me through the security hole and now I'm just tweaking what I am sending to the service. One of my questions though is how can I use a different account without passing plain text username and passwords to the service?
Also if you can provide a resource for documentation for this scenario, I would grateful because I have lots more to do in this arena besides creating a simple sales order.
Thanks in advance,
Ray
I should add that the service that the aos runs on is the same one that runs in all of our instances. However, I do not know how the security works from AX to class library to other server's endpoint with services.
Hello Martin and thank you very much for taking the time to respond. Since I had emailed you, I had taken my code out of the job I created and created a class with one method that contained my code from the job and set the class's Runon property to Server and this got me past the endpoint error. I'm still confused about why the client option didn't work because I manually placed the endpoint information in the client config file.
I saw the AxUtil option on a post from Joris D. and initially attempted that but I'm confused about how to make that work. Is there actual documentation on how to go about this inside AX? Below is some code I have but do I just scrap the code in the class library and just use the service reference in the class library in this case?
public static void aifStuff()
{
str status;
System.Exception ex;
ALTDocSvcs.SOClass qa01SOClass;
System.Type type;
try
{
qa01SOClass = new ALTDocSvcs.SOClass();
status = qa01Class.createSOHeader();
info("Status: " status);
}
catch (Exception::CLRError)
{
ex = CLRInterop::getLastException();
info(CLRInterop::getAnyTypeForObject(ex.ToString()));
}
}
I got this to debug into Visual Studio but then I get a fail message when it attempts to do the create piece and below was in the event viewer on the other server I was trying to create the sales order on:
Object Server 01: An error has occurred in the services framework. Method: AifMessageInspector::AfterReceiveRequest. Error: System.ServiceModel.FaultException: Failed to logon to Microsoft Dynamics AX.
at Microsoft.Dynamics.Ax.Services.AxServiceOperationContext.InitializeSession()
at Microsoft.Dynamics.Ax.Services.AxServiceOperationContext.InitializeContext()
at Microsoft.Dynamics.Ax.Services.AxServiceOperationContext.Attach(OperationContext owner)
at System.ServiceModel.ExtensionCollection`1.InsertItem(Int32 index, IExtension`1 item)
at System.Collections.Generic.SynchronizedCollection`1.Add(T item)
at Microsoft.Dynamics.Ax.Services.AifMessageInspector.AfterReceiveRequest(Message& request, IClientChannel channel, InstanceContext instanceContext)
With that said, what method would you suggest using? If you can point to documentation that will allow me to do all this inside my AX code, I'd be happy to do it all there. I have the code below that I came up with just from trial and error (this was on another server I was trying this on).
static void ALTTestAIFIntegration(Args _args)
{
str status;
System.Exception ex;
ALTAIFIntegration.DEVERP7SOService.AxdEntity_SalesTable deverp7SalesTable;
ALTAIFIntegration.DEVERP7SOService.AxdEntity_TableDlvAddr salesTableAddrRec;
ALTAIFIntegration.DEVERP7SOService.AxdSalesOrder deverp7SO;
ALTAIFIntegration.DEVERP7SOService.AxdEntity_TableDlvAddr[] testAddr;
ALTAIFIntegration.DEVERP7SOService.AxdSalesOrder testSO;
ALTAIFIntegration.DEVERP7SOService.CallContext testCallContext;
ALTAIFIntegration.DEVERP7SOService.SalesOrderServiceClient testClient;
ALTAIFIntegration.DEVERP7SOService.SalesOrderServiceCreateRequest testSOServiceCreateRequest;
ALTAIFIntegration.DEVERP7SOService.SalesOrderServiceFindRequest testSOServiceFindRequest;
ALTAIFIntegration.DEVERP7SOService.EntityKey testEntityKey;
ALTAIFIntegration.DEVERP7SOService.AxdEntity_SalesTable[] testSalesTable;
ALTAIFIntegration.DEVERP7SOService.SalesOrderService devservice;
ALTAIFIntegration.Deverp7SalesOrders deverp7;
System.Type type;
CLRObject clientType;
try
{
type = CLRInterop::getType('ALTAIFIntegration.Deverp7SOService.SalesOrderService');
devservice = AifUtil::createServiceClient(type);
//devservice = new ALTAIFIntegration.DEVERP7SOService.SalesOrderService();
testAddr = new ALTAIFIntegration.DEVERP7SOService.AxdEntity_TableDlvAddr[1]();
testSalesTable = new ALTAIFIntegration.DEVERP7SOService.AxdEntity_SalesTable[1]();
testSO = new ALTAIFIntegration.DEVERP7SOService.AxdSalesOrder();
testCallContext = new ALTAIFIntegration.DEVERP7SOService.CallContext();
salesTableAddrRec = new ALTAIFIntegration.DEVERP7SOService.AxdEntity_TableDlvAddr();
salesTableAddrRec.set_action(ALTAIFIntegration.DEVERP7SOService.AxdEnum_AxdEntityAction::create);
salesTableAddrRec.set_City("Molalla");
salesTableAddrRec.set_County("myCount");
salesTableAddrRec.set_State("OR");
salesTableAddrRec.set_Street("29300 S. Molalla Ave");
salesTableAddrRec.set_ZipCode("97038");
salesTableAddrRec.set_CountryRegionId("USA");
testAddr.SetValue(salesTableAddrRec, 0);
deverp7 = new ALTAIFIntegration.Deverp7SalesOrders();
deverp7SalesTable = new ALTAIFIntegration.DEVERP7SOService.AxdEntity_SalesTable();
deverp7SalesTable.set_action(ALTAIFIntegration.DEVERP7SOService.AxdEnum_AxdEntityAction::create);
deverp7SalesTable.set_CustAccount("WILLEGG");
deverp7SalesTable.set_DeliveryName("Willamette Egg Farms LLC");
deverp7SalesTable.set_ReceiptDateRequested(today());
deverp7SalesTable.set_SalesType(ALTAIFIntegration.DEVERP7SOService.AxdEnum_SalesType::Sales);
//ALTAIFIntegration.DEVERP7SOService.AxdEntity_SalesTable = new ALTAIFIntegration.deverp7SOService.AxdEntity_SalesTable[1] { salesTableAddrRec };
deverp7SalesTable.set_TableDlvAddr( testAddr );
//deverp7SalesTable.set_TableDlvAddr(salesTableAddrRec);
testSalesTable.SetValue(deverp7SalesTable,0);
testSO.set_SalesTable( testSalesTable );
testCallContext.set_Company( "USA" );
testClient = new ALTAIFIntegration.DEVERP7SOService.SalesOrderServiceClient("NetTcpBinding_SalesOrderService3","net.tcp://deverp7:8201/DynamicsAx/Services/SalesOrderCreate");
testClient = new ALTAIFIntegration.DEVERP7SOService.SalesOrderServiceClient();
testSOServiceCreateRequest = new ALTAIFIntegration.DEVERP7SOService.SalesOrderServiceCreateRequest();
testEntityKey = new ALTAIFIntegration.DEVERP7SOService.EntityKey();
testEntityKey = testClient.create(testCallContext, testSO);
//status = deverp7.createSOHeader();
//deverp7SO.set_SalesTable(salesTableAddrRec.GetType());
info("Sales Id: " testEntityKey.ToString());
}
catch (Exception::CLRError)
{
ex = CLRInterop::getLastException();
info(CLRInterop::getAnyTypeForObject(ex.ToString()));
}
}
In that code, I was trying to build the objects needed to do it all in AX but I got hung up at the end. Again, I can't find a reference or anything to help me do this inside AX. If you know where to get this documentation, I'd be very pleased as what I've learned, I've had to read about 100 forum posts and then deduce from what I read, how to do this.
I'm thinking that doing this inside AX will be the most stable approach since I will be doing this same thing with up to 5 instances. We are in 129 countries and have 5 instances and I am trying to mimic the intercompany functionality. Also creating the sales order lines inside of AX would be easier as well instead of creating the header and then adding lines one at a time in a loop that calls the class library code.
Thank you very much,
Ray
You web service client needs configuration to know where to connect, which binding to use and so on. If you create a new console application, for instance, and add a service reference, the configuration added to console application's configuration file.
When you use your library in AX, WCF again looks for the configuration in the configuration file of the current process, but that's AX in your case - and there is no configuration for your web service.
To load the configuration file, use AifUtil::createServiceClient() instead of constructing Deverp7SalesOrders by yourself.
Another option would be forgetting configuration files completely and setting up everything (endpoint address, binding etc.) from code.
That didn't help at all. I having trouble figuring out how it can't find my endpoint if it's in both the client and the server config files.
Can anyone help please?
I am still getting the following error:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.InvalidOperationException: Could not find default endpoint element that references contract 'DEVERP7SOService.SalesOrderService' in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this contract could be found in the client element.
I have since added my app.config information from my class library VS 2013 project, in my AX32.exe.config and refreshed the WCF configuration in the Client configuration utility. I still get the error.
I'm going to add the config to my AX32Serv.exe.config and see if that resolves it. If anyone has any other insights, I would greatly appreciate it.
Stay up to date on forum activity by subscribing. You can also customize your in-app and email Notification settings across all subscriptions.
André Arnaud de Cal... 291,219 Super User 2024 Season 2
Martin Dráb 230,056 Most Valuable Professional
nmaenpaa 101,156