Jun 25, 2010

The Zen of Creation
The Art of Creating New Records in Enterprise Portal

In Microsoft Dynamics Ax creating a new record is usually one of the easiest things you could possibly do. When in a grid, press ctrl+n, or the document icon, and there you go: a new record for you complete with required fields underlined in red. Press ctrl+s, or the floppy disk icon, and the records have been saved for posterity.

In Dynamics Ax you get everything free. You might not realize it, but the system synchronize your lookups based on which other field values you might have set, leaving the developer with the comfortable task of focusing on other issues than record creation.

Not so in Enterprise Portal 2009!

Pressing Ctrl+N in Enterprise Portal invokes browser behavior, usually a new browser page, which is not at all what you want! Record creation has to be coded from A to Z by the portal developer! 

This blog entry here aims to disclose the methods I have used over the years to do so along with the pros and the cons.

Method number 1: The ASP.NET wizard tag
Microsoft pushed this method onto the Enterprise Portal developers back when the product shipped. Let me just warn you: This approach is horrible. ASP.NET wizards have a bad reputation among .NET developers, “you didn’t use the ASP.NET framework wizard tag, did you?” being a common comment from a seasoned programmer. Looking at Microsoft’s own code it becomes evident that the wizard is substandard, for all record creation within the product happens using other methods.
I quickly abandoned this approach, after banging my head against the wall for several days. The approach was uncompromising, hard to implement and left the developer with little control over the flow of execution.

Method number 2: ASP.NET code behind insertion

It is pretty easy to insert code in the code behind given the following format:

DataSetView dsv = ds.GetDataSet().DataSetViews[0];
if(dsv != null)
{
DataSetViewRow dsvr = dsv.AddNew();
      Dsvr.BeginEdit();
      Dsvr.SetFieldValue(“TableField”, TxtBoxFromAspPage.Text);
      Dsvr.EndEdit();
}

Barring conversion issues this is all fine and dandy, but the method has some severe shortcomings.

1. As already mentioned (Dynamics Ax) X++ to .NET data type conversion is truly painful.

2. Field dependencies do not update automatically, meaning that if field B is dependent on field A, the asynchronous nature of Enterprise Portal means the developer has to synchronise manually.

3. Catch 22 type scenarios caused by the asynchronous nature of the web and the strong validation performed by .NET: These problems can be illustrated by the following example: You are creating a “Create New Wizard” for a software vendor specific ERP web module. One of the fields, the sales ID lookup, is dependent on the project ID. Now, to begin with there is no project ID, or there is one but you changed it. How can we filter the lookup now when you need to do a postback to register the project lookup change? Furthermore the sales ID is a required field, but without a predefined project ID your lookup returns empty! This means that you cannot save the record, for there is no sales ID, but you cannot get a sales ID for the project Id has not been saved. There you go, catch 22!

The ease of use combined with datatype conversion issues of this method means I primarily use it for simplistic tasks with no advanced filtering or field interdependencies.

Method number 3: DataSet method insertion

In the Dynamics Ax dataset you can define methods that can be called from your C# code. Doing so is fairly straightforward with the first parameter being the method name and the next parameters being the method parameters, up to a max of 3 (4 with the method name). Don’t panic yet, you can send a parameter array as the first parameter, making sure you can send as much as you need.

In C# the code looks like this:

Object[] paramList = new Object[2];
paramList[0] = TxtProjectId.Text;
paramList[1] = TxtSalesId.Text;

try
{
ProjTableDS.GetDataSet().DataSetRun.AxaptaObjectAdapter.Call("CreateNew", paramList);
}
catch (System.Exception exception)
{
AxExceptionCategory exceptionCategory;

if (!AxControlExceptionHandler.TryHandleException(this, exception, out exceptionCategory))
{
throw;
}
}

And in X++ the dataset method code looks like this:

public void createNew(str _projId, str _salesId)
{
    SalesId salesId = _salesId; //I will explain this below
    ProjId projId = _projId;
    ;
    info(“ProjId = “ + _projId); //Not much action here
    info(“SalesId = “ + _salesId); //just to illustrate
}

This might seem a lot more complicated than the method number 2, but in my opinion it is the best, most flexible way of doing things.

Note that I pass all parameters as strings. There is a reason for me doing so: datatype conversion is a true pain between X++ and .NET. You do have a vast number of Proxy helper classes that you can pass, but knowing Microsoft and the business I can tell you don’t have any guarantee that those data type mappings will stick in the future, especially between different versions of Ax and .NET.

