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 -