Mapping X++ Code

advertisement
Page Migration Overview
The 4.0 to 5.0 page migration is generally a 4-step process:
1) Ideally, all of the business logic is created in the X++ layer:
a. Create one or more data sets for the page’s web controls.
b. Map business logic from the 4.0 web forms to the data sets.
c. Create a new web menu that will later map to the toolbar.
2) Presentation is then linked to the data sets and the user control is added to the AOT:
a. In Visual Studio, connect to the data sets using an AxDataSource control.
b. Add other relevant controls such as AxGridView.
c. Right click on your user control and add it into the AOT (Axapta Object Tree). This
imports the code into Ax as a managed content that becomes accessible in SharePoint
EP.
3) The user control is complete, so now we add it to a new SharePoint EP page:
a. Create a new page in SharePoint
b. Add a custom user control and link it to your user control.
c. Add a toolbar and link it to your web menu.
d. Link the user control as a provider to the toolbar.
e. The default Quick Launch sidebar for the page is set as a parameter of the module, so
you should not set up a separate Quick Launch for each page.
4) Now that we have a page in SharePoint, we need to import it into the AOT:
a. In the Ax Client, create a web menu item for the new page.
b. Add the web menu item to applicable web menus.
Development
Mapping X++ Code
For the most part, the X++ code for web forms can map directly to data sets. In other words, you can drag
and drop all of the methods from the web form to the new data set with the exception of the init() method
and methods under Designs.
Persistence in X++ and mapping the init() method
HTTP is a stateless protocol, so EP uses a special packing/unpacking mechanism to maintain state
between requests. When a page is loaded, the current state and all objects are packed and stored in the
page that is then sent to the client. On the next page request, this state information is sent back to the
server and unpacked. A side-effect of this approach is that objects are only initialized once. Subsequent
page requests re-use the same object, so you cannot assume that the init() method is invoked during
each http request.
If you see server-side errors in the Windows Event Viewer regarding uninitialized objects, it is most likely
a result of this side effect. The solution is to move the code in the init() method into a separate helper
method that can be invoked by other methods as necessary.
Calling custom X++ methods from ASP.NET
The ASP.NET Microsoft.Dynamics.Framework.BusinessConnector.Adapter namespace contains
AxaptaObjectAdapter and AxaptaAdapter classes that allow you to call custom X++ methods and static
methods using reflection. For example, if you want to access a method on your X++ DataSet from
ASP.NET, you would use the following example code:
using Microsoft.Dynamics.Framework.BusinessConnector.Adapter;
AxDataSource dataSource = this.AxDataSource1;
string dataSourceName = this.AxGridView1.DataMember;
DataSetRun dataSet = dataSource.GetDataSet(dataSourceName).DataSetRun;
IAxaptaObjectAdapter dataSetAdapter = dataSet.AxaptaObjectAdapter;
object returnValue = dataSetAdapter.Call("methodName", "inputValue1",
"inputValue2", "etc");
The return value needs to be casted to its appropriate type using the mapping on page 201 of Inside
Microsoft Dynamics Ax 4.0. More information can be found in the .NET Framework Business Connector
SDK under Developer Help.
X++ wrapper classes in ASP.NET (BusinessConnector.Proxy classes)
The .NET Business Connector framework comes with wrappers for basic X++ classes. These are found
under the Microsoft.Dynamics.Framework.BusinessConnector.Proxy namespace. Here is an example of
accessing the proxy class for FormDataSource associated with a DataSet:
AxDataSource dataSource = this.AxDataSource1;
string dataSourceName = this.AxGridView1.DataMember;
AxDataSourceView dataSourceView =
dataSource.GetDataSourceView(dataSourceName);
DataSetView dataSetView = dataSourceView.DataSetView;
FormDataSource dataSourceProxy = dataSetView.MasterDataSource;
More information about the X++ FormDataSource class can be found in the Developer Help.
Creating a new EP Web Module
1) Under Web\Web Modules\Home, add a new Sub Module.
2) Under its properties, assign a Web Menu to the QuickLaunch field. This will automatically be the
default Quick Launch sidebar for all pages under the module.
3) Right-click on the new Sub Module and click Deploy.
4) Refresh the AOT cache by clicking “Refresh AOD” on the main page under SharePoint.
5) You can now either overwrite the module’s default.aspx or create a new page and change the Web
Module’s MenuItemName property to your new page.
Server-side error log
Errors on the server are logged under the Windows Event Viewer. Go to Run (Windows Key + R),
execute the command “eventvwr”, and then navigate to Application.
Refreshing the AOT cache
When you make changes to the AOT, they may not be immediately reflected in SharePoint due to the
cache. The quickest way to refresh the cache is to open up Run (Windows Key + R) and execute the
command “iisreset”, which will restart the entire IIS.
X++ Debugging
Dynamics Ax has a built-in debugger for X++ which can also be configured to work with EP. For setting
up the debugger, reference page 62 of Inside Microsoft Dynamics Ax 4.0. One important note is that the
debugger currently has a bug that requires you to manually open the debugger. The debugger can be
found in the Axapta client under Tools\Development Tools\Debugger. Also, breakpoints set by pressing
F9 may not work for control buttons. In this situation, you need to manually add the breakpoint by
inserting the following line of X++ code at the desired breakpoint location:
breakpoint;
ASP.NET Debugging
Visual Studio has a powerful debugger that can be attached to a running process. This is how you can
use it to debug EP:
1)
2)
3)
4)
Under Visual Studio 2005, go to the Debug dropdown menu
Click “Attach to Process…”
Select the instance of w3wp.exe that is running under your User Name
Click Attach
By having your source files open, you can set breakpoints by pressing F9, and they will trigger as you use
EP. The following web article highlights useful features of the debugger:
http://www.odetocode.com/Articles/425.aspx
1.1.1. Mapping AX 4.0 pages to AX 5.0 Pages
At a higher level, the AX 4.0 web form’s nodes map to the Ax 5.0 data set’s nodes as shown
in the following diagram. For complete information about AX 5.0 portal architecture, refer to
Enterprise Portal Migration in Ax 5.
Web Form
Code
Dataset
Code
Datasourc
es
Design
Datasourc
es
AOT
User Control
Code
Managed
Markup -
Design
The mapping of AX 4.0 entities to AX 5.0 entities is described in the following sections in
detail.
a.
DataSources:
In Ax 4.0 Webform node contains Datasources. In Ax 5.0, Dataset node will contain
Datasources. The Datsource has to be moved from Webform Node to Dataset node. It
can be moved manually or thru Axapta Job Code for copying web form elements
After completion of the job the data set would look as shown in the following diagram.
b.
DataSource Methods
The following table provides different patterns of code that exist with in webform data
sources. These methods may be retained in x++ within the data set or migrated to c#
web user controls based on the usage of method code in the web form.
Note: the sample code provided here is for understanding purpose only and depicts
certain patterns. Please evaluate every scenario.
Pattern
1. Datasource Methods which access UI
Web Form
EPProjInvoiceInfo
Source Method
projInvoiceEmpl:linkActive
X++ Code
public void linkActive()
{
super();
//the below code sets tab visible property
empl.visible(projInvoiceEmpl ? true : false);
}
C# Code
linkActive is fired when the parent record is activated. This event is not raised to C# so
any visibility changes must be done after the datasource state is final.
In this case hide the visual control in the PreRender stage. Check the status of the
control. Use the managed dataset object model to see if there is a current record on the
datasetview.
Something like Dataset().DataSetViews[“ViewName”].GetCurrentRecord()
Pattern
2. Display method pattern
Web Form
EPProjInvoiceInfo
Source Method
ProjTable:parentProject
X++ Code
display ProjId parentProject(ProjTable _projTable)
{;
if (_projTable.childExist())
{
return _projTable.ProjId;
}
else
{
return '';
}
}
C# Code
this has to stay in X++ in Datsource
Pattern
3. Sort Pattern
Web Form
EPProjCategory
Source Method
ProjCategory:init
X++ Code
this.query().dataSourceNo(1).
addSortField(fieldNum(ProjCategory, CategoryType));
C# Code
This remains in x++. Use this function instead of addSortField
addOrderByField()
addGroupByField()
on the datasource depending on if you need sorting or grouping.
Pattern
4. Title Pattern
Web Form
EPPRojCostTrans
Source Method
PRojCostTrans:init
X++ Code
if (!this.args().dataset())
element.design().titleDatasource(0);
C# Code
Use the title web part. Framework team needs to take this as a work item. App should
just put title web part on the page.
Pattern
5. Setting Records To Null Pattern
Web Form
EPProjCategory
Source Method
ProjCategory:init
X++ Code
this.args().record(NULL);
C# Code
Leave this code in X++. They are NULLing out the current record before the super() call.
The super() call will use the current record to initialize the datasources..
Pattern
6. Args pattern
Web Form
EPProjInvoiceInfo
Source Method
init
X++ Code
switch(element.args().dataset())
{
case(tablenum(ProjInvoiceTable)):
projInvoiceTable
=
element.args().record();
criteriaProjInvoiceProjId.
value(projInvoiceTable.ProjInvoiceProjId);
break;
…}
C# Code
The args will automatically be set from the querystring or from the webpart connection.
This code should work unchanged.
Pattern
7. Dynamically Setting Controls
Web Form
EPProjInvoiceInfo
Source Method
init
X++ Code
general_M_contributionRatio.label(ProjParameters::find()
.grossMarginDisplayLabel());
C# Code
Use a managed table proxy and update the label of the managed control.
If using a boundfield, bind the headerText property to the displaymethod using a eval.
Also you can access the boundfield in the prerender event and set the caption.
Pattern
8. Personalization Pattern
Web Form
EPProjTimeSheet
Source Method
init
X++ Code
if (EPPersonalize::find().ProjNoOfWebLines)
{
webGrid.visibleRows(EPPersonalize::find().ProjNoOfWebLines,
AutoMode::Fixed);
}
else
{
webGrid.visibleRows(10,AutoMode::Fixed);
}
C# Code
Use a managed table proxy to
Pattern
9. Find Value Pattern
Web Form
EPProjTimeSheet
Source Method
init
X++ Code
projValParameter =
ProjParameters::find().ValidationEmplCategory;
C# Code
Use a table proxy if required in managed.
Pattern
10. Sort Pattern
Web Form
EPProjCategory
Source Method
init
X++ Code
this.query().dataSourceNo(1).addSortField(fieldNum(ProjCate
gory, CategoryType));
C# Code
use addOrderByField if sorting. If grouping use AddGroupByField. Remove
groupByMode calls.
Pattern
11. Query Add Range pattern
Web Form
//todo
Source Method
X++ Code
qB_projCostTrans =
q.addDataSource(tablenum(ProjCostTrans));
qB_projCostTrans.name(tablestr(ProjCostTrans));
criteriaProjId =
qB_projCostTrans.addRange(fieldnum(ProjCostTrans, ProjId));
criteriaEmplId
= qB_projCostTrans.
addRange(fieldnum(ProjCostTrans, EmplId));
qB_projCostTrans.addRange(fieldnum(ProjCostTrans,
TransStatus)).value(SysQuery::
valueNot(ProjTransStatus::Adjusted));
C# Code
Leave this code in X++.
Pattern
12. Query Add Range pattern (Based on Data
Source)
Web Form
EPProjInvoiceInfo
Source Method
//todo
X++ Code
if (element.args().dataset() == tablenum(projCostTrans))
{
_projCostTrans = element.args().record();
this.query().dataSourceTable(tablenum(projCostTrans)).
addRange(fieldnum(projCostTrans,
TransId)).value(SysQuery::value(_projCostTrans.TransId));
}
C# Code
Leave this code in X++.
Pattern
13. Query Add Range Pattern (Based on
Parameter)
Web Form
EPProjInvoiceInfo
Source Method
ProjInvoiceEmpl:init
X++ Code
if (EPPersonalize::find().ProjShowAllEmplTrans
NoYes::No)
==
this.query().dataSourceTable(tablenum(ProjInvoiceEmpl)).
addRange(fieldnum(ProjInvoiceEmpl,EmplId)).
value(queryValue(SysCompanyUserInfo::current().EmplId));
C# Code
Leave this code in X++.
Pattern
14. Query Add Range Pattern (Based on
Personalize Paramter)
Web Form
EPProjInvoiceInfo
Source Method
ProjInvoiceJour:init
X++ Code
if (invoiceId)
this.query().dataSourceTable
(tablenum(ProjInvoiceJour)).
addRange(fieldnum(ProjInvoiceJour,ProjInvoiceId))
.value(invoiceId);
C# Code
Leave this code in X++.
Pattern
15. Query Filter Pattern
Web Form
EPProjInvoiceInfo
Source Method
ProjInvoiceCost:init
X++ Code
QueryBuildRange rangeEmplId =
this.query().dataSourceTable(tablenum(
ProjTimeSheet)).addRange(fieldnum(ProjTimeSheet,
EmplId));
rangeEmplId.
value(EPProjWebApp.EPProjUser().emplId()),ProjInvoiceId)).
value(invoiceId);
C# Code
Leave this code in X++.
Pattern
16. initValue Pattern
Web Form
EPProjTimeSheet
Source Method
ProjTimeSheet:initvalue
X++ Code
public void initValue()
{;
super();
projTimeSheet.EmplId =
EPProjWebApp.EPProjUser().emplId();
},ProjInvoiceId)).value(invoiceId);
C# Code
Leave this code in X++.
Pattern
17. validateWrite Pattern
Web Form
EPProjTimeSheet
Source Method
ProjTimeSheet:validateWrite
X++ Code
if (projTimeSheet.ProjId && !
ProjTable::find(projTimeSheet.ProjId))
{
ret =
checkFailed(strfmt("@SYS11217",projTimeSheet.ProjId));
}
C# Code
Managed framework supports infolog. Code should run unchanged.
Pattern
18. Validate Field Pattern
Web Form
EPProjTimeSheet
Source Method
ProjTimeSheet.ProjId:Validate
X++ Code
public boolean validate()
{;
// super() must not be called because validate on
relation to ProjEmplSetup otherwise will fail
// Validate on relation is set to yes because else
there will be no lookup button
return true;
},ProjInvoiceId)).value(invoiceId);
C# Code
Field validation overrides should stay in X++.
Pattern
19. Lookup Patterns (With Args object)
Web Form
EPProjTimeSheet
Source Method
ProjTimeSheet_ProjId:lookup
X++ Code
Args args = new Args();
;
// Build args object to pass to method call
args.caller(this);
args.record(projTimeSheet);
ProjTable::lookupProj_web(_lookupValue,
EPProjWebApp.EPProjUser().emplId(),
args);,ProjInvoiceId)).value(invoiceId);
C# Code
Use custom lookup programming model provided by the framework.
c.
WebForm Design:
Controls
Web form contains various controls as defined in the Design node of the web form in
AOT. The mapping of the controls from AX 4.0 to AX 5.0 ASP.NET controls on various
pages is as below:
AX4.0 User Control
d.
AX5.0 User Control
WebGrid
AxGridView
WebTab
AxMultiSection (under AxForm)
WebGroup
AxGroup
ButtonGroup
asp:Button: Asp.net user control
NA
AxToolBar
NA
AxFooter
WebGrid:WebCombobox
AxDropDownBoundField
WebGrid:WebEdit
AxBoundField
WebGrid:WebDate
AxBoundField
NA
AxDataSource
NA
AxToolBar
WebForm Methods and Events:
WebForm Methods
The webform methods for various pages follow a pattern. The pattern for various types of
pages and migration path is discussed below
Note: the sample code provided here is for understanding purpose only and depicts
certain patterns.. Please evaluate every scenario.
Pattern
1. Title Pattern
Web Form
EPProjInvoiceList
Source Method
Init
X++ Code
if (!this.args().dataset())
element.design().titleDatasource(0);
C# Code
C# Put a title web part on the page. Remove this code.
Pattern
2. Setting Records To Null
Web Form
EPProjCategory
Source Method
init
X++ Code
this.args().record(NULL);
C# Code
NA
Pattern
3. Args pattern
Web Form
EPProjCostTrans
Source Method
Init
X++ Code
switch(element.args().dataset())
{
case(tablenum(ProjInvoiceTable)):
projInvoiceTable
=
element.args().record();
criteriaProjInvoiceProjId.
value(projInvoiceTable.ProjInvoiceProjId);
break;
…}
C# Code
Pattern
4. Dynamically Setting Controls
Web Form
EPProjInvoiceInfo
Source Method
Init
X++ Code
general_M_contributionRatio.label(ProjParameters::find()
.grossMarginDisplayLabel());
C# Code
Covered already
Pattern
5. Transaction Pattern
Web Form
EPProjTimeSheet
Source Method
createNew
X++ Code
CreateNew()
{
ttsbegin;
projJournalTable.clear();
projJournalTable.JournalNameId =
ProjParameters::find().WEBJournalNameId;
projJournalTable.initFrom
ProjJournalName(projJournalName::
find(projJournalTable.JournalNameId));
journalId =
NumberSeq::newGetNum(ProjParameters::numRefProjJournalId())
;
projJournalTable.JournalId = journalId.num();
projJournalTable.insert();
ttscommit;
}
C# Code
Leave this in X++. Call this from the using this syntax if required from managed code
DataSet().AxaptaAdapter.Call(‘createNew”);
Pattern
6. Data Insertion Pattern
Web Form
EPProjTimeSheet
Source Method
createNew
X++ Code
projJournalTable.clear();
projJournalTable.JournalNameId =
ProjParameters::find().WEBJournalNameId;
projJournalTable.initFromProjJournal
Name(projJournalName::find(projJournalTable.JournalNameId))
;
journalId =
NumberSeq::newGetNum(ProjParameters::numRefProjJournalId())
;
projJournalTable.JournalId = journalId.num();
projJournalTable.insert();
C# Code
Same as above
Pattern
7. Data Insertion Pattern
Web Form
EPProjTimeSheet
Source Method
createNew
X++ Code
ProjJournalCreateEmplProjTimeSheet
projJournalCreateEmplProjTimeSheet = new
ProjJournalCreateEmplProjTimeSheet(projTimeSheet);
Args args = new Args();
;
args.record(projTimeSheet);
args.record().dataSource(projTimeSheet_ds);
projJournalCreateEmplProjTimeSheet.
parmProjTimeSheet_ds(projTimeSheet_ds);
projJournalCreateEmplProjTimeSheet.run();
C# Code
This is an internal call on EPProjTimeSheet. The button click on would happen in
managed code and use the adapter.call mechanism.
Pattern
9.Session Pattern
Web Form
EPProjInvoiceLine,
EPProjProposalLine, EPProjTimeSheet
Source Method
init
X++ Code
public void init()
{
enterprisePortal
projTable
enterprisePortal;
_projTable;
;
if (classidget(webSession().webApplication()) ==
classnum(enterprisePortal))
{
enterprisePortal
=
webSession().webApplication();
EPProjWebApp
=
enterprisePortal.EPProjWebApp();
}
else
{
EPProjWebApp = webSession().webApplication();
}
C#
Function to be written in x++ .
Pattern
10.Page Setup Pattern
Web Form
EPCustTableEdit,
EPContactPersonEdit,
EPCustTableEditCSS,
EPDocuInfoAdd,
Source Method
init
X++ Code
public void init()
{
if (this.args().parmEnumType() == enumnum(EPFormAction))
formAction = this.args().parmEnum();
if (formAction == EPFormAction::CreateMode)
this.args().record(null);
super();
if (formAction == EPFormAction::CreateMode)
contactInfo_M_editContactPersonName.visible(false);
}
public void run()
{
super();
if (formAction == EPFormAction::CreateMode)
{
custTable_ds.allowCreate(true);
custTable_ds.create();
element.design().caption("@SYS14364");
buttonSubmit.text("@SYS59934");
//
custTable_ds.object(fieldnum(custTable,accountNum)).allowEdit(NumberSequenceTable
::find(CustParameters::numRefCustAccount().NumberSequence).Manual);
identification.visible(NumberSequenceTable::find(CustParameters::numRefCustAccount(
).NumberSequence).Manual);
}
}
C#
This code will setup the screen mode on page_load event.
EPFormAction FormAction
{
get
{
if (this.ContentItem.enumTypeParameter ==
EnumMetadata.EnumNum(this.AxSession, "EPFormAction"))
{
return
(EPFormAction)this.ContentItem.enumParameter;
}
// Default mode
return EPFormAction.InfoMode;
}
}
void SetupMode()
{
switch (this.FormAction)
{
case EPFormAction.EditMode:
this.AxForm1.DefaultMode = DetailsViewMode.Edit;
this.AxForm1.AutoGenerateEditButton = true;
this.DataSourceView.BeginEdit();
break;
case EPFormAction.CreateMode:
this.AxForm1.DefaultMode =
DetailsViewMode.Insert;
this.AxForm1.AutoGenerateInsertButton = true;
break;
default:
this.AxForm1.DefaultMode =
DetailsViewMode.ReadOnly;
break;
}
}
void ToogleControlsVisibility()
{
// Show/hide Id field group
this.IdentificationGroup.Visible = (this.FormAction
== EPFormAction.InfoMode);
}
void Page_Load(object sender, EventArgs e)
{
this.SetupMode();
this.ToogleControlsVisibility();
}
Pattern
11.Design Control Pattern
Web Form
EPCSSItemDescription, EPCSSSalesBasket,
EPCustConfirmJournalInfo,
EPCustInvoiceJournalInfo,
EPCustPackingSlipJournalInfo
Source Method
updateDesign
X++ Code
void updateDesign(InventDimFormDesignUpdate mode)
{
inventDimParm inventDimParm;
;
switch (mode)
{
case InventDimFormDesignUpdate::Init
:
if (!inventDimFormSetup)
{
inventDimFormSetup = new InventDimCtrl_Frm(element);
}
case InventDimFormDesignUpdate::Active
:
inventDimParm.initItemDimension(callerInventTable.DimGroupId);
inventDimFormSetup.parmDimParmEnabled(inventDimParm);
inventDimFormSetup.parmDimParmVisibleGrid(inventDimParm);
inventDimFormSetup.formSetControls(true);
break;
default : throw error(strfmt("@SYS54195",funcname()));
}
}
C#
Form mode setup on control’s page_load event.
Pattern
Web Form
13. cacheAddMethod Pattern
Source Method
X++ Code
public void init()
{
super();
this.cacheAddMethod(tablemethodstr(EPTransactionSumTrans,custTable_BalanceMST
));
}
C#
This one needs to be moved to the dataset init
Pattern
14. CreateEdit Pattern
Web Form
Source Method
X++ Code
void clicked()
{
CustTable custTableLocal;
;
if (!custTable.AddressMap::validateAddress())
{
return;
}
try
{
if (formAction == EPFormAction::CreateMode)
{
custTableLocal = EP::createCustTable(custTable);
}
else
if (formAction == EPFormAction::EditMode)
{
custTable.ContactPersonId =
ContactPerson::findName(custTable.AccountNum,'',contactInfo_M_editContactPersonN
ame.text()).ContactPersonId;
EP::modifyCustTable(custTable);
}
}
catch (Exception::Error)
{
return;
}
element.closeOk();
if (formAction == EPFormAction::CreateMode)
{
EP::redirectOnCreate(new
WebUrlMenuFunction(weburlitemstr(EPCustTableInfo)),custTableLocal, "@SYS76718",
"@SYS19920", "@SYS77501");
}
else
{
EP::redirectOnCreate(new
WebUrlMenuFunction(weburlitemstr(EPCustTableInfo)),custTable, "@SYS77509",
"@SYS19920", "@SYS77501");
}
}
C#
void AxForm1_ItemUpdated(object sender,
DetailsViewUpdatedEventArgs e)
{
DataSetViewRow updatedRow =
this.DataSourceView.DataSetView.GetCurrent();
System.Diagnostics.Debug.Assert(updatedRow.IsEdit);
// Call modifyCustTable through the EP proxy class
using (IAxaptaRecordAdapter record =
updatedRow.GetRecord())
{
EP.modifyCustTable(this.AxSession.AxaptaAdapter,
record);
this.RedirectToCustomerInfo(record);
}
}
// Calls create logic on EP class to handle insert
void AxForm1_ItemInserted(object sender,
DetailsViewInsertedEventArgs e)
{
DataSetViewRow newRow =
this.DataSourceView.DataSetView.GetCurrent();
System.Diagnostics.Debug.Assert(newRow.IsNew);
// Call createCustTable through the EP proxy class
using (IAxaptaRecordAdapter record = newRow.GetRecord())
{
EP.createCustTable(this.AxSession.AxaptaAdapter,
record);
this.RedirectToCustomerInfo(record);
}
}
Pattern
Web Form
15.Delete Pattern
HRMEPVirtualNetworkCertificateDelete
Source Method
X++ Code
The Delete Page Functionality (Across the EP pages), on click of delete button
void clicked()
{
if (HRMVirtualNetworkCertificate.validateDelete())
{
HRMVirtualNetworkCertificate.delete();
element.close();
EP::redirectOnCreate(new
WebUrlMenuFunction(weburlitemstr(HRMEPVirtualNetworkCertifi
cateList)),emplTable, "@SYS34012", "@SYS32377",
"@SYS34014");
}
}
C# Code
As per new requirement, there would be no more delete
pages, that functionality would be implemented through the
adding the delete functionality to grid in the list page
and also to toolbar on list page.
Create action item for delete:
HRMEPInterviewTableDelete and object property to
SysDeleteCurrentRecordAction.
Set “allowdelete” property to true on grid
And at Data Set level set delete property to true.
Pattern
16. Modify Pattern
Web Form
HRMEPVirtualNetworkCourseEdit
Source Method
HRMEPVirtualNetworkCourseEdit:Submit
X++ Code
if (HRMVirtualNetworkCourse.HrmCourseId !=
HRMVirtualNetworkCourse.orig().HrmCourseId)
{
HRMVirtualNetworkCourse.initFromHRMCourseTable();
}
Code will be retained in X++ but at field level method “ modified”
public void modified()
{
super();
HRMVirtualNetworkCourse.initFromHRMCourseTable();
}
C# Code
Pattern
17. Image Field Pattern
Web Form
HRMEPVirtualNetworkCourseList
Source Method
WebImage:Company:Image
X++ Code
In Design control, web image bonded to data field.
C# Code
For this at user control level, we have image control which is bonded to image on load
event of page.
The path of image is retrieved through method which has functionality to access the static
method on table “EPWebSiteParameters table”
The static method in EPWebSiteParameters is as below:
Static str ImageFileName(Common record, fieldId fieldId)
{
Return Proxy. EPWebSiteParameters imageName
+ “.”Proxy. EPWebSiteParameters.ImageType;
}
Above method is called from method in C# as shown below:
string GetImagePath(IAxaptaRecordAdapter row)
{
return this.EPLayoutImagesPath +
ApplicationProxy.EPWebSiteParameters.
companyImageFileName(AxSession.AxaptaAdapter,
row);
}
Above C# method gets called from page load event as shown below
DataSetViewRow currentRow = this.dsHRMVNetworkCourseList.
GetDataSourceView("CompanyImage").DataSetView.GetCurrent();
if (currentRow != null &&
(ApplicationProxy.NoYes)currentRow.GetFieldValue("HasImage"
)
== ApplicationProxy.NoYes.Yes)
{
using (IAxaptaRecordAdapter record =
currentRow.GetRecord())
{
// Setting Image Path
if (record != null)
imgCompany.ImageUrl =
this.GetImagePath(record);
if (imgCompany.ImageUrl.Length != 0)
pnlImage.Visible = true;
else
pnlImage.Visible = false;
}
}
Pattern
18. Grid-Add New Row Pattern
Web Form
HRMEPDevelopmentPlanEdit
Source Method
HRMEPDevelopmentPlanEdit
X++ Code
C# Code
If you are creating a 5.0 user control with
adding a new row is required, you will call
create the new row at some point. Since you
effectively starting the edit operation you
expliclty end the edit operation.
a grid where
AddNew to
are then
also need to
That means you need to CancelEdit, if row editing is
cancelled on the newly added row and you need to call
EndEdit when updating the newly added row. Otherwise the
Update and Cancel buttons on the grid do not work.
/// <summary>
/// Add a new Line item row in the AxGridView.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void lnkAddLine_Click(object sender,
EventArgs e)
{
this.DataSourceViewGrid.AddNew();
}
//Grid cancel event for the current record
void
gridHRMEPDevelopmentPlan_RowCancelingEdit(object sender,
GridViewCancelEditEventArgs e)
{
// call CancelEdit operation
DataSetViewRow dsvr =
this.HRMVirtualNetworkDevelopmentPlanLineCurrent;
if (dsvr != null)
dsvr.CancelEdit();
}
//Grid update event for the current record
void gridHRMEPDevelopmentPlan_RowUpdated(object
sender, GridViewUpdatedEventArgs e)
{
if (this.gridHRMEPDevelopmentPlan.EditIndex !=
-1)
{
DataSetViewRow dsvr =
this.HRMVirtualNetworkDevelopmentPlanLineCurrent;
if (dsvr != null)
{
dsvr.EndEdit();
}
}
}
Pattern
19. showMenuFunction Pattern
Web Form
EPServiceOrderInfo
Source Method
showMenuFunction
X++ Code
public boolean showMenuFunction(WebMenuFunction
_menuFunction)
{
boolean ret;
switch (_menuFunction.name())
{
case weburlitemstr(EPServiceOrderLineCreate) :
ret =
SMAStageTable::find(SMAServiceOrderTable.StageId).StageCanM
odify;
break;
case weburlitemstr(EPServiceOrderEdit) :
ret =
SMAStageTable::find(SMAServiceOrderTable.StageId).StageCanM
odify;
break;
default :
ret = true;
}
return ret;
}
C# Code
Create Proxy
/table:SMAStageTable
/method:SMAStageTable.find
Create Set Menu Event Function
void BaseWebpart_SetMenuItemProperties(object sender,
SetMenuItemPropertiesEventArgs e)
{
string strMenuItemName =
e.MenuItem.MenuItemAOTName.ToLower();
if ((strMenuItemName == "epserviceorderlinecreate")
||
(strMenuItemName == "epserviceorderedit"))
//call the function to hide or show the menu
e.MenuItem.Hidden = hideOrShowMenu();
}
/// <summary>
/// Hide and show Proj id and OrderLine info fields
/// </summary>
private void hideFieldControl()
{
if (this.isCustomer())
{
// hide proj id field in identfication group
this.grpIdentification.Fields[2].Visible =
false;
// hide orderline info field in grid
this.gridViewEPServiceOrderInfo.Columns[0].Visible = false;
}
}
Pattern
20. SetMenuFunction with Cursor- Pattern
Web Form
EPServiceRepairLineEdit
Source Method
setMenuFunctionRecord
X++ Code
public Common setMenuFunctionRecord(WebMenuFunction
_menuFunction, Common _cursor)
{;
return _cursor;
}
return ret;
}
C# Code
//setting the toolbar context
void BaseWebpart_SetMenuItemProperties(object sender,
SetMenuItemPropertiesEventArgs e)
{
//set the employee context
if (e.MenuItem.MenuItemAOTName ==
"EPServiceRepairLineCreate"
&& e.MenuItem is AxUrlMenuItem
)
{
((AxUrlMenuItem)e.MenuItem).MenuItemContext =
this._smsReparilineContext;
}
}
//Employee context private
AxTableContext _smsReparilineContext;
//Employee Context value
AxTableContext smsReparilineContext
{
get
{
//check if the employee context is null
if (this._smsReparilineContext == null)
{
//call the getEmployeeRecord to get the
employee record from the dataset
using (IAxaptaRecordAdapter record =
this.AxSession.AxaptaAdapter.CreateAxaptaRecord(
this.dsServiceLine.GetDataSourceView("SMARepairLine").DataS
etView.GetCurrent().GetRecord()))
{
//check if the record is null
if (record != null)
{
this._smsReparilineContext =
AxTableContext.Create(
AxTableDataKey.Create(this.AxSession, record, null));
}
}
}
//return the employee context
return this._smsReparilineContext;
}
}
Pattern
20. IsCustomer Pattern
Web Form
EPServiceOrderInfo
Source Method
init
X++ Code
if (EP::isCustomer())
{
SMAServiceOrderTable_ProjId.visible(false);
orderLineInfo.visible(false);
}
C# Code
protected void Page_Load(object sender, EventArgs e)
{
//hide the fields if EP is a customer
this.hideFieldControl();
}
/// <summary>
/// Hide and show Proj id and OrderLine info fields
/// </summary>
private void hideFieldControl()
{
if (this.isCustomer())
{
// hide proj id field in identfication group
this.grpIdentification.Fields[2].Visible =
false;
// hide orderline info field in grid
this.gridViewEPServiceOrderInfo.Columns[0].Visible = false;
}
}
/// <summary>
/// Checks for the
/// </summary>
customer existence
/// <returns></returns>
private Boolean isCustomer()
{
bool isCustomerBool = false;
if
(ApplicationProxy.EP.isCustomer(AxSession.AxaptaAdapter))
{
isCustomerBool = true;
}
return isCustomerBool;
}
Pattern
22. Look Up Pattern
Web Form
EPServiceRepairLineEdit
Source Method
showMenuFunction
X++ Code
public void lookup(str _lookupValue)
{
;
SMARepairLine::webLookupSymptomCode(_lookupValue);
}
C# Code
Pattern
23. Load View State Pattern
Web Form
EPServiceOrderEdit
Source Method
loadViewState
X++ Code
public void loadViewState()
{
;
super();
if (this.viewStateContains(#PrevAgreementID))
{
previousAgreementID =
this.viewStateItem(#PrevAgreementID);
}
if (this.viewStateContains(#PrevCustAccount))
{
previousCustAccount =
this.viewStateItem(#PrevCustAccount);
}
}
C# Code
protected override void LoadViewState(object savedState)
{
//Retriving Previous agreementID
if (this.viewStateContains(StrPrevAgreementID))
_previousAgreementID =
(string)this.ViewState[StrPrevAgreementID];
//Retriving Previous CustAccount
if (this.viewStateContains(StrPrevCustAccount))
_previousCustAccount =
(string)this.ViewState[StrPrevAgreementID];
// Set the values in X++
this.dsEPServiceOrderEdit.GetDataSet().DataSetRun.
AxaptaObjectAdapter.Call("setParameters",
_previousAgreementID, _previousCustAccount);
base.LoadViewState(savedState);
}
Pattern
24. Save View State Pattern
Web Form
EPServiceOrderEdit
Source Method
saveViewState
X++ Code
public void saveViewState()
{
;
super();
// here we should persist all member state in the asp.net
view state
this.viewStateItem(#PrevAgreementID, previousAgreementID);
this.viewStateItem(#PrevCustAccount, previousCustAccount);
}
C# Code
protected override object SaveViewState()
{
//Save Previous agreementID
this.ViewState[StrPrevAgreementID] =
this._previousAgreementID;
//Save Previous CustAccount
this.ViewState[StrPrevCustAccount] =
this._previousCustAccount;
return base.SaveViewState();
}
Pattern
25. Loaded Pattern
Web Form
EPServiceOrderEdit
Source Method
loaded
X++ Code
public void loaded()
{
super();
// check and initialize the variables if this is the
first time
// we open this
if ( previousAgreementID == '' &&
previousCustAccount == '' )
{
previousAgreementID =
SMAServiceOrderTable.AgreementId;
previousCustAccount =
SMAServiceOrderTable.CustAccount;
}
}
C# Code
public void DataSet_Loaded()
{
if (_previousAgreementID == string.Empty &&
_previousCustAccount == string.Empty)
{
_previousAgreementID = this.GetAgreementID();
_previousCustAccount =
this.GetCustomerAccount();
// Set the
values in X++
this.dsEPServiceOrderEdit.GetDataSet().DataSetRun.
AxaptaObjectAdapter.Call("setParameters",
_previousAgreementID, _previousCustAccount);
}
}
Pattern
26. Look Up Image( with code) Pattern
Web Form
EPServiceTaskRelationList
Source Method
X++ Code
Webform with grid having a column with look up image.
C# Code
UI part of user control:
<dynamics:AxGridView ID="gvEPServiceTaskRelationList"
runat="server"
DataKeyNames="ServiceTaskId,RelTableId,RelKeyId"
DataMember="SMAServiceTaskRelation"
DataSourceID="dsEPServiceTaskRelationList"
ShowFilter="false">
<Columns>
<asp:TemplateField HeaderText="<%$ AxLabel:@SYS8811
%>" ConvertEmptyStringToNull="False">
<itemtemplate>
<a href="<%#
GetTaskRelationInfo(Container.DataItem) %>" >
<img
src="/_layouts/EP/images/WebIcon_LookingGlas.gif" alt=""
/> </a>
</itemtemplate>
</asp:TemplateField>
Code behind code of user control:
protected string GetTaskRelationInfo(object dataItem)
{
DataSetViewRow row = dataItem as DataSetViewRow;
string url = string.Empty;
if (row.GetFieldValue("RecId") != null)
{
AxUrlMenuItem urlMenuItem = new
AxUrlMenuItem("EPServiceTaskRelationInfo");
urlMenuItem.MenuItemContext =
AxTableContext.Create(row.GetDefaultTableDataKey(
this.dsEPServiceTaskRelationList.Metadata.DataSources["SMAS
erviceTaskRelation"]));
url = urlMenuItem.Url.ToString();
}
return url;
}
Pattern
27. Look Up Image( with no code) Pattern
Web Form
KMEPKnowledgdeCockpitList
Source Method
X++ Code
Webform with grid having a column with analogmeter image.
C# Code
UI part of user control:
<dynamics:AxHyperLinkBoundField
DataField="kmKnowledgeCockpitId"
DataSet="KMEPKnowledgdeCockpitList"
DataSetView="KMKnowledgeCockpitTable"
MenuItem="KMEPKnowledgeAnalogmeter" ShowHeader="False"
ImageUrl="/_layouts/EP/images/analogMeter.GIF"
HeaderText=" ">
</dynamics:AxHyperLinkBoundField>
Events
There are various events that are fired on AX 4.0 pages. In some scenarios X++ code is
executed when events are fired. Below is mapping of AX 4.0 events to AX 5.0 events and
corresponding mapping of X++ to C# code.
Pattern
1. Data Source Event Pattern
Web Form
Source Method
X++ Code
C# Code
Define a map of AxEvents to managed datasource control events.
Pattern
2.setMenuFunctionRecord With the EmplTable as part
of Data Source method pattern
Web Form
HRMEPVirtualNetworkCourseList
Source Method
HRMEPVirtualNetworkCertificateList:
setMenuFunctionRecord
X++ Code
public Common setMenuFunctionRecord(WebMenuFunction _p1,
Common _p2)
{
return emplTable;
}
C# Code
This code would implemented by setting up the property “Provider View “ to
Empltable at Data Source level in User Control.
<dynamics:AxDataSource ID="dsHRMVNetworkCertList"
runat="server"
DataSetName="HRMEPVirtualNetworkCertificateList"
ProviderView="EmplTable">
</dynamics:AxDataSource>
Pattern
3. setMenuFunctionRecord Without the EmplTable as
part of Data Source method pattern
Web Form
HRMEPInterviewTableList
Source Method
HRMEPInterviewTableList: setMenuFunctionRecord
X++ Code
Common setMenuFunctionRecord(WebMenuFunction _p1, Common
_p2)
{
return EmplTable::find(emplId);
}
Code moves to X++ method at data source level as shown
below
public Common getEmployeeRecord()
{
return EmplTable::find(emplId);
}
This method gets called from
context.
C# property for creating
C# Code
In C# , create property to get employee record as shown
below:
AxTableContext _employeeContext;
AxTableContext EmployeeContext
{
get
{
//check if the employee context is null
if (this._employeeContext == null)
{
//call the getEmployeeRecord to get the
employee record from the dataset
using (IAxaptaRecordAdapter record =
this.AxSession.AxaptaAdapter.
CreateAxaptaRecord(this.dsHRMInterviewTableList.GetDataSet(
).
DataSetRun.AxaptaObjectAdapter.Call("getEmployeeRecord")))
{
//check if the record is null
if (record != null)
{
this._employeeContext =
AxTableContext.Create(
AxTableDataKey.Create(this.AxSession, record, null));
}
}
}
//return the employee context
return this._employeeContext;
}
}
Then create menu event and set the context property
shown below:
as
void BaseWebpart_SetMenuItemProperties(object sender,
SetMenuItemPropertiesEventArgs e)
{
if (e.MenuItem.MenuItemAOTName ==
"HRMEPInterviewTableCreate" &&
e.MenuItem is AxUrlMenuItem)
((AxUrlMenuItem)e.MenuItem).MenuItemContext =
this.EmployeeContext;
}
Pattern
4. setMenuFunctionRecord returning null record
Web Form
HRMEPRecruitingTableList
Source Method
HRMEPRecruitingTableList: setMenuFunctionRecord
X++ Code
public Common setMenuFunctionRecord(WebMenuFunction _p1,
Common _p2)
{
return null;
}
C# Code
In user control menuitem context set to null as show below
in SetMenuItemProperties event.
((AxUrlMenuItem)e.MenuItem).MenuItemContext =null;
Pattern
5. Button Click Event Pattern
Web Form
Source Method
X++ Code
Clicked(){;
element.createProjJournalTrans();
}
C# Code
protected void Button_Transfer_Click(object sender, EventArgs e)
{
//Access the datasource name to which grid is attached.
string dataSourceName = this.EPProjTimeSheetGridView.DataMember;
//Get the datasource proxy object to which grid is attached.
FormDataSource dataSourceProxy =
this.EPProjTimeSheetDataSource.GetDataSourceView(dataSourceName).DataS
etView.MasterDataSource;
// TODO: Remove the below for-loop when AxGridView supports
checkboxes for marking records
// NOTE - temporary hack that will 'mark' all checkmarked records
for (IAxaptaRecordAdapter record = dataSourceProxy.getFirst(); record !=
null; record = dataSourceProxy.getNext())
{
int markValue = dataSourceProxy.markRecord(record, 1); // A value other
than zero results in the record being displayed as marked in grids on the form.
}
// Call X++ createProjJournalTrans method
// All 'marked' records will be moved to ProjJournalTrans table
this.EPProjTimeSheetDataSource.GetDataSet().DataSetRun.AxaptaObjectAdapt
er.Call("createProjJournalTrans");
}
Pattern
6. Pattern
Web Form
Source Method
X++ Code
public void tabChanged(int _fromTab, int _toTab)
{
#define.addressTab(2)
;
if (#addressTab == _fromTab &&
!custTable.AddressMap::validateAddress())
{
webTab.tab(#addressTab);
}
super(_fromTab, _toTab);
}
C#
This will move to C# control events.
Pattern
7. Set Menu -Provider View Pattern
Web Form
HRMEPDevelopmentPlanEdit
Source Method
HRMEPDevelopmentPlanEdit
X++ Code
public Common setMenuFunctionRecord(WebMenuFunction _p1,
Common _p2)
{
return emplTable;
}
C# Code
For getting correct context of the record on info/edit
page, on the list page ProviderView property is to table on
which edit or info is performed.
<dynamics:AxDataSource ID="dsHRMVNetworkCourseList"
runat="server" DataSetName="HRMEPVirtualNetworkCourseList"
ProviderView="HRMVirtualNetworkCourse">
Following piece is applicable in create scenario:
void BaseWebpart_SetMenuItemProperties(object sender,
SetMenuItemPropertiesEventArgs e)
{
if (e.MenuItem.MenuItemAOTName ==
"HRMEPVirtualNetworkCourseCreate" &&
e.MenuItem is AxUrlMenuItem)
((AxUrlMenuItem)e.MenuItem).MenuItemContext =
this.EmployeeContext;
}
In case of view and edit setting ProviderView
sufficient.
Pattern
8. CloseOk Button Pattern
Web Form
EPServiceOrderEdit
Source Method
closeOk
property is
X++ Code
public void closeOk()
{
SMAServiceOrderTable
serviceOrderTableLocal;
super();
if (serviceOrderTableLocal.RecId)
{
// The service order has now been created.
EP::redirectOnCreate(new
WebUrlMenuFunction(weburlitemstr(EPServiceOrderInfo)),servi
ceOrderTableLocal, "@SYS103432", "@SYS79051",
"@SYS103434");
}
else
{
//Service orders has not been created. No lines
entered.
info(strfmt("@SYS77621", "@SYS79051"));
}
}
C# Code
Code moves to redirect method through form events.
Form Event:
a) Creation
void formEPServiceOrderEdit_ItemInserted(object
sender, DetailsViewInsertedEventArgs e)
{
//check if update is successful, then
redirect to appropriate page
if (e.AffectedRows == 1)
{
//redirects to info page
this.RedirectToEPServiceOrderInfo();
}
else
{
//call info log
this.dsEPServiceOrderEdit.GetDataSet().DataSetRun.
AxaptaObjectAdapter.Call("callInfoLog");
}
}
b) Modification
void formEPServiceOrderEdit_ItemUpdated(object
sender, DetailsViewUpdatedEventArgs e)
{
//check if insert is successful, then
redirect to appropriate page
if (e.AffectedRows == 1)
{
//redirects to info page
this.RedirectToEPServiceOrderInfo();
}
else
{
//call info log method
this.dsEPServiceOrderEdit.GetDataSet().DataSetRun.
AxaptaObjectAdapter.Call("callInfoLog");
}
}
Redirection
void RedirectToEPServiceOrderInfo()
{
IAxaptaRecordAdapter record = null;
//specify the dynamics menu item
AxUrlMenuItem urlMenuItem = new
AxUrlMenuItem("EPServiceOrderInfo");
switch (this.FormAction)
{
//Insert mode
case
ApplicationProxy.EPFormAction.CreateMode:
record =
this.CreateServiceOrderTable();
break;
//Edit mode
case
ApplicationProxy.EPFormAction.EditMode:
this.ModifyServiceOrderTable();
DataSetViewRow currentRow =
this.DataSourceView.DataSetView.GetCurrent();
record = currentRow.GetRecord();
break;
}
//Set the context
urlMenuItem.MenuItemContext =
AxTableContext.Create(
AxTableDataKey.Create(this.AxSession, record, null));
//redirect to url
Response.Redirect(urlMenuItem.Url.OriginalString);
}
e.
Other guidelines

Calling X++ Methods from ASP.Net
Microsoft.Dynamics.Framework.BusinessConnector.Adapter namespace contains
AxaptaObjectAdapter and AxaptaAdapter classes that allows to call custom X++ methods and
static methods of a datasource using reflection.
// Import the following namespace to connect to the BusinesConnector object.
using Microsoft.Dynamics.Framework.BusinessConnector.Adapter;
// AxDataSource1 is the AxDataSource used by the User Control.
AxDataSource dataSource = this.AxDataSource1;
// AxGridView1 is the Ax Grid control used by the User Control.
string dataSourceName = this.AxGridView1.DataMember;
// Get the DataSetRun object from above AxDataSource.
DataSetRun dataSet = dataSource.GetDataSet( dataSourceName).DataSetRun;
// Get the AxapterAdapter object from the DataSetRun Object.
IAxaptaObjectAdapter dataSetAdapter =
dataSet.AxaptaObjectAdapter;
// Pass the Method-Name to be invoked & the parameter to the Axapter-Adapter object.
The return type is of type “Object”.
object returnValue = dataSetAdapter.Call(
"methodName",
"inputValue1")
The return value needs to be casted to its appropriate type using the mapping on page 201 of
Inside Microsoft Dynamics Ax 4.0.

Accessing X++ classes & methods from the ASP.Net
// Import the following namespace to create proxy object.
Using Microsoft.Dynamics.Framework.BusinessConnector.Proxy
// AxDataSource1 is the AxDataSource used by the User Control.
AxDataSource dataSource = this.AxDataSource1;
// AxGridView1 is the Ax Grid control used by the User Control.
string dataSourceName = this.AxGridView1.DataMember;
// Get the DataSourceView object from above AxDataSource.
AxDataSourceView dataSourceView =
dataSource.GetDataSourceView(dataSourceName);
// Get the DataSetView from the above DataSourceView object.
DataSetView dataSetView = dataSourceView.DataSetView;
// Get the proxy object from the above DataSetView object.
FormDataSource dataSourceProxy = dataSetView.MasterDataSource;
// The above proxy object can be used to access the Classes & Methods of the
datasource.
5.1.1. AX 5.0 pages in WSS3.0/MOSS 2007
In the process of migaration, last step is to host dynamics user control and pages in
Enterporise portal. The following steps would be used to deploy –




Deploy the dynamic user control into AOT using addin from Visual Studio.
The deployed user control will be available in Enterprise Portal for using it in the web
pages.
The exisiting page’s content needs to be changed/overwritten with new user controls.
The communication between the webparts need to be set as required.
For detailed steps on how to use dynamics controls on pages in Enterprise Portal refer to
Developing with Enterprise Portal 5.0
Note: When you make changes to the AOT, this may not get reflected immediately in
SharePoint due to the cache. The quickest way to refresh the cache is to open up Run
(Windows Key + R) and execute the command “iisreset”, which will restart the entire IIS.
And other option is to Click Refresh AOD link in EP home page, as shown below in the
diagram -
Download