Strings on the other hand will, and passing strings from .NET and having X++ do the conversion you are certain you’ve got a winning formula. Application internal data type conversion will always be stable, and not prone to MS data type mapping schizophrenia.

I have built SharePoint based Ax EP applications several years now and this method has proven to be the easiest to implement, quickest, most stable and truly a winning formula.

Method number 4: Proxy class insertion

Enterprise Portal Proxy classes is perhaps the #1 source of confusion for Ax techs and developers. In my experience they are cumbersome, difficult to edit, difficult to deploy and generally annoying.

They are also very powerful.

You can expose anything to .NET using the proxy methodology. In the AOT, lookup Web->Web Files->Static Files->Proxies and you find the file you need to register your proxy class, table, enum or what-have-you in. After having registered your object there you can invoke it in .NET.

Behind the scenes this creates a C# .cs file that you must deploy to the virtual folder of the SharePoint site. These sites can be found under X:\Inetpub\wss on the server and is usually called the same as the port number the site is hosted on, typically 80. Under the 80 folder look for app_code\proxies which is the subfolder in which the .cs file must be placed. This file provides wrapper classes used to contact the .NET Businesss Connector and ultimately Dynamics Ax.

If your records require advanced functionality it might be an idea to register the class that creates them as a proxy class, then call it straight from C#.

This is how you might call such a proxy class:

using Proxy = Microsoft.Dynamics.Portal.Application.Proxy;
Proxy.DemoProxyClass proxy = Proxy.DemoProxyClass(AxSession.AxaptaAdapter);
proxy.insert(parameter1, parameter2, …, parameterX);
proxy.runImportantJob();

Invoking it from C# is kinda straight forward, but there are some issues:
1.       Administrators will be dumbfounded by your need to have files under the SharePoint site virtual folder (IIS virtual folder).
2.       
      You will be dumbfounded by how hard it is to update the proxy code. Imagine rewriting the X++ proxy code, recreating the .cs file, restarting the AOS, updating the AOD and still the old code is executed. That is what you are looking at.
Proxies are powerful however, and enable you to tightly integrate your portal with Dynamics Ax, but remember to handle with care and do not use them lightly.

Method number 5: FormDataSource insertion

This method is a variant of method number 2 really.

FormObjectSet formDataSource;
formDataSource = SalesTable_DS.GetDataSet().DataSetRun.dataSource();
formDataSource.create();
formDataSource.initValue();

The main difference between this method and method number 2 is that the strong .NET validation is not invoked as the creation happened serverside. This can be a boon in catch 22 situations, as described under method number 2 (above).

This code contacts the dataset and invokes create on the datasource. You could add custom code to the create method (just override it if it doesn’t exist), enabling you to do a lot of easy work in X++ almost transparently.

As with the other means of contacting the dataset, this methodology is battle tested, works and is a good option.

Method number 6: .NET Business Connector insertion

You can go beyond the wrapper classes and invoke the .NET BC directly, though I cannot imagine why you would do so. I mention it anyhow, for in cases where you do not employ Enterprise Portal, but still want to contact Dynamics Ax (for instance from an ASP.NET web application or standard SharePoint site) it is the only way to go.

Axapta ax = new Axapta();
String company = “DMO”;
String AOSInstance = “02”;
String AOSServerName = “THINK”;
String AOSPort = “49999”;
AxaptaRecord axRec;
ax.Logon(company, "", AOSInstance + "@" + AOSServerName + ":" + AOSPort, ""); //log on to Axapta
axRec = ax.CreateAxaptaRecord(“SalesLine”);
axRec.set_Field(“PurchQty”, “1000”);


In the end business connector calls like these are what it all comes down to. The above code should be wrapped in try catches the way babies should be wrapped in their mommas’ arms, but I removed redundant code to emphasize on what’s important.

4 comments:

  1. I "think" method 6 looks familiar... :-)

    ReplyDelete
  2. I am using think as the servername in my comments. The portnumber has been changed though. The code in method 6 is from Ax 4, but the changes are probably few.

    ReplyDelete
  3. Thanks for sharing information about sharepoint it's really well. Sharepoint Development

    ReplyDelete
  4. An architect type colleague of mine had a comment the other day. The comment pertains to the proxy caching. Basically, if everything else fails, you restarted the AOS, IIS, everything, and still the old code executes, then you:

    1. Delete the .auc (axapta user cache) files on the client
    2. Delete the .aoi files (axapta object index) files on the AOS (and on the SharePoint Server "ax client", if any reside there)

    ReplyDelete