community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations...

997

Transcript of community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations...

Page 1: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 2: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 3: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

TitlePageExtendingMicrosoftDynamics365forOperationsCookbookExtendthepotentialofyourDynamics365forOperationsimplementationSimonBuxton

BIRMINGHAM-MUMBAI

Page 4: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 5: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Copyright

Page 6: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 7: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Credits

Author

SimonBuxton

CopyEditor

ZainabBootwala

Reviewers

SimonKlingler

MartinWinkler

ProjectCoordinator

VaidehiSawant

CommissioningEditor

AaronLazar

Proofreader

SafisEditing

AcquisitionEditor Indexer

Page 8: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

DenimPinto TejalDaruwaleSoni

ContentDevelopmentEditor

SiddhiChavan

ProductionCoordinator

DeepikaNaik

TechnicalEditor

DhirajChandanshive

Page 9: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 10: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

AbouttheAuthorSimonBuxtonhasworkedwithDynamics365forOperationssinceitsearliestincarnations,startingoutasaconsultantanddeveloperinearly1999whenDynamics365forOperationswasknownasDamgaardAxapta1.5.HequicklybecameateamleaderatColumbusITPartnersandcarriedoutoneofthefirstAxaptaimplementationsintheUKbeforejoininganewreseller,SenseEnterpriseSolutions,asitstechnicaldirector.SenseEnterpriseSolutionsenjoyedaglobalreachthroughtheAxPactalliance,whereSimonwasplacedasAxPact'sTechnicalLead.

SimonplayedamajorroleingrowingthecompanyintoasuccessfulMicrosoftpartnerandwastheTechnicalLeadonanumberofhighlychallengingtechnicalprojectsaroundtheworld,rangingfromtheUK,toBahrain,totheUSA.Theseprojectsincludedevelopingsolutionsforthird-partylogistics,multichannelretail,andeventuallydevelopingananimalfeedvertical,aswellasintegratingDynamics365forOperationsintoproductioncontrolsystems,governmentgateways,ande-commercesolutions,amongothers.Now,workingwithBinaryConsultants,hewaspartofateamthatimplementedthefirstDynamics365forOperationsimplementationwithsupportfromMicrosoftaspartoftheCommunityTechnicalPreviewprogram(CTP).Theknowledgegainedaspartofthisprocessledtothecreationofthisbook.

SimonhasalsoworkedonMasteringMicrosoftDynamicsAX2012R3ProgrammingandMicrosoftDynamicsAX2012R2AdministrationCookbook.

IwouldliketothankmycolleaguesatBinaryConsultantsfortheircontinuedsupportthroughoutthewritingprocess.Iwouldalsoliketothankallthosewhohelpedreviewthisbook,MartinWinklerandSimonKlinglerinparticular,whoputinalotofeffortintoreviewingeachrecipe.AlotoftheinsightwrittenintothisbookwasonlypossiblebybeingpartoftheDynamics365forOperationscommunitytechnicalpreviewprocess,andworkingwithMicrosoft'sR&Dteamisatrueprivilege.Finally,Iamtold,Ihavetothankmypartner,Evi,forherpatienceandsupportasIdisappearedforhourseachnightandweekendworkingon"yetanotherbook?".Ofcourse,nobiocouldbecompletewithoutmentioningmychildren,TylerandIsabella,whoseembemusedastowhyIwouldvoluntarilydohomework.

Page 11: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 12: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

AbouttheReviewersSimonKlinglerhasbeenworkingwithMicrosoftDynamics365forOperationsanditspredecessorproducts(MicrosoftDynamicsAX,Axapta)since2001.Hegainedexperienceasadeveloper,consultant,presalesconsultant,productmanager,andsolutionarchitect.HehassuccessfullyimplementedERPsolutionsinnationalandinternationalprojectsfrom3to1000+users.Hewasaproductmanagerforseveraladd-onsforMicrosoftDynamicsERPsolutions.

Hestartedworkingonthelatestreleaseinitsveryearlydaysandregularlyexchangedexperiences,feedbacks,andlessonslearnedwiththeauthorofthisbook.Currently,heisworkingonseveralcloudandlocalbusinessdataimplementationprojectsasasolutionarchitect.

In2013,Simonco-foundedSemantax,acompanyprovidingproductsandexpertconsultingservicesbasedonMicrosoftDynamicsAX.InDecember2014,heco-foundedtheSolutionsFactory(www.sf-ax.com),acompanydeliveringfull-scaleMicrosoftDynamicsERPimplementations,instableandlong-lastingpartnershipswithitscustomers.Heisespeciallyproudoftheteamthatwasformedinthecourseofthelastyears:therightpeoplewiththerightattitude,agreatmixofexperiencedadvisersanddevelopers,aswellasyoungpotentials.Theteammembersmotivateeachotherandunfailinglyimpresstheclients.

MartinWinklerhasover13yearsofexperiencewithMicrosoftDynamics365forOperationsanditspreviousreleases.

AfterreceivinghisMaster'sdegreeinMathematicalComputerSciencesatViennaUniversityofTechnology,hegainedexperienceasanITConsultantatCapgeminiandlater,asaBIConsultantatanAustrianconsultingcompanyspecializinginCFO-targetedservices.

In2003,MartinjoinedSolutionsFactory,anewly-foundedAustriancompanythatspecializedinMicrosoftDynamics365forOperationsservices.StartingasadeveloperandheadofIT,helaterbecametheheadofdevelopmentofupto10developers.From2007onwards,headditionallybuiltupateamofperformanceexperts.In2008,thecompany(thenFWI)becamethelargestMicrosoftDynamics365forOperationspartnerinAustria.Whilestayingindevelopment,hemostlyworkedasTechnicalLeadConsultantforseveralcorporatecustomerswithglobalimplementationsandwithupto1000users.HealsocarriedoutnumerousprojectsintheareasofperformanceandMicrosoftBIforthesecustomers.

Togetherwithtwolong-termcolleagues,hefoundedhisowncompanyin2013,Semantax,providingproductsandexpertconsultingservicesbasedonMicrosoftDynamics365forOperations.In2014,theexpertsofSemantaxteamedupwithtwofurtherlong-termcolleagueswithvastknow-howinindustryprocessesandcorporateERPprojectstorelaunchtheSolutionsFactory.

SolutionsFactory(www.sf-ax.com)isdedicatedtoshapingcustomerprocessesandtomappingthemtoDynamics365forOperationsinanefficientandoptimalway.ItfollowsitsvisionofcontributingtothehighcompetitivecapabilityofEuropeanmanufacturingcompanies.Duringthereviewofthisbook,the

Page 13: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

teamwassupportingfivelargecustomerswiththeirMicrosoftDynamics365forOperationsimplementations.

Overthelastdecade,MartinhasmettheauthorofthebookatseveraltechnicalconferencesonMicrosoftDynamics365forOperations.Hewasimpressednotonlywiththeauthor'sin-depthtechnicalknowledge,butalsowithhisunderstandingofthebusinessandprocesssidesoftheERPbranch.Heenjoyedreviewingthepreviousbooksbythesameauthorandwashonoredtosupporttheauthorwiththisbookaswell.Hehopesthereaderswillenjoythereadasmuchashedid.

Page 14: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 15: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

www.PacktPub.comForsupportfilesanddownloadsrelatedtoyourbook,pleasevisitwww.PacktPub.com.

DidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusatservice@packtpub.comformoredetails.

Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooksandeBooks.

https://www.packtpub.com/mapt

Getthemostin-demandsoftwareskillswithMapt.MaptgivesyoufullaccesstoallPacktbooksandvideocourses,aswellasindustry-leadingtoolstohelpyouplanyourpersonaldevelopmentandadvanceyourcareer.

Page 16: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Whysubscribe?

FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,print,andbookmarkcontentOndemandandaccessibleviaawebbrowser

Page 17: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 18: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CustomerFeedback

ThanksforpurchasingthisPacktbook.AtPackt,qualityisattheheartofoureditorialprocess.Tohelpusimprove,pleaseleaveusanhonestreviewonthisbook'sAmazonpageathttps://www.amazon.com/dp/1786467135.

Ifyou'dliketojoinourteamofregularreviewers,youcane-mailusatcustomerreviews@packtpub.com.WeawardourregularreviewerswithfreeeBooksandvideosinexchangefortheirvaluablefeedback.Helpusberelentlessinimprovingourproducts!

Page 19: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 20: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

TableofContents

PrefaceWhatthisbookcovers

Whatyouneedforthisbook

Whothisbookisfor

SectionsGettingready

Howtodoit…

Howitworks…

There'smore…

Seealso

Conventions

Readerfeedback

CustomersupportDownloadingtheexamplecode

Errata

Piracy

Questions

1. StartingaNewProjectIntroduction

CreatingtheVisualStudioTeamServicesprojectGettingready

Howtodoit...

Howitworks...

Seealso...

ConnectingVisualStudiotoVisualStudioTeamServicesGettingready

Howtodoit...

Howitworks...

There'smore...

Page 21: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Seealso

CreatinganewModelandPackagesGettingready

Howtodoit...

Howitworks...

There'smore...Prefixesandnamingconventions

ConfiguringprojectandbuildoptionsGettingready

Howtodoit...Dynamics365forOperations'options

Theproject-specificparameters

There'smore...

CreatingaLabelfileGettingready

Howtodoit...

Howitworks...

There'smore...

2. DataStructuresIntroduction

CreatingenumeratedtypesGettingready

Howtodoit...

Howitworks...

There'smore...UsingEnumsforcomparisonandstatus

ExtensibilityinBaseEnums

CreatingextendeddatatypesGettingready

Howtodoit...

Howitworks...

There'smore...

CreatingsetuptablesGettingready

Page 22: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Howitworks...

CreatingaparametertableHowtodoit...

There'smore...Copyingandpastingmethodstosavetime

OptimisticconcurrencyandselectForUpdate

Seealso

CreatingmaindatatablesGettingready

Howtodoit...

Howitworks...

There'smore...Moreonindexes

Seealso

CreatingorderheadertablesGettingready

Howtodoit...

Howitworks...

There'smore...

CreatingorderlinetablesHowtodoit...

Howitworks...

Seealso

3. CreatingtheUserInterfaceIntroduction

CreatingthemenustructureGettingready

Howtodoit...

Howitworks...

CreatingaparameterformHowtodoit...

Howitworks...

Page 23: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...

Seealso

CreatingmenuitemsGettingready

Howtodoit...

Howitworks...

CreatingsetupformsHowtodoit...

Howitworks...

There'smore...

Creatingdetailsmaster(maintable)formsHowtodoit...

Howitworks...

Creatingadetailstransaction(orderentry)formHowtodoit...

Howitworks...

CreatingformpartsGettingready

Howtodoit...

Howitworks...

CreatetileswithcountersfortheworkspaceGettingready

Howtodoit...

Howitworks...

There'smore...

CreatingaworkspaceHowtodoit...

Howitworks...

There'smore...

4. ApplicationExtensibility,FormCode-Behind,andFrameworksIntroduction

CreatingahandlerclassusingtheApplicationExtensionfactoryGettingready

Page 24: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Howitworks...

There'smore...

Seealso...

HookingupanumbersequenceGettingready

Howtodoit...

Howitworks...Numbersequencesetup

Hookingupthenumbersequence

There'smore...

CreatingacreatedialogfordetailstransactionformsGettingready

Howtodoit...

Howitworks...

CreatingaSysOperationprocessHowtodoit...

Howitworks...

There'smore...Executingcodeusingthebatchframework

Callingaprocessfromaform

Usingthedatacontracttomakechangestothedialog

AddinganinterfacetotheSysOperationframeworkGettingready

Howtodoit...

Howitworks...

5. BusinessIntelligenceIntroduction

CreatingaggregatedimensionsGettingready

Howtodoit...

Howitworks...

Seealso

Page 25: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingaggregatemeasuresGettingready

Howtodoit...

Howitworks...

CreatingaggregatedataentitiesGettingready

Howtodoit...

Howitworks...

CreatingandusingkeyperformanceindicatorsGettingready

Howtodoit...

Howitworks...

There'smore...

6. SecurityIntroduction

CreatingprivilegesGettingready

Howtodoit...

Howitworks...

There'smore...Impactonlicensing

Seealso

CreatingdutiesHowtodoit...

Howitworks...

There’smore…

CreatingsecurityrolesHowtodoit...

Howitworks...

Seealso...

CreatingpoliciesHowtodoit...

Howitworks...

Page 26: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...

Seealso...

7. LeveragingExtensibilityIntroduction

ExtendingstandardtableswithoutcustomizationfootprintGettingready

Howtodoit...

Howitworks...

There'smore...

Creatingdata-eventhandlermethodsGettingready

Howtodoit...

Howitworks...

There'smore...

Howtocustomizeadocumentlayoutwithoutanover-layerHowtodoit...

Howitworks...

Theremore...

CreatingeventhandlermethodsGettingready

Howtodoit...

Howitworks...

ExtendingstandardformswithoutcustomizationfootprintGettingready

Howtodoit...

Howitworks...

There'smore...

UsingaformeventhandlertoreplacealookupGettingready

Howtodoit...

Howitworks...

CreatingyourownqueryfunctionsHowtodoit...

Page 27: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

8. DataManagement,OData,andOfficeIntroduction

CreatingadataentityGettingready

Howtodoit...

Howitworks...

There'smore...

Seealso

ExtendingstandarddataentitiesGettingready

Howtodoit...

Howitworks...

There'smore...

ImportingdatathroughDataImport/ExportFrameworkGettingready

Howtodoit...

Howitworks...

Seealso

Reading,writing,andupdatingdatathroughODataGettingready

Howtodoit...

Howitworks...

Seealso

9. ConsumingandExposingServicesIntroduction

CreatingaserviceGettingready

Howtodoit...

Howitworks...

ConsumingaDynamics365forOperationsSOAPserviceGettingready

Howtodoit...

Page 28: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

Seealso

ConsumingaDynamics365forOperationsJSONserviceGettingready

Howtodoit...

Howitworks...

There'smore...

Seealso...

ConsuminganexternalservicewithinDynamics365forOperationsGettingready

Howtodoit...

Howitworks...

There'smore...

10. ExtensibilityThroughMetadataandDataDate-EffectivenessIntroduction

UsingmetadatafordataaccessGettingready...

Howtodoit...

Howitworks...

UsingInterfacesforextensibilitythroughmetadataGettingready...

Howtodoit...

Howitworks...

Makingdatadate-effectiveGettingready...

Howtodoit...

Howitworks...

There'smore...

11. UnitTestingIntroduction

CreatingaFormAdaptorprojectGettingready

Howtodoit...

Page 29: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

CreatingaUnitTestprojectGettingready

Howtodoit...

Howitworks...

CreatingaUnitTestcaseforcodeGettingready

Howtodoit...

Howitworks...

CreatingatestcasefromataskrecordingGettingready

Howtodoit...

Howitworks...

12. AutomatedBuildManagementIntroduction

CreatingaTeamServicesBuildAgentQueueGettingready

Howtodoit...

Howitworks...

SettingupabuildserverGettingready

Howtodoit...

Howitworks...

There'smore...

Seealso

ManagingbuildoperationsGettingready

Howtodoit...

Howitworks...

ReleasingabuildtoUserAcceptanceTestingGettingready

Howtodoit...

Howitworks...

Page 30: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

13. ServicingYourEnvironmentIntroduction

ApplyingmetadatafixesGettingready

Howtodoit...

Howitworks...

There'smore...

Seealso

ApplyingbinaryupdatesGettingready

Howtodoit...

Howitworks...

Seealso

ServicingthebuildserverGettingready

Howtodoit...

Howitworks...

ServicingtheSandbox-StandardAcceptanceTestenvironmentGettingready

Howtodoit...

Howitworks...

There'smore...Servicingproduction

Seealso

14. WorkflowDevelopmentIntroduction

CreatingaworkflowtypeGettingready

Howtodoit...

Howitworks...

Seealso...

CreatingaworkflowapprovalGettingready

Page 31: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Howitworks...

CreatingamanualworkflowtaskGettingready

Howtodoit...

Howitworks...

HookingupaworkflowtotheuserinterfaceGettingready

Howtodoit...

Howitworks...

CreatingasampleworkflowdesignGettingready

Howtodoit...

Howitworks...

15. StateMachinesIntroduction

CreatingastatemachineGettingready

Howtodoit...

Howitworks...

CreatingastatemachinehandlerclassGettingready

Howtodoit...

Howitworks...

There'smore...

UsingmenuitemstocontrolastatemachineGettingready

Howtodoit...

Howitworks...

HookingupthestatemachinetoaworkflowGettingready

Howtodoit...

Howitworks...

Page 32: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...

Page 33: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 34: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ExtendingMicrosoftDynamics365forOperationsCookbookCopyright©2017PacktPublishingAllrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.

Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.

PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.

Firstpublished:May2017

Productionreference:1120517

PublishedbyPacktPublishingLtd.LiveryPlace35LiveryStreetBirminghamB32PB,UK.

ISBN978-1-78646-713-3

www.packtpub.com

Page 35: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 36: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

PrefaceMicrosoftDynamics365forOperationsisanERPsolutionforcomplexsingle-site,multi-site,andmulti-languageglobalenterprises.Thisflexibleandagilesolutionprovidesadvancedfunctionalityformanufacturing,retail,publicsector,andservicesectorindustries.Notonlydoesthissolutionprovideastrongsetofbuilt-infunctionality,italsoprovidesanindustry-leadingintegrateddevelopmentenvironment,allowinganorganizationtoprovideevenhigherlevelsoffit.ThisbookshouldbeinthetoolbeltofanysoftwareengineerwhoworkswithorisabouttoembarkonacareerwithDynamics365forOperations.

ThisprovidessoftwareengineersandthoseinvolvedindevelopingsolutionswithinDynamics365forOperationswithatoolkitofpracticalrecipesforcommondevelopmenttasks,withimportantbackgroundinformationtoprovidedeepinsighttoallowtherecipestobeadaptedandextendedforyourownuse.Evenforexperiencedsoftwareengineers,thisbookwillprovideagoodsourceofreferenceforefficientsoftwaredevelopment.

ForthosemovingfromMicrosoftDynamicsAX2012,wecovercriticalchangesinhowsoftwareisadapted,howtousethenewextensibilityfeaturesofMicrosoftDynamics365forOperations,andtipsonhowtousetheminapracticalway.Wealsocoverthefundamentalchangesinthephysicalstructureoftheapplicationmetadata,theapplicationdevelopmentlifecycle,andhowwefitinwiththenewcloud-firstdevelopmentparadigmwithLifecycleservicesandVisualStudioTeamServices.IntegrationwillbeaconcerntoAXdevelopers,andwecoverthisindetailwithworkingexamplesofcodethatcanbeadaptedtoyourownneeds.

Inordertofacilitatethis,thebookfollowsthedevelopmentofasolutionasameanstoexplainthedesignanddevelopmentoftables,classes,forms,BI,menustructures,workflow,andsecurity.WebeginatthestartofthedevelopmentprocessbysettingupaVisualStudioTeamServicesproject,integratingLifecycleservices,andexplainingnewconceptssuchasPackages,Models,Projects,andwhathappenedtolayers.Thebookprogresseswithchaptersfocusedoncreatingthesolutioninapracticalorder,butitiswritteninsuchawaythateachrecipecanbeusedinisolationasapatterntofollow.

Thesamplesolutionwasdesignedanddevelopedasthebookwaswrittenandisavailablefordownload.ThereisasampleOperationsproject,ODataC#integrationtestproject,andaC#projectforusingwebservicessuppliedbyMicrosoftDynamics365forOperations.

Withthiscomprehensivecollectionofrecipes,youwillbearmedwiththeknowledgeandinsightyouneedtodevelopwell-designedsolutionsthatwillhelpyourorganizationtogetthemostvaluefromthiscomprehensivesolutionforboththecurrentandtheupcomingreleasesofMicrosoftDynamics365forOperations.

Page 37: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 38: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

WhatthisbookcoversChapter1,StartingaNewProject,coverssettingupanewVisualStudioTeamServiceproject,integratingwithLifecycleServices,andcreatingaMicrosoftDynamics365forOperationspackageandmodel.

Chapter2,DataStructures,containscommonrecipesforcreatingdatastructureelementssuchastables,enumerateddatatypes,andextendeddatatypes.Therecipesarewrittentopatterns,guidingyouthroughthestepsyouwouldtakewhencreatingthetypesoftableusedinMicrosoftDynamics365forOperationsapplicationdevelopment.

Chapter3,CreatingtheUserInterface,explainshowtocreatetheuserinterfaceelementssuchasmenus,forms,formparts,tiles,andworkspaces.ThischapterincludesrecipesforeachofthemaintypesofuserinterfacesusedwhencreatingorextendingDynamics365forOperationswithpracticalguidanceandtipsonhowtodothisefficiently.

Chapter4,ApplicationExtensibility,FormCode-Behind,andFrameworks,helpsusstepintowritingthebusinesslogicbehindouruserinterfaceandunderstandhowtowritecodedesignedtobeextensible,allowingotherpartiestoextendourcodewiththeover-layeringthatcanversion-lockcustomers.WealsocovertheSysOperationframework,usingwhichprocessesaredeveloped,andwe'llseehowtoaddauserinterfacetothem.

Chapter5,BusinessIntelligence,coversthecreationofabusinessintelligenceprojectthatcanbeusedtocreatepowerfuldashboardsinMicrosoftPowerBI.Therecipesinthischaptercoverthecreationofaggregatedimensions,measures,dataentities,andKPIsinareal-worldcontext.Thisisdoneusingthesamplevehiclemanagementapplicationthatiscreatedthroughthecourseofthisbook.

Chapter6,Security,explainsthesecuritymodeldesigninMicrosoftDynamics365forOperationsandprovidesrecipesforthecreationoftheelementsusedinsecurity.Therecipesaugmentthestandarddocumentationinordertoprovidereal-worldexamplesonhowtocreateandmodelDynamics365forOperationssecurity.

Chapter7,LeveragingExtensibility,showshowextensibilitycanbesaidtobeoneofthebiggestchangesinDynamics365forOperations.Thischapterpaysspecialattentiontothekeyaspectsofhowtouseextendthestandardapplicationwithoutbecomingversionlockedinacustomizedsolution.

Chapter8,DataEntityExtensibility,OData,andOffice,coversthemanywaysinwhichweintegratewiththeworldoutsideofDynamics365forOperations.Thiscovershowtocreateandextenddataentities,workwithMicrosoftOffice,anduseODatatoread,write,andupdatedatainDynamic365forOperationsfromaC#project.

Chapter9,ConsumingandExposingServices,providesrecipesforcreatingaservicefromwithinDynamics365forOperations,consumingexternalservices,andalsoonconsumingDynamics365forOperationsservicesinC#usingSOAPandJSON.Allthisiscoveredusingpracticalexamplesthatshouldeasilytranslateintoyourownspecificrequirements.

Page 39: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Chapter10,ExtensibilitythroughMetadataandDataDate-Effectiveness,pushesextensibilityevenfurtherbyshowinghowwecanusemetadatastoredindatatoputmorepowerinthehandsofsystemadministrators.Wealsocoverhowtomakeourtablesdateeffective.

Chapter11,UnitTesting,providesrecipestoshowhowtocreateunittestsandhowtheyareusedwiththeapplicationlifecycle.Thischaptercoversaninsightintotest-drivendevelopment,automatedunittestingonthebuildserver,andhowtocreateandusethetaskrecordertocreatetestcases.

Chapter12,AutomatedBuildManagement,helpsusmovemoreintoapplicationlifecyclemanagementwherethischapterprovidesrecipesforsettingupandusingabuildserver.

Chapter13,ServicingYourEnvironment,providespracticalrecipesthatareintendedtoaugmentthestandarddocumentationprovidedbyMicrosoftinordertoprovidereal-worldexamplesonhowweserviceourDynamics365forOperationsenvironments.

Chapter14,Workflow,coversthedevelopmentofworkflowapprovalsandtasksinDynamics365forOperations.Therecipesaregivencontextbycontinuingtoworkwiththesampleapplicationthatiscreatedthroughthecourseofthisbook,effectivelyexplainingstatemanagement,whichiseasilymisunderstood.

Chapter15,StateMachines,coversstatemachines,whichisanothernewfeatureinDynamics365forOperations.Thischaptercoversallkeyareasofthisnewfeature,explainingwhenandhowtousethisfeatureappropriately.

Page 40: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 41: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

WhatyouneedforthisbookInordertogainaccesstoMicrosoftDynamics365forOperations,youneedtobeeitheraMicrosoftpartnerorcustomer.Tosignupforagainaccessasapartner,youcanrefertoLifecycleServicesforDynamics365forOperationspartnersathttps://docs.microsoft.com/en-us/dynamics365/operations/dev-itpro/lifecycle-services/getting-started-lcs.

Tosignupforasubscriptionasacustomer,refertoLifecycleServicesforDynamics365forOperationspartnersathttps://docs.microsoft.com/en-us/dynamics365/operations/dev-itpro/lifecycle-services/getting-started-lcs.

YouwillneedtodownloadordeployaDynamics365forOperationsdevelopmentVMinAzure.ToruntheVMlocally,youwillneedatleast100GBfreespaceavailableandaminimumof12GBfreememory,ideally24GB.Itwillrunonaslittleas8GBofassignedmemory,buttheperformancewillsufferasaresult.

Theofficialsystemrequirementsareasfollows:

Systemrequirements(https://docs.microsoft.com/en-us/dynamics365/operations/dev-itpro/get-started/system-requirements)Developmentsystemrequirements(https://docs.microsoft.com/en-us/dynamics365/operations/dev-itpro/dev-tools/development-system-requirements)

Page 42: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 43: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Whothisbookisfor

IfyouareasoftwaredevelopernewtoDynamics365forOperationsprogrammingoranexperiencedsoftwareengineermigratingfromitspredecessor,DynamicsAX,thisbookisanidealtutorialtohelpyouavoidthecommonpitfallsandmakethemostofthisadvancedtechnology.Thisbookisalsousefulifyouareasolutionarchitectortechnicalconsultant,asitprovidesadeeperinsightintothetechnologybehindthesolution.

Page 44: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 45: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

SectionsInthisbook,youwillfindseveralheadingsthatappearfrequently(Gettingready,Howtodoit,Howitworks,There'smore,andSeealso).

Togiveclearinstructionsonhowtocompletearecipe,weusethesesectionsasfollows:

Page 46: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 47: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyThissectiontellsyouwhattoexpectintherecipe,anddescribeshowtosetupanysoftwareoranypreliminarysettingsrequiredfortherecipe.

Page 48: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 49: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit…Thissectioncontainsthestepsrequiredtofollowtherecipe.

Page 50: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 51: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks…Thissectionusuallyconsistsofadetailedexplanationofwhathappenedintheprevioussection.

Page 52: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 53: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore…Thissectionconsistsofadditionalinformationabouttherecipeinordertomakethereadermoreknowledgeableabouttherecipe.

Page 54: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 55: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

SeealsoThissectionprovideshelpfullinkstootherusefulinformationfortherecipe.

Page 56: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 57: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ConventionsInthisbook,youwillfindanumberoftextstylesthatdistinguishbetweendifferentkindsofinformation.Herearesomeexamplesofthesestylesandanexplanationoftheirmeaning.

Codewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandlesareshownasfollows:"ThisshouldhavecreatedafoldercalledBaseEnums."

Ablockofcodeissetasfollows:

publicvoidmodifiedField(FieldId_fieldId)

{

super(_fieldId);

switch(_fieldId)

{

casefieldNum(ConWHSVehicleServiceLine,ItemId):

this.initFromInventTable(

InventTable::find(this.ItemId));

break;

}

}

Newtermsandimportantwordsareshowninbold.Wordsthatyouseeonthescreen,forexample,inmenusordialogboxes,appearinthetextlikethis:"IntheAddNewItemdialog,selectDataModelfromtheleft-handlistandQueryfromtheright."

Warningsorimportantnotesappearinaboxlikethis.

Tipsandtricksappearlikethis.

Page 58: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 59: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook-whatyoulikedordisliked.Readerfeedbackisimportantforusasithelpsusdeveloptitlesthatyouwillreallygetthemostoutof.

Tosendusgeneralfeedback,[email protected],andmentionthebook'stitleinthesubjectofyourmessage.

Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideatwww.packtpub.com/authors.

Page 60: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 61: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.

Page 62: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 63: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

DownloadingtheexamplecodeYoucandownloadtheexamplecodefilesforthisbookfromyouraccountathttp://www.packtpub.com.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.

Youcandownloadthecodefilesbyfollowingthesesteps:

1. Loginorregistertoourwebsiteusingyoure-mailaddressandpassword.2. HoverthemousepointerontheSUPPORTtabatthetop.3. ClickonCodeDownloads&Errata.4. EnterthenameofthebookintheSearchbox.5. Selectthebookforwhichyou'relookingtodownloadthecodefiles.6. Choosefromthedrop-downmenuwhereyoupurchasedthisbookfrom.7. ClickonCodeDownload.

YoucanalsodownloadthecodefilesbyclickingontheCodeFilesbuttononthebook'swebpageatthePacktPublishingwebsite.Thispagecanbeaccessedbyenteringthebook'snameintheSearchbox.PleasenotethatyouneedtobeloggedintoyourPacktaccount.

Oncethefileisdownloaded,pleasemakesurethatyouunziporextractthefolderusingthelatestversionof:

WinRAR/7-ZipforWindowsZipeg/iZip/UnRarXforMac7-Zip/PeaZipforLinux

ThecodebundleforthebookisalsohostedonGitHubathttps://github.com/PacktPublishing/Extending-Microsoft-Dynamics-365-for-Operations-Cookbook.Wealsohaveothercodebundlesfromourrichcatalogofbooksandvideosavailableathttps://github.com/PacktPublishing/.Checkthemout!

Page 64: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 65: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Errata

Althoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyoufindamistakeinoneofourbooks-maybeamistakeinthetextorthecode-wewouldbegratefulifyoucouldreportthistous.Bydoingso,youcansaveotherreadersfromfrustrationandhelpusimprovesubsequentversionsofthisbook.Ifyoufindanyerrata,pleasereportthembyvisitinghttp://www.packtpub.com/submit-errata,selectingyourbook,clickingontheErrataSubmissionFormlink,andenteringthedetailsofyourerrata.Onceyourerrataareverified,yoursubmissionwillbeacceptedandtheerratawillbeuploadedtoourwebsiteoraddedtoanylistofexistingerrataundertheErratasectionofthattitle.

Toviewthepreviouslysubmittederrata,gotohttps://www.packtpub.com/books/content/supportandenterthenameofthebookinthesearchfield.TherequiredinformationwillappearundertheErratasection.

Page 66: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 67: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

PiracyPiracyofcopyrightedmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.IfyoucomeacrossanyillegalcopiesofourworksinanyformontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.

Pleasecontactusatcopyright@packtpub.comwithalinktothesuspectedpiratedmaterial.

Weappreciateyourhelpinprotectingourauthorsandourabilitytobringyouvaluablecontent.

Page 68: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 69: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

QuestionsIfyouhaveaproblemwithanyaspectofthisbook,[email protected],andwewilldoourbesttoaddresstheproblem.

Page 70: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 71: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

StartingaNewProjectInthischapter,wewillcoverthefollowingrecipes:

CreatingtheVisualStudioTeamServicesprojectConnectingVisualStudiotoyourVisualStudioTeamServicesCreatinganewModelandPackageConfiguringprojectandbuildoptionsCreatingaLabelfile

Page 72: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 73: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

IntroductionMicrosoftDynamicsAX2012underwentanamechangeinwhatwouldhavebeenversion7.TheofficialnameisnowMicrosoftDynamics365forOperations.Itisn'tjustthattheversionnumberhasbeendropped,butitappearstohavebeenadoptedintotheMicrosoftDynamics365productsuite.TheproductisnotacomponentofMicrosoftDynamics365,whichisjustawaytogroupMicrosoft'svariousbusinesssolutions.Wecan't,therefore,shortenthenametoDynamics365,wewillrefertheproductbyeitheritsfullnameortheabbreviationOperations.

NewfeatureswillbeintroducedtoOperationsasbothacontinualandcumulativeprocess.Therearetwomaintypesofupdate,PlatformandApplication.Platformupdatesaresimilartothebinaryupdatesinpriorreleases,butalsocontainAOTelementsthatarenowlockedandcannolongerbechanged.Platformupdatescancontainchangestothelanguage,andnewfeatureshavebeenbroughtinwitheachbi-yearlyrelease.WhenrunningintheCloud,MicrosoftwillperiodicallyreleaseupdatestothePlatformforyou.Thisisneeded,sincetheitusesAzureSQLServerandtheymayneedtoservicetheplatforminordertomaintaincompatibilitytothedatabaseserver.

ApplicationupdatesarechangestotheotherpackagesthatmakeupthesourcecodeofOperations,andcanbeconsideredsimilartothemetadataupdatesinpreviousreleasesofOperations.

ThisbookwasstartedontheMay2016,orUpdate1releaseandhasbeenupdatedwitheachrelease.TheversiononpublicationisUpdate5,releasedinMarch2017.

AlldevelopmentworkiscarriedoutinconjunctionwithVisualStudioTeamServicesorVSTS.Itusedtobeoptional,buttheimplementationprocessthatismanagedthroughLifecycleServices(LCS)requiresthatwehaveaVSTSprojectlinkedtoittofunctiontoitsfullest.Thisisnotjustforsourcecontrolandworkmanagement,butitisalsousedwhenperformingcodeupgrades.

PleaseseethefollowinglinksforfurtherreadingonMicrosoftDynamics365forOperations:

FormoreinformationonLCS,pleaseseethelink,https://lcs.dynamics.com/Logon/IndexAnoverviewofMicrosoftDynamics365forOperationsforDevelopersandITProsisavailableathttps://docs.microsoft.com/en-us/dynamics365/operations/dev-itpro/ToobtainanevaluationcopyofMicrosoftDynamics365forOperations,pleaseseethelink,https://docs.microsoft.com/en-us/dynamics365/operations/dev-itpro/dev-tools/get-evaluation-copy

AlldevelopmentworkinOperationsiseitherperformedonadevelopmentvirtualmachinehostedinAzure,oralocalvirtualmachine.Eachdeveloperwillusetheirownvirtualmachine.AzurehostedvirtualmachinesaredeployedthroughLCSunderyourownAzuresubscription,andcanbeusedtodevelopment,learning,anddemonstration.Once,asacustomer,acloudhostedsubscriptionhasbeenbought,youareprovided3environmentsaspartofthatsubscriptionunderMicrosoft'ssubscription.TheseareBuild,Sandbox,andProduction.ThebuildseverisaOneBoxserver(allinonevirtualmachine)thatisalsolabelledDevelopment,butitshouldalwaysbeusedasabuildserverandnotfordevelopment.Thesandboxserverisafullenvironment,withmultipleserversusingaseparateAzureSQLServer.Theproductionenvironmentistheenvironmentthatyou(asacustomer)willgolivewith.All

Page 74: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

codemustfirstbedeployedtothesandboxbeforeitisappliedtolive,whichthisisenforcedbyLCS-nomore'quickfixes'directlyintolive,andnoaccesstoSQLserverfortheproductionenvironment.

TheonpremiseversionofOperationsmayallowustobypasssomeoftheserules,butweshouldn'ttry-thesepracticesofforcingcodethroughafulltestingcycleareveryimportant.Waitingacoupleofdaysforaneededfeaturemaybeinconvenient,butregressionisperceivedverynegativelybyusers.Duringtheimplementationweaskalotoftheusers,theyarealreadybusywiththeirjobsandarebeingaskedtoalsohelpwithtestingnewsoftware,souserbuy-intotheprojectisacriticalfactor,andregressionisthemostefficientwayoferodingtheinitialexcitementofdeliveringnewsoftware.

Forlocaldevelopmentvirtualmachinesthatareoftenthecheapestoption,wewilldownloadthevirtualmachinefromMicrosoftConnect.ThisisawebsiteusedformanyprogramsatMicrosoft,andaccessisprovidedtopartnersandcustomers.

Page 75: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 76: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingtheVisualStudioTeamServicesprojectThetermsVisualStudioTeamServicesandTeamFoundationServer(TFS)areoftenusedinterchangeably.InVisualStudio,theuserinterfacestatesthatweareconnectingtoaTeamFoundationServer.However,weareactuallyconnectingtoVSTS,whichisanonlineservice.VSTSisrequiredforOperationsdevelopment,andthatiswhatwewilluse.

Theprojectisnormallycreatedundertheend-user'sVSTSsite,unlesstheworkisbeingwrittenasanISVsolution(orapersonaldevelopmentorlearningproject).Thereasonforusingtheclient'sVSTSsystemisthatLCSisassociatedwiththeVSTSsite,andsupportcallscreatedthroughCloud-poweredsupportaregeneratedwithintheassociatedVSTS.CloudpoweredsupportisanonlinesupportsolutionwithinLCSthatisexposedtotheOperationsclient,allowinguserstologsupportissueswiththeirinternalsupportteam.

Foruptofiveusers,VSTSisfree,andthecustomercancreatemanyaccountswithlimitedaccesswithoutcharge.Theseaccountsascalledstakeholderaccounts,andallowstheuseraccesstoworkitems,whichalsoallowstheuserstheabilitytologsupportcallsfromwithinOperations.ForthosewithanMSDNsubscription,thefiveuserlimitisnotcounted.

ThisprocessisnormallyperformedaspartoftheLCSprojectcreation.Ifthiswereanimplementationprojecttype,theprojectiscreatedwhenthecustomersignsupforOperations.ThecustomerwouldtheninvitetheirCloudsolutionprovider(Partner)totheproject.Ifthiswereaninternaldevelopmentproject,suchasanewverticalsolutionbyanISV,aMigrate,createsolutions,andlearnDynamics365forOperationsprojecttypewouldbeused.

Ineithercase,wewillhaveanLCSproject,whichwillusuallyhaveanAzureVMdeployedthatactsasabuildserver.

Forsimplicity,andtokeepthefocusonsoftwaredevelopment,aprojectoftypeMigrate,createsolutions,andlearnDynamics365forOperationswascreatedforthepurposeoftheexampleofthebook.

Page 77: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 78: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyBeforewegetstarted,wewillneedanLCSprojectandaVSTSsite.TheVSTSsitecanbecreatedthroughthefollowinglink:

https://www.visualstudio.com/en-us/products/visual-studio-team-services-vs.aspx

Oncewehavethesitecreated,wecanthencreateourproject.

Page 79: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 80: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreatetheproject,followthesesteps:

1. NavigatetoyourVSTSsite,forexample,https://<yourdomain>.visualstudio.com/.2. UnderRecentprojects&teams,clickonNew.3. Completetheformasshownasfollows:

Field Value

Projectname

Uniquename,carefultonametheprojectsforeasyrecognition,andhowtheyareordered.ThisismoreimportantforISVswhomayhavemanyprojects.

Description Shortdescriptionoftheproject

Processtemplate Agile

Versioncontrol TeamFoundationVersionControl

4. PressCreateproject.

5. Oncecomplete,youcanthennavigatetoyourprojectandworkwithVSTSinordertoplanyourproject.

6. ToauthenticatewithLCS,wewillneedtogenerateapersonalaccesstoken;tosetthisup,clickonthecontrolpanel(cog)icon,asshowninthefollowingscreenshot:

7. Thistakesyoutothecontrolpanel,again,onthetop-rightclickonyournameandchooseSecurity,asshowninthefollowingscreenshot:

Page 81: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

8. Thepersonalaccesstokensoptionisselectedbydefault;ontheright-handpane,clickonAdd.9. OnCreateapersonalaccesstokenform,enterashortdescription,forexample,theLCSproject

name.SettheExpiresinfieldbasedonhowlongyouwouldlikeittolastfor.10. LeavingtheAccountsandAuthorizedscopesfieldsasdefault;pressCreatetoken.11. Finally,copytheresultantaccesscodeintoasafeplace;wewillneeditwhenwelinkVSTStoLCS.

Ifwedon't,wewillhavetocreateanewaccesstokenasyoucan'tseeitafterthewebpageisclosed.

Next,wewillneedtolinktheprojecttoourLCSproject.IfanLCSprojectisnotcurrentlylinkedtoaVSTSproject,wegetthefollowingmessageonthelefthandside,asshowninthefollowingscreenshot:

ToconfigureVSTSfortheLCSproject,followthesesteps:

1. ToauthenticatewithLCS,wewillneedtogenerateapersonalaccesstoken,sofromwithinVSTS.2. ClickontheSetupVisualStudioTeamServicesbuttonintheActioncenterdialogbox.3. OntheEntertheVisualStudioTeamServicesitepage,entertheURLofourVSTSsiteintothe

VisualStudioTeamServicessiteURLfield;forexample,https://<mysite>.visualstudio.com/.4. EnterthepersonalaccesstokengeneratedearlierintothePersonalaccesstokenfield.5. PressContinue.6. OntheSelecttheVisualStudioTeamServiceprojectpage,selecttheprojectfromtheVisualStudio

TeamServicelist.7. YouarethenshowntheWorkitemtypemappinglist.ThisallowsyoutoselecthowtoLCSWorkitem

Type/LCSWorkitemSubTypeelementstoVSTSWorkitemTypeelements.LeavethisasthedefaultandpressContinue.

8. OnthefinalReviewandsavepage,pressSave.9. Thistakesusbacktothemainprojectpageandtheactioncenterwillaskyoutoauthorizethe

project;clickonAuthorize.

Page 82: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

10. Youwillbewarnedaboutbeingredirectedtoanexternalsite;clickonYes.11. Youmaybeaskedtologon;ifso,doitwiththeaccountyouuseforVSTS,whichmightbeyour

Microsoftaccount.12. ThiswillopentheAuthorizeapplicationpagefromwithinVSTS,andyouwillbetoldthatyouare

allowingMicrosoftDynamicsLifecycleServicestoaccesstheVSTSandthespecificpermissionsitwillreceive.PressAccept.

Page 83: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 84: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

OperationsusesVSTSforitssourcecontrol,work,andbuildmanagement.Theonlystepsherethatwetechnicallymustperformarestep1throughstep5,butwithoutperformingtheprevioussteps,welosetheabilitytointegrateLCS.Ifourprojectwasforacustomerimplementation,weshouldconsideritmandatorytointegrateVSTSwithLCS.

Page 85: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 86: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Seealso...

FormoreinformationonVSTSandLCS,pleasecheckoutthefollowinglinks:

AXDevALMusageguideandresources(https://blogs.msdn.microsoft.com/axdevalm/)LCSforMicrosoftDynamics365forOperationscustomers(https://ax.help.dynamics.com/en/wiki/how-lifecycle-services-for-microsoft-dynamics-ax-works-lcs/)Developertopologydeploymentwithcontinuousbuildandtestautomation(https://ax.help.dynamics.com/en/wiki/developer-topology-deployment-with-continuous-build-and-test-automation/)

Thenextlinkisusefulbackgroundknowledge,butalotofthisisoneforyouwhenusinganimplementationLCSproject:

SetuptechnicalsupportforMicrosoftDynamics365forOperations(https://ax.help.dynamics.com/en/wiki/ax-support-experience/)

ThislinkisforwhenwehaveacustomerimplementationprojectanddemonstratesomeofthesynergyofleveragingVSTSandLCSwithOperations.

Page 87: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 88: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ConnectingVisualStudiotoVisualStudioTeamServices

EachdeveloperhastheirowndevelopmentVM,hostedeitherinAzureorlocally.Thisisbydesignandispartoftheapplicationlifecycleprocess.Eachdeveloperwouldgetthelatestcodefromsourcecontrol,andthencheckintheirchangesaccordingtheirorganization'sdevelopmentmethodology.Aspartofthischeckintheycanlinkthecheck-ins.Thisallowsabuildtothenbecreated,andwegainaleveloftraceabilitysinceeachworkitem(userstory,feature,bug,andsoon.)islinkedtothecheck-insinthatbuild.Thisalsoallowstestprojectstobeautomaticallyexecutedwhenthebuildisgenerated.

Page 89: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 90: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Gettingready

Oncethevirtualmachinehasstarted,ensurethatithasInternetaccess,andthatyouhaveusedtheadminuserprovisioningtooltoassociateyourO365accountwiththeadministratoraccountofOperations.

Beforeyoustartthis,especiallywhenworkinginateam,wemustrenametheVMtomakeituniqueacrossourteam;seetheThere'smore...sectionfordetailsonthis.

Page 91: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 92: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...ToconnectVisualStudiotoVSTS,followthesesteps:

1. Createafolderforyourprojects,andunderneathasubfolderwithyourinitials,orothersthatmakethefolderunique,withinyourteam;inmyexample,IchoseC:ProjectsTFS.

2. StartVisualStudio.3. Youwillbepresentedwiththelicensingpage.Usethepagetologintotheaccountusedtocreatethe

projectwithinVSTS.WhichcouldbeeitheryourMicrosoftaccount,orWork(O365)account.4. Onthetoptoolbar,selectTeamandthenManageconnections.5. TheTeamExplorerwillopen,underdefaultlayout,ontheright-handside.Onthispane,select

ManageConnections|ConnecttoTeamProject:

6. ThiswillopentheConnecttoTeamFoundationServerdialog,intheSelectaTeamFoundationServerdrop-downlistandselectyourVSTSsite.

7. Selectyourprojectinthelowerportionofthedialog,asshowninthefollowingscreenshot:

8. AfterpressingConnect,VisualStudioisconnectedtoyourproject.9. Wehaveonefinalstepbeforewecontinue;wehavetoconfigureourworkspacesoVisualStudio

knowswhichfoldersareundersourcecontrolandhowtheymaptoVSTS.OnTeamExplorer,clickonConfigureworkspaceundertheProjectsection.ThiswillshowtheConfigureWorkspacesectionatthetopoftheTeamExplorer.

Page 93: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

10. DonotpressMap&Get.11. PressAdvanced....12. TheEditWorkspacedialogwilllooksimilartothefollowingscreenshot:

ForOperationsdevelopment,wewillneedtomapaprojectsfolderandtheOperationslocalpackagesfolder(theapplicationsourcecode,ormetadataasitisoftenreferredto)totwodifferentfoldersinVSTS.TheProjectsfolderistheonewecreatedearlier,whichwasC:ProjectsSBinmycase.TheOperationslocalpackagesfolderisC:AOSServicePackagesLocalDirectory.

Ifyoulookaroundthelocalpackagesfolder,youcanseehowtherelationshipbetweenpackageandModelisactuallystored.

13. Inmycase,theprojectisB05712_AX7_DevelopmentCookbook,soIwillconfigurethedialogasshowninthefollowingscreenshot:

14. PressOK.15. Youwillthenbetoldthattheworkspacehasbeenmodified,andifyouwanttogetthelatestcode.

Eitheroptionhasnoeffectifwearethefirstdeveloper,butitisagoodhabittoalwayspressYes.

Page 94: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 95: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

SourcecontrolinOperationshascomealongwayinthisrelease,mainlybecauseourdevelopmenttoolisnowVisualStudioandthatthesourcefilesarenowactualfilesinthefilesystem.OperationsnolongerneedsspecialcodetointegratewithaTeamFoundationServer.

Thereasonwehavetwofoldersisthatourprojectsdon'tactuallycontainthefileswecreatewhenwritingcode.Whenwecreateafile,suchasanewclass,itiscreatedwithinthelocalpackagesfolderandreferencedwithintheproject.Thisalsomeansthatwecan'tjustzipupaprojectande-mailittoaco-worker.ThisisdonebyconnectingtothesameVSTSprojectorusingaprojectexportfeature.

Page 96: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 97: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...

Whenworkingwithmultipledevelopers,oneoftenoverlookedtaskistorenamethevirtualmachine.Thishasgotteneasierwitheachupdate,andthestepswetakeatthecurrentreleaseareasfollows:

1. UseComputermanagementtorenamethemachine.UsesomethinglikeprojectIDandyourinitialsforthis;forexample,B05712SB.

2. Restartthevirtualmachine.3. UsetheSQLServerReportingServicesconfigurationutilitysothatitreferencesthecorrectserver

name.

Page 98: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 99: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Seealso

RenamingaVM(https://ax.help.dynamics.com/en/wiki/visual-studio-online-vso-machine-renaming/)ConfiguringyourVSTSmappingafteracodeupgrade(https://ax.help.dynamics.com/en/wiki/configuring-your-vso-solution/)

Inthislink,itsuggestsusingthedefaultvisualstudiofolder;however,forlocaldevelopmentVMs,thiswillalwaysbeAdministrator.Forthisreason,usingafoldersuchasC:ProjectsTFSisbetterintermsofeaseofuse.

Page 100: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 101: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatinganewModelandPackagesWhencreatinganewproject,itisusuallyanewPackageandanewModel.Thiskeepsthingssimple,andthereisusuallynobenefitinseparatingthem.YoumaywishtocreateatestprojectinadifferentModelinthesamesolution,butyoumaynotwishtodeploythetestprojectstolive.

Therearetwotypesofprojects:anextensionprojectandanover-layerproject.Over-layeringmeansmodifyingthesourcecodeofOperations,andrequiresacodeupgradeforeachapplicationhotfix.Extensionprojectsworkondeltachangestothestandardobject,orusingdelegatestoaffectcodeexecution.Extensionprojectsshouldn'tneedacodeupgradewhenapplicationhotfixesareapplied.Avoidanceofover-layeringcannotbeoverstated,inthetimethisbookwasbeingwrittenPlatformandFoundationhavebeenlocked,meaningthattheover-layeringmustberemoved.Theabilitytowritegoodcodethroughextensionhasbeenimprovedwitheachrelease,andwithcleverdesigntheneedtoover-layerhasbeensignificantlyreduced.

Wewilluseextensionprojectsexclusively,inordertotoavoidconflictswithfutureupgrades.Theymakeitpossibletoservicetheenvironmentwithouthavingtodeployanewbuildofthecustomsolution.ThisisveryexcitingforISVsolutions,butalsoveryimportantforVARandend-usercustomers.

SeetheThere'smore...sectionforinformationontherelationshipbetweenpackages,modelsandprojects.

Page 102: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 103: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyStartupVisualStudioandensurethatwearecorrectlyconnectedtoVSTS.Asofthecurrentrelease,youmuststartvisualstudioasanadministrator.

Page 104: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 105: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreatetheproject,followthesesteps:

1. UndertheDynamics365menu,chooseModelManagement|Createmodel....2. TheModelnameisnamedaswewouldinAX2012,andshouldbenamedlikeanewtype,suchas

<prefix><area/module><ShortName>.3. Completethefirststepsasfollows:

Field Value

Modelname

Inourcase,ourcompanyiscalledContoso,soourprefixwillbeCon,theareaofchangeisanexistingmodule(WHS)anditisforgeneralextensions(extendingstandardobjectswithoutcustomization).

ItisthereforenamedConWHSGeneralExtensions.Youshoulduseyourownprefixandprefixes,andthenameforexplainedfurtherintheThere'smore...section.

Modelpublisher Yourorganization'sname.

Layer

Asfollows:

ISV/verticalsolution/add-on:ISV

VAR/Partnersolution/add-on:VAR

Customersolution/add-on:CUSorUSR

Thecustomerlayersweretraditionallyusedtosegregateacustomer'sglobalsolutionlayerfromtherequirementsofeachcountryimplementation.

Thelayertechnologyisprocessedverydifferentlyfortheextensionprojects,andhaslostsomesignificanceinthisrelease.

Version

Leaveas1.0.0.0

Youcanupdatethistomaintainaversionofthemodel.Thisdoesnotauto-incrementandisusuallyleftasdefault.

Modeldescription Afulldescriptionofthemodelforotherdeveloperstoread.

Page 106: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Modeldisplayname

Leaveasdefault,whichshouldbetheModelname.

4. PressNext.5. IntheSelectpackagepage,chooseCreatenewpackage.

IfyouchooseSelectexistingpackage,itwillmeanthatyourmodelwillbeplacedunderthepackageandisintendedtoover-layertheelementsininthatpackage.Youcannotover-layerelementsinextensionprojects,butunlessweabsolutelymustover-layer,alwayschooseCreatenewpackage.

6. PressNext.7. Wearenowofferedalistofpackagesthatwecanreference,thesearelistedaspackage'snameand

themodelsthatthepackagecontains,checkApplicationSuiteandpressNext.

MostoftheelementsinOperationsareinApplicationSuite;so,unlessourpackagedoesn'tneedanystandard,typethiswillalwaysbeselected.Theotherswewouldselectbasedontheelementsweknowwewilluse.Wecanaddmorepackagereferenceslater,butifweknowwhichelementswewilluse,itsavessometime..

8. Thetwocheckboxes,CreatenewprojectandMakethismydefaultmodelfornewprojects,shouldbothbechecked.

9. PressFinish.10. ThisopenstheNewProjectdialog.TheprojectnameisusuallythesameasthepackageandModel

name;then,enterthepackagenameintheNamefield.11. TheLocationfieldmustbechanged;itwillcreatetheprojectinthedefaultprojectfolder,butwe

linkedC:Projects<initials/username>tosourcecontrol.Theprojectmustbecreatedunderthisfolder.So,inmycase,LocationmustbeC:ProjectsSB.

12. TheSolutionnamefieldshouldbeleftastheprojectname.13. EnsurethatbothCreatedirectoryforsolutionandAddtosourcecontrolarechecked.14. PressOK.

Page 107: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 108: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Toseewhatwejustdid,wecansimplylookattheresults.UseWindowsexplorertonavigatetothelocalpackagesfolder,whichisusually,C:AOSServicePackagesLocalDirectory.There,youwillseethefollowingstructure,fortheexamplepackage,ConWHSGeneralExtensions:

Folder Description

ConWHSGeneralExtensions Thisisapackagefolder

+

ConWHSGeneralExtensions Thisisamodelfolderwithasubfolderpertype

+Descriptor Thiscontainsadescriptorfileforeachmodel

+XppMetadata

ThissystemmanagedfolderfortheXppmetadataforallmodelsinthepackage.Thisholdscompilermetadatainformationabouteachelement,nottheactuallysourcecode.Thisincludesthemethodsinaclass,thetypeofmethod,theparameters,andsoon.

Wewouldnevernormallychangeanythinghere,butthereareexceptions:

Iftwodeveloperscreateadifferentpackageatthesametime,theycanbothgetthesamemodelID,inwhichcase,badthingsstarttohappen.Thesolutionistocheckoutthemodel'sdescriptorxmlfileintheSourceControlExplorerandmanuallychangetheIDtothenextnumber.Youmaydecidethatastandardpackageshouldbedeleted,suchasthetutorialorthesamplefleetmanagementsolution.Youcandothisbysimplydeletingthepackagefolder.Shouldyouwanttoremoveastandardmodel,youcandeletethemodelfolder,butyoumustalsodeletetherelevantmodeldescriptorfilefromthepackage'sDescriptorfolder.Obviouscareneedstobetaken,asyoucan'tgetitback!

Thefirstpointaboutcanbesolvedbynominatingapersontocreatepackagesandmodels.

IfyoulookintheSourceControlExplorerinVisualStudio,youwillonlyseethattheProjectsfolderhasbeenadded.Thisiscorrect.TheMetadatafolderwillonlyappearswhenwecreatenewelements.

Page 109: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 110: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...Whenasolutionisdesigned,itwillbedonebybreakingthesolutionintopackagesoffunctionality.Thisisanormaldesignparadigmthathasnowbeenimplemented(and,toanextent,enforced)withinOperations.Thismeansthatoursolutiondesignwillnowdefinethevariouspackages,andhowtheydependoneachother.InthecaseofOperations,thepackageisadeployableunitthatbecomesadistinctDLL.

Wecanmakeahotfixtoapackageand,technically,deployitseparatelytootherpackagesinthesolution.Althoughthisispossible,wewouldnormallycreateareleaseofpackagesasaDeployablepackage.ADeployablepackageisacollectionofoneormorepackagesthatcontainsboththebuiltpackagecodeofoneormorepackages,andtheroutinerequiredtoinstallthem.Thisprocessissimplifiedusingabuildserverthatperformsthebuildprocessforus,executesanytests,andcreatesDeployablepackagesthatwecanthenapplytoourtestenvironment.

ThereisafurtherlevelwithinOperations,whichisaModel.AModelisasubsetofelements,suchasclasses,withinapackageandcanbeusedtomovecodefromonedevelopmentsystemtoanother,forexample.AModelcanonlybelongtoonepackage,andaPackagecancontainoneormoreModels.EachpackagebecomesaDLL,thathastohavereferencesaddedinorderto'see'elementsinorderpackages.Becauseofthisweshouldusealimitednumberofpackages.Asaguidewetendtohaveonepackageforthemainstream,andoneforreportingandbusinessintelligence.Tosimplifymanagementofdevelopmenttasks,wetendtohaveaprojectperspecification/TechnicalDesignDocument(TDD),allwithinthemainorreportingpackages,simplifyingmulti-developerprojects.JustlikeworkingoncomplexC#projects,wecanperformcodemerges,branching,andshelvingwithinVSTS.

Layershasbeenacorepartofpriorreleasesfromitsfirstrelease,butisnolongerthatsignificant.AsapartnerwestillusetheVARlayer,andrecommendthesameguidelinesasbeforetocustomersasbefore,butsinceweavoidover-layeringthisfeaturewillnotbecoveredinthisbook.

ThedependenciesaredefinedagainstthePackage,nottheModel.Whenwecreateaproject,theprojectisassociatedwithaModel.Itistypical,anddesirable,tokeepthisstructuresimpleandonlyhaveoneModel(orlimitedtoafewModels)foreachpackageandtogivebothentitiesthesamename.

ThefollowingdiagramshowsatypicalPackage,Model,andProjectstructure:

Page 111: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

TheApplicationSuitepackageisastandardpackagethatwenormallyalwaysreference,asitcontainsthemajorityofthetypesthatweusuallyneed.Thearrowsindicatethereferencedirection,showingthatitisnotpossiblefortheVehiclemanagementpackagetoseetheelementscreatedintheVehiclemanagementreportingpackage.

Page 112: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 113: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

PrefixesandnamingconventionsOperationsdoesnotusenamespaces.Neitherpackagesnormodelsequatetoanamespace.Amodelsimplyimpliesascope,butalltypesmustbegloballyunique;evenindifferentmodelsandpackages.Anamespacewouldallowaclasstohavethesamenameasanotherclassinadifferentnamespace.

Therefore,everyelementmustbegloballyuniquebytype;thisincludesmodels,packages,andeveryelementintheapplicationmetadata.So,wewillstillneedprefixes.Evenifwecreateanextensionofanelement,suchasaform,wemustchangethenamesothatitisguaranteedtobegloballyunique.

Forexample,ifwewantedtocreateanextensionoftheWHSLoadTabletable,itwillcalltheWHSLoadTable.extensionobjectbydefault.Asourcustomermightwantanadd-onthatalsoaddsfieldstothistable,weneedawanttoensurethattheelementisunique.

Thebestwaytodothiswouldbetouseourprefix,whichisConinourcase.Tomakeitobviousofwheretheelementisused,weusethepackagenameasthesuffix,forexample,WHSLoadTable.ConWHS.Thereisnoofficialbestpracticeavailableforthis,butthepointtorememberisthatallelementsofatypemustbegloballyunique-andextensionsarenoexception.

Page 114: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 115: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ConfiguringprojectandbuildoptionsBeforewecangetstuckintowritingsomecode,weneedtosetupsomeparameters.Manysettingsdescribedherearegoodforallprojects,butsomeyoumaywishtochange,dependingonthescenario.

Page 116: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 117: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyThisfollowsthepreviousrecipe,butcanapplyequallytoanyOperationsproject.JustloadupVisualStudioandtheprojectyouwishtoconfigure.

Page 118: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 119: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Thiswillbesplitintotwoparts:genericoptionsforallprojects,Operationsoptions;andprojectspecificparameters.

Beforewedoeither,weshouldalwayshavetheApplicationExploreropen,whichishiddenbydefault.ThisistheApplicationObjectTree(AOT)ofpriorversions.ThisisopenedfromtheViewmenu.

Page 120: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 121: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Dynamics365forOperations'options

Toconfigurethegenericoptionsforallprojects,followthesesteps:

1. SelectOptionsfromtheDynamics365menu.

TheformisactuallytheVisualStudiooptionsdialog,butittakesyoutothesectionspecifictoOperations.

2. Thedefaultoptionintheleft-handtreeviewisDebugging;theoptionshereareusuallyfine.TheLoadsymbolsonlyforitemsinthissolutionoptionaffectsdebuggingandshouldbeleftcheckedforperformanceofthedebugger.Wewoulduncheckthisifwewanttotracecodethatcallscodeoutsideofthecurrentpackage.

3. SelecttheProjectsoptionontheleftandcheckOrganizeprojectsbyelementtype.Whenaddingnewelementstotheproject,itwillautomaticallycreateasubfolderintheprojectfortheelementtype.Thismakesorganizationmucheasiertomaintain.

4. Theothertwooptionsshouldbeleftblank.AlthoughtheSynchronizedatabaseonbuildfornewlycreatedprojectoptioncanbeuseful,thisdatabasesynchronizationtakestime,anditisusuallypreferabletodothisasrequired.Thatis,beforeyourunit.

5. TheBestpracticesnodeletsyouchoosewhichbestpracticechecksyouwishtobeexecutedonbuild.Thisdetailisbeyondthescopeofthisbookasthechecksrequiredarecasespecific.

Page 122: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 123: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Theproject-specificparameters

Theseareusuallyfineasdefaultfordevelopment,butweoftenwanttotestourcodeusingthesampledatathatcomeswiththevirtualmachine.

Tosetupthecommonparameters,followthesesteps:

1. Right-clickontheprojectinSolutionExplorerandchooseProperties.2. Tosavetimewhilstdebugging,youselectwhichobjectyouwishtostartwith.Tostartwiththeform

SalesTable,settheStartupObjectTypeoptiontoForm,andStartupObjecttoSalesTable.

3. SetCompanytoUSMForanyothercompanyyouwishtostartwithwhendebugging.4. LeavePartitionasinitial.Thesearenowonlysupportedforcertainunittestscenarios.5. Ifyouwishtoalwayssynchronizethedatabaseonbuild,youcansetSynchronizeDatabaseonBuild.

Myadviceistodothismanually,asrequiredafterthebuildcompletes.

Page 124: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 125: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...YoumaynoticethatwecanchangetheModelintheproject'sparameters.Ouradviceis,don'tdoit.Tochangetheproject'sModelalsomeansthatthefoldershavetobemovedinthelocalpackagesfolder.

Page 126: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 127: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingaLabelfileMostprojectshavesomekindofuserinterface,andthereforeweneedtodisplaytexttotheuserotherthanthefieldnames.Thebestpracticemethodtodothisistousealabelfile.ThelabelfilecontainsalanguagespecificdictionaryoflabelIDsandthetranslation.

StandardelementstendtohavethelegacylabelIDsofan@symbol,followedbyathree-digitlabelIDandanumber.Thisformatworkedwellforthepast15years,buttheprefixwaspotentiallylimiting,especiallytoaidISVs.Labelsarenolongerrestrictedtothreedigits,whichhelpsMicrosoftattainoneofitsgoalsofmakingISVadd-onseasiertowrite,maintainandadopt.

Thechoiceofhowmanyandwhichpackagesneedalabelfiledependsonthesolutiondesign.

Itisn'taterribleideaforanendusercustomersolutiontohaveonepackagejustforlabelsthatarere-usedbyeachpackage.Thisdoesmeanthatweruntheriskofusingalabeloutofcontext.YoumaychoosetouseastandardlabelforNameonpersonalrecord,onlyforthelabeltobechangedbytheoriginaldevelopertosomethingspecifictotheoriginalcontext,forexample,Productname.

WetendtocreatealabelfileforeachpackageasthisensuresthatthepackagecancorrectandchangelabelswithoutworryingaboutregressioninotherModels.

Page 128: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 129: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyTogetstarted,openVisualStudio,andtheprojectinquestion,inmycaseIwillcontinuewiththeConWHSGeneralExtensionsproject.

Page 130: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 131: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Tocreatethelabelfile,followthesesteps:

1. Right-clickontheprojectandselectAdd|Newitem...orusethekeyboardshortcut,Ctrl+Shift+A.2. ChooseLabelsandResourcesfromtheOperationsArtifactslist.3. Fromthelistontheleft,selectLabelFile.4. IntheNamefield,enterashort,butuniquelabelname,inmycaseConWHS.Iwantittobeasshortas

possible,butbecompletelysureitwillbegloballyunique,regardlessofanyfutureadd-onwemaychoosetoinstall.

5. PressAdd.6. IntheLabelfilewizard,leaveLabelfileIDasdefault.

Itseemsthatwecanspecifyadifferentnameinthisstep,butthepreviousonlycalledthiswizardwithasuggestedfilename.TheLabelfileIDfieldcontainsboththeIDandthefilenamethatwillbecreated.

7. PressNext.8. Inthelanguageselection,movethelanguagesfromtheleft-handlistintotheright-handlistusingthe

buttons.Onlyleavelanguagesselectedthatyouwillmaintain.Thisinvolvescreatingalabelineachlanguagefile.

Thisalsoappliestolanguagewithsubtledifferences,suchasEnglishandSpanish.Eventhoughthelabelwilloftenbethesame,westillneedtomanuallymaintaineachlanguagefile.CurrentlythisalsomeanswehavetobecarefultoensurewegivethemthecorrectID.

9. PressNext.10. CheckthattheSummarypageiscorrect,andpressFinish.

Page 132: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 133: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Thecreationisstraightforward.Theprocesscreatesatextfileonthediskthatcontainsatab-separatedlistoflabelIDsandtranslations.

Whenalabelisselectedagainstacontrol,itwillbegivenalabelfileIDthatensuresitisunique.Inourexample,thelabelfileIDwasConWHS.Aswecreatealabel,itwillbegiventheIDsinthesequence@ConWHS:ConWHS1,@ConWHS:ConWHS2,andsoon.

Inourexample,thelabelIDsgiventothecontrolwillbe@ConWHS:ConWHS1.Thisseemsneedlesslylong.Since,wecanactuallyspecifythelabelIDmanually,wecanchoosetoenterashorterIDperlabel,generatingalabelsuchas@ConWHS:L001,orenteramemorablenameasanID,wheretheIDwouldthenbecome@ConWHS:ItemNotExists.

Page 134: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 135: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...Currently,themaintenanceoflabelscanbetimeconsuming,inthat,wedonothaveatranslationlistperlabelIDaswedidinDynamicsAX2012,butaseparatefileperlanguage.Thisistargetedforimprovement,andmaychangebyrelease.Theconcept,however,willremainthesame.

Whenwritinglabelsforvariantsofthesameorsimilarlanguages,wecancopyandpastethelabelsbetweenfiles.Expandthelabelfiletothelowestnodetothefilewitha.txtextension,andrightclickonittoselectOpenWith....ChooseNotepadfromthelist.

Eachlabelwillhavetwolines,oneforthelabel,andtheotherforthecomment,asshowninthefollowingextractfromanen-gblabelfile:

ConWHS1=Vehicletype

;ConWHS

ConWHS2=Thetypeofvehicle

;ConWHS

VehTransComplete=Completeinspection

;ConWHS

VehTransCompleteHT=Complete,andfinalisethevehicleinspection

;ConWHS

Youcanthantranslatetothedesiredlanguageandpasteitintothetargetlabelfile,againbyopeningthetxtfileinNotepad.YoumustdothisfromwithinVisualStudio,otherwisethefilemaynotbecheckedoutfromsourcecontrol.Becareful,asitwillnotvalidatethattherearen'tduplicatesorfileformattingerrors.

Ifyouintendtowriteadd-ons,youshouldalwaysmaintaintheen-uslanguagefile.YouwillgetcompilationwarningsthatthelabelIDdoesnotexist,ifyoudonot.Ifyouaretoreleasethesoftwaretoaregionwithavariantofalanguage(en-au,en-gb,en-ie,en-us,andsoon),pleaseusethecorrecttranslation,asnotonlywillitmakeyouradd-onmoreprofessionalandglobal,butsometermshavecompletelydifferentmeanings.Forexample,stockmeansinventoryinen-gb,butmeansfinancialshareholdingsinen-us.

Page 136: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 137: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

DataStructuresInthischapter,wewillcoverthefollowingrecipes:

CreatingenumeratedtypesCreatingextendeddatatypesCreatingsetuptablesCreatingaparametertableCreatingmaindatatablesCreatingorderheadertablesCreatingorderlinetables

Page 138: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 139: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

IntroductionInthischapter,wewillcoverthetasksrequiredtowritethedatadictionaryelementscommonlyusedwithinOperationsdevelopment.

DatastructuresinOperationsarenotjusttablesandviews,butalsoincludetheabilitytodefineadatadictionary.ThisisnowcalledtheDataModelinOperations.Thedatamodelhasn'tchangedmuchinstructuresinceAX2012,butismuchmorerefinedwithmanymorefeaturestoaiddevelopmentandminimizethefootprintwhenextendingstandardtablesandtypes.

Thischapterdoesnotcovercreatingextensionsofstandardtypes,butitdoescoverhowtocreatetypesthatallowyourstructurestobeextensible.ExtensibilityiscoveredinExtendingstandardtableswithoutcustomizationfootprintsectionofChapter7,LeveragingExtensibility.

MostdevelopmentinOperationsisbasedonpatterns,evenifwearenotawareofit.Forexample,theVendorandCustomerlistsareverysimilar,andarecalledMaintables.PurchaseordersandSalesordersarealsoverysimilar,whichareWorksheettables;inthiscase,orderheaderandlines.

TablesinOperationshaveapropertythatdefinesthetypeoftable,andeachtypeisassociatedwithaparticularstyleofformfortheuserinterface.Wecouldthereforeconsiderthesetypesaspatterns.Ifwethinkofthemaspatterns,itisfareasiertoapplytherecipestoourowndevelopment.

Thefollowinglistisofthecommontabletypes,listedwiththeusualformdesignandtheirtypicalusage:

TablegroupFormdesignpattern

Usage

Miscellaneous Thisisessentially'undefined'andshouldn'tbeused,otherthanfortemporarytables.

Parameter Tableofcontents Thisisusedforsingle-recordparameterforms.

Group

Simplelist

SimplelistandDetails-ListGrid

Thisisusedforthebackingtablefordrop-downlists.TheSimplelistdesignpatternissuitableforwhenonlyafewfieldsarerequired;otherwise,theSimplelistandDetailspatternsshouldbeusedsothefieldsarepresentedinausefulwaytotheuser.

Main DetailsMaster Thisisusedformaintables,suchascustomers,suppliers,anditems.

Page 140: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

TransactionHeader

TransactionLine

SimpleListandDetailswithStandardtabs

Thisisusedfordatasets,suchastheinvoicejournalform,thatcontainpostedtransactionaldatawithaheaderandlines.

Transaction

SimpleListandDetailsw/Standardtabs

Thisisusedfortables,suchastheInventorytransactionform,thatcontainpostedtransactionaldata,butatasinglelevel.

Worksheetheader

Worksheetline

DetailsTransaction

Thisisusedfordataentrydatasets,suchasthepurchaseorder,wherethedatasetismadeupofheaderandlinetables.Thepatternhastwoviews,alistviewofheaderrecords,andadetailviewwherethefocusisthelines.

Worksheet Detailsmaster Thisisasingle-leveldataentrydataset,whichisrarelyusedinOperations.

Inthischapter,wewillcreatetypesandtablesforthecommontypesoftable,butwewillcompletethepatternswhenwecovertheuserinterfaceinChapter3,CreatingtheUserInterface.

Page 141: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 142: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingenumeratedtypesEnumeratedtypesarecalledBaseEnumsinOperations,whicharesimilartoenumeratedtypesinC#.TheyarecommonlyreferredtoasEnums.Itisanintegerreferencedasasymbol,whichcanbeusedincodetocontrollogic.Itcanalsobeusedasafieldonatablethatprovidesafixeddrop-downlisttotheuser.Whenusedasafield,ithastheabilitytoprovideuser-friendlylabelsforeachsymbol.

BaseEnumsareusuallyonlyusedasadrop-downlistifweneedtounderstand,incode,whateachvaluemeans.Theyshouldcontainasmallnumberofoptions,andwhenusedasafield,thelistcannotbesearchedorextendedbytheuser.

AllEnumshavetobedefinedinthedatamodelbeforeweusethemandcan'tbedefinedwithinaclassormethod.

BaseEnumsaregiventheabilitytobeextensibleinthisrelease;themechanicsofthisiscoveredinmoredetailintheThere'smore...section.

Page 143: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 144: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyThefollowingtaskscontinuethevehiclemanagementproject,ConWHSVehicleManagement,whichwascreatedinChapter1,StartingaNewProject,although,thisisnotaprerequisite.WejustneedanOperationsprojectthatwascreatedasanextensionproject.

Page 145: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 146: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...InordertocreateanewEnum,followthesesteps:

1. Eitherright-clickontheproject(oranysubfolderintheproject)andchooseAdd|Newitem....

Alwaystrytousekeyboardshortcuts,whicharedisplayednexttotheoption.Inthiscase,tryCtrl+Shift+A.

2. ThiswillopentheAddNewItemwindow.Ontheleft-handlist,selectDataTypes,whichisundertheOperationsArtifactsnode.

3. Thiswillfiltertheavailableoptionsontheright-handlist.Fromthislist,chooseBaseEnum.4. Enteraname,prefixedappropriately.Inthisexample,wearecreatinganEnumtostorethetypeof

vehicle;so,wewillenterConWHSVehicleTypeasNameandpressAdd.

ThisshouldhavecreatedafoldercalledBaseEnums,oraddedthenewEnumtothefolderifitalreadyexisted.ThisisregardlessofthefolderweselectedwhentheEnumwascreated.Ifthisdidnothappen,theOrganizeprojectsbyelementtypesettinginDynamics365|Optionswasnotchecked.

5. WewillnowhaveanewtabopenwithouremptyEnum.Beforewecontinue,wemustcreatelabelsfortheLabelandHelpproperties.Double-clickonthelabelfilesowecanaddanewlabel.

Thiswascreatedinthepreviouschapter;ifyoudon'thavealabelfile,onemustbecreatedbeforewecontinue.

6. Thiswilladdanemptylineforus.Onthisemptyline,clickonLabelIDandenterthenextnumber,forexample,1,andpressTab.ThiswillchangethenumbersothatitisprefixedwiththelabelfileID;inourcase,ConWHS1.

7. EnterVehicletypeintheLabelcolumnandConWHSinthecomment:thisistoprovidecontexttootherdevelopers,incaseitisreused.

8. PressNewandrepeatforthenextlabelandenterthetextThetypeofthevehiclefortheLabelproperty.

Repeattheseforeachlabelfilelanguagewecreated;ifwesupportUSandBritishEnglishandNorwegianBokmål,wewillneedtocreatealabelwiththesameIDforConWHS.en-us.label.txt,ConWHS.en-gb.label.txt,andConWHS.no-no.label.txt.

9. ReselectthetabfortheEnum.Ifwedon'thaveapropertysheetopen,whichisusuallyonthelowerrightofthescreen,right-clickontheEnuminsidethetabandselectProperties.

10. Inthepropertysheet,fillintheLabelpropertyasVehicletype.11. Pressthelookupbuttonforthisproperty,selectourlabel,andpressPastelabel.

Page 147: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

12. RepeatthisprocessfortheHelpproperty,whichshouldbeThetypeofvehicle.

Thesepropertiesdonothavetobepopulated,butitwillcauseabestpracticedeviationwarningwhentheprojectisbuiltandthesepropertiesareleftempty.

13. TheUseEnumValuepropertyshouldalwaysbeYes,asitallowsustosetthenumericvalueofeachsymbolwecreated.

14. TheIsExtensiblepropertyshouldusuallybeFalse.Thisaltersthewaythenumericvalueisassigned,andisdescribedlater.

15. Next,wewilladdtheelementstotheEnum.Theremustalwaysbeanelementwith0,asthisisthedefaultonnewrecords.Alsoconsiderthathavingadefaultisdesirable.Ifyouwishtoforcetheusertoselectanoption,makethefirstelement"Undefined".Since0isclassedasemptybythesystem,makingthefieldmandatorywillforcetheusertoselectanoption.Theoptionswewilladdshouldbecreatedasfollows:

Name(Symbol) EnumValue Label

Bike 0 Motorbike

Car 1 Car

Truck 2 Truck

TheLabelcolumnintheprecedingtableshowstheliteraltouse,theactualvaluewillbethelabelId,suchas@ConWHS:Motorbikeor@ConWHS:ConWHS2ifyouareusingnumbersforlabelIDs.

16. Toaddeachsymbol,orelementasitisreferredtointheeditor,right-clickontheEnumandchooseNewElement,asshowninthefollowingscreenshot:

17. Completethepropertysheetasperthetable,andrepeatforeachrequiredelement.18. YoucanreordertheelementsusingtheAlt+upkeyandAlt+downkey,butthiswillonlyaffecthow

theyaredisplayedintheeditor.YoumustalsochangeEnumValue.19. PresstheSaveorSaveallbuttonsinthetoolbar.

Page 148: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 149: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Enumsarestoredasintegersinthedatabase,andwecanassignanintegervaluetoafieldbasedonanEnum.

WhenthevaluesoftheEnumareshowntotheuser,OperationswilllookthisupusingtheEnum'sdefinitionfromthefieldintheuser'slanguage.Ifalabelisnotdefinedfortheuser'slanguage,thelabelID,andnotthesymbol,isshowntotheuser.Ifalabelwasnotdefined,thesymbolisshown.

Forstandard,non-extensibleEnums,thefollowinglinesofcodeareeffectivelythesame:

InventTable.ABCRevenue=1;

InventTable.ABCRevenue=ABC::B;

Ofcourse,wewouldneverwritethefirstoption-weuseenumeratedtypesspecificallytoavoidhavingtorememberwhateachnumbermeans.IftheEnumwascreatedasextensible,thevaluesassociatedwiththesymbolarenotassignedatdesigntime.Theyareinstallationspecific.ExtensibleEnumsareimplementedasaclass,andwethereforehaveaslightperformancehitaswitharenolongerdealingwithnumbers.

WeuseEnumsformanypurposes,rangingfromtableinheritance(differentfieldsarerelevanttoatypeofvehicle)toprovidinganoptionlisttoauser,andevencontrollingwhichtypeofclassiscreatedtohandlelogicspecifictothevehicletype.

Page 150: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 151: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...

BaseEnumsarethereforeagreatwaytoprovidealinkbetweenlogicandtheuserinterface.However,therearelimitations;theoptionsaredefinedincodeandtheusercannotaddtothelist.Theproblemwithmakingtheoptionsuser-definableisthatwecan'tusethevaluesincode,unlesswewanttoaddtheoptionstoaparameterform,whichisnotveryextendable!

WhatwecandoisusethecodepatternexemplifiedintheItemmodelgroupform.Here,wehaveauser-definablelist,withanEnumthatdeterminestheInventorymodelthattheitemswilluse.

Page 152: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 153: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

UsingEnumsforcomparisonandstatusItisverycommontouseanEnumtodefinethestatusofarecord,andwewillcoverstatemachinesinChapter15,StateMachines.WhendefininganEnumforstatus,wewillordertheelementssothatwecancomparethemincode.

InthecaseoftheSalesStatusEnum,theelementsareasfollows:

Symbol Value

None 0

Backorder 1

Delivered 2

Invoiced 3

Canceled 4

Thisway,wecanselectallsalesorders,whicharenotyetinvoiced,usingthefollowingcodelines:

selectSalesId,SalesName

fromsalesTable

wheresalesTable.SalesStatus<SalesStatus::Invoiced

Sometimes,wewillneedtoreversetheorder.InthecaseoftheInvenTranstable,therearetwostatusfields:StatusIssueandStatusReceipt;oneisforissuetransactions,suchassalesordersorpositiveinventoryadjustments,andtheotherforreceipts.Whenthetransactionisanissue,StatusReceiptwillholdavalueof0(None),andviceversa.

Thismethodofusingcomparisonsonextensibleenumscannotbeused.Theywillprobablybenumberedinorderoftheelementsthatarelistedintheenum'sdefinition,butwecannotassumethat.Itispossibleforthemtodiffer.

ThefirstthreeelementsofStatusIssueareasfollows:

Symbol Value

Page 154: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

None 0

Sold 1

Deducted 2

ThefirstthreeelementsofStatusReceiptareasfollows:

Symbol Value

None 0

Purchased 1

Received 2

Thereasonthestatusappearstobereversedistoallowustoformaquerysuchasthefollowingpieceofcode:

selectsum(Qty),ItemId

frominventTrans

whereinventTrans.StatusIssue<=StatusIssue::Sold

&&inventTrans.StatusReceipt<=StatusReceipt::Purchased

ThiswillsumtheQtyfieldforalltheInventTransrecordsthatwerefinanciallyissuedorreceived.

Page 155: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 156: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ExtensibilityinBaseEnumsOneconfusionthatoccursinOperationsisthetermsextendandextension.Extendisthetermusedwhereatype,suchasaclass,tableorEDT,extendsaparentofthesametype.Extensionorextensibleisusedwherewecanalteranelementinanotherpackagewithoutmodifyingtheoriginal.Whenthisisdoneandusedcorrectly,theoriginalauthorcanpublishrevisionstothepackagewithoutforcingtheircustomerstomergeandupdatetheircode,allowingend-usercustomersmoreflexibilityandenablingthemtoefficientlystayuptodatewithupdates.

Wewillcovertheextensibilityofeachtypethroughoutthisbook,withaparticularfocusinChapter7,LeveragingExtensibility.ThereisabigdifferencewithEnums;inthat,theauthoroftheEnumdecideswhetherornotthisisallowed.Forothertypesthatareextensible,theobjectsareallextensible.

AcommonviewistojustmakeallEnumsextensible,butthishasadrawback,specificallyintermsofanoverheadwhenthetypeisloaded.StandardEnumsarecompiledtoCLRenumerations,inthesamewaythatC#enumerationsare,andareexceptionallyquick.IfwemakeanEnumextensible,theyarecompiledtoaclasswithstaticmethodsforeachelementintheenumeration.Theactuallynumericvaluesareinstallation-specific,andwecan'tusethemforstatusfieldswherewewanttocompare;forexample,SalesTable.SalesStatus<SalesStatus::Invoicedmakesnosenseinthiscase.

Intermsofrefreshingdatabasesbetweenenvironmentsthishasbeenconsidered.TheactualvalueisstoredinatablewithintheOperationsdatabase,whichiscreatedwhenthedatabaseissynchronized.Itwillnotchangeafterthatpoint,andthismeansthatthevaluesaremovedwiththedatabase.

Page 157: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 158: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingextendeddatatypesExtendedDataTypesarecommonlyreferredtoasEDTs.Theyextendbasetypes,suchasStringsandIntegersbyaddingpropertiesthataffecttheappearance,behavior,data(size),andtablereference/relationships.ThismeansthatwecanhavetypeslikeCustomeraccountthathavealabel,size,tablerelationinformation,andotherpropertiesthatprovideconsistencyandgreaterunderstandingwithinthedatamodel.

AnotherexampleofanEDTisName.ShouldwechangetheStringSizepropertyofthisfield,allfieldsbasedonthisEDTwillbeadjusted;andifwereducethesize,itwilltruncatethevaluestothenewsize.

AllfieldsshouldbebasedonanEDToranEnum,buttheyarenotjustusedtoenforceconsistencyintheDatamodel,butareusedastypeswhenwritingcode.

TheEDTinthisexamplewillbeaprimarykeyfieldforatablethatwewilluselaterinthechapter.

Page 159: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 160: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyWejustneedtohaveaOperationsprojectopeninVisualstudio.Tolookatstandardexamples,ensurethatApplicationExplorerisopenbyselectingView|ApplicationExplorer.

Page 161: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 162: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...WewillcreateanEDTforthevehiclenumber.Avehicletableisofasimilarpatterntocustomersandvendors,andwewillextendtheAccountNumEDTforthistype.

TocreatetheEDT,followthesesteps:

1. CreatinganEDTstartsinthesamewayasallnewOperationsartifacts:bypressingCtrl+Shift+Aorright-clickingonafolderinthesolutionexplorerandchoosingAdd|NewItem.

2. SelectDataTypesintheleft-handlist,andthenselectEDTString.3. IntheNamefield,enterConWHSVehicleIdandpressAdd.4. Next,wewillneedtocompletethepropertysheet;themainpropertiesarecoveredinthefollowing

table:

Property Value Description

Label VehicleID Thisisthelabelthatwillbepresentedtotheuserontheuserinterfacewhenaddedasafieldtoatable.

HelpText Thevehiclenumber

ThisisthehelptextshowntotheuserwhenthisfieldisselectedorthemousehoversoverthecontrolsbasedonthisEDT.

Extends AccountNum Thisiscompletedformostfields,asweareusuallyfollowingapattern,suchasID,Name,andgroupingfields.ThisisexplainedintheThere'smore...section.

SizeThiswillberead-only,aswehavebasedthisEDTonanotherEDT.ThisisundertheAppearancesection,butitdoescontrolthesizeoftheassociatedfieldsinthedatabase.

ReferenceTable

Fortypesusedasaprimarykeyfieldonatable,thispropertyshouldbepopulated.

AlongwiththeTablereferences,itcanbeusedtocreatetheforeignkeyrelationonchildtables.

Asalways,remembertocreatelabelsfortheLabelandHelpTextpropertiesforeachofyoursupportedlanguages.

5. IfthisEDTistobeusedasaprimarykeyfield,wewillneedtopopulatetheTableReferencesnode.

Wewillcompletethislaterinthechapter,butyoucanseeagoodexamplebylooking

Page 163: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

atthestandardAssetIdEDT.NavigateApplicationExplorertoAOT|DataTypes|ExtendedDataTypes,right-clickonAssetId,andselectOpendesigner.

6. PressSaveorSaveallinthetoolbartosavethechanges.

Page 164: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 165: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

ThereisabackandforthelementtoEDTcreationwhenwearecreatingaprimarykeyfield.Wecan'tcreatethefieldwithouttheEDT,yetwecan'tcompletetheEDTwiththefieldbeingonthetable.

EDTsaretypes.Therefore,theymustbegloballyuniqueamongstallothertypes,suchastables,views,dataentities,enums,classes,andotherEDTs.TheEDT'spropertiesaren'tjustdefaults,buttheycontrolbehaviortoo.ShouldweaddanunboundcontroltoaformbasedonanEDT,theEDTcanusetheTableReferencepropertytoprovideadrop-downlist,andthecontentswillbetakenfromafieldgrouponthetable.

Page 166: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 167: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...EDTscanalsoextendotherEDTs;although,thesechildEDTscanonlyaffectappearanceproperties.Thisisusefulwhenwewanttoenforcephysicalstorageattributesofarangeoftypes,buthaveadifferentlabeldependingoncontext.IfwechangethesizeofabaseEDT,allEDTsthatextenditwillbeaffectedand,consequently,allofthefieldsthatarebasedonthem.

WeoftenextendspecificEDTswhencreatinganEDTforcertaintypesoffields.

ThetypicalEDTsweusedforthisareasshowninthefollowingtable:

EDT Type Size Reason

SysGroup String 10

Thisisusedfortheprimarykeyfieldsforgrouptables.Grouptablesarethoseusedforbackingtablesfordrop-downlists.Theymayprovidefurtherdefinitiontoarecord,orjustbeusedforreporting.Examplesincludethefollowing:

ItemgroupCustomergroupItemmodelgroup

Num String 20

Thisisusedforprimarykeysonworksheettables,suchasthesalesordertable(SalesTable).Thesefieldsareusuallynumberedbasedonanumbersequence,whichmusthaveastringsizeof20characters.

Examplesincludethefollowing:

SalesordernumberPurchaseordernumber

AccountNum String 20

Thisisusedforprimarykeyfieldsformaintables,suchastheCustomertable.Thesetablesarealso,usually,basedonanumbersequence.

Examplesincludethefollowing:

CustomeraccountVendoraccount

Name String 60AllnamefieldsshouldbebasedonthisEDT,suchasvehiclename,customername,andsoon.ThisEDTcanbeusedasis,unlesswewishtospecifyalabelandhelptext.

Thisisusedasthedescriptionfieldongrouptables.ThisEDTisusuallyusedas

Page 168: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Description String 30 is,andisn'tusuallyextended.

AmountMST Real AllmonetaryvalueEDTsthatstoretheamountinlocalcurrencyshouldbebasedonthisEDT.MSTstandsforMonetaryStandard.

AmountCur Real AllmonetaryvalueEDTsthatstoretheamountinthetransaction'scurrencyshouldbebasedonthisEDT.

Qty Real AllfieldsthatstoreaquantityshouldbebasedonthisEDT.

Therearemanymore,andratherthanlistingthemallhere,agoodpracticeistolocateapatternusedinstandardOperationsandfollowthesamepattern.

Page 169: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 170: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Creatingsetuptables

Inthissection,wewillcreateagrouptable.Agrouptableisusedasaforeignkeyonmaintables,suchasthecustomergrouponthecustomertableandthevendorgrouponthevendortable;thecustomerandvendortablesareexamplesofmaintables.Grouptableshaveatleasttwofields,anIDandaDescriptionfield,butcancontainmoreasrequired.

Inthiscase,toaidflow,wewillcreatethegrouptablefirst.

Page 171: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 172: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyWejustneedaOperationsprojectopeninVisualStudio.

Page 173: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 174: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...WewillcreateaVehiclegrouptable.Wedon'thavemuchchoiceonthenameinthisasithastostartwithourprefix,andendwithGroup;therefore,itwillbeConWHSVehicleGroup.Tocreatethistable,followthesesteps:

1. UsingtherecipeforcreatingEDTs,createaVehiclegroupEDTusingthefollowingparameters:

Property Value

Name ConWHSVehicleGroupId

Label Vehiclegroup

HelpText Thevehiclegroupid

Extends SysGroup

2. SavetheEDT,butdon'tclosethedesigner.3. Fromwithintheproject,choosetocreateanewitem.4. ChooseDataModelfromtheleft-handlistandselectTablefromtheright.5. EnterConWHSVehicleGroupintheNamefieldandpressAdd.6. Thisopensthetabledesignerinanewtab.Fromtheproject,dragtheConWHSVehicleGroupIdEDTontop

oftheFieldsnodeinourtable,asshowninthefollowingscreenshot:

7. ThiscreatesthefieldwiththesamenameastheEDT.Asthisisourtable,weshouldremovetheprefixandnameitVehicleGroupId.

Page 175: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

8. WecannowcompleteourEDT,opentheConWHSVehicleGroupIdEDT(orselectthetabifitisstillopen),andenterConWHSVehicleGroupintheReferenceTableproperty.

9. Right-clickontheTableReferencesnodeandselectNew|TableReference.10. Inthepropertysheet,selecttheRelatedFieldpropertyandthenselectVehicleGroupIdfromthe

drop-downlist.

Ifthedropdownisblank,itislikelythattheReferenceTablepropertyonthetablewasmistyped.

11. SavetheEDTandcloseitsdesigner.ThisshouldmaketheactivetabtheConWHSVehicleGrouptabledesigner;ifnot,reselectit.

12. FromApplicationExplorer,whichisopenedfromtheViewmenu,expandDataTypesandthenexpandExtendedDataTypes.

13. LocatetheDescriptionfield,anddragitontotheFieldsnodeofourtable.

ThiscanbedonebyselectingthefirstEDTinthelistandtypingdescriptionuntilyoucanseeitinview.

14. Wewillnowneedtoaddanindex;eventhoughthistablewillonlyhaveafewrecords,wewillneedtoensurethattheIDfieldisunique.Right-clickontheIndexesnodeandchooseNewIndex.

15. Withthenewindexhighlighted,pressF2andrenameittoGroupIdx.ChangetheAlternateKeypropertytoYes.AlluniqueindexesthatwillbetheprimarykeymusthavethissettoYes.

Atthetimeofwriting,thispropertystillneedstobesettoyesinordertosupportcertaindataimportandexportscenariosandmayberemoved.Youwillgetabestpracticedeviationwarningshouldtheprimarykeynothavethispropertyset.

16. DragtheVehicleGroupIdfieldontopofthenewindex.

Thedefaultsforindexestocreateauniqueindex,sotheyarecorrectinthiscase.Indexeswillbediscussedlaterinthischapter.

17. Openthefield'spropertiesandsettheMandatorypropertytoYes,AllowEdittoNo,andleaveAllowEditOnCreateasYes.

SincewewillleaveAllowEditOnCreateasYes,wecanentertheID,butnotchangeitaftertherecordissaved;thishelpsenforcereferentialintegrity.TheMandatory,AllowEdit,andAllowEditOnCreatefieldpropertiesonlyaffectdatamanipulatedthroughaform.Theserestrictionsaren'tenforcedwhenupdatingdatathroughcode.

18. Wecannowcompletethetableproperties,selectthetablenodeinthetabledesign(thetablename),andcompletethepropertysheetasfollows:

Page 176: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Property Value Comment

Label Vehiclegroups Thisisthepluralnamethatappearstotheuser.

TitleField1 VehicleGroupId Thisappearsinthetitleofforms.

TitleField2 Description

Cachelookup Found

None:nocachingisfetchedfromtheDBeverytime.

NotInTTS:Fetchedoncepertransaction

Found:Cachedoncefound,notlookedupagain

EntireTable:Entiretableisloadedintomemory

Thecacheisonlyinvalidatedwhenrecordsareupdatedorflushed.

ClusteredIndex GroupIdx

Thisindexiscreatedasaclusteredindex.Clusteredindexesareusefulastheyincludetheentirerecordintheindex,avoidingabookmarklookup.Thismakesthemefficient,butthekeyshouldalwaysincrement,suchasasalesorderid,otherwiseitwillneedtoreorganizetheindexwhenrecordsareinserted.Thistableissmall,soitwon'tcauseaperformanceissue.ItwillalsosortthetableinVehicleGrouporder.

PrimaryIndex GroupIdx Thisdefinestheprimaryindexandisusedwhencreatingforeignkeyjoinstothistable.

TableGroup Group SetuptablesshouldalwaysbeGrouptables.

CreatedBy

CreatedDateTime

ModifiedBy

ModifiedDateTime

YesThiscreatesandmaintainstheCreatedbytrackingfieldsandisusefulifwewanttoknowwhocreatedandchangedtherecord,andwhen.

Page 177: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

DeveloperDocumentation

TheConWHSVehicleGrouptablecontainsdefinitionsofvehiclegroups.

Thisisrequiredforbestpracticeandshouldcontainanythingthatotherdevelopersshouldunderstandaboutthetable.

FormRef ConWHSVehicleGroup

Thisshouldbeleftblankuntilwehavecreatedtheform,andresultingmenuitem.Thisallowstheuserinterfacetoknowwhichformtoopenwhenviewingdetailsviaaforeignkey.

Thepatternstatesthatthisshouldbethesamenameasthetable.

RememberthatLabel,HelpText,andDeveloperDocumentationrequirealabelineachofyoursupportedlanguages.

19. Wewillnowneedtocreateafieldgroup;aswewilluseaSimplelistdesignpatternforthistypeoftable,wewillonlyneedone.Right-clickontheFieldgroupsnodeandselectNewGroup.

Fieldgroupsareusedmainlyintheuserinterface.Wecanaddthegrouptotheuserinterfaceandthecontrolswillmatchthefieldsinthefieldgroup.Allvisiblefieldsmustbeinafieldgroup.

20. SelectNewgroupandrenamethegrouptoOverviewbypressingF2.21. CreatealabelforOverview.

Asweusesomelabelsinmanycontexts,typeinOverviewforthelabelID.ThelabelIDthatweuseforthefieldgroup'sLabelpropertywillbe@ConWHS:Overview.Forcommonlabels,thismakesthemeasiertorememberandreadwithouthavingtoopenthelabeleditor.

22. Inthepropertysheet,enterOverviewastheLabelpropertyandclickonthelookupbuttontolocatethelabelwecreatedinthepreviousstep.

Wecanreuseexistinglabels,andthisisfineifwearesurethatthecontextiscorrect.IfwechosealabelcalledOverview,wewillruntheriskthatMicrosoftmayrenametoFinancialoverview,forexample.Also,onlyreuse@SYSlabels--youmayfindthatyouhaveusedalabelinasampleapplicationthatisn'tshippedtotheproductionenvironment.

23. DragthetwofieldsontothegroupandorderthemsothatVehicleIdisfirstinthelist.

24. InorderforanyautomaticlookupstothistabletoshowbothIDandDescriptionfields,addbothfieldstotheAutoLookupfieldgroup.

25. WecanskiptotheMethodsnode,wherebestpracticedictatesweneedtoprovidetheFindandExistmethods.

Page 178: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

MethodsinOperationshavechangedthenamingconvention,inthattheyshouldnowstartwithacapitalletter.Youwillfindmanystandardmethodsstillstartwithalowercaseletter,astheyhavenotbeenrefactored.

26. Right-clickontheMethodsnodeandchoseNewMethod.

Thisopensthecodeeditor.Elementsthatcontaincode,suchastablesandforms,canbeopenedinbothdesignerandcodeeditors.

27. Thecodeeditorhaschangedinthisreleasesothatallmethodsareshowninthesameeditor.Wearegivenamethodstub,asshownhere:

///<summary>

///

///</summary>

privatevoidMethod1()

{

}

WecanseethatthefirstlineintheeditorispublicclassConWHSVehicleGroupextendsCommon.Allmethodsarecreatedinsidethetwooutbraces.

28. RemovetheXMLdocumentationcommentsectionandcreatetheFindmethodasfollows:

publicstaticConWHSVehicleGroupFind(

ConWHSVehicleGroupId_groupId,

boolean_forUpdate=false)

{

ConWHSVehicleGroupvehGroup;

vehGroup.selectForUpdate(_forUpdate);

selectfirstonly*fromvehGroup

wherevehGroup.VehicleGroupId==_groupId;

returnvehGroup;

}

29. Createablanklineabovethemethoddeclarationandtypethreeslashes(///),whichcausesOperationstocreatetheXMLdocumentationbasedonthemethoddeclaration.Fillinthisdocumentationasfollows:

///<summary>

///Returnsarecordin<c>ConWHSVehicleGroup</c>

///basedonthe_groupIdparameter

///</summary>

///<paramname="_groupId">

///TheVehiclegroupIDtofind

///</param>

///<paramname="_forUpdate">

///Trueiftherecordshouldbeselectedforupdate

///</param>

///<returns>

///The<c>ConWHSVehicleGroup</c>record

///</returns>

Shouldthesuppliedvehiclegroupnotbefound,itreturnsanemptybuffer(wherethesystemRecIdfieldiszero).The_forUpdateparameterisexplainedintheThere'smore...section.

Page 179: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

30. Now,tocreatetheExistmethod,gototheendofourFindmethodandcreateanewlineafterthemethod'sendbraceandjustbeforethefinalbraceforthetable,andtypeasfollows:

///<summary>

///Checksifarecordexistsin<c>ConWHSVehicleGroup</c>

///</summary>

///<paramname="_groupId">

///TheVehiclegroupIDtofind

///</param>

///<returns>

///Trueiffound

///</returns>

publicstaticbooleanExist(

ConWHSVehicleGroupId_groupId)

{

ConWHSVehicleGroupvehGroup;

if(_groupId!='')

{

selectfirstonlyRecIdfromvehGroup

wherevehGroup.VehicleGroupId==_groupId;

}

return(vehGroup.RecId!=0);

}

Thesemethodsareverysimilar,anditisbettertowritetheFindmethodandcopyitinordertocreatetheExistmethod.

31. Wewillhavetwotabsopen,thecodeeditorandthetabledesigner.Sincethecodeeditorwillbeopen,closethisfirst,savingthechanges.Thencloseandsavethetabledesigner.

32. Finally,wewillneedtosynchronizethedatabasesothatthetableiscreatedwithintheSQLServer.Beforewedothis,wewillneedtobuildthemodelbyfollowingthesesteps:1. Fromthemenu,selectOperationsandthenBuildmodels....2. CheckConWHSVehicleManagementfromthelistandpressBuild.

33. Tosynchronizethedatabasewiththetabledefinitions,right-clickontheprojectintheSolutionExplorerandchooseSynchronizeConWHSVehicleManagementwithDatabase.

34. ThiswillstarttheprocessandwillbevisibleintheOutputwindow.Wewillonlyneedtodothiswhenweneedtoeitherrununittestsortestswithinourenvironment.

Page 180: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 181: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...CreatingatablecreatesadefinitionthatOperationswillusetocreatethephysicaltableintheSQLServer.Thetablesarealsotypesthatcontainalotofmetadataatapplicationlevel.

Whencreatingthefields,wedidn'tspecifythelabel,size,ortype.ThiscomesfromtheEDT.Wecanchangethelabel,giveitspecificcontext,butthesizeandtypecannotbechanged.

TherelationswecreatedareusedatapplicationlevelandnotwithinSQL.Theyareusedtogeneratedrop-downlistsandhandleorphanrecords.Withintheclient,youcannavigatetothemaintable.ItdeterminesthetableviatherelationandusestheFormRefpropertyonthemaintabletoworkoutwhichformtouse.

TheFindandExistmethodsareabestpracticerule,andshouldalwaysbewrittenandused.Forexample,althoughSelect*fromPurchLinewherePurchLine.InventTransId==_idmayappeartobecorrectasInventTransIdisauniquekey,itwouldbewrongasthereisnowafieldonPurchLinetoflagwhetheritismarkedasdeleted.UsingPurchLine::findInventTransIdwouldonlyfindarecordifitwasnotmarkedasdeleted.

Therearealsomanymethodsthatwecanoverridetoprovidespecialhandling.Whenoverridingamethod,itcreatesamethodthatsimplycallsthesuper()method.Thesuper()methodcallsthebaseclass'(Common)method,whichforupdate,insert,anddeleteaspecialmethodthatstartswithdo.Thedomethodscannotbeoverridden,butcanbecalleddirectly.ThedomethodisamethodonabaseclasscalledxRecordthatperformsthedatabaseoperation.

Themethodsforvalidation,suchasvalidateField,validateWriteandvalidateDelete,areonlycalledfromeventsonaformdatasource;thisiscoveredinthenextchapter.

Page 182: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 183: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Creatingaparametertable

Aparametertableonlycontainsonerecordpercompany.Thetablecontainsalistoffields,whichcanbedefaultsorcompany-specificoptionsusedincodetodeterminewhatshouldhappen.Theparametertableisusuallycreatedfirst,andthevariousfieldsthatactasparametersareaddedaswecreatethesolution.

ThisfollowsdirectlyfromtheCreatingsetuptablesrecipe.

Page 184: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 185: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreatetheparametertablefollowthesesteps:

1. CreateanewtablecalledConWHSVehiclePararameters;again,theprefixandsuffixisbasedonpractice.Usually,thenamewillonlybe<Prefix>+<Module>+Parameters.

2. Setthetable'sparametersasfollows:

Property Value

Label Vehiclemanagementparameters

Cachelookup Found

TableGroup Parameter

CreatedBy

CreatedDateTime

ModifiedBy

ModifiedDateTime

Yes

DeveloperDocumentation

TheConWHSVehicleParameterstablestorestheparametersfortheVehiclemanagementsolution

3. DragtheConWHSVehicleGroupIdEDTontotheFieldsnodeandrenameittoDefaultVehicleGroupId.4. DragtheParametersKeyEDTfromtheApplicationExplorertoourFieldsnode.5. RenameittoKeyandchangetheVisiblepropertytoNo.

Thisisonlyusedasaconstrainttolimitthetabletoonlyhavingonerecord.Allvisiblefieldsneedtobeinafieldgroup.

6. CreateafieldgroupcalledDefaultsandsettheLabelproperty.Usethelookuptolocateasuitablelabel.

Thelabel@SYS334126hasadescriptionindicatingthatwecanusethisforadefaultsfieldgroup.Youwillseeotherexamplesthatareclearlyunsuitablebyreadingthe

Page 186: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Descriptionfield.

7. DragtheDefaultVehicleGroupIdfieldtothenewDefaultsfieldgroup.

WewillusethisontheparameterformsothatithastheheadingasDefaults.Thisiswhywedon'tneedtochangethefield'slabeltospecifycontext.

8. Right-clickontheRelationsnodeandselectNew|ForeignKeyRelation.9. Completetheparametersasfollows;ifnotspecified,leaveasdefault:

Parameter Value Comment

Name ConWHSVehicleGroupThenameoftherelationisusuallythenameofthetable,unlesswehavetwoforeignkeystothesametable.

RelatedTable

ConWHSVehicleGroup Thetabletowhichourforeignkeyrelates.

Cardinality ZeroOne

Therewillbeeitheroneornoparameterrecordrelatingtothevehiclegrouprecord.

Aone-to-manyrelationshipwoulduseZeroMoreorOneMore.

RelatedTableCardinality

ZeroOneThevalueisnotmandatory,sowecanthereforerelatetozerovehiclegrouprecords,orone.

RelationshipType

Association

Theparameterrecordisassociatedwithavehiclerecord.Compositionwouldbeusedinheader/linesdatasets,wheredeletingtheheadershoulddeletethelinesrecords.

OnDelete Restricted

Thiswillpreventavehiclegrouprecordfrombeingdeleted,ifitisspecifiedonthistable.

SeetheThere'smoresectionformoreinformationondeleteactions.

Role

Thisistheroleoftherelation,anditmustbeuniquewithinthistable.

IfUseUniqueRoleNamesisYes,wewillonlyneedtospecifythisifwehavetwoforeignkeyrelationstothesametable.

Page 187: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

10. Right-clickonRelationandchooseNew|Normal.11. IntheFieldproperty,specifytheforeignkey(thefieldinthistable),DefaultVehicleGroupId.12. IntheRelatedFieldproperty,specifythekeyintheparenttable:VehicleGroupId.13. CreateanewindexcalledKeyIdxandaddtheKeyfieldtoit.Itisuniquebydefault,soitactsasa

constraintindex.14. WecannowcreatetheFindandExistmethod.Thereisadifferenceforparametertables,inthatthe

Findmethodcreatesarecordinaparticularway.CreatetheFindmethodasshowninthefollowingpieceofcode:

publicstaticConWHSVehicleParametersFind(

boolean_forupdate=false)

{

ConWHSVehicleParametersparameter;

parameter.selectForUpdate(_forupdate);

selectfirstonlyparameter

indexKey

whereparameter.Key==0;

if(!parameter&&!parameter.isTmp())

{

//Createtheparameter

Company::createParameter(parameter);

}

returnparameter;

}

15. Wewilluseaslightlydifferentselectstatementwherewecanwritetheselectstatementinline,whichmeansthatwedon'thavetodeclarethetypeasavariable;writetheExistmethodasfollows:

publicstaticbooleanExist()

{

return(selectfirstonlyRecId

fromConWHSVehicleParameters).RecId!=0;

}

16. Wewanttoensurethattherecordcannotbedeleted.So,wewilloverridetheDeletemethod.PressReturnatthestartoftheFindmethodtocreateablanklineatthetop.Right-clickonthisblanklineandchooseInsertOverrideMethod|Delete.Changethemethodsothatitreadsasfollows:

publicvoiddelete()

{

throwerror("@SYS23721");

}

Thismethodiscalledwheneverwetryanddeletearecord,eitherthroughtheUIorincode.Thecalltosuper()thatweremoved,callsdoDelete(),whichinturntellsthekerneltodeletetherecord.

17. WesettheTableCachepropertytoEntireTable.Wheneverthistableisupdated,wewillneedtoflushthecachesothatthesystemusestheupdatedvalues.Overridetheupdatemethodasfollows:

publicvoidupdate()

{

super();

flushConWHSVehicleParameters;

}

Page 188: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

WewanttotellOperationstowritetherecord,andthenflushthecache.Thisiswhysuper()isthefirstline.

Page 189: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 190: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...

ThebuildoperationwillvalidateandcompilethepackageintoaDLL.Thismustbedonebeforewesynchronizethedatabase.Thiscanfail,andatthisstage,itisnormallyduetomissingreferences.WithintheApplicationExplorer,eachelementshowsthepackagetowhichitbelongs.Wemustensurethatourmodelreferencesforalltypesthatweusewithinourproject.Ifwedon't,wewillgetbuilderrorslike

this:

Toaddtherequiredreferences,wecanfollowthesesteps:

1. LocatethetypewiththeerrorinApplicationExplorer.2. Notethepackageitisin,whichisinsquarebrackets.3. NavigatetoDynamics365|ModelManagement|Updatemodelparameters....4. SelecttheConWHSVehicleManagementmodel.5. ClickonNext.6. Checkiftherequiredpackageischecked,andthenpressNext.

WewouldnormallyreferencetheApplicationPlatform,ApplicationFoundation,andApplicationSuitepackagesanyway,aswewouldoftenuseelementsfromthesepackages.

7. PressFinish.8. NavigatetoDynamics365|ModelManagementandselectRefreshmodels.9. Trythebuildoperationagain;youmayneedtorepeatthisasoneerrorcanmaskanother.

Page 191: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ConWHSVehicleGroupgroup;<br/>group=ConWHSVehicleGroup::Find(<val>);

ConWHSVehicleGroup::Find(<val>).Description;

Thiswillcompilewithouterror,butitwilltrytofindvalueintheInventItemGrouptable.

Page 192: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 193: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

OptimisticconcurrencyandselectForUpdateTherecordinOperationscontainsatleastfoursystemfields:

Field Description

DataAreaId ThisisthecompanyIDiftheSaveDataPerCompanyissettoYes.Thisfilters,atapplicationlevel,thedatainformsandselectstatementstobedatajustinthecurrentcompany.

RecId Thisisasystem-maintainedrecordID.

Partition Thisisusedtofilterdataatkernellevel;itislargelydeprecated,exceptfortestingscenarios.

RecVersion Thisisusedtodetermineiftherecordhaschangedsinceitwasread.

Optimisticconcurrency(OCC)isenabledbydefaultonnewtables.Weselectarecord"forupdate"byaddingaforUpdateclausetoaselectorwhileselectstatement,orbyusingtheselectForUpdate(true)methodthatexistsonalltables.

Whenweselectarecordforupdate,aphysicallockisnotplacedontherecordanditisthereforepossiblefortwodifferentprocessestoselectthesamerecordforupdate.

Astherecordorrecordsareread,theyarereadfromthedatabaseintoaninternalrecordbuffer.Whentherecordiswrittenback,itwillcheckthatthevalueoftheRecVersionfieldinthephysicaltableisthesameaswhentherecordwasfetchedintotheinternalbuffer.

IfRecVersionisdifferent,anexceptionwillbethrown.Ifthisisthrownwhilsteditingdata,theuserisgivenamessagethattherecordhaschangedandaskedtorefreshthedata.Iftheerroristhrownwithincode,wewillgetanupdateconflictexceptionthatcanbecaught.Shouldtheupdatesucceed,theRecVersionfieldwillbechangedtoadifferentnumber.

IfweareusingOCC,wecanmakethecalltoselectForUpdate()evenaftertherecordhasbeenfetchedfromthedatabase.

Page 194: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 195: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

SeealsoThefollowingdocumentation(TableRelationProperties[AX2012])isforAX2012,butthedetailsarestillcorrectforOperations:

TableRelationProperties[AX2012](https://msdn.microsoft.com/en-us/library/hh803130.aspx)Tableproperties(https://ax.help.dynamics.com/en/wiki/table-properties/)

Page 196: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 197: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingmaindatatablesInthissection,wewillcreateamaintable,similartothecustomertable.Thestepsaresimilartothevehiclegroup,andwewillabbreviatesomeofthestepswehavealreadydone.Thepatterndescribedinthisrecipecanbeappliedtoanymaintableusingyourowndatatypes.

Thetableinthisexamplewillbetostorevehicledetails.Thetabledesignwillbeasfollows:

Field Type Size EDT(:indicatesextends)

VehicleId String 20 ConWHSVehicleId:Num

VehicleGroupId String 10 ConWHSVehicleGroupId

RegNum String 10 ConWHSVehRegNum

AcquiredDate Date ConWHSAcquiredDate:TransDate

Page 198: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 199: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyInordertofollowthesesteps,theelementscreatedearlierinthischaptermustbecreated.

Page 200: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 201: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreatethevehicletable,followthesesteps:

1. CreatetheConWHSVehicleIdstringEDTwiththefollowingproperties:

Property Value

Name ConWHSVehicleId

Extends Num

Label VehicleId

HelpText Thevehicleid

2. CreatetheConWHSVehRegNumstringEDTwiththefollowingproperties:

Property Value

Name ConWHSVehRegNum

Size 10

LabelRegistration

Addacommentthatthisisavehicleregistrationnumber

HelpText Thevehicleregistrationnumber

Changecase UpperCase

3. CreatetheConWHSAcquiredDatedateEDT,rememberingthatwewillneedtocreatetheEDTasanEDTdate.Usethefollowingproperties:

Page 202: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Property Value

Name ConWHSAcquiredDate

Extends TransDate

Label Dateacquired

HelpText Thedatethatthevehiclewasacquired.

AlthoughwecreatedthisEDTasadate,thisismainlyforthewayitappears.Itiscreatedinthedatabaseasadatetime,andcompilestoaCLRdatetimetype.

4. Createanewtable,andnameitConWHSVehicleTable.Theconventionisthatitstartswithourprefix,followedbytheentitynameasasingularnoun,andsuffixedwithTable.

5. DragtheEDTstothetableinthisorder:ConWHSVehicleIdDescriptionConWHSVehicleGroupIdConWHSVehicleTypeConWHSVehRegNumConWHSAcquiredDate

Thereasonfortheorder,isspecificallyfortheID,description,andgroupfields.Theseareusuallyplacedasthefirst3fields,andtheIDfieldisusuallyfirst.

6. RemovetheConWHSprefixfromthefieldsastheyareonourtable.

7. OntheVehRegNumfield,changetheAliasForpropertytoVehicleId.

TheAliasForpropertyallowstheusertoenteraregistrationnumberintheVehicleIdfieldinforeigntables,causingOperationstolookupavehicleandreplacetheentrywithVehicleId.Thisconceptiscommononmostmaintables.

8. SavethetableandopentheConWHSVehicleIdEDT.CompletetheReferenceTablepropertyontheTableReferencesnode.

9. ClosethedesignertablefortheEDTandnavigatebacktothetabledesigner.10. ChangetheVehicleIdfields'propertiesasanIDfieldasfollows:

Page 203: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Property Value

AllowEdit No

AllowEditOnCreate Yes

Mandatory Yes

Theprecedingpropertiesonlyaffectthewaythefieldbehavesonaform.

11. Amaintable'sGroupIdfieldusuallyhasanimpactonlogic,andisusuallymandatory.Evenifitdoesnot,weshouldstillmaketheVehicleGroupIdfieldmandatory.

Carefulconsiderationmustbetakenwhendecidingonwhetherthefieldismandatoryorwhenitcanbeedited.Insomecases,thedecisiononwhetheritcanbechangedisbasedondatainotherfieldsortables.ThiscanbeaccomplishedinthevalidateFieldeventmethods.

12. DonotmaketheVehicleTypefieldmandatory.

Enumsstartat0,andincrementbyoneeachtime.Operationsvalidatesthisusingtheintegervalue,whichwouldmakethefirstoptioninvalid.AsEnumsalwaysdefaulttothefirstoption,theonlywaytoforceaselectionfromthelistwouldbetomakethefirstelementcalledNotSet,forexample,withablanklabel.

13. CreateauniqueindexcalledVehicleIdxwiththeVehicleIdfield.14. Groupfieldsareoftenusedforaggregationorsearchqueries,soweshouldcreateanindexcalled

VehicleGroupIdxandaddtheVehicleGroupIdfieldtoit.Theindexmustnotbeunique.15. Completethetable'spropertiesasfollows:

Property Value

Label Vehicles

TitleField1 VehicleId

TitleField2 Description

Page 204: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Cachelookup Found

ClusteredIndex VehicleIdx

PrimaryIndex VehicleIdx

TableGroup Main

CreatedBy

CreatedDateTime

ModifiedBy

ModifiedDateTime

Yes

DeveloperDocumentation ConWHSVehicleTablecontainsvehiclerecords.

FormRef Thisisblankuntilwehavecreatedtheform

16. CreateafieldgroupOverview,labelledappropriately,anddraginthefieldsyouwishtoshowonthemainlistgridontheform.

17. Createafieldgroup,Details,andfindanappropriatelabel.Draginthefieldsthatshouldshowontheheaderoftheformwhenviewingthedetailsofthevehicle.

Itisusualtohavemanymorefieldsthanwehaveonthistable,whichnecessitatesthatseveralfieldgroupsareneeded.Whentheformiscreated,theformdesignerwillusethesefieldgroupstocreatetheform.Thedesignerwillusuallyhaveseveralfasttabs(collapsibleregions)andplaceoneormorefieldgroupsintothem.

18. Maintablesareusuallyreferencedinworksheettables,andOperationswillcreatealookupforusbasedontherelationontheforeigntable.Tocontrolthefieldsintheautomaticlookup,dragthefieldsyouwishtoseeintotheAutoLookupfieldgroup,andensurethatVehicleIdisfirst.

19. CreateaforeignkeyrelationfortheVehicleGroupIdfieldusingthefollowingproperties:

Parameter Value

Name ConWHSVehicleGroup

Page 205: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

RelatedTable ConWHSVehicleGroup

CardinalityOneMore

Thefieldismandatory

RelatedTableCardinality ZeroOne

RelationshipType Association

OnDelete Restricted

20. Addanormalfieldrelationtotherelation,connectingtheGroupIdfields.21. Itiscommontoinitializemaintablesfromdefaults,heldinparameters.TheinitValuemethodis

calledwhentheusercreatesanewrecord.Right-clickontheMethodsnodeandselectOverride|initValue.

22. Inthecodeeditor,adjustthecodesothatitreadsasfollows:

publicvoidinitValue()

{

ConWHSVehicleParametersparm;

parm=ConWHSVehicleParameters::Find();

super();

this.VehicleGroupId=parm.DefaultVehicleGroupId;

}

23. Next,addtheFindandExistmethodsusingthetable'sprimarykeyfieldasusual.24. Finally,wewilladdafieldvalidationmethodtoensurethattheacquisitiondateisnotbeforetoday.

OverridethevalidateFieldmethodandaddthefollowingcodebetweencodetheret=super();lineandreturnret;:

switch(ret)

{

casefieldNum(ConWHSVehicleTable,AcquiredDate):

TimezoneclientTimeZone=

DateTimeUtil::getClientMachineTimeZone();

TransDatetoday=

DateTimeUtil::getSystemDate(clientTimeZone);

if(this.AcquiredDate<today)

{

//Theacquisitiondatemustbetodayorlater

ret=checkFailed("@ConWHS:ConWHS29");

}

break;

}

25. CreatealabelfortheerrormessagereturnedbycheckFailedandreplacetheliteralwiththelabelID.26. Oncecomplete,saveandclosethetablecodeeditoranddesignertabpages.

Page 206: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 207: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Wehaveintroducedacoupleofnewconceptsandstatementsinthisrecipe.

TheswitchstatementshouldalwaysbeusedonthevalidateFieldmethod,evenifweonlyeverintendtohandleonecase.Anifstatementmightseemeasier,butitwillmakethecodelessmaintainable.Thisgoesforanychecklikethis,wherethecaseshavethepossibilitytoincrease.

Thenextnewconceptisthatwecannowdeclarevariablesasweneedthem.Thishelpswithscope,butshouldn'tbeoverused.TheinitValueandvalidateFieldmethodsaregoodexamplesofexplainingwherethecodeshouldbedeclared.

TheAX2012systemGetDate()functionisdeprecatedinthisrelease.DateTimeUtilprovidesbetterhandlingfortimezones.Thedatecanbedifferentacrosstimezones,andcandifferbetweentheclient'smachine(thebrowser)andtheserverwhereOperationsishosted.

InthevalidateFieldmethod,wewillallowthestandardcodetorunfirst;thestandardcallwillvalidatethefollowing:

Thevaluevalidforthetype,suchasavaliddateinadatefieldIfthefieldisaforeignkey,doesthevalueexistintheparenttable?Ifthefieldismandatory,checkthatitisfilledin,orthatitisnotzerofornumericandEnumfields

Page 208: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 209: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...

Everyelement(table,tablefield,class,form,andsoon)hasanID.TablesandfieldsarecommonlyreferencedbytheirIDandnotbytheirname.ThevalidateFieldmethod,forexample,usesthefieldidastheparameterandnotthefield'sname.Aswecan'tknowtheID,Operationsprovidesintrinsicfunctions,suchastableNumandfieldNumtoassistus.Thepeculiarnatureofthesefunctionsisthattheydonotacceptastring,theywantthetypename.

Otherintrinsicfunctions,suchastableStr,fieldStr,andclassStr,simplyreturnthetypeasastring.Thereasonisthatthesefunctionswillcauseacompilationerrorshouldthetypebetypedincorrectly.Ifwedon'tusethem,notonlydowefailabestpracticecheck,butwemakeanyfuturerefactoringunnecessarilydifficult.

Page 210: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 211: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

MoreonindexesTableindexesareaphysicalstructurethatareusedtoimprovereadperformance,ensureuniquenessofrecords,andfororderingofdatainthetable.Whenrecordsareinserted,updated,ordeleted,theindexisalsoupdated.Wemustthereforebecarefulwhenaddingindexes,astheycancarryaperformancehitwhenwritingdatabacktothetable.

Atypicalindexisanorderedcollectionofkeysandabookmarkreferencetotheactualdata.Findingarecordmatchingagivenkeyinvolvesgoingtotheappropriatelocationintheindexwherethatkeyisstored.Then,youwillhavetofollowthepointertothelocationoftheactualdata.This,ofcourse,requirestwoOperations:anindexseekandalookuptogettheactualdata.

Whenwesearchforarecord,theSQLServerisabletodeterminethebestindex,orindexes,touseforthatparticularquery.Ifwerealizethatweoftenrequirethesamesetoffieldsfromaspecificquery,wecancreateanindexthatcontainsthekeyswewishtosearchon,andthefieldswewishtofetch.Thisimprovesperformanceconsiderably,asSQLwillusethatindexandcanthensimplyreturnthevaluesthatalreadyexistintheindex.

WecanimprovethisfurtherbymarkingthefieldswesimplywishtoreturnasIncludedColumn(apropertyofthefieldsinaOperationsindex).So,inourcase,wemaywishtoselectthedescriptionfromthevehicletablewherethevehiclegroupisArtic,forexample.

Therefore,asolutioncanbetoaddtheDescriptionfieldtoourVehGroupIdxindexandmarkitasIncludedColumn.However,thereisabettersolutioninthiscase,whichistouseclusteredindexes.

Aclusteredindexissimilartothis,buttheclusteredindexwillcontaintheentirerecord,avoidingalookupinthedataforanyfieldinthetable.Clusteredindexesaresortedbytheirkeys;astheindexcontainstheentirerecord,itcanaddasignificantloadtotheSQLServerifrecordsareinserted,asopposedtobeingappendedattheendofthetable.

Forsetuptables,wherethenumberofrecordsissmallandchangesinfrequently,thisisn'taproblem,andthereadbenefitfaroutweighsanydrawback.Fortransactionaltables,wemustbecareful.Weshouldalwayshaveaclusteredindex,butthekeymustbesequentialandtherecordsmustbeaddedattheendofthetable.

Anexampleofthisisthesalesordertable,whichhasaclusteredindexbasedonSalesId.Thisisagreatchoiceaswewilloftenusethiskeytolocateasalesorderrecord,andthefieldisalsocontrolledbyanumbersequence;recordsshouldalwaysbeappendedattheend.However,shouldwechangethenumbersequencesothatrecordsareinserted"mid-table,"wewillexperienceadelayininsertingrecordsandwewillbeaddingunnecessaryloadtotheSQLServer.

Page 212: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 213: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

SeealsoThefollowingisbasedonAX2012,butthecontentisstillrelevantinOperations:

IntrinsicFunctions[AX2012](https://msdn.microsoft.com/en-us/library/aa626893.aspx)

Thislinkgoesalittlebeyondwhatwehavedoneinthischapter,buthassomeveryusefulinformationabouttypes.

X++variablesanddatatypes(https://ax.help.dynamics.com/en/wiki/xpp-variables-and-data-types/)

Thefollowinglinkfocussesonmodellingaggregatedata,butalsoincludesimportantinformationaboutNon-ClusteredColumnStoreIndexes(NCCI),whichareinmemoryindexesusedforanalysingaggregatedata.

BIR100:Modelingandusingaggregatedata(https://ax.help.dynamics.com/en/wiki/modeling-and-using-aggregate-data/)

Page 214: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 215: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Creatingorderheadertables

Orderandlinetablesareusedwheneverweneedaworksheettoenterdatathatislateractedupon.Oncetheyhavebeenprocessed,theyshouldnolongerberequired.Reportsshouldactuponthetransactionsthattheordercreated,suchasinventorytransactions,salesledgertransactions,invoices,andmore.

Page 216: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 217: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyAlthoughwewillbeusingthetablescreatedearlier,thepatterncanbefollowedwithyourownsolution.

Page 218: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 219: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Wewillfirstcreatetheworksheetheadertable,whichwillbeavehicleserviceordertable:

1. CreateanewtablenamedConWHSVehicleServiceTable.2. CreateaprimarykeyEDT,ConWHSVehicleServiceId;thistime,extendNum.CompletetheLabelandHelpText

propertieswithappropriatelabels.3. DragtheEDTfromSolutionExplorertotheFieldsnodeofourtable,andrenameittoServiceId.4. CompletetheServiceIdfieldasanIDfield:Mandatory=Yes,AllowEdit=No,AllowEditOnCreate

=Yes.5. CompletetherelationinformationontheEDT.6. CreatetheprimarykeyindexasServiceIdxwithServiceIdastheonlyfield.7. SettheClusteredIndexandPrimaryIndexpropertiesasServiceIdx.8. DragtheConWHSVehicleIdEDTtoourtableandrenameittoVehicleId.9. MaketheVehicleIdfieldmandatory.Thedecisiontomakethefieldeditabledependsontheassociated

logic(referentialintegrity)andthebusinessrequirement.10. CreateaforeignkeyrelationforConWHSVehicleIdtoConWHSVehicleTable.VehicleId.

ThecardinalityshouldbeOneMoreasitismandatory.OnDeleteshouldbeRestrictedonforeignkeyrelationstomaintables.

11. DragtheDescriptionEDTontoourtablefromtheApplicationExplorer.12. CreateanewBaseEnumfortheservicestatus,asdefinedhere:

Property Value

Name ConWHSVehicleServiceStatus

Label Status(forexample@SYS36398)

Help Theserviceorderstatus

IsExtensibleTrue

Rememberthatwecan'tuse>or<comparisonsincodewiththisset.

Elements Label

None <empty>

Confirmed Confirmed

Complete Complete

Cancelled Cancelled

13. DragConWHSVehicleServiceStatustoourtableasServiceStatus.14. SaveanddragthenewEnumtoourtableandrenameittoServiceStatus.15. MaketheServiceStatusfieldreadonly.AllowEditandAllowEditOnCreateshouldbeNo.Statusfields

shouldbecontrolledthroughbusinesslogic.

Page 220: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

16. CreatedateEDTsConWHSVehicleServiceDateReq'Requestedservicedate'andConWHSVehicleServiceDateConf'Confirmedservicedate'.ThedatesshouldextendTransDate.Labelappropriatelyanddragtothenewtable.

17. RenamethefieldstoServiceDateRequestedandServiceDateConfirmed.18. Completethetablepropertiesasshownhere:

Property Value

Label Vehicleserviceorders

TitleField1 ServiceId

TitleField2 Description

Cachelookup Found

ClusteredIndex ServiceIdx

PrimaryIndex ServiceIdx

TableGroup WorksheetHeader

CreatedBy

CreatedDateTime

ModifiedBy

ModifiedDateTime

Yes

DeveloperDocumentation ConWHSVehicleServiceTablecontainsvehicleserviceorderrecords.

FormRef Blankuntilwehavecreatedtheform

19. Createthefieldgroupsasfollows:

Page 221: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Group/fields Label

Overview

ServiceIdVehicleIdDescriptionServiceStatus

Overview

Details

ServiceIdVehicleIdDescriptionServiceStatus

Details

ServiceDates

ServiceDateRequestedServiceDateConfirmed

Servicedates

20. CreatethenowusualFindandExistmethodsusingServiceIdasthekey.21. Youcanalsocreateyourownvalidationontheservicedates.22. Finally,wewillintroducethevalidateWritemethod.Thisistoenforcetherequirementthatonly

serviceordersatstatusconfirmedorlesscanbechanged;themethodshouldbewrittenasfollows:

publicbooleanvalidateWrite()

{

booleanret;

ret=super();

ret=ret&&this.CheckCanEdit();

returnret;

}

publicbooleanCheckCanEdit()

{

If(!this.CanEdit())

{

//Serviceordercannotbechanged.

returncheckFailed("@ConWHS:ConWHS33");

}

returntrue;

}

publicbooleanCanEdit()

{

switch(this.ServiceStatus)

{

caseConWHSVehicleServiceStatus::None:

caseConWHSVehicleServiceStatus::Confirmed:

returntrue;

}

returnfalse;

}

23. Finally,wewillwriteamethodthatinitializesthedefaultsfromthemaintablerecord,thatis,vehicle,whenitisselected.Writethefollowingmethod:

Page 222: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

publicvoidInitFromVehicleTable(ConWHSVehicleTable_vehicle)

{

this.Description=_vehicle.Description;

}

24. Then,overridemodifiedFieldasfollows:

publicvoidmodifiedField(FieldId_fieldId)

{

super(_fieldId);

switch(_fieldId)

{

casefieldNum(ConWHSVehicleServiceTable,

VehicleId):

this.InitFromVehicleTable(

ConWHSVehicleTable::Find(

this.VehicleId));

break;

}

}

25. Savethetableandclosetheeditortabs.

Page 223: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 224: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Therearefewnewconceptshere,andI'llstartwiththecodestructureattheendofthesteplist.

Themostimportantpartofthiscodeisthatwedidn'twritethis.ServiceStatus<=ConWHSVehicleServiceStatus::Confirmed.Thisisanextensibleenum,andwecan'tbesureofthenumericvaluethatthesymbolshave.

TheotherpartisthatwehavesplitwhatmayseemtobeasimpleifstatementinvalidateWriteintothreemethods.Thereasonisreusability.Itisnicertomakearecordread-onlyintheformthanitistothrowanerrorwhentheusertriestosave.So,wecanuseCanEdittocontrolwhethertherecordiseditableontheform,makingallcontrolsgreyedout.

Checkmethodsarewrittentosimplifythevalidationmethodswheretheyareused,andalsotomakethechecksreusable,ergoconsistent.Checkmethodsareexpectedtoreturnasilenttrueifthecheckpasses,ortodisplayanerrorshouldthecheckfail.TheerrorissenttotheuserusingthecheckFailedmethod,whichdoesnotthrowanexception.

ThenextistheInitFrommethod.Thisisaverycommontechniqueandshouldalwaysbeusedtoinitializedatafromforeigntables.Theoddthingisthatwedon'tcheckthatitexistsfirst.

Thisisdeliberate.RecordsinOperationsinitializesothatallthefieldsareempty,orzero(dependingonthefield'stype).So,iftherecordisnotfound,thevaluesthatareinitializedwillbemadetobeempty,whichisdesirable.Also,modifiedFieldoccursafterthefieldisvalidated.So,themethodwon'tbetriggeredshouldtheuserenterandinvalidvehicleID.Ifthevehicleisnotmandatory,wemayfindthevehicleIDisempty;however,again,thisisfine.

Page 225: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 226: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...TheOnDeletepropertyfortablerelationsissimilartothefunctionalitycontrolledbytheDeleteActionsnodeonthetable.ThedifferenceisthattheDeleteActionisplacedontheparenttable.Thisisaproblemiftheparenttableisastandardtable,andwearetryingtoavoidover-layering.UsingtheOnDeletepropertyisthereforecontrolledinamuchbetterlocation,eventhoughtheresultisthesame.

WehavethefollowingoptionsforbothDeleteActionsandtheOnDeleteproperty:

None

Restricted

Cascade

Cascade+Restricted

Nonehasnoeffect,andeffectivelydisablesthedeleteaction;thisisusefulifyouwanttospecificallystate"Donothing"sosomeoneelsedoesn'ttrytocorrectwhatseemstobeanomission.

Restrictedwillpreventtherecordfrombeingdeleted,iftherearerecordsintherelatedtablethatmatchtheselectedrelation.ThisoccurswithinthevalidateDeletetableevent,whichiscalledbythevalidateDeleteformdatasourceevent.

Cascadewilldeletetherecordintherelatedtablebasedontherelation;itisnousehavingasalesorderlinewithoutasalesorder.Thisisanextensiontothedeletetableevent.

Cascade+Restrictedisalittlespecial.Inatwo-tablescenario,itisthesameasRestricted;itwillstoptherecordfrombeingdeletedifarelatedrecordexists.However,iftherecordisbeingdeletedaspartofacascadefromatablerelatedtoit,therecordswillbedeleted.

Page 227: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 228: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Creatingorderlinetables

Thisrecipecontinuesfromthepreviousorderheadertablerecipe.Theexampleinthisrecipeisthatwewillhaveserviceorderlinesthatreflecttheworkrequiredonthevehicle.Theconceptsinthisrecipecanbeappliedtoanyorderlinetable;tofollowexactly,thepreviousrecipesshouldbecompletedfirst.

Page 229: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 230: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreatetheorderlinetable,followthesesteps:

1. CreateanewtablenamedConWHSVehicleServiceLine.2. DragthefollowingEDTsontothetable:

ConWHSVehicleServiceIdLineNumItemId(youmayreceiveanerrorifyouhaven'treferencedApplicationSuite)ItemNameConWHSVehicleServiceStatus

3. RemovetheConWHSVehicleprefixes.4. TheServiceIdandLineNumfieldsareusuallycontrolledfromcode,somakethemread-onlyand

mandatory(thisensuresthatthecodethatsetsthemhasrunbeforetheusersavestheline).

TheLineNumfieldisusuallyusedtoorderthelinesandcanbemadenotvisibleifthisisn'ttobedisplayedintheuserinterface.Allvisible(non-system)fieldsshouldeitherbeinafieldgroupormadenotvisible.

5. MakeItemIdmandatoryandonlyallowittobeeditedoncreation.6. CreateauniqueindexcalledServiceLineIdxandaddtheServiceIdandLineNumfields.Wewillusethisas

aclusteredindexasitwillnaturallysortthelinesontheform.7. AddarelationtoConWHSVehicleServiceTable,butservicelinesarecontainedwithinaserviceorder

record,socompleteitasfollows:

Parameter Value

Name ConWHSVehicleServiceTable

RelatedTable ConWHSVehicleServiceTable

Cardinality ZeroMore

RelatedTableCardinality ZeroOne

RelationshipType Composition

OnDelete Cascade

Page 231: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

8. AddarelationtoInventTableonItemIdasfollows:

Parameter Value

Name InventTable

RelatedTable InventTable

Cardinality OneMore

RelatedTableCardinality ExactlyOne

RelationshipType Association

OnDelete Restrict

9. CreateanOverviewgrouptocontrolwhatappearsonthelinesandaddallfields.Inourcase,thisissufficient.Wewouldusuallyhavemanymorefieldsonaline,andwewouldorganizethefieldsintologicalgroupsthatareusedintheformdesign.

10. Updatethetable'spropertiesasfollows:

Property Value

Label Vehicleserviceorderlines

TitleField1 ItemId

TitleField2 ItemName

Cachelookup Found

ClusteredIndex ServiceLineIdx

Page 232: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

PrimaryIndex SurrogateKey(default)

TableGroup WorksheetLine

CreatedBy

CreatedDateTime

ModifiedBy

ModifiedDateTime

Yes

DeveloperDocumentation ConWHSVehicleServiceLinecontainsvehicleserviceorderlinerecords.

11. TheFindandExistmethodswillneedtwokeysinthiscase,ServiceIdandLineNum.Theselectstatementclauseshouldbewrittenasfollows:

selectfirstonly*fromserviceLine

whereserviceLine.ServiceId==_id

&&serviceLine.LineNum==_lineNum;

12. Finally,wewillneedtoinitializetheItemNamefieldfromtheitemtable;first,createtheinitFromInventTablemethod.Wewillfollowthesamepatternaswedidearlierandwritethefollowinglinesofcode:

publicvoidinitFromInventTable(InventTable_inventTable)

{

this.ItemName=_inventTable.itemName();

}

13. OverridethemodifiedFieldmethodandcalltheprecedingmethodasshownhere:

publicvoidmodifiedField(FieldId_fieldId)

{

super(_fieldId);

switch(_fieldId)

{

casefieldNum(ConWHSVehicleServiceLine,ItemId):

this.initFromInventTable(

InventTable::find(this.ItemId));

break;

}

}

14. Oncecomplete,saveandclosetheeditors.

Page 233: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 234: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Thefirstnewconceptistheuseoftheclusteredindextocontroltheorderthattherecordsaredisplayedingridcontrols.ThisissimplyusingthefactthatSQLwillreturnrecordsintheorderoftheclusteredindex.Compositekeysarefineforthispurpose,butwejustwouldn'tusuallyusethemasaprimarykey.

WealsotouchedonSurrogatekeys,whichwehavesofaravoided.Theexplanationrequiresalittlebithistorytounderstand.ThesewereintroducedinAX2012asaperformanceaidandallowedfeaturesliketheledgeraccountlookupwhenenteringgeneralledgerjournals.TheproblemisthattheyarehardwiredtobeRecId.So,whenweaddedforeignkeyrelations,thefieldcreatedcontainedanunhelpfulRecId.Tosolvethis,analternatekeywasadded,whichisapropertyontheindexdefinition.Thisallowsamoremeaningfulrelationtobeusedforaforeignkey.TheprimarykeycouldonlybeuniqueindexesthathadtheAlternateKeypropertyset.

TheothertypeofkeyintroducedwastheReplacementkey.TheReplacementkeyisawaytoshowameaningfulkey,otherthanthenumericRecIdbasedSurrogateKey.

WhatSurrogateKeystillallowsustodoistouseRecIdastheforeignkey,butshowsmeaningfulinformationfromaFieldGroupontheparenttable.AnexampleisthatwecouldaddaforeignkeyrelationtoConWHSServiceOrderLine,whichshoulduseSurrogateKey.Whenweaddtheforeignkey,containingthemeaninglessnumber,weaddaReferenceGroupcontrolthatcandisplayfieldsfromafieldgroupontheConWHSServiceOrderLinetable;theuserisoblivioustothemagicalreplacementthatisgoingonbehindthescenes.

ThefinalpointbroughtoutinthisrecipeisintheinitFromInventTablemethod.Thepatternisstraightforward,butthecalltoinventTable.itemName()isamethod,hencetheparentheses.ThedeclarationforthemethodispublicItemNameDisplayitemName([Common]).AsalltablesderivefromCommon,wecanpassinanytable,whichisastrueasitispointless.Ifwelookatthemethod,itcanactuallyonlyhandleInventDim.

Readingthroughthemethodsisalwaysagoodinvestment,takingtimetounderstandthereasonwhythecodewaswrittenthatparticularway.

Page 235: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 236: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

SeealsoForthehistoryoftheSurrogatekey,seehttps://msdn.microsoft.com/en-us/library/hh812105.aspx.

Page 237: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 238: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingtheUserInterface

Inthischapter,wewillcoverthefollowingrecipes:

CreatingthemenustructureCreatingaparameterformCreatingmenuitemsCreatingsetupformsCreatingdetailsmaster(maintable)formsCreatingadetailstransaction(orderentry)formCreatingformpartsCreatetileswithcountersfortheworkspaceCreatingaworkspace

Page 239: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 240: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Introduction

Inthischapter,wewillmanyofthetaskswhencreatingauserinterface.ThiscontinuesonfromChapter2,DataStructures.AsdiscussedinChapter2,DataStructures,weusuallycreatetablesandformsaspartofthesameprocess;so,therecipesinthischapterwillinvolvecompletingafewpropertiesinthedatamodel.

Whencreatingforms,wemustprovideconsistencytotheusers,andtoaidwiththis,wewilluseformdesignpatterns.Theformdesignpatternisdeterminedbythetablegroup,asstatedinChapter2,DataStructures.Therearespecialcaseswhenwecanhavevariationsbut,forthemainpart,weshouldsticktothepatternssuggestedinthischapter.

Page 241: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 242: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingthemenustructureThemenustructureiscarriedoverfromtheuserinterfaceconceptsinAX2012.InOperations,themenushavethesamestructure,butareopenedfromthe'burger'menubuttonjustbelowtheOffice365logoonthetopleftofthepage,asshowninthefollowingscreenshot:

EachmenuappearsundertheModulesoption,asshowninthefollowingscreenshot:

Allmenuitemsforforms,reports,andprocessesmustbeplacedinthemenustructure,evenifwelatercreateaworkspace.

ThemenustructuremayseemtohavebeenrelaxedslightlyfromtheerigidstructurerecommendedinDynamicsAX2012.InDynamicsAX2012,wealwayshavethefollowingmainheadingsinamenu:Common,Inquiries,Reports,Periodic,andSetup.IfwelookattheAccountsPayablemenuintheApplicationExplorer,wecanseethattheoldstructurehasbeenflattened,asshowninthefollowingscreenshot:

Page 243: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Theactualstructureisnoworganizedasfollows:

Workspaces:VendInvoiceWorkspace,VendPaymentWorkspaceTile.DetailsMaster(Main)tables:VendorsDetailsTransaction(Worksheets):PurchaseOrders,VendorInvoicesJournals:PaymentsInquiriesandReports:VendPackingSlipJournal,VendTransList,VendAccountStatementIntPeriodictasks:PeriodicTasksSetupandconfiguration:Setup,PolicySetup,andsoon

Themenustillneedsacommonstructureinordertomakeiteasierforuserstofindtheoptionsmoreeasily,butwearen'tconstrainedtoarigidstructure.Youmayarguethatthemenudoesnothaveaworkspacenode,thisisbecausethiswasaddedthroughamenuextension,asshowninthefollowingscreenshot:

Page 244: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 245: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyWewilljustneedourprojectopeninVisualStudio.

Page 246: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 247: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreatethemenustructure,followthesesteps:

1. Addanewitemtotheproject(selectafoldernodeandpressCtrl+Shift+A).2. SelectUserInterfacefromtheleft-handlistandthenMenufromtheright.3. TypeConWHSVehicleManagementintheNamefieldandpressOK.4. Inthedesigner,createalabelforVehiclemanagementandenterthisasthemenu'sLabelproperty.5. Right-clickonnewmenuinthedesignerandchooseNew|Submenu.6. Completeasfollowsforthefollowingsubmenus:

Name Label

Workspaces @SYS:Platform_Menu_ColHeading_Workspaces

Vehicles Vehicles(newlabel)

ServiceOrders Serviceorders(newlabel)

PeriodicTasks @SYS76406

InquiriesAndReports @SYS3850

Setup @SYS333869

7. Wecanaddmoresubmenustohelporganizethestructure,shouldthisberequired.8. Saveandclosethemenudesigner.9. Wewillnowneedtoextendthemainmenusothatwecannavigatetoourmenu.10. IntheApplicationExplorer,navigatetoAOT|UserInterface|Menus.11. Right-clickonMainMenuandchooseCreateextension.

ThiscreatesanextensiontotheMainMenumenu,butdoesnotover-layerit,allowingourchangetositnicelyalongsidetheotherextensionofthesameelementwithouthavingtodoacodemerge.

12. RenamethenewiteminourprojectfromMainMenu.ExtensiontoMainMenu.ConWHSVehicleManagement.

Leavingthename,asitisgivenautomatically,isacommonomissionandisamistake;

Page 248: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

allmenuextensionsmusthaveauniquename.Manypartnerswillinsistweprefixextension,suchasConWHSMainMenu.Extension.ThepointofthenameistomakeiteasytofindinApplicationExplorerandthatitisunique.

13. OpenMainMenu.ConWHSVehicleManagement.14. Right-clickontherootnodeandchooseNew|Menureference.15. SettheNameandMenuNamepropertiestoConWHSVehicleManagement.16. Saveandclosethedesigner.

Page 249: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 250: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Thisisastructuretowhichwewilladdthevariousmenuitems.Thisstructureispresentastheuseropensthemenufromtheleft-handmenubuttonfromwithintheclient-userinterface.

Thisbecomesmoreapparentaswecompletetheuserinterface.

Page 251: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 252: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Creatingaparameterform

Parameterformsshowsettingsgroupedintofieldgroupsandtabsusingatableofcontentsstyleform.Theyarealsousedtoshowthenumbersequencessetupforthatmodule.NumbersequencesarecoveredinChapter4,ApplicationExtensibility,FormCode-Behind,andFrameworks.Wearefollowingourvehiclemanagementsamplesolution,butthispatterncanbeappliedtoanyparametertable.

Page 253: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 254: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreatetheparameterform,followthesesteps:

1. DragtheConWHSVehicleParameterstablefromtheprojectontotheDataSourcesnodeinthetop-leftpaneofformdesigner.

2. Nametheformasperthetable'sname;inthiscase,ConWHSVehicleParameters.3. SelectUserInterfacefromtheleft-handpaneandFormfromtheright-handpane.4. Choosetoaddanewitemtotheproject.5. Datasourcesprovideadditionaloptionsofhowthistableshouldbehaveonthisform.Wedon'twant

theusertobeabletodeleteandcreaterecords,sochangethefollowingpropertiestoNo:AllowCreateAllowDeleteInsertAtEndInsertIfEmpty

6. Theformdesignerisbrokenintothreeareas:TheFormpane,Designpane,andPreview/Patternconformancepane,asshowninthefollowingscreenshot:

7. Let'sapplysomebasicpropertiesfortheform,whichareheldagainsttheDesignnode,asshownhere:

Property Value Comment

Caption Vehiclemanagementparameters

Thisisshowninthetitleoftheformandisusuallythetable'sname.

DataSource ConWHSVehicleParameters ChildnodesintheformwillusethisasthedefaultDataSourceproperty.

Page 255: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

TitleDataSource

ConWHSVehicleParameters Thisformwillusethetitlefieldpropertiesfromthetableanddisplaythemontheform.

8. TheDesignnodestatesthatapatternisnotspecified,whichwemustdo.Toapplythemainformpattern,right-clickontheDesignnodeandchooseApplyPattern|TableofContents.

9. ThelowerpanechangestothePatterntabandshowsthepattern'srequiredstructure,asshowninthefollowingscreenshot:

Wewillrefertoeachnodeinthepatternstructureasapatternelementand,toaideaseofdesign,namethecontrolsbasedonthepatternelement'sname.

10. ThisenablesustoaddaTabcontrol,soright-clickontheDesignnodeandchooseNew|Tab.11. TheerrorisremovedfromthePatternpane,butshowsthatwehavenotabpageswithintheTOC

Tabs(Tab)patternelement.12. First,renamethenewcontroltoParameterTab,andthenaddanewTabPagebyright-clickingonitin

theDesignpaneandselectingNewTabPage.

13. Thefirsttabisusuallyageneralsettingstab,sonamethisnewcontrolGeneralTabPage.14. FindasuitablelabelforGeneralandenterthatintotheLabelproperty.15. Aswealterthedesign,thedesignerwillcontinuallycheckthatweareconformingtothepattern.It

hasfoundthefollowingissueswiththetabpagecontrol:

16. WemustaddtwoGroupcontrols,onefortitlefieldsthatprovidehelptotheuser,andtheotherforthefieldswewishtoshowinthispage.

17. Right-clickontheGeneralTabPagecontrolandselectNew|Group.

Oncethisisdone,reselecttheGeneralTabPagecontrolandnotethatthepatternconformancepanehaschangedfromarederrortoayellowwarning.Thismeansthatthatthecontrolsbelowthislevelhavepatternconformanceerrors.

18. RenamethenewgroupcontroltoGeneralTitleGroup.

Allcontrolsmustbeunique,andnaminginawaythathelpsusnavigatetothecontrol

Page 256: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

easilywillhelpusgreatly.shouldwegetabuilderrorlateron.

19. ThepatternconformancepaneshowswemusthaveaStaticTextcontrolforthemaintitle,and0or1StaticTextcontrolsforasecondarytitle.Right-clickonGeneralTitleGroupandchooseNew|StaticText.

20. RenamethenewStaticTextcontroltoGeneralTitleText.21. CreateanewlabelforthetextGeneralparametersandentertheIDintotheTextproperty.22. ReselecttheGeneralTitleGroupcontrolandcheckifthepatternerrorsaregone.23. ReselectGeneralTabPage,aswenowhaveonepatternerrorforamissinggroup.AddanewGroup

control.24. Thenewgroupcontrolknowsthatitmustalsohaveapatternapplied,indicatedbythePattern:

<unspecified>textafterthecontrol'sname.Right-clickonthecontrolandchooseApplyPattern|FieldsandFieldGroups.

25. RenamethecontroltoGeneralFields.26. Ontheformpane,expandDataSources,ConWHSVehicleParameters,andthenFieldGroups.

27. Dragthefieldgroup,Defaults,ontotheGeneralFieldscontrolinthedesignpane,asshowninthefollowingscreenshot:

Wecanmanuallycreategroupsandthenaddtheappropriatefields.However,usingafieldgroup,anynewfieldsaddedtothegroupwillgetautomaticallyaddedtotheform,andtheLabelpropertywillbesetbasedonthefieldgroup'slabel.

28. Checkeachnodeforpatternconformanceerrors.29. Finally,wewillhaveaspecialtaskforparameterforms;toensurethatatleastonerecordexists,

clickontheMethodsnodeandchooseOverride|init.30. Createablanklinebeforethecalltosuper()andenterthefollowinglineofcode:

ConWHSVehicleParameters::Find();

Wedon'tcareaboutthereturnvalueinthiscase.WeareusingthefactthattheFindmethodforParametertableswillautomaticallycreatearecordifthetableisempty.

31. Saveandclosethecodeeditorandformdesigners.

Page 257: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 258: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

Whenwespecifytheformdesign'smainpattern,wearethenguidedastothecontrolsweshouldaddandwhere.Thishelpsusensurethattheformswedesignfollowabestpracticeuser-interfacedesign,whichinturnmakesourformseasiertouse,quickertotrain,andlesspronetousererror.

WecanstilloptoutofthepatternusingtheCustompattern;thisallowsustoaddanycontrolinanyorder.Thisshouldbeavoided,andtheformdesignshouldberedesignedsothatitusesstandardpatterns.

Page 259: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 260: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...TheformisbasedontheFormRunclass.Wecanseethisbyopening(double-click)classDeclarationfortheCustGroupform:

publicclassConWHSVehicleParametersextendsFormRun

ThisdiffersfromAX2012,wherethedeclarationwaspublicclassFormRunextendsObjectRun,whichwasprobablyalittlemorehonest,astheformisnotatype;thisiswhyitcanhavethesamenameasatable.

ThisisactuallyadefinitionfilethatFormRuninstantiateswithinordertobuildandexecutetheform.

OncethesystemhasbuilttheformdesignusingSysSetupFormRun,itwillperformtheinitializationtasksandruntheform.ThekeymethodsinthisareInitandRun.Thesecanbeoverriddenontheforminordertoperformadditionalinitializationtasks.

OneofFormRunInitmethod'skeytasksistoconstructtheform'sdatasources;thesearen'ttablereferencesbutFormDataSourceobjectsconstructedfromthetableslistedundertheDataSourcesnode.

InthecaseoftheConWHSVehicleParametersform,thesystemcreatesthefollowingobjectsfromtheConWHSVehicleParametersdatasourceforus:

ConWHSVehicleParametersastypeConWHSVehicleParameters(table)ConWHSVehicleParameters_DSastypeFormDataSourceConWHSVehicleParameters_QastypeQueryConWHSVehicleParameters_QRastypeQueryRun

Wearen'tnormallyconcernedwiththeQueryandQueryRunobjectsaswecanaccessthemthroughtheFormDataSourceobjectanyway.

Thedatasourcesaredeclaredasglobalvariablestotheformandprovidealayeroffunctionalitybetweentheformandthetable.Thisallowsustocontroltheinteractionbetweentheboundformcontrolsandthetable.

Let'soverridetheinitmethodonthedatasource,andthenoverridethemodifiedFieldeventonafield;thecodeeditorwillpresentthechangeasfollows:

[Form]

publicclassConWHSVehicleParametersextendsFormRun

{

publicvoidinit()

{

ConWHSVehicleParameters::Find();

super();

}

[DataSource]

classConWHSVehicleParameters

{

publicvoidinit()

{

super();

Page 261: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

}

[DataField]

classDefaultVehicleGroupId

{

publicvoidmodified()

{

super();

}

}

}

}

Itaddsthedatasourceasaclasswithintheform'sclassdeclaration,andthenaddsthefieldasaclasswithinthedatasourceclass.ThisistheonlyplaceinOperationswherethisoccurs.Ifthedatasourceclasswereaclass,itwouldhavetoextendFormDataSource.TheForm,DataSource,andDataFieldattributesareaclueastowhat'sgoingonhere.AsallexecutablecodecompilestoCLRtypes,thecompilerusestheseattributesinordertocreatetheactualtypes.Thestructureiswrittenassuchforourconvenienceasapresentationofcode.

Let'stakethemodifiedFieldmethod.ThisisaneventthatoccursafterthevalidateFieldeventreturnstrue.Thecalltosuper()callsthetable'smodifiedFieldmethod.Wemaywonderwhythecalltosuper()hasnoparameter.Thishappensbehindthescenes,anditisusefulthatthisishandledforus.

Thispatternisfollowedforthefollowingmethods:

DataSourcemethod Callstablemethod

validateWrite validateWrite

write write(inturn,insertorupdate)

initValue initValue

validateDelete validateDelete

delete delete

DataField.validateField validateField(FieldId)

DataField.modifiedField modifedField(FieldId)

Thetable'sinitValue,validateField,modifiedField,validateWrite,andvalidateDeletemethodsareonlycalledfromformevents;thewritemethoddoesnotcallvalidateWrite.

Fromthis,wehaveachoiceastowhereweplaceourcode,andthisdecisionisveryimportant.Therule

Page 262: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

tofollowhereistomakechangesashighaspossible:table,datasource,andformcontrol.

Itiscriticallyimportantthatcodeonaformmustonlybewrittentocontroltheuserinterfaceandshouldnotcontainvalidationorbusinesslogic.

Wecangofurtherwiththisandwriteforminteractionclassesthatallowuserinterfacecontrollogictobesharedacrossforms;forinstance,controllingwhichbuttonsareavailabletoalistpageandtheassociateddetailform.

Page 263: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 264: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

SeealsoThegeneralformguidelines(https://ax.help.dynamics.com/en/wiki/general-form-guidelines/).

Page 265: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 266: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Creatingmenuitems

Menuitemsareareferencetoanobjectthatwewishtoaddtoamenu.Wehavethreetypesofmenuitems:Display,Output,andAction.Displayisusedtoaddforms,Outputisusedforreports,andActionisusedforclasses.

Menuitemsarealsoaddedasprivilegestothesecuritysystem.Usersthataren'tanadministratorwillnotbeabletoseethemenuitemsunlesstheyareassignedarole,duty,orprivilegethatgivesthemaccesstothemenuitem'srequiredaccesslevel.

Page 267: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 268: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyWewilljustneedaform,report,orclassthatweneedtocreatethemenuitemfor.

Page 269: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 270: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

1. Choosetoaddanewitemtotheproject.2. SelectUserInterfacefromtheleft-handpaneandFormfromtheright-handpane.3. Namethemenuitemtobethesameastheform;inthiscase,ConWHSVehicleParameters.4. Enterthelabel@SYS7764astheLabelproperty;thisisthelowest@SYSlabelthathasnospecificcontext

described.5. CreateahelptextlabelforMaintainsthevehiclemanagementsettingsandnumbersequences.AssigntheIDto

theHelpTextproperty.6. EnterConWHSVehicleParameterstotheObjectproperty.7. TheObjectTypepropertyisFormbydefault,sothisiscorrect.8. SavethemenuitemandopentheConWHSVehicleParameterstable,andthenenterConWHSVehicleParametersto

theFormRefproperty.9. Finally,opentheConWHSVehicleManagementmenuanddragtheConWHSVehicleParametersmenuitemontopof

theSetupnode.10. Saveandclosetheopentabs.

Page 271: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 272: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

Althoughthisrecipeislistedinisolation,itshouldalwaysbedoneaspartoftheprocess.Forexample,theprocessofcreatingaParametertableinvolvesfourmainsteps:

1. Createthetable2. Createtheform3. Createthemenuitem4. Addthemenuitemtothemenu

NearlyallofthetablesthatwecreateinOperationswillhaveaformthatisusedtomaintainit.So,thelastpartofthetable'sdesignistotellthetabledesignwhichformisusedforthatpurpose.ThisishowtheViewdetailsoptionworksintheclient.Theforeignkeyisusedtodeterminethetable,andthetable'sFormRefpropertyisusedtodeterminetheformtobeused.Theforeignkeydetailsarethenusedtofiltertheform.Allthishappensautomaticallybasedonthemetadata.

Menuitemsforformsandreportstriggerthesystemtobuildandexecutetheformorreportdefinition.TheformisrenderedinthebrowserusingsomeverycleverJavaScriptthatinterfaceswithserver,andreportsareessentiallyrenderedusingSSRS.Classesmusteitherhaveastaticentrypointmethodinordertobecalled.ThisiscoveredinChapter4,ApplicationExtensibility,FormCode-Behind,andFrameworks.

Page 273: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 274: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Creatingsetupforms

ThisfollowsasimilarpatterntotheParameterform.UsingthetableinChapter2,DataStructures,weknowweshoulduseSimplelistorSimplelistandDetails-ListGridpatterns.Ourtablehastwofields,sowewillusetheSimplelistformdesignpattern.ThisfollowsthepatternofcreatingatableoftypeGroup.

Page 275: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 276: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

1. Choosetoaddanewitemtotheproject.2. SelectUserInterfacefromtheleft-handpaneandFormfromtheright-handpane.3. Nametheformasperthetable'sname;inthiscase,ConWHSVehicleGroup.4. DragtheConWHSVehicleGrouptablefromtheprojectontotheDataSourcesnodeoftheformdesigner.5. UsethesamelabelasthetablefortheCaptionpropertyoftheDesignnode.6. SettheTitleDataSourceandDataSourcespropertiestothetablename,ConWHSVehicleGroup.7. ApplytheSimpleListpatterntoDesign(right-clickandchooseApplyPattern|SimpleList).

Wewillusethepatternconformationerrorsasourto-dolist.Ifwedon'tconformtothepattern,theprojectwillnotbuild.However,justconformingtothepatternmaynotbeenough;thebuildwillalsospotsomeerrorsinproperties,whicharestatedasmandatorybythepattern.

8. AddanActionPanecontrol;renametoFormActionPaneControlastherewillonlybeoneonthisform.

Wedon'tneedtospecifytheDataSourceproperty,asthiswilldefaultfromtheDesignnode.

9. ReselecttheDesignnode.ThepatternhighlightsthatweneedaCustomFilterGroup,whichisaGroupcontrol.So,addanewcontroloftypeGroup.

10. RenamethecontroltoCustomFilterGroup,foreaseofreference.

Apartfromnavigatingformbuilderrors,thisbecomesmoreimportantforcomplicatedpatterns,aswewillbeabletoseeifthepatternisusingthecorrectcontrol.Thenameisnothowthepatternknowswhichcontrolsmaptothepattern'sstructure.

11. Wecanseethatthenewgroupcontrolneedsapattern;assignCustomandQuickFilters.12. Wewillnowhavemoretodoforthiscontrol.Thepatternhighlightsthatwemusthaveone

QuickFilterControl.Right-clickontheCustomFilterGroupcontrolandchooseNew|QuickFilterControl.

WewillneedtolinkthistoaGridcontrol,sowewillcompletethiscontrolalittlelater.

13. Right-clickontheDesignnodeandselectNew|Grid.14. WecanrenamethecontroltoFormGridControlastherewillonlybeonegridcontrolwiththispattern.15. TheDataSourcepropertydoesnotinheritfromtheparentnode,andwemustspecifythisas

ConWHSVehicleGroup.16. WecreatedanOverviewfieldgroupforthistable,sosettheDataGrouppropertytoOverview.

Page 277: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Renamethecontainercontrolsbeforeaddingfieldstothem,orsettingtheDataGroupproperty,asthefieldswillbeprefixedwiththecontainercontrol'sname.

17. GobacktotheQuickFiltercontrolandsetTargetControltothegridcontrol'snameandDefaultColumntothedesireddefaultcontrolofthetargetcontrol.

18. Doublecheckthatpatternpaneforerrors,andthensavetheform.19. Next,createamenuitemusingthesamenameastheformandthesamelabelasweusedinthe

Captionproperty.20. Completethetable'sFormRefpropertywiththemenuitemname.21. Finally,addthemenuitemtothesetupmenusoitliesundertheParametersmenuitem.

Page 278: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 279: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Thestepsweresimilartotheparameterform,althoughtherewerejustafewmoretocomplete.Wecanseethatthepatternisactuallyato-dolist,althoughpatternerrorswillpreventtheprojectfrombeingbuilt.

Page 280: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 281: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...

Ifyouwanttoactuallytesttheform,youcandosobyfollowingthesesteps:

1. BuildthepackageusingDynamics365|BuildModels;selectthepackagefromthelist.2. Ifweaddedtablesorfieldssincethelastdatabasesynchronization,synchronizethedatabasesby

right-clickingontheprojectandchoosingSynchronize<yourprojectname>withdatabase.3. Opentheproject'sproperties.4. SettheStartupObjectTypeoptiontoMenuItemDisplay.5. SettheStartupObjectoptiontothemenuitemtoopen,forexample,ConWHSVehicleGroup.6. SpecifyInitialCompany;USMFisausefulcompanyforwhenweareusingthedeveloperVMsthatuse

Microsoft'sdemodata.7. ClosethepropertyformandpressF5.

Thisisalsohowwedebugcode,andshouldabreakpointbeencountered,VisualStudiowillmovetotheforeandallowustodebugthecode.

Page 282: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 283: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Creatingdetailsmaster(maintable)forms

ForthosethatareusedtoDynamicsAX2012,thisstyleofformreplacestheListpageandseparatestheDetailsformusedformaintables,suchasProductsandCustomers.Listpagesareeffectivelydeprecatedforthispurpose.Itmayseemalittleconfusingatfirst,aswearedevelopingtwoviewsofaformatthesametime.However,oncewehavedonethisafewtimes,itbecomesmoreautomatic.

Thepatternpanewillbeourguideinthisrecipe,helpingussimplifytheprocess,andremindinguswhenwehaveforgottenakeystep.Wearecontinuingtheformdesignforthemaintable,ConWHSVehicleTable.However,thisrecipecanbeusedasapatternforanymaintable.

Page 284: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 285: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreatethedetailsmasterform,pleasefollowthesesteps:

1. Choosetoaddanewitemtotheproject.2. SelectUserInterfacefromtheleft-handpaneandFormfromtheright-handpane.3. Nametheformasperthetable'sname;inthiscase,ConWHSVehicleTable.4. DragtheConWHSVehicleTabletablefromtheprojectontotheDataSourcesnodeoftheformdesigner.

5. SelecttheConWHSVehicleServiceTabledatasourceandsettheInsertIfEmptyandInsertAtEndpropertiestoNo.

InsertIfEmptywillcreateanewrecordiftherearenorecords,whichisundesirableformaintableandorderheadertables.TheInsertAtEndpropertycanalsobeundesirable;iftheuserpressesthedownarrowkeyonthelastrecord,anewemptyrecordwillbecreated.

6. UsethesamelabelasthetablefortheCaptionpropertyoftheDesignnode.7. SettheTitleDataSourceandDataSourcespropertiestothetablename,ConWHSVehicleTable.8. ApplytheDetailsMasterpatterntotheDesign.9. Asrequiredbythepattern,addanActionPanecontrolnamedFormActionPane.10. AddaGroupcontrolcalledNavigationListGroup.Thissectionisusedinthedetailsviewsothattheuser

canchangerecordswithoutgoingbacktothelistview.

ThetasksforNavigationListGrouparesimilartotheSimpleListpattern,sothestepswillbesummarized.

11. UndertheNavigationListGroupcontrol,createaQuickFiltercontrolandnameitNavgationQuickFilter.12. Then,createaGridcontrolnamedNavigationGrid.

Therewillbeanothergridandquickfilteraddedlater,andcontrolnamesmustbeunique.

13. SettheDataSourcepropertyofthegridcontroltoConWHSVehicleTableandaddtheVehicleIdandDescriptionfieldsbydraggingthemfromtheFieldsnodeofthedatasource.

Wecanaddwhicheverfieldswelikehere,butkeepthenumberoffieldstojustafew.

14. CompletethequickfiltercontrolwithTargetControlasthegridcontrolandDefaultColumnasdesired.

15. CheckingthenodesonandundertheDesignnode,wewillnowneedtocreateaTabcontrolforthe

Page 286: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

PanelTab.CreateanewTabcontrolasfollows:

Property Value

Name PanelTab

ArrangeMethod Vertical

Youcanseeherethatwehaveatabpageforthedetailspanelandthelistpanel.Thesystemcontrolsthepagesthatarevisibleforus.

16. AddatabpagenamedDetailsPanelTabPagefirst,andthenListPanelTabPage.

ThepatternseemstomakeamistakeasweaddDetailsPanelTabPageandthinksthatweareaddingthelistpanel.Itcorrectsitselfoncewehavecreatedthesecondtabpagecontrol.

17. Let'scompletetheListPanelTabPagecontrolfirst.Asrequiredbythepattern,underthiscontrol,addaGroupcontrolnamedListQuickFilterGroupandaGridcontrolnamedListGrid.

18. Completethegridcontrol'sDataSourcepropertyandusetheOverviewfieldgroupfortheDataGroupproperty.

19. WewillcompleteListQuickFilterGroupinthesamewayaswedidfortheSimpleListpattern.ApplytheCustomandQuickFilterspatterntothecontrol.Then,addaQuickFiltercontrolandnameitListQuickFilter.CompletethecontrolbyreferencingittothegridcontrolwenamedListGrid.

20. ThepatternnowstatesthatwewillneedMainGridDefaultAction,whichisaCommandButtoncontrol.AddCommandButtontoListPanelTabPage,settingtheproperties,asshowninthefollowingtable:

Property Value

Name ListGridDefaultButton

Command DetailsView

Youmaynoticetherearemanycommandswecoulduse,sowemustbecarefulwhichcommandisselected!

21. OntheListGridcontrol,enterListGridDefaultButtontotheDefaultActionproperty.22. MovingontotheDetailsPanelpatternelement,reselecttheDetailsPanelTabPagecontrolandadda

groupcontrolnamedDetailsTitleGroup.

Page 287: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

23. AddaStringcontrolfortheheadertitles,asshownhere:

Property Value

Name DetailsHeaderTitle

DataSource ConWHSVehicleTable

DataMethod TitleFields

ViewEditMode View

Skip Yes

ThetitleFieldsmethodisasystem-provideddatamethodthatusesthetitlefieldpropertiesfromthetable.Wecanwriteourown,shouldweprefer.

24. Checkingourprogressagainstthepatternpane,wewillseethatweneedtoaddtheDetailsTab(Tab)patternelement.AddanewTabcontroltotheDetailPanelTabPagetabpagecontrol,settingthepropertiesasfollows:

Property Value

Name DetailsTab

ArrangeMethod Vertical

Wewillnowcreateatabpagetoorganizethevariousfieldsontotheform,andeachtabpagecanbealistoffieldsorsomethingmoreelaborate.TheVendTableformisagoodexampleofamorecomplicatedform.

25. AddanewtabpagenameDetailsTabGeneralandapplytheFieldsandFieldGroupspattern.

ThefirsttabpageiscustomarilyatabpagelabelledGeneral.

26. Inthiscase,wecansimplydragtheDetailsfieldgroupfromtheConWHSVehicleTabledatasource,butfeelfreetoreorganizethefieldsintomoreappropriategroups.

Page 288: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Shouldnewfieldgroups(orfields)beadded,youcanrefreshthedatasourcebyright-clickingonthedatasourceandchoosingRestore.

27. Wewillneedtocreatethemenuitemusingthesamelabelasthetable's,butwewillneedtodefaultFormViewOptiontoGridsowecangetthelistviewwhentheformisopened.

28. Next,completetheFormRefpropertyoftheConWHVehicleTabletable.

29. Finally,addthemenuitemtoourmenu.

Page 289: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 290: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Theconceptisthesameasforanyform,wejusthavemorefeatures.Thepeculiarpartofthisformisthatwehavetwoviews--oneforthelistviewandtheothertoeditorviewthedetailsoftheform.

Thisisdonebyshowingandhidingthedetailandlisttabs.Itknowswhichcontroltoshoworhide,becausewefollowedthepattern--thisisoneofthereasonswhyapatternconformationerrorwillresultinthecompilation,shouldwetryandbuildtheproject.

TotestthisonthedevelopmentVMprovidedbyMicrosoft,buildtheprojectandusethefollowingURL:

https://usnconeboxax1aos.cloud.onebox.dynamics.com/?cmp=usmf&mi=ConWHSVehicleTable

Youcanusethispatterntoopenanydisplaymenuitem.

Page 291: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 292: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Creatingadetailstransaction(orderentry)form

Theseworksheetformsarethemostcomplicatedintermsofthestepsrequired,aswenowhavethreestatestodesign:list,header,andlinesviews.Tofamiliarizeyourselfwiththeendresult,openandusetheAllpurchaseordersformfromAccountsPayable|Purchaseorders|Allpurchaseorders.

ThefirstpartofthepatternisverysimilartotheDetailsMasterpattern,sowewillsummarizeslightly.Wewillcontinuethevehicleserviceordertable,butagain,therecipeiswrittensothatitcanbeappliedtoanyworksheettable.

Page 293: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 294: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreatetheform,followthesesteps:

1. Choosetoaddanewitemtotheproject.2. SelectUserInterfacefromtheleft-handpaneandFormfromtheright-handpane.3. Nametheformasperthetable'sname;inthiscase,ConWHSVehicleServiceTable.4. DragtheConWHSVehicleServiceTabletablefromtheprojectontotheDataSourcesnodeoftheform

designer.5. SelecttheConWHSVehicleServiceTabledatasourceandsettheInsertIfEmptyandInsertAtEndproperties

toNo.6. DragtheConWHSVehicleServiceLinetabletotheDataSourcesnode.7. SelecttheConWHSVehicleServiceLinedatasourceandsettheJoinSourcepropertyto

ConWHSVehicleServiceTable.

Wedon'tspecifyandjoininformationbeyondthename,asitwillusetheforeignkeyrelationdefinedinthechildtable.

8. OverridetheinitValuemethodonConWHSVehicleServiceLinesothatwecansettheServiceIdfieldasthisisnotsetforus.Usethefollowingcode:

publicvoidinitValue()

{

super();

ConWHSVehicleServiceLine.ServiceId=

ConWHSVehicleServiceTable.ServiceId;

}

9. Closethecodeeditorandgobacktotheformdesignedtab.10. Setthepropertiesasfollows:

Property Value

Caption Usethelabelfromthetable

DataSource ConWHSVehicleServiceTable

TitleDataSource ConWHSVehicleServiceTable

Thisissothattheheaderwillusethecurrentlinefortheheadersection.Thisisn'tmandatory,andisdoneheretodemonstratethatwehaveachoice.

Page 295: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

11. ApplytheDetailsTransactionpatterntotheDesignnode.12. AddanActionPanecontrolnamedHeaderActionPaneandthenaGroupcontrolcalledNavigationListGroup

undertheDesignnode.

WewillhavetwoActionPanes,onefortheheaderandoneforthelines.

13. AddaQuickFiltercontroltotheNavigationListGroupcontrolnamedNavgationQuickFilter.14. Then,createaGridcontrolnamedNavigationGrid.15. SettheDataSourcepropertyofthegridcontroltoConWHSVehicleServiceTableandaddtheServiceIdand

DescriptionfieldsfromtheConWHSVehicleServiceTabledatasource.16. CompletethequickfiltercontrolwithTargetControlasthegridcontrolandDefaultColumnas

desired.17. UndertheDesignnode,createaTabcontrolforPanelTab.CreateanewTabcontrolandnameit

MainTab.SetArrangePropertytoVertical.18. AddatabpagenamedDetailsPanelTabPagefirst,andthenGridPanelTabPage.

ThenamesdifferfromtheDetailsMasterpattern.WewillnameourcontrolsafterthetextcontroldescriptionsinthePatternpane.

19. WewillcompletetheGridPanelTabPagecontrolfirst.Underthiscontrol,addaGroupcontrolnamedGridPanelQuickFilterGroupandaGridcontrolnamedListGrid.

20. Completethegridcontrol'sDataSourcepropertyandusetheOverviewfieldgroupfortheDataGroupproperty.

21. ForGridPanelQuickFilterGroup,applytheCustomandQuickFilterspatterntothecontrol.ThenaddaQuickFiltercontrol,andnameitListQuickFilter.CompletethecontrolbyreferencingittothegridcontrolwenamedListGrid.

22. ThepatternnowstatesweneedMainGridDefaultAction,whichisaCommandButtoncontrol.AddCommandButtontoGridPanelTabPagecalledMainGridDefaultAction.

23. SettheCommandpropertytoDetailsView.

Youmaynoticethattherearemanycommands,andwemustbecarefulwhichcommandisselected!

24. OntheMainGridcontrol,enterMainGridDefaultActiontotheDefaultActionproperty.25. TocompletetheDetailsPanel(TabPage)patternelement,reselecttheDetailsPanelTabPagecontroland

addagroupcontrolnamedDetailsPanelTitleGroup.26. AddaStringcontrol,settingthepropertiesasfollows:

Property Value

Page 296: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Name DetailsPanelHeaderTitle

DataSource ConWHSVehicleServiceLine

DataMethod titleFields

ViewEditMode View

Skip Yes

Optionally,createagroupundertheDetailsPanelTitleGroupcontrolcalledDetailsPanelStatusGroup,andthendragtheServiceStatusfieldfromtheConWHSVehicleServiceTabletable.RenamethefieldcontroltoDetailsPanelTitle_ServiceStatus.

27. WewillnowneedtocompletetheHeaderandLinePanels(Tab)patternelement.AddanewTabcontroltoDetailPanelTabPage,asfollows:

Property Value

Name HeaderAndLinePanelTab

ArrangeMethod Vertical

28. AddanewtabpageforLinesPanelusingthefollowingproperties:

Property Value

Name LinesPanelTabPage

Style DetailsFormDetails

TheStylepropertyaddsmoreinformationtothepatterninordertocontrolpresentationandbehavior.

29. Addasecondtabpagecontrol,shownasfollows:HeaderPanelTabPage.

Page 297: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Property Value

Name HeaderPanelTabPage

Style DetailsFormDetails

30. AddanewTabcontroltotheLinePanelTabPagecontrolnamedLineViewTab.31. Wewillnowaddthreetabpagestothiscontrol.32. AddtheLineViewHeaderDetails(TabPage)patternelementusingthefollowingproperties:

Property Value

Name LineViewHeaderTabPage

Label Vehicleserviceorderheader

Pattern ApplytheFieldandFieldGroupspattern

33. AddtheLineViewLines(TabPage)patternelementusingthefollowingproperties:

Property Value

Name LineViewLines

Label @SYS9664

34. AddtheLineViewLineDetails(TabPage)patternelementusingthefollowingproperties:

Property Value

Name LineViewLineDetailsTabPage

Page 298: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Label VehicleServiceorderlines

DataSource ConWHSVehicleServiceLine

35. AddanActionPanecontroltotheLineViewLinesTabPagecontrolnamedLinesActionPane,andunderthat,anActionPaneTabcontrolnamedLineActionRecordActions.

36. Right-clickonthenewLineActionRecordActionscontrolandselecttheNewButtonGroupcontrolunderthisnamedLineActionRecordActionsGroup.

Forthisactionpane,wewillneedtoaddbuttonstoaddandremovelines.TheseareonlyaddedautomaticallyinthemainheaderActionPanecontrol.

37. UndertheLineActionRecordActionsGroupbuttongroup,addaCommandbuttonnamedLineActionAddLine.38. Thisistoallowtheusertoaddlinestothelinesgrid.Setthefollowingproperties:

Property Value Description

NormalImage Delete Thisaddsawastebinsymbol

Label @SYS135131 Remove

Command New Thistriggersthenewrecordtaskforthedatasource

39. Then,addasecondCommandbuttonnamedLineActionRemove,settingthepropertiesasfollows:

Property Value Description

NormalImage Add Thisaddsasimpleplussymbol

Label @SYS319116 Addline

Command DeleteRecord Thistriggersthedeleterecordtask

SaveRecord No Wewanttoallowrecordsthatarenotyetsavedtobedeleted

Page 299: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

40. Underthisbuttongroup,wewillneedagridforthelines.AddanewGridcontrol,settingthefollowingproperties:

Property Value

Name LinesGrid

DataSource ConWHSVehicleServiceLine

DataGroup Overview

Asweusuallyhavemanymorefieldsthatcanrealisticallyfitinthegrid,wehaveatabcontrolthatallowstheusertoseethesefieldsgroupedinalogicalorderusingtabpages.

41. CreateaTabcontrolunderLineViewLineDetailsTabPageandsetthefollowingproperties:

Property Value

Name LineViewDetailsTab

Label @SYS23823

DataSource ConWHSVehicleServiceLine

ArrangeMethod Vertical

42. Wewouldaddoneormoretabpagesbut,inourcase,wewillonlyneedone.43. AddaTabPagecontrolnamedLineViewDetailsTabDetailsandsettheLabelpropertyto@ConWHS:Details.44. DragIdentificationandDetailsfromtheConWHSVehicleServiceLinesdatasource.45. Wewillnowneedtocompletetheheaderviewoftheform,whichisgovernedbytheHeaderPanel

patternelement.Right-clickonHeaderPanelTabPageandchooseNew|Tab,andcompleteasfollows:

Property Value

Page 300: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Name HeaderDetailsTab

ArrangeMethod Vertical

DataSource ConWHSVehicleServiceTable

46. Wewouldusuallyhavemanyfieldgroupstoaddbut,inthiscase,wewilljustneedone.CreateanewTabPagecontrolnamedHeaderDetailsTabDetails.SettheCaptionpropertyto@ConWHS:Details.

47. DragtheDetailsandServiceDatesfieldgroupsfromtheConWHSVehicleServiceTabledatasource,butrenamethem,prefixedwithHeaderDetailsTabDetails.

48. Wewillneedtocreatethemenuitemusingthesamelabelasthetable's,butwewillneedtodefaultFormViewOptiontoGridsowecangetthelistviewwhentheformisopened.

49. Then,completetheFormRefpropertyoftheConWHVehicleServiceTabletable.50. Finally,addthemenuitemtoourmenu.

Page 301: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 302: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

Theprocess,albeitextended,isthesameastheDetailsMasterpattern.Therearesomeadditionalpropertiestosetinthiscasetohelpwiththeform'sbehavior.Therearealotofsteps,anditiseasytogetlostandpotentiallysetthewrongproperty.Thisiswhywenamethecontrolsafterthepatternelementname.

Thisformpatternwasdeliberatelyassimpleasitcanbe,andoncewearecomfortablewiththeprocess,itshouldbestraightforwardtoexpandthistomorecomplicateddatastructures.

Page 303: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 304: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Creatingformparts

Formpartsareusedfortwopurposes.Oneistoprovideapopupformasyouhoveroveraforeignkey,suchasthepopupwhenyouhovertheProductnumberwhenenteringsalesorpurchaseorders.Thisisgreatforusers,asitmeanstheydon'tnecessarilyhavetonavigateawayfromthetasktheyareperforming.

Anotheruseistocreateareusedformpartthatwecanplacewithinotherforms.Wecouldhaveaformpartthatcontainsproductinformation,whichwecouldaddtotheproductandsalesorderforms.Wecanspecifyalinkwhenweaddtheformpart,makingthemeasytoimplement.

Page 305: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 306: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyWewillcreateasimpleformpart,whichisalistofvehicleserviceorders,sowewillonlyneedatablecreatedfromwhichthedatawillbedisplayed.

Page 307: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 308: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreateaformpartforopenvehicleserviceorders,followthesesteps:

1. CreateanewformcalledConWHSVehicleServiceOpenPart.2. DragtheConWHSVehicleServiceTabletabletotheDataSourcesnode.3. Setthepropertiesofthedatasourceasfollows:

Property Value

AllowEdit No

AllowCreate No

AllowDelete No

InsertIfEmpty No

InsertAtEnd No

4. SetthepropertiesontheDesignnodeasfollows:

Property Value

DataSource ConWHSVehicleServiceTable

ShowDeleteButton No

ShowNewButton No

5. ApplytheFormPartSectionListpattern.

YoucanuseanyofthepatternsstartingwithFormPart,eachofwhichisforadifferentstyle;forinstance,FormPartFactboxCardisusefultoapplytothePreview

Page 309: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Partpropertyofatableinordershowanicepopupwhentheuserhoversoveraforeignkey.

6. AddanewgroupcontrolnamedHeaderGroupandapplytheFiltersandToolbar-inlinepattern.7. AddagrouptothiscalledFilterGroupandaddaQuickFiltercontroltothis.8. ReselecttheHeaderGroupcontrolandaddanActionPanecontrolcalledToolbarActionPane.9. AddaButtonGroupcontrolnamedToolbarButtonGroup.10. DragtheConWHSVehicleServiceTableDisplayMenuItemontothebuttongroupandsetthepropertiesas

follows:

Property Value

Name ActionNew

Label @SYS2055(New)

NormalImage New

FormViewOption Details

OpenMode Edit

11. Dragthemenuitemasecondtimebut,thistime,setthepropertiesasfollows:

Property Value

Name ActionDetails

Label @ConWHS:Details

NormalImage Details

NeedsRecord Yes

Page 310: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

SaveRecord No

CopyCallerQuery Yes

FormViewOption Details

OpenMode New

12. CreateanewgridcontrolundertheDesignnodenamedContentGrid.13. SettheDataSourcepropertytoConWHSVehicleServiceTableanddragthefields,asdesired,fromthedata

sourcetothegrid.

Youcouldconsidercreatingafieldgroupforthispurpose.Thefieldsonaformpartwillbelessthanthelistviewofthedetailsformastheyshouldonlyoccupyathirdofthewidthofthescreen.

14. SettheDefaultActionpropertyofthegridtoActionEdit.15. Createamenuitemusingthesamenameastheformpart,thatis,ConWHSVehicleServiceOpenPart.

Page 311: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 312: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...FormPartsarejustforms,butthepatternforcesustodesigninaccordancewithhowtheformpartwillbeused;whichiswhytherearefourtypesofformpartpatterns.

Wewillusetheprecedingpartwhencreatingtheworkspaceformlater.FormPartFactBoxCardisalsoveryusefulandeasytocreate.Justusethepatterntocreatetheformpart,createamenuitemfromit,andcompletethetable'sPreviewPartRefproperty.

Later,wewillusequeriesandcompositesubqueriestoapplyfilterstoforms.Wecanusethetechniqueofspecifyingaquery,oroneofthequery'scompositequeries,toamenuitem.Thismeansthatwecanhaveoneformpart,butthemenuitemcanbeusedtoapplyafiltertotheform.

Page 313: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 314: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatetileswithcountersfortheworkspaceTilesareusedasanentrypointtoaformwhilsthavingtheabilitytoshowinformationaboutthedata.Itcanactasaprompttoactionandispresentedonatilesectionofaworkspace,asshowninthefollowingscreenshot:

ThepagethatopenswhenyoufirstsignintoOperationsisalsoalistofTiles,andeachopensaworkspace.Theworkspacewillhaveanimageresourcethatwewoulddesign;akeyguidelineisthatallgraphicsareminimalistic.Allsolutionsshouldlookandfeelasiftheywerepartofthestandardsolution.

Page 315: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 316: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Gettingready

Thisrecipecanbeusedtocreateatileforanyform,andisusuallydoneforDetailsMasterandDetailsTransactiontoshowbothallrecordsandcommonlyusedsubsetsofdata.

Inthiscase,wewillcreateatileforallvehicles,andthenatileforatypeofvehicle.

Page 317: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 318: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreateatile,followthesesteps:

1. Wewillfirstcreateaquerythatthetilewilluse.CreateanewoperationsartifactandselectQueryfromtheDataModelartifacts.

2. SetNametoConWHSVehicleTableAllandpressAdd.3. DragtheConWHSVehicleTabletabletotheDataSourcesnode.4. ChangetheDynamicsFieldspropertytoYes.

Thiswillensurethatthequeryalwaysreferencesallthefieldsinthetable,whichisimportantwhenitwillbeusedwithaform.

5. Saveandclosethequery.6. ChoosetoaddanewOperationsArtifactandselectTilefromtheUserInterfacenode.7. ChangeNametoConWHSVehiclesAllTileandpressAdd.8. Setthefollowingproperties:

Property Value

Query ConWHSVehicleTableAll

Label Allvehicles

MenuItemName ConWHSVehicleTable

NormalImage GenericDocument

CopyCallerQuery Yes

Next,wewillcreateatileforbikes.Wewillcreateacompositequery,whichwillallowustouseabasequeryandaddrangestoit.

9. Createanewartifact,andchooseCompositeQueryfromtheDataModelnode.10. SetNametoConWHSVehicleTableBikesandpressAdd.11. IntheQueryproperty,enterConWHSVehicleTableAll.12. Right-clickontheRangesnodeandselectNewCompositeQueryRange.13. IntheDataSourceproperty,selectConWHSVehicleTable.

Page 319: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

14. SettheFieldpropertytoVehicleType.15. Wewillenterthecriteriabyenteringtheenum'ssymbolintheValueproperty;so,enterBikeinthe

Valueproperty.

Youmayalsoenterthenumericvalue,butthiscanonlybeusedfornon-extensibleenums.

16. CreateanewtilecalledConWHSVehicleBikesTile.17. CompletethepropertiesoftheTileasfollows:

Property Value

Query ConWHSVehicleTableBikes

Label Bikes

MenuItemName ConWHSVehicleTable

NormalImage <Blank>

CopyCallerQuery Yes

Type Count

18. Saveandclosealldesignertabs.

Page 320: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 321: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Tileshavetwobasicproperties,thesourcequeryandthetargetform'smenuitem.Whenthetileisclicked,thequeryisappliedtothetargetform,filteringthedata.TheCopyQueryCallerpropertyisimportanthere,andshouldthefilternotappeartowork,itisnearlyalwaysbecausethispropertywasnotset.

Thistechniqueofapplyingqueriescanalsobeappliedtomenuitems,andwecancreatemenuitemsthatwillopenthevehicletablefilterbasedonthequery.ThisisdonebyspecifyingtheQueryandCopyCallerQuerypropertiesonaduplicateofthemenuitems.

Whenexperimentingwithfilters,don'tfilterbasedondatathattheusercanchange;atileforaparticularvehiclegroupwouldbeaverygoodexampleofabadidea.

Page 322: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 323: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...Agoodtileisatilethattakesyoutoserviceordersduetomorrow,sowewillneedtofilterbasedonafunction.

Inthequeryrange'sValueproperty,wecanalsoenterqueryfunctions.ThestandardfunctionsaredefinedintheSysQueryRangeUtilclass.So,tofilterontoday'sorders,youwillenter(currentDate())inthequeryrange'sValueproperty.

WeusedtomodifythisclasstoaddnewfunctionsinDynamicsAX2012,butsinceoverlayeringisdiscouraged,wehaveabetterwaytodothisinOperations.

CreateanewclassandcallitConWHSQueryRangeUtil.Thenameisnotimportant.Tocreateamethodthatreturnsadateanumberofdaysfromtoday,usethefollowingcode:[QueryRangeFunction]publicstaticdateRelativeDate(intrelativeDays=0){utcdatetimecurrentDateTime;

currentDateTime=DateTimeUtil::applyTimeZoneOffset(DateTimeUtil::getSystemDateTime(),DateTimeUtil::getUserPreferredTimeZone());

returnDateTimeUtil::date(DateTimeUtil::addDays(currentDateTime,relativeDays));}

Tousetheprecedingfunctiontoreturntomorrow'sdate,addanewrangefortherequireddatefieldandenter(ConWHSQueryRangeUtil::RelativeDate(1))intheValueproperty.Tofilterrecordsonorbeforetomorrow,use..(ConWHSQueryRangeUtil::RelativeDate(1)).

Page 324: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 325: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Creatingaworkspace

Theworkspaceprovidesanareaforeverythingauserwillneedforataskorgroupoftasks.Theworkspaceshouldbeabletodisplayallofthekeyinformationwithoutscrolling,andisstructuredasahorizontalspacewiththefollowingsections:

TilesTabbedlistsofkeydataCharts(Optional)PowerBI(Optional)Relatedinformation,forexample,linkstokeyforms

Thedashboardisnormallycreatedoncewehavecompletedmostofthesolution;otherwise,wewillhavenothingtoadd.Thepatterncanbeeasilytransposedtoyourownrequirement.

Page 326: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 327: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreatetheworkspace,followthesesteps:

1. CreateanewformandnameitConWHSVehicleWorkspace.2. ApplytheWorkspaceOperationalpatterntotheDesignnode.

YoucanalsousetheWorkspace,whichprovidesasimplerdesignwherewewillonlyshowtilesandlinks.

3. CompletetheCaptionpropertyasVehiclemanagementworkspace.4. AddaTabcontrolandcallitPanoramaTab,followingtheideathatwewillnamecontrolsalongthe

linesofthepatternelementwearecreating.5. Createanewtabpage,namedPanoramaSectionTiles,andapplytheSectionTilespattern.6. Setthefollowingpropertiesonthenewtabpage:

Property Value

Caption Summary

ExtendedStyle panoramaItem_backgroundNone

7. Forthetabpage,createtheTileButtoncontrolsforeachofourtiles.Usethefollowingpropertiesasaguide:

Property Value

Name ConWHSVehiclesAllTile

Tile ConWHSVehiclesAllTile

CopyCallerQuery Yes

8. AddanewtabpagetothePanoramaTabcontrol,namedPanoramaSectionList,andapplytheSectionTabbedListpattern.

9. ProvideCaption,suchasServiceorders.

Page 328: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

10. AddaTabcontrolnamedTabbedListTab.11. TotheTabbeListTabcontrol,addatabpageandnameitTabbedListPageOrders.12. CompletetheCaptionpropertyasOrders.13. AddanewFormPartcontrol,specifyingthefollowingparameters:

Property Value

Name ConWHSVehicleServiceOpenPart

MenuItemName ConWHSVehicleServiceOpenPart

RunMode Local

14. Right-clickonthePanoramaTabcontrolandselectNewTabPage.15. ApplytheSectionRelatedLinkspattern.

Here,wewilladdlinkstoformsthatareneededfortheworkspacestask;thesecanincludesetforms,andotherlinksthattheuserwilluse.

16. RenamethecontroltoPanoramaSectionLinksandsettheCaptionpropertytoLinks.17. CreateanewgroupandcallitLinksSetupGroup.SettheCaptionpropertytoSetup.18. DragthemenuitemsConWHSParametersandConWHSVehicleGroupstothenewgroup.19. Saveandclosethedesigntabs.20. Createamenuitemfortheform,settingtheLabelpropertytoVehiclemanagementworkspace.21. WewillneedaTiletoaddtothemainnavigationworkspace;createtheTileasfollows:

Property Value

Name ConWHSVehicleWorkspaceTile

MenuItemName ConWHSVehicleWorkspace

NormalImage Workspace_PurchaseReceiptAndFollowup(thisisacheat,moreonresourceslater)

Size Wide

Page 329: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

TileDisplay BackgroundImage

22. LocatethenavpanemenumenuinApplicationExplorer,andright-clickonit.ChooseCreateextension.23. Renameittonavpanemenu.ConWHS.24. Openthemenuextension.Right-clickontherootnodeandchooseNew|Submenu.25. RenamethenewsubmenutoConWHSMenu.26. Right-clickonthesubmenuandchooseNew|MenuElementTile.27. CompletetheTilepropertyasConWHSVehicleWorkspaceTile.28. Saveandcloseallwindowsandperformabuild.

Page 330: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 331: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

Theformdesignhereisjustliketheotherforms,andmuchsimplerthantheDetailsMasterandTransactionpatterns.Thecleverpartofthisisthatwearethinkingtask-based.Wearecreatingaworkspacebasedonwhattheuserdoes.ThetraditionalformdesignfromDynamicsAX2012wastoprovideallthefeaturesthattheusercouldwant,whichresultedinalotoffeaturesthatmostusersneverused.Withworkspaces,theconceptisthatmostoftheworkspaceisusedregularly.Theusershouldn'thavetochangeworkspacetolocateaformforthattask.ItisalsoOKforausertousemorethanoneworkspace.Wewanttoprovideeverythingtheuserneeds,butwithoutclutter.

Page 332: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 333: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...Formoreinspirationonworkspaces,investigatetheReqCreatePlanWorkspaceform.Specifically,lookatthewayithandlesthewaythefiltercontrolsthetiles.

Page 334: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 335: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ApplicationExtensibility,FormCode-Behind,andFrameworksInthischapter,wewillcoverthefollowingrecipes:

CreatingahandlerclassusingtheapplicationextensionfactoryHookingupanumbersequenceCreatingacreatedialogfordetailstransactionformsCreatingaSysOperationprocessAddinganinterfacetoaSysOperationframework

Page 336: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 337: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

IntroductionInthischapter,wewillgetstraightintowritingcode.Therecipeschosenforthischapterarecommontasksthatwillbeusedonmanydevelopmentprojects.

Asweprogressthroughthechapter,referencestocodeplacementismade.Codeplacementiscriticaltoamaintainableandextendablesolution.Wewillseethatcodecanbewrittenontheform,inaclass,orinatable.Theruleofthumbhereisthatwemustplacecodeaslowinthestackaspossible.Ifwewritecodeonaform,thatcodeisonlyavailabletothatformandcannotbereused.Thisisfinewhenwearehidingabutton,butdata(validation,andotherdataspecificcode)logicusuallybelongstoatable.Asthecodeontheformortablegetsmorecomplicated,thecodeshouldbemovedtoaclass.

TheSalesTableformandtableisanexample.InthiscasetableeventsarehandledbytheSalesableTypeandSalesLineTypeclassesandformlogicishandledbytheSalesTableFormclass.Thereasonhereisthattherecanbedifferenttypesofsalesorder,andthebestsolutionwastohaveabaseclassforthebasecodeandaspecializedclasstohandlethedifferentrequirementsofeachordertype.

Page 338: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 339: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingahandlerclassusingtheApplicationExtensionfactoryFormhandlerclasseshavearoleinmorecomplicatedforms,suchasDetailsTransaction(orderentry)forms.Thesearethetwomainreasonswhywewouldconsiderdevelopingaformhandlerclass:

Weintendtocreateaseparateformforthecreationoftherecord,whichiscommononorderentryformsTheuserinterfacelogicisparticularlycomplicated,orvariesbytypeofrecord

Wecanalsohavetablehandlerclasses,forsimilarreasons.Thetablehandlerwillhavecodethatisrecord-specific,whereverthedataispresented.Thecodeintheformhandlerisformspecific.Theplacementofcodeisthereforeimportant.Codethatdetermineswhetherafieldiseditablewillbeinthetablehandlerclass,andtheformhandlerclasswillusethatmethodinordertomakethecontroleditable.

Wemaynotalwayshaveatablehandlerclass,andthesemethodscanbeplaceddirectlyonthetable;althoughwecan'tchangethemethoddeclarationsofpublicmethodsafterrelease(incaseotherpartieshaveusedthem),wecanchangetheinternallogicwithimpunity.

Inthisexample,wewillcontrolwhichfieldsonthevehicleservicetableareenabled,basedontheservicestatus.Itisasimpleexampleinordertodemonstrateapatternthatcanbeusedtoabstractcomplicatedscenarios,simplifyingcodecreation,andmaintenance.Wewillcreateatableandformhandlerclassusingastandardpattern.WewillthencreatethetablehandlerusingtheSysExtensionpattern.Thisallowsthecodetobeextendedbyotherpartieswithoutover-layering.

Page 340: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 341: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyWeshouldhaveatableandformcreatedtotheDesignTransactionpatternforthisrecipe.Inourcase,wewilluseConWHSVehicleServiceTable.

Page 342: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 343: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Let'screatethetablehandlerclassfirst,sincewewillneedthisinordertocompletetheformhandlerclass.TousetheSysExtensionpattern,wewillneedatleasttwoclasses:theattributeclassandtheclassthatwillbeconstructedusingthepattern.

1. Tocreatetheattributeclass,choosetoaddanewitemtotheproject,selecttheCodenode,andthenchooseClassfromtheartifactlist.

2. EnterConWHSVehicleServiceStatusAttributeintheNamefieldandpressAdd.3. Altertheclassdeclarationtobeasfollows:

classConWHSVehicleServiceStatusAttribute

extendsSysAttribute

implementsSysExtensionIAttribute

{

}

4. Wewillneedtostoretheattributewearehandlingasavariableintheclass,sodeclaretheservicestatusasfollows:

ConWHSVehicleServiceStatusstatus;

5. Overridethenewmethodsoitconstructsusingthestatusfield,asshowninthefollowinglinesofcode:

publicvoidnew(ConWHSVehicleServiceStatus_status)

{

status=_status;

}

6. Ifwesavethechangessofar,thecompilerwillcomplainthatcertainmethodsaren'timplemented;thequickwaytosolvethisistoclickinsidetheSysExtensionIAttributetextandpressF12.

7. Thisopensthecodeeditorfortheinterface,copiestheparmCacheKeymethodsanduseSingletonintoyourclass,soitreadsasfollows:

publicstrparmCacheKey()

{

//youcan'tcastanextensibleenumdirectly

//toanint,thefollowingwarningwillbegiven

//bythecompiler:

//Castfromextensibleenum'Extensible

//Enumeration(ConWHSVehicleServiceStatus)'to'int'

//potentiallyharmfulanddeprecated.

returnclassStr(ConWHSVehicleServiceStatusAttribute)

+';'+int2Str(enum2int(status));

}

publicbooleanuseSingleton()

{

returnfalse;

}

Readthemethoddocumentationforthesemethods,withspecificthoughttowhethertheclasswillbeimmutable.Returningfalseissafeasitwon'tconstructfromacachedinstance,butwillhaveanimpactonperformance.Beforemakingthistrue,youmustensurethatitisimmutable.

Page 344: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

8. Next,wewillcreatethetablehandlerclasses.CreateanewclassnamedConWHSVehicleServiceTableType.

Thisnamingconventionisimportantsothatweknowthattheclassisahandlerclassforatableandwhichtableithandles.

9. WewillconstructtheinstancefromConWHSVehicleServiceTableandwill,therefore,storethisasaglobalvariableontheclass;addthisasfollows:

classConWHSVehicleServiceTableType

{

ConWHSVehicleServiceTableserviceTable;

}

10. Inordertoconstructtheclass,wewillneedtobeabletosetthevalueofserviceTableusinganaccessormethod(awaytogetandsetinternalvariables),whichiscalledaParmmethodinOperations:

publicConWHSVehicleServiceTable

ParmConWHSVehicleServiceTable(

ConWHSVehicleServiceTable

_serviceTable=serviceTable)

{

serviceTable=_serviceTable;

returnserviceTable;

}

11. WewillwritetheconstructorsothatitusestheSysExtensionpatterntoreturntheclassbasedonthevalueoftheServiceStatusfield.Thiscouldsimplybeaswitchstatementintheconstructmethod,butthefollowingmethodisnaturallyextensible:

publicstaticConWHSVehicleServiceTableTypeConstruct(

ConWHSVehicleServiceTable_serviceTable)

{

ConWHSVehicleServiceTableTypetableHandler;

tableHandler=

SysExtensionAppClassFactory::getClassFromSysAttribute(

classStr(ConWHSVehicleServiceTableType),

newConWHSVehicleServiceStatusAttribute(

_serviceTable.ServiceStatus));

tableHandler.ParmConWHSVehicleServiceTable(

_serviceTable);

returntableHandler;

}

YoucanalsousethegetClassInstanceListFromSysAttributemethodtocheckiftheattributeisbeinghandled.

12. Wewillnowneedtocreateamethodonthetablethatreturnstheprecedingclass;themethodisalwayscalledtype.OpenthecodeeditorfortheConWHSVehicleServiceTabletableandcreatethefollowingmethod:

publicConWHSVehicleServiceTableTypeType()

{

returnConWHSVehicleServiceTableType::Construct(this);

}

13. Thestandardpatternfortablehandlerclassesisthatalltableeventsaremovedtotheclass.Ensure

Page 345: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

thatallofthefollowingtableeventsareoverriddenontheConWHSVehicleServiceTabletable:insert

update

delete

validateField

validateDelete

validateWrite

14. Tohandletheinsertandupdatemethods,wecanwritetwomethodsthatarecalledeithersideofthesuper()call.CreateplaceholdermethodsforInsert,Delete,andUpdateusingthefollowingpattern:

publicvoidInsertPre()

{

}

publicvoidInsertPost()

{

}

PleaseseetheHowitworks...sectionfordetailsonwhythisdiffersfromothertablehandlers.

15. Fortherest,thequickestwayistocopyalltableeventmethodstotheclassandrefactorthem,asshowninthefollowingpieceofcode:

publicbooleanValidateWrite()

{

booleanret=true;

ret=ret&&this.CheckCanEdit();

//morecheckscanbeaddedusingthesamepattern

returnret;

}

publicvoidModifiedField(FieldId_fieldId)

{

Switch(_fieldId)

{

casefieldNum(

ConWHSVehicleServiceTable,VehicleId):

serviceTable.InitFromConWHSVehicleTable(

ConWHSVehicleTable::Find(

serviceTable.VehicleId));

break;

}

}

publicbooleanValidateDelete()

{

returntrue;

}

publicbooleanCheckCanEdit()

{

If(!this.CanEdit())

{

//Serviceordercannotbechanged.

returncheckFailed("@ConWHS:ConWHS33");

}

returntrue;

}

publicbooleanCanEdit()

{

switch(serviceTable.ServiceStatus)

Page 346: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

{

caseConWHSVehicleServiceStatus::None:

caseConWHSVehicleServiceStatus::Confirmed:

returntrue;

}

returnfalse;

}

DonotbetemptedtocallserviceTable.validateWrite()intheInsertorUpdatemethods,orvalidateDelete()intheDeletemethod.Theform'sFormDataSourceobjectmustdothis.

WewillnowcompletethechangesrequiredtoConWHSVehicleServiceTable.

1. RemovetheCheckCanEditandCanEditmethodsfromthetable;CanorMaymethodsshouldalwaysbeonthehandlerclassifoneexists.

2. Alterthetable'seventmethodssothattheyusethehandlerclassinstead;toinsert,update,anddelete,usethefollowingpattern:

publicvoidinsert()

{

this.type().InsertPre();

super()

this.type().InsertPost();

}

Whenoverridinginsert,delete,orupdateconsiderperformance,thiswillforcetheoperationtobedoneoneatatime,andnotasaset.Sodelete_from,update_recordsetandinsert_recordsetcommandswillnolongerbeasetbasedoperation,significantlyaffectingperformance.Also,ifyouremovesuper()fromthecall,thetableeventdelegateswillnotfire.

3. Forthevalidateandmodifiedmethods,usethefollowingpieceofcode:

publicbooleanvalidateField(FieldId_fieldIdToCheck)

{

booleanret=super(_fieldIdToCheck);

if(ret)

{

ret=ret&&

this.type().ValidateField(_fieldIdToCheck);

}

returnret;

}

publicbooleanvalidateWrite()

{

booleanret=super();

if(ret)

{

ret=this.type().ValidateWrite();

}

returnret;

}

publicbooleanvalidateDelete()

{

booleanret=super();

if(ret)

{

ret=this.type().ValidateDelete();

}

returnret;

}

Page 347: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

publicvoidmodifiedField(FieldId_fieldId)

{

super(_fieldId);

this.type().modifiedField(_fieldId);

}

4. WenowneedtocontrolwhetherornottheVehicleId,ServiceDateRequested,andServiceDateConfirmedfieldscanbeedited.CreatethreemethodsontheConWHSVehicleServiceTableTypeclass,asshownhere:

publicbooleanCanEditVehicleId()

{

returntrue;

}

publicbooleanCanEditServiceDateRequested()

{

returntrue;

}

publicbooleanCanEditServiceDateConfirmed()

{

returntrue;

}

5. Eachofthechildclasseswilloverridetheprecedingmethods,returnthecorrectstatusforthestatus,andcreatefourclasses,whicharenamedasfollows:

ConWHSVehicleServiceTableTypeNone

ConWHSVehicleServiceTableTypeConfirmed

ConWHSVehicleServiceTableTypeCompleted

ConWHSVehicleServiceTableTypeCanceled

6. Foreachclass,addextendsConWHSVehicleServiceTableTypetotheclassdeclaration:

ClassConWHSVehicleServiceTableTypeNone

extendsConWHSVehicleServiceTableType

7. OverridetheCanEditmethodsineachclasssothatthefollowingresultwillbeachieved:

Status/action None Confirmed Completed Canceled

CanEdit True True True False

CanEditVehicleId True True False False

CanEditServiceDateRequested True False False False

CanEditServiceDateConfirmed True False False False

8. Repeatthispatternforeachoftheotherthreeclasses.

YoumayalsonoticethatthecodeinConWHSVehicleServiceTableType.CanEdit()isnow

Page 348: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

redundant,andweshouldjustreturntrue(orfalse,asdesired).Itmayseemthatthecurrentcodeisn'tdoinganyeffectiveharm,butitlookslikeitisdoingsomething,anditshouldbechanged.

9. ThefinalstepistodecoratethefourclasseswiththeConWHSVehicleServiceStatusAttributeattributeclass,butwewillneedtobuildtheprojectfirst.Ctrl+Shift+Bmaysufficeinthiscase,ifwehavebuiltrecently.Also,inordertoallowintellitypetoworkforthenewattribute,restartVisualStudio.

10. Addthefollowingline,changingtheenumvalueasappropriate,totheverytopofeachofourfourclasses:

[ConWHSVehicleServiceStatusAttribute(

ConWHSVehicleServiceStatus::Cancelled)]

Thiscompletesthetablehandlerfornow,sowecanmoveontotheformhandlerclass:

1. CreateanewclassnamedConWHSVehicleServiceTableForm.

Again,thenamingconventionisimportantsothatweknowthattheclassisahandlerclassforaformandwhichformisbeinghandled.

2. Thisisthebaseclassandwillcontaincodecommontoallclassesthatextendit,whichwewillconstructfromaFormDataSourceobjectthattheformwillhaveconstructedautomatically(inourcase,ConWHSVehicleServiceTable_DS).Wewill,therefore,needtostorethisasavariableglobaltothecall,asshownhere:

classConWHSVehicleServiceTableForm

{

protectedFormDataSourceserviceTableDS;

}

Wearedeclaringthisasprotectedtoshowthatwearedeliberatelymakingthevariableavailabletothisclassandallclassesthatextendit.

3. Wewillalsoneedawaytosetthismethod,whichcouldbedonedirectlyifitwasmadepublic;so,wewillneedtoprovideanaccessormethod.InOperations,thesearecalledtheparmmethodsandarecreatedasfollows:

publicFormDataSourceParmServiceTableDS(

FormDataSource

_serviceTableDS=serviceTableDS)

{

serviceTableDS=_serviceTableDS;

returnserviceTableDS;

}

Inthiscase,thereislittledifferencebetweenmakingthevariablepublicandusingitasapropertydirectly.However,oncewemakesomethingpublic,wemaynotbeabletoundoit,especiallyifitisusedinotherpackagesorbyotherparties.

4. Althoughwewon'tusethismethoduntillater,weshouldwriteitnow.Oneofthemainreasonstowriteaformhandlerclassistohandletheinteractionbetweenacreatedialogandthemaindetails

Page 349: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

form.Thisisdonebyaddinganaccessormethodtothehandlerclasssothatthenewrecordcanbepassedbetweenthedialogandthedetailsform.DefinethevariableandParmmethods,asshownhere:

publicConWHSVehicleServiceTableParmServiceTableCreated(

ConWHSVehicleServiceTable

_serviceTable=serviceTableCreated)

{

serviceTableCreated=_serviceTable;

returnserviceTableCreated;

}

YoumaynoticethatthismethodiswritteninotherpartsofOperationswithouttheCreatedsuffix,butthiscanleadtoconfusionandthevariablecanbeusedforthewrongpurpose.

5. Next,wecanwriteourconstructor,whichwillbeasfollows:

publicstaticConWHSVehicleServiceTableForm

NewFromDataSource(FormDataSource_serviceTableDataSource)

{

//Checkfirstthatthetablethatthe

//datasourceisboundtoissupported

if(_serviceTableDataSource.table()

!=tableNum(ConWHSVehicleServiceTable))

{

//Table%1isnotsupported

throwerror(strFmt("@SYS31187",

tableId2Name(_serviceTableDataSource.table())));

}

//nopointconstructingtheclass,beforethispoint.

ConWHSVehicleServiceTableFormform

=newConWHSVehicleServiceTableForm();

form.ParmServiceTableDS(_serviceTableDataSource);

returnform;

}

Thisisaprettystraightforwardconstructor,butyoumaywonderwhyweconstructfromthedatasourceandnotthetable.Thereasonisthatthedatasourceispassedbyreference,sowecanalwaysgetthecurrentrecordusingthecursor()method.

6. Tosimplifytheclass'usage,writethefollowingmethod:

publicConWHSVehicleServiceTablecurrentRecord()

{

returnserviceTableDS.cursor()

asConWHSVehicleServiceTable;

}

Thecursor()methodreturnstherecordastheglobalbasetypeforalltables,whichisCommon.Althoughwedon'tneedtouseASinthiscase,usingtheASperformsthecastatthatpoint,andisagoodhabitwhenusinggenerictypessuchasCommonandObject.

7. Ourrequirementisthatwecontrolwhethercertainfieldsareeditablebasedonthecurrentstatus.ThestandardmethodtodothisistocreateamethodcalledEnableFields.Tocontrolourthreefields,writethefollowingcode:

publicvoidEnableFields()

{

ConWHSVehicleServiceTableserviceTable;

booleancanEdit;

serviceTable=this.currentRecord()

Page 350: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

canEdit=serviceTable.type().CanEdit()

serviceTableDS.allowEdit(canEdit);

serviceTableDS.allowDelete(canEdit);

//iftherecordcan'tbeedited,nopoint

//checkingeachfield.

if(canEdit)

{

RefFieldIdfieldId;

booleancanEditField;

FormDataObjectdsField;

//thisiswrittenthiswaytomakethecodeeasier

//toreadbymakingeachlineappearononeline

ConWHSVehicleServiceTableTypehndlr;

hndlr=serviceTable.type();

fieldId=fieldNum(ConWHSVehicleServiceTable,

VehicleId);

canEditField=hndlr.CanEditVehicleId();

dsField=serviceTableDS.object(fieldId);

dsField.allowEdit(canEditField);

fieldId=fieldNum(ConWHSVehicleServiceTable,

ServiceDateRequested);

canEditField=hndlr.CanEditServiceDateRequested();

dsField=serviceTableDS.object(fieldId);

dsField.allowEdit(canEditField);

fieldId=fieldNum(ConWHSVehicleServiceTable,

ServiceDateConfirmed);

canEditField=hndlr.CanEditServiceDateConfirmed();

dsField=serviceTableDS.object(fieldId);

dsField.allowEdit(canEditField);

}

}

Eachprecedingblockwouldnormallybewrittenasoneline,butthisbecameunreadableasitwrapped.Onelineperblockispreferableasitmakesiteasiertoreadandlesspronetocopyandpasteerrors.Forexample,serviceTableDS.object(fieldNum(ConWHSVehicleServiceTable,

VehicleId)).allowEdit(hndlr.CanEditVehicleId());

8. Theactivemethodonthedatasourceistriggeredwheneveranewrecordbecomesactive,soweshouldhookintothismethod.Wewillfirstcreateamethodtohandlethisinourformhandlerclassandwritethemethodasfollows:

publicvoidHandleActivePost()

{

this.EnableFields();

}

9. Next,opentheConWHSVehicleServiceTableforminthedesigner.10. Right-clickontheMethodsnodeandchooseOverride|Init.11. Wewillneedtodotwothings:declareanobjectoftypeConWHSVehicleServiceTableFormandthenconstruct

it.Wewillconstructtheformhandlerafterthesuper()call,becausetheConWHSVehicleServiceTable_DSobjectisconstructedbythecalltosuper().Writethecodeasfollows:

ConWHSVehicleServiceTableFormformHandler;

publicvoidinit()

{

super();

formHandler=

ConWHSVehicleServiceTableForm::NewFromDataSource(

ConWHSVehicleServiceTable_DS);

}

Page 351: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

12. Finally,tohookuptheHandleActivePostmethod,right-clickontheMethodsnodeoftheConWHSVehicleServiceTabledatasourceandchooseOverride|active.

13. Alterthemethodsothatitreadsasfollows:

publicintactive()

{

intret=super();

formHandler.HandleActivePost();

returnret;

}

14. Saveandcloseallwindows,andbuildthepackage.15. OpentheD365Oinyourbrowser,whichishttps://usnconeboxax1aos.cloud.onebox.dynamics.com/?cmp=usmfon

thestandarddevelopmentVM,atthetimeofpublication.16. Navigatetotheserviceorderform,creatingaserviceorderifnecessary.17. ClosetheformanduseSQLServerManagementStudiotoeditServiceStatustodifferentvaluesinthe

ConWHSVehicleServiceTabletable.Eachtimeyouopentheform,checkthatthefieldsbehavecorrectlyforeachchangeinstatus.

Page 352: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 353: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Therewerealotofnewconceptsinthisrecipe.Thefirstwastheuseofthetablehandlerclass,forgettingtheattribute-basedconstructorfornow.

Thetablehandlerisusefulwhenthelogicgetsverycomplicatedandwouldbenefitfrombeingabstractedintoaclassstructure.

IfyoucomparetheInsert,Update,andDeletemethodstothewaythiswasdoneinSalesTableandSalesTableType,youwillseeakeydifference.InSalesTable,thecalltosuper()wasremovedandthehandlerclasscallstheequivalentdomethoddirectly.SincethestandardInsert,Update,andDeletemethodssimplycalltheequivalentdomethodanyway,thismayseemfine.

Thereisadrawback,whichdetractsfromtheaimofmakingourcodeextensible.Thisisthatthetableevents,thosethatliveundertheEventsnodeinthetabledesigner,donotfireunlesssuper()iscalled,effectivelydisablingtheeventsforinsert,delete,andupdate.ThisiswhythetableeventOnInsertedonSalesTablewillnotfire.

TheSysExtensionframeworkisgenius,butshouldbeusedwithcare.Byusingthis,wehaveincreasedtheoverheadofwritingrecordstothetable.Thepracticaldifferencemaybesmallinthiscase,butshouldwehaveusedthisontransactionaltableslikeInventTrans,theresultcouldbesignificant.

Theclassisinstantiatedbylookingattheclassesthatextendthebaseclass(asspecifiedinthecalltoSysExtension::getClassFromSysAttribute)forclassesthathavetheappropriateattributedecoration.

Thismeansthatanotherpartycouldextendtheenumandwritetheirownclasstohandleit;noover-layeringisrequired,andwecanshipupdatesregularlywithoutcausingourcustomerunduepainastheyapplythem.

TheformwashookedupusingapatternthatcanbeseenthroughoutOperations,although,thehandlerissometimesconstructedfromthetableandnotthedatasource.Thismeansthatcallstothehandlerclassmustincludethecurrentrecord,whichisnotrequiredinthepatternusedinthisrecipe.

Page 354: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 355: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...Let'ssaywedonotwantadefaultvalueinourbaseclass,andwanttoforcethatanyclassthatextendsthebaseclasstoimplementamethodwewoulduseaninterface.Inourexample,wecouldthrowanerrorshouldabaseclassnotbecaught.Thisisfine,butitwouldbeniceifitwascaughtbythecompiler.

Interfaceshavemanyuses,andsomewillbeexploredfurtherinlaterchapters;however,inthiscase,wewillneedtoensurethatclassesthatinheritfromourbaseclassimplementtherequiredmethods.

Thisisoneofthemanyfeaturesthatinterfacesprovide.Ifwedeclarethataclassimplementsaninterface,itwillnotcompileuntilallmethodsintheinterfaceareimplemented.However,thereisn'tawaytoforceasubclasstoimplementtheinterface.Ifthebaseclassimplementstheinterface,thebaseclassmusthavethemethodsand,byinheritance,thechildclasseswillbeseentoimplementthemethods.

Wecouldmakethebaseclassabstract,butthislimitswhatwecandoaswehavetiedthemtogetherthroughinheritance.Implementinganinterfacemerelyenforcesthattheclassimplementsitsmethods,thereisnoinheritance.

InolderversionsonD365O,thestandardRunBaseabstractclassusedtouseabstractmethods,suchaspackandunpack,toforcethedevelopertoimplementthesemethods,butnowMicrosofthaschangedthissothatitusesaninterfaceinstead.IfyoulookatthecodeinRunBase,itnowimplementsthreeinterfaces,ofwhichoneisSysSaveable,whichextendsSysPackable.ThisforcesanyclassthatimplementsSysSaveabletocreatethepackandunpackmethods.

Thebenefitsofinterfacesgofurther.Althoughwecanneverinstantiateaninterface(itisn'taclass,itisacontract),wecanassignaninstanceofaclassthatimplementsittoit.Thisbringsusbacktoourscenario.Wewanttoenforcethatthesubclassesimplementamethodthatisn'tinthebaseclass;ifitwereinthebaseclass,therewouldbeadefaultvalue.

Interfacesarecreatedjustlikeclasses;justchooseInterfacefromthelist.Inourexample,theinterfacewouldbewrittenasfollows:

interfaceConWHSVehicleServiceTableCheckable

{

publicbooleanCanEdit()

{

}

publicbooleanCanEditVehicleId()

{

}

publicbooleanCanEditServiceDateRequested()

{

}

publicbooleanCanEditServiceDateConfirmed()

{

}

}

Wemustfirstremovethefourmethodsfromthebaseclass,aswedon'twantadefaultinthisscenario.Also,ifthemethodexistsinthebaseclass,thecompilerwillconsiderthattheinterfacemethodsare

Page 356: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

implemented.

Then,foreachofourfourConWHSVehicleServiceTableTypechildclasses,addimplementsConWHSVehicleServiceTableCheckable.Forexample:

classConWHSVehicleServiceTableTypeCancelled

extendsConWHSVehicleServiceTableType

implementsConWHSVehicleServiceTableCheckable

Tousetheinterfaceinstead,changetheConWHSVehicleServiceForm.EnableFieldsmethodsothatthehndlrvariableisdeclaredandinstantiatedasfollows:

ConWHSVehicleServiceTableCheckablehndlr;

hndlr=serviceTable.type()asConWHSVehicleServiceTableCheckable;

Thisshouldbedeclaredatthetop,andusedthroughthemethod.

ThefinalchangeistoConWHSVehicleServiceTable.CheckCanEdit(),whichshouldnowusethenewpattern,forexampleasfollows:

publicbooleanCheckCanEdit()

{

if(!(thisisConWHSVehicleServiceTableCheckable))

{

Returntrue;//orthrowerror!

}

ConWHSVehicleServiceTableCheckablecheckable;

checkable=thisasConWHSVehicleServiceTableCheckable;

if(!checkable.CanEdit())

{

//Serviceordercannotbechanged.

returncheckFailed("@ConWHS:ConWHS33");

}

returntrue;

}

Thiswouldresultinanastyclienterrorshouldthereturnedclassnotimplementtheinterface,whichisn'tdesirable!WecouldusetheDictClassclasstocheckifthereturnedobjectdoesimplementitfirstandgiveanerrorthatwillmakesomesense.Abettersolutionistousetestdrivendevelopmentpractices.Withthismethod,wewillcreateatestcasethattestswhetherthemethodsreturntheexpectedresultforeachpossiblestatus,andanyerrorwouldbeblockedbythebuildserver.ThisiscoveredinChapter11,UnitTesting.

Page 357: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 358: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Seealso...Formoreinformationoninterfaces,checkouthttps://msdn.microsoft.com/en-us/library/aa892319.aspx.

Page 359: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 360: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

HookingupanumbersequenceThenumbersequenceframeworkisusedonmostDetailsMaster(Maintables)andDetailsTransaction(Worksheettables)forms,forexample,thesalesordernumberisgeneratedthroughanumbersequence.Theseusedtobehookeduptotheformdirectly,orintheformhandlerclass.Thismadesensepreviouslyasuserinterfaceevents(newrecord,deleterecord,abandonanewrecord,andsoon)wouldneedtobehandled.Theproblemwiththisisthatifwehavetwoformsthathandlethesametable,wemayneedtowritethecodetwice.

Thenewpatternisthatitishandledonthetableortablehandler,butcalledfromtheformorformhandlerclass.

Wewillfirstneedtocreateaclassthatdefinesournumbersequences,andthenwritethecodetohandlethem.

Page 361: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 362: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyTodothis,weshouldhaveatableandformcomplete,ideallyusingthehandlerclassesforthetableandform.

Page 363: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 364: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Tocreatethenumbersequencedefinitionclass,followthesesteps:

1. First,wewillneedtoaddanelementtotheNumberSeqModulebaseenum;so,locatethisenum,right-clickonit,andchooseCreateextension.

2. RenamethenewbaseenumextensiontoNumberSeqModule.ConWHS.3. Openitinthedesignerandaddanewelement.SettheNamepropertytoConWHSVehicleManagementandthe

LabelpropertytoVehiclemanagement.4. Saveandclosethedesigner,andcreateanewclassname:ConWHSNumberSeqModule.5. ChangethedeclarationsothatitextendsNumberSeqApplicationModuleandoverridesthefollowing

methodssothattheyreadasfollows:

publicNumberSeqModulenumberSeqModule()

{

returnNumberSeqModule::ConWHSVehicleManagement;

}

///<summary>

///Appendsthecurrentclasstothemapthatlinks

///modulestonumbersequencedatatypegenerators.

///</summary>

[SubscribesTo(classstr(NumberSeqGlobal),delegatestr(

NumberSeqGlobal,

buildModulesMapDelegate))]

staticvoidbuildModulesMapSubsciber(Map

numberSeqModuleNamesMap)

{

NumberSeqGlobal::addModuleToMap(

classnum(ConWHSNumberSeqModule),

numberSeqModuleNamesMap);

}

protectedvoidloadModule()

{

}

ThosemigratingfromDynamicsAX2012mayrememberthemanualjobthatmustberuntoinitializethenumbersequence.ThisisnowdoneautomaticallybysubscribingtoNumberSeqGlobal.buildModulesMapDelegate.

6. TocompletetheloadModulemethod,wewilldefineeachnumbersequencefromtheEDT,andisdoneinblocksofcode.ThefollowingcodedefinessequencesforConWHSVehicleIdandConWHSVehicleServiceId:

protectedvoidloadModule()

{

NumberSeqDatatypedatatype;

datatype=NumberSeqDatatype::construct();

//Vehiclenumber

datatype.parmDatatypeId(

extendedtypenum(ConWHSVehicleId));

//Uniquekeyfortheidentificationofvehicles.

//Thekeyisusedwhencreatingnewvehicles

datatype.parmReferenceHelp(

literalstr("@ConWHS:ConWHS42"));

datatype.parmWizardIsContinuous(false);

datatype.parmWizardIsManual(NoYes::No);

datatype.parmWizardIsChangeDownAllowed(NoYes::No);

Page 365: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

datatype.parmWizardIsChangeUpAllowed(NoYes::No);

datatype.parmWizardHighest(999999);

datatype.parmSortField(1);

datatype.addParameterType(

NumberSeqParameterType::DataArea,

true,false);

this.create(datatype);

//Vehicleserviceorder

datatype.parmDatatypeId(

extendedtypenum(ConWHSVehicleServiceId));

//Uniquekeyfortheidentificationofserviceorders.

//Thekeyisusedwhencreatingnewservicesorders

datatype.parmReferenceHelp(

literalstr("@ConWHS:ConWHS43"));

datatype.parmWizardIsContinuous(false);

datatype.parmWizardIsManual(NoYes::No);

datatype.parmWizardIsChangeDownAllowed(NoYes::No);

datatype.parmWizardIsChangeUpAllowed(NoYes::No);

datatype.parmWizardHighest(999999);

datatype.parmSortField(2);

datatype.addParameterType(

NumberSeqParameterType::DataArea,

true,false);

this.create(datatype);

}

Thenextpartistoupdatetheparametersform,sothatwecanmaintainthenewsequence:

1. Wewillnowneedtoupdatetheparametersformsothatwecanmaintainthem.Wecansavetimeherewithsomecopyingandpasting.OpenthedesignforourConWHSVehicleParametersform,andthenthedesignerfortheInventParametersformfromtheApplicationExplorer.

2. IntheformdesignforInventParameters,expandDataSources.3. Right-clickontheNumberSequenceReferencedatasource,andchooseCopy.4. ChangetabtoourConWHSVehicleParametersformdesigner,right-clickontheDataSourcesnode,and

selectPaste.5. Wewillneedtorefactorthecodethatwasbrought,butwewillneedtosetupthenumbersequence

handlingcode.Double-clickontheclassDeclarationnodeoftheMethodsnodeontheform.6. Justafterthefirstbrace,enterthefollowinglines:

BooleanrunExecuteDirect;

NumberSeqReferencenumberSeqReference;

NumberSeqScopescope;

ConWHSNumberSeqAppModulenumberSeqApplicationModule;

TmpIdReftmpIdRef;

containernumberSequenceModules;

7. Wewillnowneedtocreatecodetoinitializethenumbersequenceclass,whichisdonebythefollowingmethod:

privatevoidnumberSeqPreInit()

{

runExecuteDirect=false;

numberSequenceModules=

[NumberSeqModule::ConWHSVehicleManagement];

numberSeqApplicationModule=new

ConWHSNumberSeqAppModule();

scope=NumberSeqScopeFactory::createDataAreaScope();

NumberSeqApplicationModule::createReferencesMulti(

numberSequenceModules,scope);

tmpIdRef.setTmpData(

Page 366: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

NumberSequenceReference::configurationKeyTableMulti(

numberSequenceModules));

}

8. Wewillrequireasecondmethodthatperformssomefurtherinitialization,butrequiresthatthedatasourcebesetup.Thismust,therefore,runafterthesuper()callintheinitmethod.Writethepostinitinitializationcodeasfollows:

privatevoidnumberSeqPostInit()

{

booleansameAsActive=

numberSeqApplicationModule.sameAsActive();

numberSequenceReference_ds.object(

fieldNum(NumberSequenceReference,

AllowSameAs)).visible(sameAsActive);

labelSameAs.visible(sameAsActive);

}

9. Theprecedingtwomethodsshouldbeplacedaboveandbelowthesuper()callintheinitmethod,asdemonstratedinthefollowingpieceofcode:

publicvoidinit()

{

ConWHSVehicleParameters::Find();

NumberSeqApplicationModule::loadAll();

this.numberSeqPreInit();

super();

this.numberSeqPostInit();

}

10. WhenwecopiedthedatasourcefromInventParameters,itbroughtoverthecodeaswell.TheActivemethodwasoverriddeninInventParametersforaspecialcase,anditisnotrequired.DeletetheActivemethod.

11. Let'scheatagainandcopythetabpagefromInventParameters.Select(oropen)theformdesignerforInventParameters.

Normally,Iprefercreatingeverythingmanually,butaddingthenumbersequenceelementstoaformsavesalotoftimeandisrelativelyriskfreefromcopyandpasteerrorsoromissions.

12. Right-clickontheTabcontrolandchooseCopy.13. Next,gobacktoourform'sdesigner,right-clickontheParameterTabcontrolandchoosePaste.14. Expandthecontrols,NumberSeqandNumberSeqBody.Then,deletetheActionPanecontrol.15. WithintheNumberSeqtabpage,expandtheHeadergroupcontrol.16. ChangetheTextpropertyoftheStaticText10controltoSetupnumbersequencesforvehiclemanagement

documents.

17. Forconsistency,andtoavoidpotentialnamingconflicts,renamethefollowingcontrols:

Page 367: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Originalcontrolname Correctcontrolname

Header NumberSeqHeader

StaticText10 NumberSeqHeaderText

GridContainer NumberSeqQuickFilter

Grid NumberSeqGrid

18. Finally,wedon'tneedthetabpagetobeautomaticallydeclaredasaglobalvariable:selecttheNumberSeqtabpagecontrolandchangetheAutoDeclarationpropertytoNo.

19. Saveandclosealldesignersandcodeeditors.20. OpenthecodeeditorfortheConWHSVehicleParameterstable.21. Addanewmethodthatwillreturnthenumbersequencereference,asshowninthefollowing

method:

publicstaticNumberSequenceReferenceNumRefServiceId()

{

returnNumberSeqReference::

findReference(

extendedTypeNum(ConWHSVehicleServiceId));

}

Itisconventiontoplaceastaticmethodpersequenceontheparameterstable,andotherdeveloperswillexpecttofindthesehelperfunctionsthere.

22. Buildtheprojectandlookoutforcompilationerrorsandcorrectasrequired.

Thefinalstageistointegratetheformwiththenumbersequenceframework:

1. OpenthecodeeditorfortheConWHSVehicleServiceTableTypehandlerclass.2. Atthetopofourclass,declareavariableglobaloftypeNumberSeqFormHandler,asshownhere:

NumberSeqFormHandlernumberSeqFormHandler;

3. Next,createamethodtoconstructaninstance,ifitisnotalreadyinstantiated,asperthefollowingcode:

protectedNumberSeqFormHandlernumberSeqFormHandler(

FormRun_formRun,FormDataSource_serviceTableDS)

{

if(!numberSeqFormHandler)

{

Page 368: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

RefRecIdlocalNumSeqId;

RefFieldIdserviceIdField;

localNumSeqId=

ConWHSVehicleParameters::

NumRefServiceId().NumberSequenceId;

serviceIdField=

fieldNum(ConWHSVehicleServiceTable,

ServiceId);

numberSeqFormHandler=

NumberSeqFormHandler::newForm(localNumSeqId,

_formRun,

_serviceTableDS,

serviceIdField);

}

returnnumberSeqFormHandler;

}

4. Next,wewillneedtowritethemethodsthatcontrolwhathappenswhenthevariousdatasourceeventmethodsarerun.Writethemethodsasshownhere:

publicvoidformMethodClose()

{

if(numberSeqFormHandler)

{

numberSeqFormHandler.formMethodClose();

}

}

publicvoidformMethodDataSourceCreate(

FormRun_element,

FormDataSource_serviceTableDS)

{

this.numberSeqFormHandler(

_element,

_serviceTableDS).formMethodDataSourceCreate();

}

publicvoidformMethodDataSourceDelete(

FormRun_element,

FormDataSource_serviceTableDS,

boolean_forced=false)

{

this.numberSeqFormHandler(

_element,

_serviceTableDS).formMethodDataSourceDelete(

_forced);

}

publicvoidformMethodDataSourceLinkActive(

FormRun_element,

FormDataSource_serviceTableDS)

{

this.numberSeqFormHandler(

_element,

_serviceTableDS).formMethodDataSourceLinkActive();

}

publicbooleanformMethodDataSourceValidateWrite(

FormRun_element,

FormDataSource_serviceTableDS)

{

booleanret=true;

if(!this.numberSeqFormHandler(

_element,_serviceTableDS).

formMethodDataSourceValidateWrite())

{

ret=false;

}

returnret;

Page 369: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

}

publicvoidformMethodDataSourceWrite(

FormRun_element,

FormDataSource_serviceTableDS)

{

this.numberSeqFormHandler(

_element,

_serviceTableDS).formMethodDataSourceWrite();

}

Thiscodedoesseemalot,butitislargelythesameinmostimplementationsand,withsomerefactoring,itcansimplybecopied.TheSalesTableTypeclassusesthispattern.

5. ThenexttaskistooverridecertainmethodsontheConWHSVehicleServiceTabletableinordertocallthemethodswehavejustwritten.

Whenreadingthefollowingsteps,itmayseemquickerandeasiertocallConWHSVehicleServiceTable.type().formMethodDataSourceWrite().Thiswouldcarryasignificantperformanceoverhead,asthehandlerwouldbeconstructedwheneveramethodwascalled.

6. CreateaglobalvariabletotheformbyopeningtheclassDeclarationnodeoftheformandtypingthefollowing,justafterthefirstopeningbrace:

ConWHSVehicleServiceTableTypeserviceTableType;

7. Wenowneedtohookupthedatasourceeventmethodstoourhandlerclass.Thenamingschemeforthenumbersequencemethodstellsuswhichtouseoneachdatasourceeventmethod.ThefirstisformMethodClose,sowewillneedtooverridetheClosemethodatformlevel.Doso,andenterthefollowingcode:

publicvoidclose()

{

if(!serviceTableType)

{

serviceTableType=

ConWHSVehicleServiceTable.type();

}

serviceTableType.formMethodClose();

super();

}

8. TherestoverridemethodsontheConWHSVehicleServiceTabledatasource.Tosavetime,overridethefollowingmethods:

CreateDeleteLinkActiveValidateWriteWrite

Page 370: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

9. Thesemethodsshouldbewrittenasfollows:

publicvoidcreate(boolean_append=false)

{

super(_append);

if(!serviceTableType)

{

serviceTableType=

ConWHSVehicleServiceTable.type();

}

serviceTableType.formMethodDataSourceCreate(

element,this);

}

publicvoiddelete()

{

if(!serviceTableType)

{

serviceTableType=

ConWHSVehicleServiceTable.type();

}

serviceTableType.formMethodDataSourceDelete(

element,this);

super();

}

publicvoidlinkActive()

{

if(!serviceTableType)

{

serviceTableType=

ConWHSVehicleServiceTable.type();

}

serviceTableType.formMethodDataSourceLinkActive(

element,this);

super();

}

publicbooleanvalidateWrite()

{

booleanret;

ret=super();

if(!serviceTableType)

{

serviceTableType=

ConWHSVehicleServiceTable.type();

}

ret=ret&&

serviceTableType.formMethodDataSourceValidateWrite(

element,this);

returnret;

}

publicvoidwrite()

{

if(!serviceTableType)

{

serviceTableType=

ConWHSVehicleServiceTable.type();

}

serviceTableType.formMethodDataSourceWrite(

element,this);

super();

}

10. Saveandclosealldesigners,andbuildthepackage.11. Althoughweshouldtestateachstage,thisrequiressettingupanumbersequence.Thefollowing

Page 371: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

stepsarearoughguide,justsowecantesttheform'sbehavior.12. Inthewebclient,opentheVehiclemanagementworkspace,andclickonparameters.

ThisistotriggertheNumberSeqApplicationModule::loadAll()method.

13. Onceopen,checkthatthetworecordsappearinthegridintheNumbersequencestab.

Iftheydonot,weeithermissedaddingthecalltoNumberSeqApplicationModule::loadAll()inourinitmethodorthedelegatesubscriptioninourConWHSNumberSeqModuleclassisincorrect.

14. NavigatetoOrganizationaladministration|Numbersequences|Numbersequences.15. ClickonNew.16. EnterConWHSServintheNumbersequentcodefield(theprefixhelpsgroupsequencetogether)and

VehicleserviceidasName.17. SetScopetoCompany,andenterthecurrentcompanyIDasCompany.18. ConfiguretheSegmentsgridasshowninthefollowingscreenshot:

19. ChangethevalueforLargestto999999intheGeneraltab.20. Thenextpartisthefirsttest;expandReferencesandclickonAdd.21. SelectthemodulefromtheAreatab;inourcase,Vehiclemanagement,andthenServiceId.ClickonOK.22. Next,openVehiclemanagementworkspaceandcreateanewserviceorderrecordtotestthatthevarious

eventsworkcorrectly.

Page 372: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 373: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

Theprocessisdoneinthreeparts:

WritingtheclassthatregistersourEDTinthenumbersequenceframeworkUpdatingtheparameterformtoallowustomaintainthenumbersequencesHookinguptheformsothatitusesthenumbersequence.

Thenumbersequenceframeworkisnotjustawaytogetanewnumberinaprescribedformat.Italsoautomaticallyhandleswhathappensshouldtherecordnotbesaved,andwhatshouldhappenifwedeletearecord.

Page 374: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 375: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

NumbersequencesetupWheneverweneedanumbersequence,wewillalwaysusethenumbersequenceframeworktodeclareandmaintainthenumbersequenceweneed.ThefirstpartwastocreateaclassthatextendstheNumberSeqApplicationModuleclass.Thisallowsustodefinethenumbersequences,andalsosetuptheeventthatwillgeneratethenumbersequencesetupdata.

Thismakesthenumbersequenceavailable,andwecancreatethesequenceusingtheNumberSequenceformfoundinOrganizationaladministration|NumberSequences|NumberSequences.Wecancreateonemanuallyandrefertothesequencedefinedinourclass,orusetheGeneratebutton.

IfwetakealookattheNumberSeqModule::ConWHSVehicleManagementcode,wecanseethattheextensionisseamlesslyappliedtothebasetype,eventhoughtheextensionisnamedNumberSeqModule.ConWHS.Thiswouldonlyworkinthispackage,orpackages,thatreferenceit.WehavenotmodifiedNumberSeqModule.ThesecondpointtonoteisthattheConWHSprefixweaddedtotheelementisrequired,asallelementsinsideanenummustbeuniqueacrossallextensionstoit.

Itistheconventiontobeabletoseeandmaintainthenumbersequencesfromthemoduletowhichtheybelong.Thisiswhywemodifiedourparametersform.Thisnicelyshowsthelinktothenumbersequencereferenceandthenumbersequencecodethatdefineshowthenumberswillbegeneratedandmaintainedbytheframework.

Thescopeinourcasewasonemodule,asdefinedbytheenum;however,wecanincludemore,shouldwehavemorecomplicatedrequirements.LookingattheimplementationintheInventParametersformdemonstrateshowthiswouldbedone.

TheTmpIdReftemporarytableisusedtogeneratethequeryusedbytheNumberSequenceReferencedatasource.ThiswasdoneintheexecuteQuerymethod.

ThereisanewconceptdemonstratedinthecodefortheNumberSequenceReferencedatasource.TheNumberSequenceCodeIdfieldisactuallyaRecIdrelation,butisdisplayedashumanreadable.ThisisdonebyusingacontroloftypeFormReferenceFieldGroupControl.Thedatasourcefieldmethods,lookupReferenceandresolveReferencewillfacilitatethisprocess.

Page 376: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 377: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Hookingupthenumbersequence

Thecodetotiethevariousformdatasourceeventstothenumbersequenceframeworkwasdoneinitsmostabstractedmethod.Thiswouldallowtheserviceordertobeextended.Forexample,ifweaddedaserviceordertypefield,wecouldhaveadifferentsequencepertypeofserviceorderwithoutmuchreworktothecode.

Inthesemethods,weusedthekeywordselementandthis.Onaform,thethiskeywordisthemostconfusingoneasitchangesdependingonwhereitisused.Thisisbecausewehavenestedclassdefinitionsinsidethemainformclass.So,inthecontextwhereweusedit,thismeantConWHSVehicleServiceTable_DS.Withintherootformmethods,thismeanstheFormRunobjectthatiscreatedwhentheformrun,butwetendtouseelementinstead,asthisisalwaystheFormRuninstance.

Page 378: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 379: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...WecoulddothesamethingtoConWHSVehicleTable.

Wedon'tneedaformhandlerclassinthiscase;wecanaddthemethodsdirectlytotheConWHSVehicleTableform.

DeclaretheNumberSeqFormHandlerclassglobaltotheform,andwritetheinitializemethodalsoatformlevel.Callthemethodafterthesuper()callininit.

Ineachdatasourcemethodwechangedintherecipe,justcallnumberSeqFormHandler(element,ConWHSVehicleTable_DS).<method>directly.TheCustTableformisagoodexampleofthis.

Page 380: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 381: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Creatingacreatedialogfordetailstransactionforms

Mostorderformsuseadialogwhencreatinganewrecord.Thisisbecausethefieldsthatareneededforordercreationarenotthesameasthosedisplayedontheheadersectionoftheform.Thedialogcanbedesignedspecificallytobringallthefieldstheuserwillwant,allinviewwithouthavingtohuntthevariousfasttabsforthefield.

Thecomplexityintheprocessistheinterplaybetweentheorderform'sdatasourceandthedialogusedtocreatetherecord.Thepatternisthesamefor'createforms'.

Page 382: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 383: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyWeshouldhaveaDetailsTransactionformcompleted,withaformandtablehandler.

Page 384: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 385: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreatethecreatedialog,followthesesteps:

1. Createanewform,suffixingthedetailsformnamewithCreate;forexample,ConWHSVehicleServiceTableCreate.

2. Dragthetablesassociatedwiththeheaderrecordtotheform'sDataSourcenode;inourexample,theConWHSVehicleServiceTabletable.

3. SetthepropertiesfortheConWHSVehicleServiceTabledatasourceasfollows:

Property Value

AllowDelete No

AllowNotify

No:wewantthistoactasasinglerecorddialoganddisablemostoftheeventsthatthedatasourceperformsforus

AutoSearch No

InsertAtEnd No

InsertIfEmpty No

DelayActive No

Wewillneedtocontrolthebehaviorofthedatasourceinthiscase,asitwillbecalledfromanother,sowehavetodisablecertainuseroptionsandevents.

4. Overridetheform'sinitmethodandwritethefollowingpieceofcode:

//globaltotheform/element

ConWHSVehicleServiceTableFormserviceTableForm;

ConWHSVehicleServiceTableTypeserviceTableType;

///<summary>

///theformmustbecalledwithaformhandlerclass

///</summary>

publicvoidinit()

Page 386: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

{

if(element.args())

{

if(element.args().caller()is

ConWHSVehicleServiceTableForm)

{

serviceTableForm=element.args().caller();

}

}

if(!serviceTableForm)

{

//Formwasincorrectlycalled

throwerror("@SYS22996");

}

super();

serviceTableType=ConWHSVehicleServiceTable.type();

}

5. Continuetooverridetherunandcloseformmethodswiththefollowinglinesofcode:

publicvoidrun()

{

ConWHSVehicleServiceTable.clear();

ConWHSVehicleServiceTable_DS.create();

super();

}

publicvoidclose()

{

if(serviceTableType)

{

serviceTableType.formMethodClose();

}

super();

}

6. WewillnowneedtoadjustsomeoftheConWHSVehicleserviceTabledatasource'smethods;startthisbyoverridingtheresearchmethodandwritingthefollowingpieceofcode:

publicvoidresearch(boolean_retainPosition=false)

{

//super(_retainPosition)Disabletherefreshfeature.

}

publicvoidreread()

{

//AllowtheDStorereadonlyifsaved

if(ConWHSVehicleServiceTable.RecId)

{

super();

}

}

voidwrite()

{

//thisensuresthattheformcloseif

//thenumbersequencecan'tbeused

//andpassesanullrecordback

try

{

serviceTableType.formMethodDataSourceWrite(

element,this);

super();

}

catch(Exception::Error)

{

ConWHSVehicleServiceTable.RecId=0;

serviceTableForm.ParmServiceTableCreated(

ConWHSVehicleServiceTable);

element.close();

throwException::Error;

}

Page 387: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

this.reread();

this.refresh();

//updatethehandlerformwiththenewrecord.

serviceTableForm.ParmServiceTableCreated(

ConWHSVehicleServiceTable);

}

voidcreate(booleanappend=false)

{

//onlyallowcreateifthecurrentrecord

//hasn'tbeensaved

if(!ConWHSVehicleServiceTable.ServiceId)

{

super(append);

serviceTableType.formMethodDataSourceCreate(

element,this);

}

}

7. ApplytheDialog-basicpatterntotheDesignnode.8. CompletetheDesignproperties,butsettheCaptionpropertytoNewvehicleserviceorder.9. Completetheformlayoutaccordingtothepattern.Usethefollowingasaguide:

WithintheDialogCommitContentpatternelement,usetheFieldsandFieldGroupspattern,andaddfieldsandfieldgroupsasdesired.TheOKandCancelbuttonsinthepatternareCommandButtons,andthepatternwillhidetheCommandproperty.MaketheOKbuttonthedefaultbutton.

10. Next,opentheConWHSVehicleServiceTableFormclassandaddthefollowingmethods:

publicstrCreateFormName()

{

returnformStr(ConWHSVehicleServiceTableCreate);

}

publicbooleancreate()

{

Argsargs=newArgs();

FormRuncreateFormRun;

ConWHSVehicleServiceTablecurrentRecord;

currentRecord=this.currentRecord();

args.name(this.CreateFormName());

args.caller(this);

createFormRun=classfactory.formRunClass(args);

createFormRun.init();

createFormRun.run();

if(!createFormRun.closed())

{

createFormRun.wait();

}

if(createFormRun.closedOk())

{

returntrue;

}

else

{

serviceTableCreated=currentRecord;

returnfalse;

}

}

11. OpentheConWHSVehicleServiceTableform,openthecodefortheConWHSVehicleServiceTable.activemethod,andchangeitsoitreadsasfollows:

publicvoidcreate(boolean_append=false)

{

ConWHSVehicleServiceTablenewServiceTable;

Page 388: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

if(formHandler.create())

{

newServiceTable=

formHandler.ParmServiceTableCreated();

if(newServiceTable)

{

super(_append);

ConWHSVehicleServiceTable.data(

newServiceTable);

this.setCurrent();

}

}

}

12. Nowweshouldsaveall,build,andtest.YoushouldtesttheNewbuttonontheVehicleserviceform,andalsotheNewbuttonontheworkspace.

Page 389: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 390: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...WhentheuserclicksNew(ortheformisopenedwithamenuitemthathasOpenModesettoYes),theformtriggersataskthatcallsthecurrentdatasource'sCreatemethod.Inourcase,wemustletthedatasourcedothis;however,atthispoint,wewillopenthecreatedialogusingthecreatemethodwewroteontheformhandler.

Thecreatemethodisastandardwaytocallanyformfromcode.Neverusetheformnameasastringliteralwithoutusinganintrinsicfunction;inthiscase,formStr.

Thiscallsthecreatedialogthatissetupsothenormalformeventsnolongerfire.Wewillinitializetheformfromtheformhandlerclassinordertopassbackthenewrecord.

Oncecontrolreturnsbacktoourdetailsform,wewillreplacethedataoftherecordwiththerecordcreatedbythecreatedialog;thiswasdoneinthedata([Common])method.

Thekeyactivitiescanbeseeninthisdiagram:

Page 391: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 392: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingaSysOperationprocessThisframeworkprovidesasimplemethodtoallowustowriteroutinesthatcanbesynchronousorasynchronouswithnofurtherefforttoallowthistohappen.

Thecomplexityincreatingaroutinethatisruninabatchprocessishowtostorethevariousparametersthattheroutinemayrequire.ThiswasdoneinthepreviousversionusingtheRunBaseBatchframework.Thisolderframeworkstoredthisdatainalooselytypedblob,andrequiredspecialhandlingshouldthedeveloperaddorchangetheparameters.Ithadotherproblems,includingthefactthatthedataandprocessweretightlycoupled.

TheSysOperationframeworkprovidesanew,andsimplerwaytocreateprocesses.Itdecouplesthedata(orparameters)fromtheprocessbyusingadatacontract.Thedatacontractisaclassthatistheparameterfortheentrypointtotheclassthatperformstheprocess.Theframeworkaddsfurtherhelpinautomaticallycreatingadialogfromthedatacontract,whichwillusetheEDTsinthecontracttoprovidethelabel,andevendrop-downlistsbasedonthetablereferenceontheEDT.

Tousetheframework,wewillneedthefollowingthreeclasses:

Controller:ThisisaclassthatextendsSysOperationServiceController,whichcontrolsboththeUIandinstantiationoftheprocessingclassDatacontract:ThisisaclassthatcontainsthepropertiesrequiredbytheprocessclassProcessingclass:Thisistheclassthatactuallydoesthework

Wecanusethistocreateanyprocess,andthepatternbelowcanbereusedforyourownneeds.Inthisrecipewewillcreateaprocessthattheusercanusetochangethevehicle'sgroup.

Page 393: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 394: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Thefirstpartofthisistocreatethedatacontract,whichisdonebyfollowingthesesteps:

1. CreateanewclassnamedConWHSVehicleGroupChangeContract.2. Completethedeclarationasfollows:

[DataContract]

classConWHSVehicleGroupChangeContract

extendsSysOperationDataContractBase

{

ConWHSVehicleIdvehicleId;

ConWHSVehicleGroupIdvehicleGroupId;

}

3. Thenaddthedatamemberpropertymethods,asshownhere:

[DataMember]

publicConWHSVehicleIdvehicleID(ConWHSVehicleId

_vehicleId=vehicleID)

{

vehicleID=_vehicleID;

returnvehicleID;

}

[DataMember]

publicConWHSVehicleGroupIdvehicleGroupId(

ConWHSVehicleGroupId

_vehicleGroupId=vehicleGroupId)

{

vehicleGroupId=_vehicleGroupId;

returnvehicleGroupId;

}

Now,tocreatetheclassthatperformstheupdate,let'sfollowthesesteps:

1. CreateanewclassnamedConWHSVehicleGroupChange.2. Withintheclass,declarethedatacontractasaglobalvariabletotheclass,asfollows:

publicConWHSVehicleGroupChangeContractcontract;

Thisallowsotherobjectstosetthispropertydirectly,whichneedstobethoughtthroughcarefully.ItissafeinthiscaseaswecalltheRunmethodwiththecontract,andcallValidatewithintheRunmethod.ThisallowsacallertosetthecontractpropertyandcallValidatebeforeRuniscalled.

3. Thisclasswillhavethefollowingmethodsasaminimum:

publicRun(<datacontract>)

publicbooleanValidate()

public<class>Construct()

Page 395: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

4. Createthesemethodsasfollows:

publicbooleanValidate(){

If(contract.VehicleId()=="")

{

//VehicleIdmustbespecified

returncheckFailed("@ConWHS:ConWHS45");

}

If(!ConWHSVehicleTable::Exist(contract.VehicleId()))

{

//Vehicle%1doesnotexist

returncheckFailed(strFmt("@ConWHS:ConWHS46",

contract.VehicleId()));

}

If(contract.VehicleGroupId()=="")

{

//Vehiclegroupisrequired

returncheckFailed("@ConWHS:ConWHS47");

}

If(!ConWHSVehicleGroup::Exist(

contract.VehicleGroupId()))

{

//Vehiclegroup%1doesnotexist

returncheckFailed(strFmt("@ConWHS:ConWHS48",

contract.VehicleGroupId()));

}

returntrue;

}

publicvoidRun(ConWHSVehicleGroupChangeContract)

{

if(!this.Validate())

{

return;

}

ttsBegin;

this.UpdateVehicleGroup();

ttsCommit;

}

privatevoidUpdateVehicleGroup()

{

ConWHSVehicleTablevehicle=

ConWHSVehicleTable::Find(

contract.VehicleID(),true);

if(vehicle.RecId!=0)

{

vehicle.VehicleGroupId=contract.VehicleGroupId();

vehicle.update();

}

}

Thenextpartoftheprocessistocreatethecontrollerclass,whichisdoneasfollows:

1. CreateanewclasscalledConWHSVehicleGroupChangeController.2. ModifyclassDeclarationsothatitextendsSysOperationServiceController.3. Next,overridetheCaptionmethodandchangethistoreturnVehiclegroupchangeasalabel.Thissetsthe

caption,ifitisaddedtoabatchqueue.4. DothesamefortheparmDialogCaptionmethod;thissetsthetitleofthedialogthattheframeworkcreates

fromthedatacontract.5. Finally,wewillneedanentrypoint,whichisamainmethod.Themethodshouldbecreatedasperthe

followinglinesofcode:

publicstaticvoidmain(Args_args)

{

ConWHSVehicleGroupChangeControllercontroller;

Page 396: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

controller=newConWHSVehicleGroupChangeController(

classStr(ConWHSVehicleGroupChange),

methodStr(ConWHSVehicleGroupChange,

Run),

SysOperationExecutionMode::Synchronous);

controller.startOperation();

}

6. SavethechangesandcreateActionMenuIteminourproject.7. NamethemenuitemasConWHSVehicleGroupChangeControllerandsettheObjectTypepropertytoClassand

theObjectpropertytoConWHSVehicleGroupChangeController.8. AddthistothePeriodicTasksubmenuoftheConWHSVehicleManagementmenuandbuildtheproject.

Page 397: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 398: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Wewillgetadialogwithafieldcreatedautomaticallyfromthecontract.Wecanevensubmitittothebatchqueue.Prettycool,andverylittleadditionaleffort!

Thiswitchcraftdoesdeserveanexplanation.

AsourclassextendsSysOperationServiceController,wegetalotoffunctionality,thefirstbeingthatitcanconstructadialogfromthedatacontract.However,howdiditknow?

Whenthenewmethodexecuted,theframeworklookedattheRunmethodanddeterminedthecontractfromthemethod'sinputparameter.

ThecalltostartOperationcausedthesystemtobuildtheUIandhandlethecodeexecutionbasedonwhattheuserdoes.ThedialogisconstructedusingtheEDTsspecifiedinthedatamembermethods.SinceweconstructedtheEDTscorrectlywithreferencetothemaintable,itcanalsoprovideasimplelookupforus.

Thisexplainswhythenewmethodrequirestheclassandmethodnames,butthethirdparameterisanexecutionmode.Thiscomesintoitsownifweexecutethecontrollerprogrammatically.Synchronousmeansitwillruninlinewiththecurrentprocess,unlesstheuseriselectedtoRuninthebackground.Ifthiswaschosen,theexecutionmethodwouldhavechangedtoScheduledBatch.

TheclassStrandmethodStrintrinsicfunctions,asexplainedinthepreviouschapters,areusedtocheckwhethertheelementthefunctionsrefertoexistsatcompiletime.

Ifyourunthisprocesstwice,youwillseethepreviousoptionsarerememberedandactasdefaults.Thisisbecausethecontractisserializedanddeserializedtotheuser'susagedata.

Page 399: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 400: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...

ThereisanothermethodtowritetheMainmethodinthecontrollerclass.Thisistospecifytheclassandmethodinthemenuitem'sParameterproperty,forinstance,ConWHSVehicleGroupChange.Run.WecanthenusetheinitializeFromArgsmethodtobuildthecontroller.Thisisincommonuse,butsincethispropertyisfreetext,thecompilerwillnotpickupthatthisvalueisinvalid.

Page 401: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 402: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ExecutingcodeusingthebatchframeworkToforcetheprocessthroughthebatchframework,wewillusetheexecutionmodes:ReliableAsynchronousandScheduledBatch.

Bothofthesemethodssubmitjobstothebatchserverforexecution,wheretheReliableAsynchronousmethodauto-deletesthejobsafterexecution.

Thejobsdonotexecuteimmediately,butwithinaminuteofsubmission;thebatchserverpollsforwaitingjobseveryminute.Weshouldusethismethodtoperformasynchronousorscheduledjobsthatrequireheavyprocessing.

Programmaticallyspeaking,wewouldn'twanttousetheScheduledBatchmode.Thiswouldbesetbasedontheuserchoosingtorunasabackgroundtask.

TosubmitthetaskasReliableAsynchronous,simplychangethecodethatconstructsthecontrollertousethisexecutionmode.Onexecution,youmaynoticethattheinformationmessageindicatesthatnothingwasdone,butifyouwaitforaboutaminuteandcheckthevehiclerecord,youwillseethatithassucceeded.Youcanalsoseethebatchhistoryforthis.

Page 403: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 404: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CallingaprocessfromaformTheexampleinthisrecipesimplydemonstratestheframework,buttheexampleisnotparticularlyuseful.Todoso,itwouldbebettertocalltheprocessfromthevehicleformanddefaulttheparameterstothecurrentvehicles.

Todothis,wewillneedtofetchtheconstructedcontractandupdateitwiththerecordsetbythecallerfromtheArgsobject.

Thefollowingcodedoesthis:publicstaticvoidmain(Args_args){ConWHSVehicleGroupChangeControllercontroller;ConWHSVehicleTablevehicle;

switch(_args.dataset()){casetableNum(ConWHSVehicleTable):vehicle=_args.record();break;}if(vehicle.RecId==0){//Activebufferrequired.throwerror("@SYS25821");}controller=newConWHSVehicleGroupChangeController(classStr(ConWHSVehicleGroupChange),methodStr(ConWHSVehicleGroupChange,Run),SysOperationExecutionMode::Synchronous);

ConWHSVehicleGroupChangeContractcontract;contract=controller.getDataContractObject('_contract');if(!contract){//Function%1wascalledwithaninvalidvaluethrowerror(strFmt("@SYS23264",classStr(ConWHSVehicleGroupChangeController)));}contract.VehicleGroupId(vehicle.VehicleGroupId);contract.VehicleId(vehicle.VehicleId);controller.startOperation();if(FormDataUtil::isFormDataSource(vehicle))

Page 405: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

{//Thiswillcallthetable'sdatasource'sresearch//methodtorefreshthedatafromthetable.//ThetrueparameterwillkeepthecurrentrecordFormDataUtil::getFormDataSource(vehicle).research(true);}}

Wewillthenneedtoaddtheactionmenuitemtoourform.OnthemainFormActionPanecontrol,addanewActionPaneTab,andsettheCaptionpropertytoVehicles.CreateabuttongroupwiththeTextpropertysetto@SYS9342.

Finally,dragtheactionmenuitemtothebuttongroupandsetthefollowingproperties:

Property Value

DataSource ConWHSVehicleTable:Thissetsargs.Record()

NeedsRecord Yes:Thisdisablesthemenuitemifthereisnocurrentrecord

SaveRecord Yes:Thisisdefault,butwecouldendupwithanerrorduetooptimisticconcurrency

MultiSelect No:Ifmultiplerecordsareselected,disablethebutton

Buildandtestthenewfeaturewithatleasttwovehiclestoprovethatthevaluesarenotdefaultingfromyouruser'susagedata.

Page 406: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 407: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

UsingthedatacontracttomakechangestothedialogLet'stakeastepbacktothepointwherewewerecallingthemainmethodofConWHSVehicleGroupChangeControllerdirectly.

Thesystemverycleverlyconstructstheuserinterface;itdoesthissimplybyaddingthedatacontract'sdatamemberdirectlytothedialog.Therearesomesimplechangeswecanmaketothedatacontracttocontrolhowtheyaredisplayed.

Wecanmakethesechangesbyalteringthedecorationatthetopofeachdatamember.Thefollowingexamplesareusefulforthis:

SysOperationLabel("..."), Thissetsthelabeldisplayedonthedialog

SysOperationHelpText("..."); Thissetsthehelptextfortheresultingcontrol

SysOperationDisplayOrder("...") Thiscausesthecontroltobeplacedfirstintheresultingdialog

Thecompletedcodeforthedatamembermethodsisasfollows:[DataMember,SysOperationControlVisibility(false)]publicConWHSVehicleIdVehicleId(ConWHSVehicleId_vehicleId=vehicleId){vehicleId=_vehicleId;returnvehicleId;}

//Newvehiclegroup//Pleaseselectanewvehiclegroupforthevehicle[DataMember,SysOperationLabel(literalStr("@ConWHS:ConWHS50")),SysOperationHelpText(literalStr("@ConWHS:ConWHS51")),SysOperationDisplayOrder('1')]publicConWHSVehicleGroupIdVehicleGroupId(ConWHSVehicleGroupId_vehicleGroupId=vehicleGroupId){vehicleGroupId=_vehicleGroupId;returnvehicleGroupId;}

Page 408: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

BuildandtestthenewformandfeelfreetotesttheotherSysOperationattributes.Wedon'tneedtheAttributesuffixinOperations.

Page 409: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 410: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

AddinganinterfacetotheSysOperationframeworkWecandoalotbyjustdecoratingthecontractdatamethodsbut,sometimes,weneedmorecontrol.ThisrecipestepsthroughaddingmorecontroltotheuserinterfacecreatedbytheSysOperationframework.

Page 411: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 412: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyWejustneedanexistingSysOperationprocessclassthatwewishtoaddacustomizedinterfaceto.

Ifyouarefollowingonfromthepreviousrecipe,removetheSysOperationControlVisibilityattributefromtheVehicleIddatamethod.

Page 413: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

vehGroupIdField.registerOverrideMethod(<br/>methodStr(FormStringControl,validate),<br/>methodStr(ConWHSVehicleGroupChangeUIBuilder,<br/>validateVehicleGroupId),<br/>this);

Nearlydone!However,wehaven'thandledhidingthevehicleIDfieldifitwascalledfromavehiclerecord.Wecan'tdeterminethisfromwithintheUIbuilderclass;onlythecontroller'smainmethodknowsthis.ThismeanswewillneedamechanismtotelltheUIbuilderwhetherornottohidethefield.SinceweattachtheUIbuildertothedatacontract,wewilladdahiddenfieldtothedatacontract,asfollows:

1. First,changethedecorationinclassDeclarationofConWHSVehicleGroupChangecontractsothatitisassociatedwiththeUIBuilder:

[DataContract,SysOperationContractProcessing(

classStr(ConWHSVehicleGroupChangeUIBuilder))]

2. ThenaddavariabledeclarationtoclassDeclaration,asfollows:

NoYesIdhideVehicleId;

3. Addadatamembermethodforthevariablewiththevisibilityattribute:

[DataMemberAttribute,

SysOperationControlVisibilityAttribute(false)]

publicNoYesIdHideVehicleId(

NoYesId_hideVehicleId=hideVehicleId)

{

hideVehicleId=_hideVehicleId;

returnhideVehicleId;

}

4. Finally,wewillneedtomodifyourcontroller'smainmethodtohandlethecasewhereitwascalledfromavehiclerecord,whichisdonelikethis:

publicstaticvoidmain(Args_args)

{

ConWHSVehicleGroupChangeControllercontroller;

ConWHSVehicleTablevehicle;

switch(_args.dataset())

{

casetableNum(ConWHSVehicleTable):

vehicle=_args.record();

break;

}

controller=newConWHSVehicleGroupChangeController(

classStr(ConWHSVehicleGroupChange),

Page 414: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

methodStr(ConWHSVehicleGroupChange,

Run),

SysOperationExecutionMode::Synchronous);

ConWHSVehicleGroupChangeContractcontract=

controller.getDataContractObject(

'_contract');

if(!contract)

{

//Function%1wascalledwithaninvalidvalue

throwerror(

strFmt("@SYS23264",

classStr(ConWHSVehicleGroupChangeController)));

}

controller.initParmDefault();

controller.loadFromSysLastValue();

controller.parmShowDialog(true);

contract.HideVehicleId(false);

if(vehicle.RecId!=0)

{

contract.VehicleGroupId(vehicle.VehicleGroupId);

contract.VehicleId(vehicle.VehicleId);

contract.hideVehicleId(true);

}

controller.saveLast();

controller.startOperation();

//vehicle.IsDataSource()isdeprecated

if(FormDataUtil::isFormDataSource(vehicle))

{

FormDataUtil::getFormDataSource(vehicle).

research(true);

}

}

5. Saveallandbuildtheprojecttotesthowthisworks.

Page 415: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 416: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Byusingthismethod,wehaveaddedacouplingtotheprocessclassstructurebymeansofthebindingtotheUIbuilder.Inordertoavoidthis,wewoulduseinheritancetodefineadatacontractbaseclassandextenditforthepurposesofthisbinding.

Theprocessmayseemalittlecomplicatedatfirstbut,whenwebreakthisdown,itwillbecomeclearerwhatisactuallyhappening.

ThefirsttaskwastodeclaretotheDialogFieldvariablesthatwewantaddedtotheresultingdialog,andthentobindthemtothedatacontractdatamethods.However,inorderforthistowork,wehadtobindtheUIbuilderclasstothedatacontract.WedidthisbyaddingtheSysOperationContractProcessingattributetothedatacontract.

Oncethisisdone,theDialogFieldvariablesarenowboundtothedatacontractmethods.Wethenwroteamethodtovalidatethevehiclegroupcontrol,andboundthistothecontrol'svalidateeventusingthecontrol'sregisterOverrideMethodmethod.

Oncethisisalldone,thedialogcreatedbytheframeworkcanbevalidatedinteractively,allowingtheusertocorrectanyerrorswithouthavingtostartagain.

Page 417: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 418: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

BusinessIntelligenceInthischapter,wewillcoverthefollowingrecipes:

CreatingaggregatedimensionsCreatingaggregatemeasurementsCreatingaggregatedataentitiesCreatingandusingkeyperformanceindicators

Page 419: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 420: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

IntroductionBusinessintelligence(oftenreferredtoasBIoranalytics)hasevolvedagreatdealinthisrelease;manyelementsofBIthatweredevolvedtoexternalapplicationsarenowfirstclasscitizensofthedevelopmentenvironment.WewillnowdefinedimensionsandmeasurementsinanOperationsprojectthatarebuiltandreleasedasanyotherOperationsproject.Ourroleinthisistoprovidethebasiswhichthebusinessintelligencedesignerwillusetodeveloppowerfulbusinessintelligencesolutions.

WhendevelopingaBIsolution,itisusualtocreateanewprojectthatreferencesthepackagesitanalyzes.Thisallowsthesolutionstobedevelopedatthesametime,andalsoallowstheanalyticsandotherprojectstobedeployedindependently.Ofcourse,ifthedatastructureschangeinawaythatbreakstheanalyticssolution,thebuildserverwillusuallyhighlightthis.

Whenplanningthistypeofwork,theanalyticsshouldbedesignedalongwiththemainsolutiondesignandnotasanafterthought,eventhoughwewillstartanalyticslaterintheprojectlifecycle.Thisresultsinthedatastructuresbeingcorrectlydesignedwithanalyticsandreportinginmind;whenwecometodeveloptheanalyticselementoftheproject,weshouldbeabletodosowithoutchangestotheunderlyingtables.Changestounderlyingtablescouldbeconsideredlikeaddingabasementgaragetoyourhouse.

Therecipesinthischapterarebasedoncreatingananalyticsprojectforcustomerinvoiceanalysis.

Page 421: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 422: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Creatingaggregatedimensions

Aggregatedimensionsareusedtodefineattributesthatsplicethedatatheyareassociatedwith.Thedimensionisthereforebasedonatableorviewandwillhaveoneormoreattributes,whicharethewayswewillallowthedatatobespliced.Definingthedatainthiswayallowsexceptionallyfastdataanalysis.ThisisbecausethedataisexpandedintoadatawarehousedatabaseautomaticallybyOperations.

Insomecases,youmaywanttocreateaviewtosimplifytheprocess,inwhichcase,usetheviewastheTableproperty.

Page 423: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 424: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Gettingready

Beforewestart,itisbesttocreateanewmodelforanewpackageandaddreferencesasrequired.Theseareusually,ApplicationPlatform,ApplicationFoundation,ApplicationSuite,andanyotherwhoseelementsweintendtouse.Whendeliveringanimplementationproject,itisusuallybesttocreateonepackageforanalytics.

Page 425: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 426: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Dimensionsshouldbebasedonviewsinordertodenormalizethedata.Thisistolettheusersseenameanddescriptionfields,andnottheidentitycolumns,sothefirstpartistocreateorextendaviewtoallowthisdatatobeeasilyaccessed.

Tocreatetheview,followthesesteps:

1. CreateanewviewcalledConWHSVehicleTableExpanded.2. AddtheConWHSVehicleTabletableastherootdatasource,andConWHSVehicleGroupasachilddatasource,as

showninthefollowingscreenshot:

3. EithersettheUseRelationspropertyontheConWHSVehicleGroupdatasource,oraddarelationmanually.4. Addallnon-systemfieldsfromtheConWHSVehicleTabletable,andtheDescriptionfieldfrom

ConWHSVehicleGroup.RenamethisfieldtoVehicleGroupDescriptionandsettheLabelpropertytoVehicleGroup.5. Saveandclosetheview.

Thefollowingstepscreatethedimensionattributebasedontheview:

1. Intheproject,addanewitem,andintheAddNewItemdialog,selectAnalyticsfromtheleft-handlistandAggregateDimensionfromtheright.

2. NamethenewitemasConWHSVehicles.Thishastobeuniqueamongstotheraggregatedimensions.3. SettheTablepropertytoConWHSVehicleTableExpanded,asthisisthetablethathasthedataweneed.

Thesystemhasdeterminedthatthemaindatasourceoftheviewhasaprimarykeyandhasusedthistocorrectlycreatetheattributeusedasauniquekey;donotdeletethiskey.Allattributesthatarecompany-specificshouldincludetheDataAreaIdfield.

4. [email protected]. ChangeNameandtheNameFieldpropertiestoVehicleGroupDescription.6. Right-clickontheattributeandchooseNewDimensionFieldReference.7. SpecifyVehicleGroupDescriptionastheDimensionFieldproperty,andthenaddDataAreaId.

Addingafieldreferencewillautomaticallysettheattribute'sNameproperty,whichiswhyweaddthefieldsinthisorder.

Page 427: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

8. RepeatthisfortheVehicleTypeandDescriptionfields.9. Right-clickontheHierarchiesnodeandchooseNewDimensionAttributeHierarchy.10. Right-clickonthenewattributehierarchyandchooseNewDimensionHierarchyLevel.Addalevel

forSourceAttributeVehicleTypefirst,andthenVehicleGroup.11. Theresultshouldlooklikethefollowingscreenshot:

12. Saveandclosethedesigner.

Page 428: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 429: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

Thisisusedasadefinitionwhenwecreatetheaggregatemeasures.Theviewwasanecessarystepinordertosimplifyaccesstothedatainawaythatuserscanrelate.Wewillthendefineattributes.Althoughweaddtheaggregatedimensiontotheaggregatemeasure,theuserwillusetheattributestosplicethedata.

Webasedtheattributeonthevehicletable,sowecaneasilyrelatethistotheservicetable,whichiswhatwewillbeanalyzing.Thehierarchyisnotmandatory,anditisusedwhenwewanttopredefinetheanalytichierarchiesatthistime.

Page 430: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 431: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

SeealsoFormoreinformationonAnalyticsinOperations,pleasevisitAnalytics(https://ax.help.dynamics.com/en/wiki/analytics/)

Page 432: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 433: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingaggregatemeasuresThesecanbethoughtofascubesfrompriorreleasesofOperationsandformthatbasistowhichweanalyzeourdata.Theycontaintheaggregatedatathatthedimensionsspliceorpivoton.

Page 434: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 435: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyYoumayfindthatthedrop-downsusedinpropertiesmaynotworkinthisrecipe;toresolvethis,simplybuildtheprojectbeforeyoustart.

Page 436: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 437: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...First,wewillneedtocreateaviewthatflattenstheservicedataintooneview.Todothis,followthesesteps:

1. CreateanewviewcalledConWHSVehicleServiceExpanded.2. AddtheConWHSVehicleSErviceTabletableastherootdatasourceandConWHSVehicleServiceLineasachilddata

source,asshowninthefollowingscreenshot:

3. EithersetUseRelationspropertyontheConWHSVehicleServiceLinedatasource,oraddarelationmanually.

4. Addallnon-systemfieldsfromtheConWHSVehicleServiceTabletable,andtheItemIdfieldfromConWHSVehicleServiceLine.

5. Saveandclosetheview.

Wecannowcreatetheaggregatemeasure,whichisdonebyfollowingthesesteps:

1. Intheproject,addanewitem,andintheAddNewItemdialog,selectAnalyticsfromtheleft-handlistandAggregateMeasurementfromtheright.

2. NamethenewitemasConWHSVehicleServiceMeasure.

YoumaynoticethatmanyexistingaggregatemeasuresaresuffixedwithCube;thisisthelegacynamingconventionfromAX2012.

3. ThedesignerwillopenwithadefaultmeasuregroupnodenamedMeasureGroup1.RenamethistoServiceInformation.

4. SettheTablepropertytoConWHSVehicleServiceExpanded.5. Wewilladdthreemeasures,acountofservicerecords,acountofitemsused,andacountof

vehicles.Right-clickontheMeasuresnodeandchooseNewMeasure;addthemusingthefollowingsettings:

Name DefaultAggregate Field

ServiceRecords Count ServiceId

Page 438: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Items Count ItemId

Vehicles Count VehicleId

Toaddsumaggregatesforvalueorquantityfields,settheDefaultAggregatepropertytoSum.

6. Let'saddtheaggregatedimensionsfirstbyexpandingtheDimensionsnodeandnoticingthatithasalreadycreatedtwodimensions:CompanyandDate_.

TheunderscoreistoavoidproblemsshouldthisbedeployedtoSQLServerAnalysisServices(SSAS).Ifyousavewithoutthisunderscore,youwillgetawarning.ThisalsooccursforotherTransact-SQLkeywords,suchasDescription.

7. RenametheDate_dimensionandthesub-nodetoServiceDate,asshowninthefollowingscreenshot:

8. ItwillhaveguessedtherelationfortheCompanydimensionautomatically;however,ithasnoideahowtorelatetheDate_dimensiontotheview.Selecttherelationnode(thefinalnodeundertheServiceDatedimension)andsettheRelatedFieldpropertytoServiceDateRequested.

9. ThecompletedServiceDateandCompanydimensionsshouldlooklikethefollowingscreenshot:

10. Let'saddourConWHSVehiclesaggregatedimension:dragtheConWHSVehiclesaggregatedimensionfromtheprojecttotheDimensionsnode.

11. Inthiscase,ifyouexpandthenewdimension,youwillseethatthattheVehicleIdrelationissetforus;ifthisisnotset,wemustspecifyit.

12. Let'saddastandarddimensiontoouraggregatemeasure.FromApplicationExplorer,locatetheReleasedProductsaggregatedimensionfromAnalytics|Perspectives|AggregateDimensions.

13. DragReleasedProductstotheDimensionsnode.14. Thistime,therelationisnotset.Todothis,settheDimensionAttributepropertytoReleasedProducts.

ThiswasselectedasthisisthekeyattributeandhasItemIdasthekeyattribute.Conventionguidesustoknowthattheattributewiththesamenameastheaggregatedimensionisthekeyattribute.Ofcourse,wecansimplyopentheaggregatedimensioninthedesignertocheck.

15. SelectthenewrelationandtheRelatedFieldpropertytoItemId.Thecurrentstateofourdesignshouldbeasinthefollowingscreenshot:

Page 439: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

16. Finally,let'saddacustomattributetotheaggregatemeasure.Thisisnotnormallydone,becausetheycan'tbereusedelsewhere.Right-clickonthe@AttributesnodeandselectNewDimensionAttribute.

17. Completeasbefore,theresultshouldbeasperthefollowingscreenshot:

18. Saveandclosethedesigner.19. Buildtheprojectandperformdatabasesynchronization.

Next,wewillconfiguretheentityrefreshbatchjob.

1. Inorderforthedatatobedeployedandautomaticallyrefreshed,navigatetoSystemadministrationandchooseSetup|EntityStore.

2. SelecttheentitiesthatshouldberefreshedandclickonRefresh.3. IntheConfigurerefreshdialog,expandRuninthebackground,clickonRecurrence,andconfigureas

perthefollowingscreenshot:

Page 440: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Onlymakethisasfrequentasitneedstobe.Asthiswillrunfortheselectedentities,wecanconfigureentitiessothattheyarerefreshedatdifferentrates.

4. PressOKontheDefinerecurrencedialog,andOKontheConfigurerefreshdialog.

Page 441: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 442: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Theaggregatemeasuredefinesthevalueswewishtodisplayandthedimensionsbywhichwewillfilterandsplicethedata.Itmayseemoddthatwedon'thavemeasuressuchasVehiclesServicedThisYear,butthisisn'tafunctionofameasure.ThisisaccomplishedbyacombinationoftheServiceDatedimensionandtheVehiclesmeasure.

Theentityrefreshcreatesandmaintainsthedatainthedatawarehousedatabase.ThisisAxDWondevelopmentenvironments,andfortheclouddeployedsandbox,youwillgetthisfromLCS.

IfyouopentheSQLServerManagementStudio,thetablesarecreatedasfollows:

Youcanquerythisdatatodiagnosewhymeasuresorkeyperformanceindicators(KPIs)maynotworkasexpected.

Page 443: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 444: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingaggregatedataentitiesAggregatedataentitiesallowustouseaggregatemeasuresinthesamewaywewoulduseatable.TheycanalsobeusedtoexposetheaggregatedatathroughOData.Thisexamplewillbeusedtocreateachartformpartinthenextrecipe.

Page 445: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 446: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyWewillneedanaggregatemeasureforthis,sowearefollowingonfromtheprevioustworecipes.

Page 447: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 448: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreatetheaggregatedataentity,followthesesteps:

1. CreatenewitembychoosingAnalyticsfromtheleftpaneoftheAddNewItemdialogandAggregatedDataEntityfromtheright.NametheentityConWHSVehicleServiceDataEntity.

2. DragtheConVehicleServiceMeasureaggregatemeasuretotheDataSourcenode.3. ExpandtheMeasuresnodeanddragallthreemeasurestotheFieldsnode.4. Wecanalsoadddimensionsasfields;inourcase,wewillneedtheServiceDatedimension.Drag

ServiceDatetotheFieldsnode.5. Tomakethismoreusefulasanaggregatedataentity,wewillchooseaspecificattributeofthis

dimension.SettheAttributepropertytoMonth.6. Thisaggregatedataentityshouldbegroupedbyvehicle;toaddtheVehicleIDtothefieldlist,right-

clickontheFieldsnodeandselectNewMappedField.7. Setthepropertiesofthis,asshownhere,andsetthemintheorderlistedasfollows:

Property Value

Name Vehicle

MeasureGroup ServiceInformation:thisiswhatwerenamedfromMeasure1whenwecreatedtheaggregatemeasure.

Dimension ConWHSVehicles

Attribute ConWHSVehicles

ExtendedDataType ConWHSVehicleId:thisshouldbesetautomatically

8. Wewillnowneedtobuildtheproject,andthensynchronizethedatabase.

Page 449: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 450: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

Aggregatedataentitiesaresimilartotheotherentities,inthat,theycanbeseenastables,usedtoexportdatatoExcel,andtherefore,canbeusedforanalyticsoutsideofPowerBI.Theyareactuallystoredasviews.

Wecanalsousethemasdatasourcesinchartcontrols,givingmoreeasilydigestibleinformationtotheuserwithoutnavigatingaway.

Page 451: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 452: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingandusingkeyperformanceindicatorsKeyperformanceindicators,inOperationsprovideasimplewaytocreatemetricsthatcanbeviewedwithinOperations.Inourcase,weshallcreateaKPIthattargetsanumberofserviceordersinamonth.

Page 453: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 454: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyFirst,weshouldhavecreatedanaggregatemeasurebeforewestartthis.

Page 455: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 456: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...FollowthesestepstocreatetheKPI:

1. Createanewitem;selectAnalyticsfromtheleft-handlistandKeyPerformanceIndicatorfromtheright.

2. SettheNamefieldtoConWHSVehicleServiceThisMonthandpressAdd.3. DragtheConWHSVehicleServiceMeasureaggregatemeasurefromtheprojectontotheKPI.Thissetsthe

Measurementpropertyforus.4. Completetheremainingproperties,asfollows:

Property Value

Label Serviceorders

BadThreshold 20

GoodThreshold 1

ScoringPattern LessIsBetter

MenuItemName Mustleavethisblank

5. OntheValuenode,setthepropertiesasfollows:

Property Value

MeasureGroup ServiceInformation

Measure ServiceRecords

WearebasingourKPIonthenumberofserviceordersinamonth.

6. ExpandtheValuenode,andaddanewrangetotheRangesnodeandconfiguretheproperties,as

Page 457: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

shownhere:

Property Value

Dimension ServiceDate

Attribute Month

Name MonthRange

ThedefaultPeriodisCurrent,andtheKPIwillknowthatwemeanthecurrentperiodbasedontheAttributeproperty.

7. Wecannowaddtrends.UndertheTrendnode,createanewtrendandsetthepropertiesasshownhere:

Property Value

Dimension ServiceDate

Attribute Week

Name WeeklyTrend

ItemCount 10,thismeans10weeksinthiscase

Label Weeklytrend

8. Now,weshouldcreatethetoptrend,whichshowsthetopncontributoryfactorstothevalue.Completethenewtrendasfollows:

Property Value

TrendType TopTrend

Page 458: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Dimension ConWHSVehicles

Attribute VehicleGroupDescription

Name TopTrend

Label Vehiclegroups

ItemCount 5,thismeansthetop5vehiclegroups

9. Finally,let'saddabottomtrend,whichisdoneasfollows:

Property Value

TrendType BottomTrend

Dimension ConWHSVehicles

Attribute VehicleGroupDescription

Name BottomTrend

Label Vehiclegroups

ItemCount 5,thismeansthebottom5vehiclegroups

Wewillchooseadifferentmeasureforthis,butwecanusethesamemeasureforboth.

10. Youcancreatemultipletrendsofeachtype,andtheywillappearasoptionstotheuser.Tryandcreateatoptrendforvehicletypes.

11. Saveandclosethedesigner.

Page 459: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

TotestourKPIweshouldcreateatilefortheKPIsowecanaddittoaworkspace.Thisisdonebythefollowingsteps:

1. CreateanewtilecalledConWHSVehicleServiceThisMonthTile.2. DragtheConWHSVehicleServiceThisMonthKPIontothetileinthedesigner.ThissetstheTypepropertyto

KPIandtheKPIpropertytoConWHSVehicleServiceThisMonth.3. SetLabeltoVehicleserviceordersandtheSizepropertytoWide.4. Saveandclosethedesigner.

Addthetiletothevehiclemanagementworkspaceform:

1. Wewillneedtoaddthistoaworkspace.LocatetheConWHSVehicleWorkspaceforminApplicationExplorer.

2. Right-clickontheformandchooseCreateextension.3. ChangethesuffixsothatitisnamedConWHSVehicleWorkspace.ConAnalytics.4. Opentheforminthedesigner.5. Right-clickonthePanoramaSectionTilestabpageandchooseNew|TileButton.6. SettheNameandTilepropertiestoConWHSVehicleServiceThisMonthTile.

Buildtheprojectandsynchronizethedatabase:

1. Right-clickontheprojectandchooseProperties.2. ChangeSynchronizeDatabaseonBuildtoTrue.3. PressOK.4. Buildtheproject.

TotesttheKPI,followthesesteps:

1. OpentheOperationsclient;inourcase,wecanjustopenthefollowingURL:

https://usnconeboxax1aos.cloud.onebox.dynamics.com/?cmp=usmf&mi=ConWHSVehicleWorkspace

2. IfyoureceiveanerrorthattheKPIdoesnotexist,rebuildtheprojectwiththeSynchronizeDatabaseonBuildprojectpropertysettoTrue.

3. TheKPIwilldisplaywithnodata;tosetthisup,navigatetoSystemadministration|Setup|Datacache|Datacacheparameters.

4. SelecttheCacherefreshtabpage.5. Ifyouhaveabatchgroupsetupforsystemjobs,enterthisintheBatchgroupfield;otherwise,leave

itblank.6. ClickonInitializebatchjob.7. OpenSystemadministration|Inquiries|Batchjobs.8. Thebatchjob,Datacacherefreshbatch,willbeinthelistandwillremainexecuting.Thismayseem

unusualforbatchjobs,astheynormallyhavearecurrence;however,thisjobrunscontinuously.9. Ifthebatchjobisnotexecuting,ensurethattheMicrosoftDynamics365forOperationsBatchManagement

Servicewindowsserviceisrunning.10. Waitaroundaminuteforthedatatoprocessandtesttheworkspaceagain.

Page 460: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Testingtheworkspace:

1. First,youshouldcreatesometestdatasowecantestifourKPIworkscorrectly.Wewillprobablyneedaround30ordersformultiplevehicletypesandgroups.

2. TheKPItilecontainsaconcisesummaryoftheKPIandwillresemblethefollowingscreenshot:

3. Ifyouclickonthetile,youaretakentotheKPIview.4. Inthisview,theusercanchangethedefaultswesuppliedintheKPI,changethetrendviews,and

applydifferentfilterstothosesupplied.Theviewshouldlooklikethefollowingscreenshot:

Page 461: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 462: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...ThesimpleKPIdefinitionisapowerfultool.TheKPIdefinitionhasenoughinformationtodefineboththetileandtheKPIworkspace.

Page 463: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 464: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...PowerBIisabusinessintelligencetoolusedtodesignandviewBIreports.AgoodresourcetolearnaboutPowerBIisavailableforthefollowinglink:GuidedLearningforPowerBI(https://powerbi.microsoft.com/en-us/guided-learning/?WT.mc_id=PBIService_GettingStarted)InordertoleveragePowerBI,afewmorestepsarerequired.

Thebasicstepsareasfollows:

1. EnsurethatyouhavesetupyourO365accountswithaccesstoPowerBIFreeorPowerBIPro.Tosignupforafreeaccount,usethislink:

https://powerbi.microsoft.com/en-us/documentation/powerbi-admin-free-with-custom-azure-directory/

2. RegisterthePowerBIappandgetaClientIDandApplicationKey.Thedetailsofthisarelistedhere:

https://powerbi.microsoft.com/en-us/documentation/powerbi-developer-register-a-web-app/

3. OpenOperationsandnavigatetoSystemadministration|Setup|PowerBIandcompletetheformwiththeClientIDfromthepreviousstep;anexamplesetupisshowninthefollowingtable:

Field Value

AzureADauthorityURL https://login.windows.net(default)

AzureADPowerBIresourceURI https://analysis.windows.net/powerbi/api(default)

AzureADtenant contoso.com-thetenantyousignedupwith.

ClientID Fromthepreviousstep

Applicationkey Fromthepreviousstep

RedirectURL https://<productionurl>/oauth

PowerBIAPIAddress https://api.powerbi.com/beta/myorg

Page 465: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Alsoseethefollowinglink:https://ax.help.dynamics.com/en/wiki/configuring-powerbi-integration/

InordertoauthoranddistributePowerBIreports,usethefollowinglink:https://blogs.msdn.microsoft.com/dynamicsaxbi/2016/06/23/authoring-and-distributing-power-bi-reports-with-dynamics-ax7/

Whenfollowingthisforacloudsolution,whereyouwanttoseerecentdatafromthesandboxenvironment,doitonthesandboxserver.YoucanconnectusingtheAxDWdatabasedefinedinLCS,butsuffixedwithdatabase.windows.net.ConnectusingtheDatabasetab,astheAxDWaccountwon'thaveaccesstotheserver.

Page 466: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 467: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

SecurityInthischapter,wewillcoverthefollowingrecipes:

CreatingprivilegesCreatingdutiesCreatingsecurityrolesCreatingpolicies

Page 468: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 469: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

IntroductionWhendesigningthesecuritymodel,therearethreemethodsdependingonthesolution.ForISVsolutions,thesecurityshouldbedesignedintothemainpackageandshippedtogether.Thisalsogoesforpartnersolutionsfornewcustomerimplementationswhenthepackageisadiscreetpackageoffunctionality.Sometimes,customersneedrolescustomizedfortheirownneeds,andtheserolesmayencompassprivilegesforbothnewandexistingfunctionalities.Inthiscase,anewpackageshouldbedeveloped.Havingaseparatepackagealsoaidsworkplanningefficiency.

Shouldtheendusercustomerdeveloptheirownsecuritymodel,thisshouldalwaysbeinanewpackage;although,thesamefunctionalitycanbeachievedwithintheapplication.

ThesecuritymodelinOperationsisbrokendownintothefollowingstructure:

RolesDutiesPrivilegesPolicies

TheprimarystructureofthesecuritymodelincludesProcesscycles,Roles,Duties,andPrivileges.WewillexplorethesefirstbeforetakingalookatPoliciesandCodePermissions.

EntrypointsdefinetheaccesslevelgrantedtomethodsofentrytoOperationsfunctionality,suchasmenuitemsandservices.

Permissionsdefineaccesstotablesandfields,servermethods(suchasaservicecallwherethecodewon'trunundertheuser'ssecuritycontext),andformcontrols(buttons,fields,orothercontrolsplacedonaform).

Asapartofthefunctionalrequirementsdefinition,thebusinessprocessesareanalyzed,alongwiththerolesthatperformthem.ThebusinessprocesseswillthenbemappedtotheOperationssystemprocesses.Thisisusedformanypurposes,includinggapfitlevel,trainingplans,testingplans,andsoon.ThismethodofanalyzingrolesandprocessesalsofitsnicelyintotheOperationssecuritymodel,allowingthesecuritymodeltobedesignedbasedonthis.

Thesecuritymodelthatwedesignandimplementshouldalwaysbesimpletouse,followingthepatternofthestandardrolesprovidedbyMicrosoft.Animportantdesignprincipleistothinkoftheuser'sroles,andnottothinkofspecificusers,whichshouldresultinthefollowingoutcomes:

ReducednumberofrolesLesscomplicatedassignmentofuserstorolesRolesareeasiertomaintain,withreducedriskoferrors,suchastheunintendedassignmentofaprivilegetoauser

Page 470: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 471: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Creatingprivileges

Privilegesarenormallycreatedforeachmenuitem(display,output,oraction)foranaccesslevel.Everymenuitemshouldbeinaprivilege,butyoucanaddmorethanonemenuitemtoaprivilegeiftheymustneverbeassigneddifferentpermissions,suchasmenuitemsthatpointtothesameform.Thisisthemostgranularlevel,andwillbegroupedintodutiesandroleslater.

Sincetheprivilegeassignstheaccesslevel,wewillusuallyhavetwo--toprovideviewonly,andtomaintain(full)accessrights.

Page 472: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 473: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyWewilljustneedanOperationsprojectopeninVisualStudio.

Page 474: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 475: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Tocreateaprivilegetoprovideviewaccesstothevehicleform,followthesesteps:

1. Choosetoaddanewitemtotheproject.2. IntheAddNewItemdialog,selectSecurityfromtheleft-handlistandSecurityPrivilegefromthe

right.3. EnterConWHSVehicleTableViewintheNamefieldandclickonAdd.4. CompletetheDescriptionproperty;thisshoulddescribetothesecurityadministratorwhatthis

privilegegrantsaccessto.5. CompletetheLabelpropertybygivingitashortdescriptiontothesecurityadministrator,suchasView

vehiclerecords.

Itcanbeagoodideatoincludeatextorevenaprefixinthetexttouniquelyidentifywhichmodulethissecurityelementrelates.ThesecurityadministratorwillonlybeabletoeasilyseetheLabelandDescriptionproperties,anditcouldbeeasytoconfusethesecurityelementwithanother.

6. Inthedesigner,dragtheConWHSVehicleTablemenuitemontotheEntryPointsnode.7. Changetheentrypoint'sAccessLevelpropertytoRead.8. Tocreatetheprivilegetomaintainthevehicletable,createanewprivilegenamed

ConWHSVehicleTableMaintain.9. CompletetheDescriptionproperty.10. CompletetheLabelproperty.Forexample,Maintainvehiclerecords.11. Inthedesigner,dragtheConWHSVehicleTablemenuitemontotheEntryPointsnode.12. Changetheentrypoint'sAccessLevelpropertytoDelete.13. Shouldtheformhaveanyassociateddataentities,suchasthosethatallowustoedittheform'sdata

inExcel,theyshouldalsobeaddedtotheprivilegeundertheDataEntityPermissionsnodewiththeappropriateaccesslevel.

Page 476: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 477: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

Theprivilegesaresimplyawaytograntpermissionstoanentrypoint,whichcanbeservices,toaduty,role,orevendirectlytoauser.Typically,weonlyaddentrypointstoaprivilege,suchasmenuitems.Inordertograntaccesstotheuser,thesystemappliestheaccessleveltoformcontrolsanddatasources.WhenwesettheNeededPermissionpropertyonformcontrols,itcanhavetheeffectofhidingthecontroliftheprivilegedoesn'tgranttheneededpermission.

Sincewecan'textendsecurityprivileges,wewouldalwayscreateanewprivilege.Thisisnotarealrestriction,andhelpsenforcegoodpractice;wecan'tgetmoregranularthanaprivilege,intermsofassigningpermissionstoauser.Weshouldneverover-layer(customize)anexistingsecurityprivilege,asthereisnoneedasdutiesandrolesareextensible.

Page 478: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 479: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...

Onthevehicleform,wehaveabuttonthatallowsustochangethevehiclegroup,andthisshouldbehiddenfortheviewprivilege.Whenaddingcontrolstoforms,wherethesystemcan'tdeterminetheneededpermission,wemustsetitonthecontrolwhilstdesigningtheform.Inourcase,thebuttonwascreatedwithaNeededPermissionpropertyofUpdate.So,thebuttonwillbehiddenwhentheprivilegesgrantedtotheuserarelessthanUpdate.

Insomecases,wemaywishtoelevatethepermissionlevelabovethatoftheprivilege'saccesslevel.Thisisaveryspecialcase,andisdonebyfollowingthesesteps:

1. ExpandtheEntryPointsnodeandtheConWHSVehicleTableentrypoint.2. Right-clickontheControlsnodeandchooseNewControl.3. IntheNameproperty,typethenameofthecontrol,suchasConWHSVehicleGroupChangeController.4. SelecttheappropriateGrantpropertyandtograntaccess,makeitUpdate.

WecanalsousetheFormControlPermissionsnodetograntthistypeofpermissiontoformsnotassociatedwiththemenuitem.

Page 480: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 481: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ImpactonlicensingOperationsislicensedbasedonnamedusers.Basedonyourorganization'srequirements,amixofclientaccesslicense(CAL)typescanbebought.

Theuser'sCALtypeisdeterminedbytheentrypoints(effectively,themenuitems)towhichtheyhavereadaccessorhigher.Foreachmenuitem,wehavetwopropertiesthatcontrolwhichlicensetypewillberequired:

ViewUserLicense:ThisiswhenauserisgivenReadaccesstothismenuitemMaintainUserLicense:ThisiswhenauserisgivenUpdateorhigheraccesstothismenuitem

TheCALtypeisdeterminedbythehighestuserlicensetypetowhichtheuserisassigned.Wearenotforcedtoentervaluesinthesemenuitemproperties,butitwouldbeabreachofthelicenseagreementtocreateamenuitemtoopenastandardformifwedon'tmatchtheuserlicensetypeoftheoriginalmenuitem.Microsoftreservestheright,attheirexpense,toinspectthesystem.

Securityadministratorsshouldplanacyclicapproachtosecurity.CheckwiththeNamedUserLicenseCountsreportandadaptthesecuritysetuptoensurethatyouarecomplyingwiththelicense.Checkingandmaintaininglicensecomplianceisthelicenseholder'sresponsibility.

Page 482: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 483: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

SeealsoSecurityanddataentities(https://docs.microsoft.com/en-us/dynamics365/operations/dev-itpro/data-entities/security-data-entities)

Page 484: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 485: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingdutiesAdutyisacollectionofoneormoreprivileges.Theparadigmisthatwearecreatingalistofdutiesthattherolewillperform,and,therefore,addtherequiredprivilegeinorderfortheroletobeabletoperformthatduty.

Itiscommontohaveonlyoneprivilegeinaduty,butmorecanbeadded,forexample,thesetupformsmaybeaddedtooneduty.

Dutynamesaresuffixedwithaverbtodenotetheactionthedutywillallowtheroletoperform;commonsuffixesareMaintain,Inquire,andApprove.Inordertodeterminethecorrectsuffix,lookatthestandarddutiesandsuffixyourdutiesusingthesamenamingconvention.

Page 486: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 487: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Tocreateaduty,followthesesteps:

1. Choosetoaddanewitemtotheproject.2. IntheAddNewItemdialog,selectSecurityfromtheleft-handlistandSecurityDutyfromtheright.3. EnterConWHSVehicleTableInquireintheNamefieldandclickonAdd.4. CompletetheDescriptionproperty;thisshoulddescribetothesecurityadministratorwhatthisduty

does,suchas"Respondstoinquiriesintovehiclerecords".5. CompletetheLabelpropertybygivingashortdescriptionforthesecurityadministrator,suchas

Inquireintovehiclerecords.6. Inthedesigner,dragtheConWHSVehicleTableViewsecurityprivilegeontothePrivilegesnode.7. Repeatthisforalldutiesrequired,forexampleaConWHSVehicleTableMaintaindutythatwillhavethe

ConWHSVehicleTableMaintainprivilege.

Page 488: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 489: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

Youcanconsiderthatadutyisacollectionofsecurityprivileges,whichitis;however,whendesigningthesecuritymodel,wewoulddothistheotherwayaround--wedesigntherolewiththerequiredduties,andthenaddtherequiredsecurityprivilegetosupporttheduty.

Page 490: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 491: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There’smore…

Whenextendingthestandardapplication,weoftencreatenewformsandthereforenewmenuitems.Thesecurityprivilegemaythereforeberequiredforanexistingduty.Inthiscasewewouldcreateanextensionoftherequiredduty.Todothis,wewouldrightclickonthatduty,andchooseCreateextension,rememberingtochangethe.extensionsuffixtoonetherelatestothemodelweadeveloping:applicationelementsbelongtoamodel,andmodelsbelongtoapackage.Wecanthenjustdragtheprivilegefromtheprojectontotheduty.Thissameprocesswouldbefollowedshouldwewishtoaddanewdutyandanstandardrole-oranexistingroleinadifferentpackage.

Page 492: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 493: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingsecurityrolesAroleisacollectionofdutiesandprivileges.Theroleiswhatweassociatewithauser,whichcanbedoneautomaticallybasedontheemployee'sinformation,suchastheirpositioninthecompany.ThesecurityrolesshouldbethoughtofintermsofthepersonasthatfirstappearedwithDynamicsAX2012.Thechangeisintendedtomovethethinkingawayfromcreatinggroupsoffunctionalitytodesigntherolesbasedonhowtheorganizationisstructured.Forexample,aSalesmanagerwouldbeinaSalesmanagerrole,whichwillhavedutiesassigned.Thedutieshaveprivileges,whichinturngiveaccesstothesalesmanagerinordertoperformthatrole.

Inourcase,wecouldconsiderthattheyhavethreeroles:vehiclemanagementsupervisor,vehicleservicesupervisor,andvehicleserviceentryclerk.Whendefiningtheroles,wedosobydefiningthedutiesthateachrolewillhave.Thenamingconventionissimilartootherobjects,andsuffixedwiththetypeofrole.ThesetypesincludeSupervisor,Manager,Clerk,orothersshouldthesenotfittherequiredrole;forexample,ConWHSVehicleManagerwouldhavetheMaintaindutiesforvehiclemasterdata.

Wecanaddprivilegesdirectlytoarole,butweshouldbestrictandonlyadddutiesdirectlytoarole.Thisassistsinmaintenance,and,additionally,itisabestpracticechecktohaveallprivilegesinoneormoreduties,andalldutiesmustbeinoneormoreroles.

Wecanalsoaddrolesasasub-role.Thismayhelpinrarecases,but,again,trytoavoidthis.Itmakesmaintenancealittlemorerestrictiveforthesecuritymanager.Theymaywanttograntorrestrictaccesstothesub-rolewithoutchangingtherightsoftheparentrole.

Page 494: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 495: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Tocreatearole,followthesesteps:

1. Choosetoaddanewitemtotheproject.2. IntheAddNewItemdialog,selectSecurityfromtheleft-handlistandSecurityRolefromtheright.3. EnterConWHSVehicleManagerintheNamefieldandclickonAdd.4. CompletetheDescriptionproperty;thisshoulddescribetothesecurityadministratorwhatthisduty

does,suchasManagersvehiclemasterdata.5. CompletetheLabelpropertybygivingashortdescriptiontothesecurityadministrator,suchas

Responsibleforthemaintenanceofvehiclerecordsandassociatedsetupdata.6. Inthedesigner,dragthedutiesthatprovidefullrightstothevehicleandsetupmenuitemsontothe

Dutiesnode.7. Repeatthisforallrolesrequired.

Page 496: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 497: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

Technically,thisisstraightforward.Thecomplicatedpartisdesigningthesecuritymodelwiththecustomerinordertohaveacommonviewofsecurityfromahumanresourceperspective.Whensecurityrolesaresynchronizedwiththeorganizationalhierarchy,securitybecomesmoreofahumanresourcemanagementprocessthananITadministratorrole.

Page 498: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 499: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Seealso...Role-basedsecurity(https://docs.microsoft.com/en-us/dynamics365/operations/dev-itpro/sysadmin/role-based-security)

Page 500: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 501: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Creatingpolicies

Theterm"Securitypolicies"isaslightmisnomer.ItisalsoknownunderamoreaccuratetermofExtensibleDataSecurity(XDS).ItisanevolutionofrecordlevelsecuritythatwasdeprecatedfromAX2012:youcouldstilldothis,butitwasn'tarecommendedapproach.

Inthisscenario,wewillcreateapolicythatonlyallowsaccesstovehiclesoftypetruck.Inthisscenario,wehaveateamthatonlyhasaccesstotruckswhencreatingserviceorders.

Page 502: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 503: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Tocreatearole,followthesesteps:

1. Choosetoaddanewitemtotheproject.2. IntheAddNewItemdialog,selectDataModelfromtheleft-handlistandQueryfromtheright.3. EnterConWHSVehicleTruckPolicyintheNamefieldandclickonAdd.4. Inournewquery,dragtheConWHSVehicleTabletabletotheDataSourcesnode.5. AddtheVehicleTypefieldtotheFieldslistbyright-clickingontheFieldsnodeandchoosingNew|

Field.6. DragthefieldontotheRangesnode.7. OnthenewVehicleTyperange,changetheValuepropertytoTruck.

Whendeployed,thiswillbeupdatedforus.Wecanalsousetheenum'svalueasthetypeisnotextensible.

8. Saveandclosethequerydesigner.9. Addanewitemtotheproject.10. IntheAddNewItemdialog,selectSecurityfromtheleft-handlistandSecurityPolicyfromtheright.11. EnterConWHSVehicleTruckPolicyintheNamefieldandclickonAdd.12. SetLabeltoVehiclemanagementclerkvehicletableaccesspolicy.

13. SetHelpTexttoRestrictsaccesstothevehicletablesothatonlytruckscanbeselected.

14. SetPrimaryTabletoConWHSVehicleTable.ThistellsOperationsthenameofprimarytableintheQueryproperty.

15. EnterConWHSVehicleTruckPolicyintotheQueryproperty.16. LeaveUseNotExistJoinasNo.Otherwise,thiswouldhavetheeffectofmakingthepolicyallow

inactivevehiclesonly.17. SetConstrainedTabletoYes.18. LeaveOperationasSelect;weareintendingthispolicytocomeintoeffectwhenselectingrecords.19. UsingtheCreatingsecurityrolesrecipe,createaroleforaTruckserviceentryclerkcalled

ConWHSVehicleTruckServiceClerk.20. SetContextTypetoRoleNameandenterConWHSVehicleTruckServiceClerkintheRoleNameproperty.21. Finally,settheEnabledpropertytoYes.

Page 504: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 505: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Whentheuserinthepolicy'sroleopensaform,orwhenadrop-downisdisplayed,thesystemwillcreateaquerythatcombinestheform'sdatasourcewiththepolicy'squerydefinitionasanExistsorNotExistsjoin.Thequerycannotbechangedbytheuser,andisenforcedatkernellevel.

Here'sawordofcaution:sincethedatapolicyisdefinedusingaquery,itcouldaddsignificantserverload,especially,whenthequeryisnotwrittenefficiently.Inordertousepolicies,thereshouldbeaclearbusinesscase,anditmaybemoreappropriatetohaveasecondarylistpageinstead.Thepoliciesshouldbebasedonbusinessprocessrules,whereaccessmustberestrictedforsecuritypurposes.SeethelinkinSeealsoformoredetails.

So,creatingapolicythatisbasedondatatheuserenters,suchasvehiclesforaparticularvehiclegroup,indicatesthatitisn'tapolicybutafilteronthevehicleslistpage.Policiesarenormallybasedonrulesthatareunaffectedbyusersetup,suchasanenum.

Thiscouldveryeasilyhaveanimpactonperformance,sotherangesandjoinswecreateinthequerymustbewrittencorrectlyandcoveredbyappropriateindexes.

Page 506: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 507: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...Wecanalsoconstrainrelatedtablesandviews;forexample,restrainingaccesstovehicleservicerecordsthatarenotlinkedtovehiclesoftypeTruck.

Wecandothisintwoways--tablesthathavearelationdefined,andthosethatdon't,whichincludesviews.

Toaddaconstrainedtable,followthesesteps:

1. Right-clickontheConstrainedTablesnodeandchooseNew|ConstrainedTable.2. SettheConstrainedpropertytoYes.

Constrainedtablescanbenested,andwecanaddintermediarytablestoeventuallygettothetablewewanttoconstrain.Wemaynotwanttheseintermediarytablestobeconstrained,andwouldthereforeleavethisvaluetobeNo.

3. SettheNamepropertytoConWHSVehicleServiceTable.4. SelectConWHSVehicleTableintheTableRelationproperty.

Toaddaview,oratablewithoutarelation,usetheConstrainExpressionoptioninstead.Inthiscase,wehaveaValueproperty,whereweentertherelation.Forexample,ConWHSVehicleTable.VehicleId==ConWHSVehicleServiceTable.VehicleId.Thevalueisnotvalidated,sowemustensurethatitiscorrect.

Page 508: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 509: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Seealso...BestPractices,TipsandTricksforImplementingXDS(ExtensibleDataSecurity)policies(https://blogs.msdn.microsoft.com/daxserver/2013/06/26/best-practices-tips-and-tricks-for-implementing-xds-extensible-data-security-policies/)Securityarchitecture(https://docs.microsoft.com/en-us/dynamics365/operations/dev-itpro/sysadmin/security-architecture)SecurityPoliciesPropertiesforAX2012(https://msdn.microsoft.com/en-us/library/gg731857.aspx)

ThisisstillrelevantinDynamics365forOperations

Page 510: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 511: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

LeveragingExtensibility

Inthischapter,wewillcoverthefollowingrecipes:

ExtendingstandardtableswithoutcustomizationfootprintCreatingdataeventhandlermethodsHowtocustomizeadocumentlayoutwithoutanover-layerExtendingstandardformswithoutcustomizationfootprintUsingaformeventhandlertoreplacealookupCreatingeventhandlermethodsCreatingyourownqueryfunctions

Page 512: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 513: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

IntroductionThefocusonthischapterishowtoextendthestandardsoftwarewithoutanover-layer.TothosethatcomefromDynamicsAX2012orprior,itmaynotseemabigissue:over-layeringwastheonlychoice,andwhencomparedtootherproductsinthespace,itwasoneofitskeyfeatures.Whenweover-layer,thesystemcopiesthecodeintothepackage'slayerandthedevelopermakeschangestothiscopy.ItisallseamlessandhasworkedwellsinceAxapta1.5.

Theproblemiswhenweneedtoservicetheapplication,suchasdeployingahotfix,wewillneedtocheckforconflictsandpotentiallyupdateourcode.Also,ifwehaveanISVsolutionthatover-layersapackage,wewillneedanupdatefromtheISVifthehotfixmodifiesanover-layeredelement.

Theotherdownsideofover-layeristhatwecan'thavetwoISVsolutionsthatover-layerthesameelement.Thishasalwaysbeenaproblem,anditcanonlybesolvedbymanuallymerginginahigherlayer.

ThesolutionMicrosofthasforthisiscalledextensibility.Thisiswherewestorethechangeswewanttomakeinadeltachangeformatinaseparateobject.ThismeansthattwoISVsandthecustomercouldalladdfieldstotheSalesLinetable,andnotversion-lockthecustomer.Allofthesefieldsaremergedbythesystem,andthetablewillhaveallfieldsinallpackagesaddedtothetableinSQL.Thereisnolongeraneedtohaveseparatetableswiththeadditionalfieldsandcodeinordertoavoidover-layeringatable.

Page 514: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 515: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ExtendingstandardtableswithoutcustomizationfootprintTableextensionsprovideawaytoaddfieldstoatablewithoutover-layering.Thismeansthatwedon'tneedtoperformacodemergewhenthebasepackageischanged.

Inthisrecipe,wewilladdafieldtotheSalesConfirmHeaderTmptable,whichwewilluseintheHowtocustomizeadocumentlayoutwithoutanover-layerrecipe.

Itiscommontohavethemaindevelopmentworkinonepackage,andreportsinahigherpackage,asthiscanhelpwithdeployingupdates.Whendecidingwhichpackagethistableextensionshouldbedonein,weneedtoconsiderthescopeofitsusageshouldbe.Ifwecreatetheadditionalfieldsinareportingpackage,whichreferencesamaindevelopmentpackage,themaindevelopmentpackagewillnotbeabletousethisfield.

Sometimesareportdrivestherequirementforadditionalfields.So,itcanseemnaturaltoaddthenewfieldstoatableextensioninthereportingpackage.Thismeansthatwewillneedatableandformextensioninthereportingpackage,whichmayseemfineuntilapieceofcodeinthemaindevelopmentpackageneedsthefield.Thereferencesinpackagesonlygoesinonedirection,themaindevelopmentpackagecannotseetheadditionalfieldsinthereportingpackage-eventhoughtheyphysicallyexistinthebasetable.

Intheabovetypeofscenario,thefieldsshouldbeaddedtoanewprojectinthemaindevelopmentpackage.LookbacktoChapter1,StartingaNewProject,forthediscussiononpackages.

Intheexampleinthisrecipe,thefieldisspecifictoareportingpackage,becausethefieldisaddedtoatemporarytablethatisonlyusedforthereportgeneration.

Page 516: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 517: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyCreateanewprojectinanewmodeltoreport,forexample,ConReports,asanewpackage,whichisanextensionpackage.

Page 518: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 519: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Toaddafieldtoatableasanextension,followthesesteps:

1. LocatetheSalesConfirmHeaderTmptableintheApplicationExplorer.2. Right-clickonitandchooseCreateextension.3. Rememberingthatallelementnamesmustbegloballyunique,renamethenewextensionfrom

SalesConfirmHeaderTmp.extensiontoSalesConfirmHeaderTmp.ConReports.4. Openthetableextensioninthedesigner.5. LocatetheEDTnameintheApplicationExplorer.6. DragtheNameEDTtotheFieldsnode.7. RenamethefieldtoConSalesPoolName.8. SettheLabelpropertyto@SYS84547(Salespool).9. Saveandclosethedesigner.

Page 520: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 521: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Thisrecipeiscloselylinkedtothenexttworecipes,andsomeexplanationisrequiredastohowweknewwhichtablestomodify.Tokeepthisinformationinoneplace,thisexplanationisintheThere'smore...sectionoftheHowtocustomizeadocumentlayoutwithoutanover-layerrecipe.

Thiscreatesanentitythatonlystoresthechangestotheelementbeingextended.Thisistrueofallextensions.Thereisnocopyingfromlayertolayer;itisareferenceandalistofchanges.

Theactualsourceofthetableextensionisshowninthefollowingpieceofcode:

<?xmlversion="1.0"encoding="utf-8"?>

<AxTableExtensionxmlns:i="http://www.w3.org/2001/XMLSchema-instance">

<Name>SalesConfirmHeaderTmp.ConReports</Name>

<FieldGroupExtensions/>

<FieldGroups/>

<FieldModifications/>

<Fields>

<AxTableFieldxmlns=""

i:type="AxTableFieldString">

<Name>ConSalesPoolName</Name>

<ExtendedDataType>Name</ExtendedDataType>

<Label>@SYS84547</Label>

</AxTableField>

</Fields>

<Indexes/>

<Mappings/>

<PropertyModifications/>

<Relations/>

</AxTableExtension>

Whentheprojectisbuiltandthedatabasesynchronizationisdone,thephysicaltableSalesConfirmHeaderTmpwillhavethebasefieldsplusthefieldsinallofitsextensions.Thisiswhythefieldneedstobeprefixed.

Page 522: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 523: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...Youcanalsoextendthefieldgroups.Thisallowsustoaddfieldstoafieldgroupthatisusedinformstokeepthepresentationconsistentandreducethenumberofchangesandcodemaintenanceeffort.

Wecanalsoaddindexesandrelations.

TheindexesarecreatedwithinthephysicaltableinSQL,sothesemustalsobeprefixedtoensurethatwedon'tcauseanycollisions(duplicatename).

Page 524: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 525: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Creatingdata-eventhandlermethodsThedata-eventhandlershandlethedelegatesexposedoneverytable.ThesedelegatesarelistedundertheEventsnode.

Theseeventsdonotfireiftheassociatedmethod(forexample,insert)isoverriddenonthetableandsuper()isnotcalled.

Howtheeventhandlermethodsareorganizedisuptothedeveloper;theyjustneedtobeplacedlogicallysothatotherswillfindthemeasily.

Inourcase,theeventhandlerisusedpurelyforareportandpopulatingafieldoninsertthatisusedinareport.So,wewillplacethesemethodsinahelperclass.

Page 526: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 527: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyThisrecipecontinuesfromthepreviousrecipe.

Page 528: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 529: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Tocreateadataeventhandler,followthesesteps:

1. CreateanewConReportManagerclass.2. Openthetabledesignforthetableinquestion;inourcase,double-clickonthe

SalesConfirmHeaderTmp.ConReportstableextension.3. ExpandEventsandlocateonInserting.

TheonInsertedeventistoolateaswewanttofillinafieldbeforetherecordiswritten;ifwesubscribedtoonInserted,thedatawillnotbesaved.

4. Right-clickontheeventandchooseCopyeventhandlermethod.Thiswillcreateacodesnippet,andplaceitinthepastebuffer.

5. OpentheConReportManagerclass,andpasteinthecodegeneratedbystep4intotheclassbody,asshownhere:

classConReportManager

{

///<summary>

///

///</summary>

///<paramname="sender"></param>

///<paramname="e"></param>

[DataEventHandler(tableStr(SalesConfirmHeaderTmp),

DataEventType::Inserting)]

publicstaticvoidSalesConfirmHeaderTmp_onInserting(

Commonsender,DataEventArgse)

{

}

}

Lookattheeventdeclaration:itreferencesthetable,nottheextension.Thisisimportant,astheextensionisnotatable,andthesystemcorrectlychangesthesubscriptiontothecorrecttable.

6. Next,wewillneedtowritethecode,andtheobviouscodetowritemayseemtobeasfollows:

SalesConfirmHeaderTmpheader=sender;

SalesTablesalesTable;

selectSalesPoolId

fromsalesTable

whereSalesTable.SalesId==header.SalesId;

header.ConSalesPoolName=SalesPool::find(

SalesTable.SalesPoolId).Name;

Thiswouldbewrong,asweassumethatthesalesorderrecordwillneverbedeleted-andonceinvoiced,theycanbedeleted.

Page 530: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

WhatweshoulddoisaddSalesPoolIdtotheCustConfirmJourtable,whichisthepermanentrecordofthatconfirmation.

7. CreateanextensionofCustConfirmJour,calledCustConfirmJour.ConReports.8. DragtheSalesPoolIdEDTfromtheApplicationExplorer,andrenameittoConSalesPoolId.9. Right-clickontheonInsertingeventandchooseCopyeventhandlermethod.10. PastethemethodintothebodyofourConReportManagerclass.11. Adjustthemethodsothatitreadsasfollows:

///<summary>

///Handlestheinsertingeventof<c>CustConfirmJour</c>

///</summary>

///<paramname="sender"></param>

///<paramname="e"></param>

[DataEventHandler(tableStr(CustConfirmJour),

DataEventType::Inserting)]

publicstaticvoidCustConfirmJour_onInserting(

Commonsender,DataEventArgse)

{

CustConfirmJourjour=sender;

jour.ConSalesPoolId=jour.salesTable().SalesPoolId;

}

Therecordispassedbyreference,andwemustnotcallinsertorupdate.

12. Finally,adjustourSalesConfirmHeaderTmphandlersoitreadsasfollows:

///<summary>

///Handlesinsertingevent<c>SalesConfirmHeaderTmp</c>

///</summary>

///<paramname="sender">Thecallingrecord</param>

///<paramname="e"></param>

[DataEventHandler(tableStr(SalesConfirmHeaderTmp),

DataEventType::Inserting)]

publicstaticvoidSalesConfirmHeaderTmp_onInserting(

Commonsender,DataEventArgse)

{

alesConfirmHeaderTmpheader=sender;

CustConfirmJourjour;

selectConSalesPoolId

fromjour

wherejour.RecId==header.JournalRecId;

header.ConSalesPoolName=

SalesPool::find(jour.ConSalesPoolId).Name;

}

13. Saveandclosethedesigner.14. Totestthis,performafullbuildincludingdatabasesyncandcreateasalesconfirmation.Then,

checkthattheSalesPoolIdfieldwaspopulatedintheCustConfirmJourtable.

Page 531: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 532: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

Theeventhandlerswewroteinthisrecipeareboundwhenthepackageisbuilt--nochangesaremadetothebasepackagesandwecanshipthepackageasadeployablepackagetoadifferentsystemanditshouldallworkfine.

Thesubscribersarenotcalledinanyorder,andthiscannotbereliedupon.So,thecodemustbewritteninawaythatdoesn'tmakethisassumption.Whenconsideringtransactiondurability,theeventsarecalledwithinthetransactionofthecaller--andthrowinganexceptionwillcausethewholetransactiontobeaborted.

Page 533: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 534: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...Addingfieldsasanextensionisrelativelystraightforward,butwealsoneedtohandleeventssuchasmodifiedFieldandvalidateField.

Iftheseareoverriddenonthetable,wecoulduseaPreorPost-eventhandler.However,thesearenotalwaysoverridden,andthesetypesofeventhandlersshouldalwaysbethelastchoice;thesearediscussedlaterinthischapter.

Tohandletheseevents,wewouldsubscribetotheappropriatedataevent.Therearetwomethodsforeach:apresent-continuous(ing)methodandapasttense(ed)method.

Theingmethodsarecalledbeforetheeventfires,andtheedmethodfiresaftertheevent.Theyonlyfireonsuper();so,ifthedeveloperdoesn'tcallsuper()inthemethod,theeventwillnotfire.ThisisthecaseonSalesLine,SalesTable,PurchLineandPurchTable,forexample.

LocatethetableInventTableintheApplicationExplorer,rightclickonitandchooseOpendesigner.ExpandtheEventsnode,andrightclickontheonValidatedFieldevent(whichisactuallyadelegate).Let'susetheCopyeventhandlermethodoptionwhichwillplacethefollowingcodeinthepastebuffer:///<summary>//////</summary>///<paramname="sender"></param>///<paramname="e"></param>[DataEventHandler(tableStr(InventTable),DataEventType::ValidatedField)]publicstaticvoidInventTable_onValidatedField(Commonsender,DataEventArgse){}

Thishelps,buttheDataEventArgsobjectdoesn'tcontainanythingaboutthefieldbeingvalidated,oramethodbywhichwereturnfalseshoulditnotbevalid.

Youcanthenchangethecodeusingthefollowingpattern:///<summary>///Validatesthefieldsonthe<c>InventTable</c>table///</summary>///<paramname="sender"></param>///<paramname="e"></param>[DataEventHandler(tableStr(InventTable),DataEventType::ValidatedField)]publicstaticvoidInventTable_onValidatedField(Commonsender,DataEventArgse){ValidateFieldEventArgsfieldArgs=e;

Page 535: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Booleanok;switch(fieldArgs.parmFieldId()){casefieldNum(InventTable,ProdPoolId)://if(<condition>)//{//ok=checkFailed("message");//}break;}if(!ok){fieldArgs.parmValidateResult(false);}}

WechosetousetheonValidateFieldeventdelegatebecausewewouldneverhandlefieldvalidation,ormodifyeventsontablesthatwedon'tenterdatainto.Asdiscussedinpreviouschapters,theseeventsaretriggeredfromtheformcontrol,passedthroughtothedatasource,andfinallythetable.

ShouldweaddafieldtoanextensionofInventTable,thecodeintheeventhandlerwouldbethesame;thatisthetableStr,methodStr,andfieldNumfunctionswouldalluseInventTableandnotthenameoftheextension.Theeventhandlerissimplytriggeredwhentheeventhappens,thecodewewritewillbeabletoseeallfieldsaddedtothecurrentpackageandfieldsinallpackagestowhichthecurrentpackagereferences.

TheotherspecializedclassesunderDataEventArgsthatareusefulareasfollows:

Class Usage

ValidateFieldEventArgs

onValidatedField

onValidatingField

ValidateEventArgs

onValidateWrite

onValidatingWrite

onValidateDelete

onValidateingDelete

ModifyFieldEventArgs

onModifiedField

onModifyingField

Page 536: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 537: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtocustomizeadocumentlayoutwithoutanover-layerTheexamplehereistoaddanextensionfieldtoaprint-managedstandarddocumentwithoutover-layeringthereport.Wewillusethesalesorderconfirmationreporttoaddthesalesorderpool'snametothereport.

Therearetwomaintypesofreports:listingreportsanddocuments.Thedocuments,suchasthesalesorderconfirmationdocument,usetemporarytablestomakethelayouteasiertowrite.Anyreportcanusethistechnique,butitismorecommonondocumentlayoutsandcomplicatedlistingreports.

Itisoftengoodpracticetohaveaseparatemodelforreports.Reportscanuseelementsthatwehavewrittenacrosspackages,forexample,wemayhaveanextensionpackageandanISVpackagethathaselementswewishtoreporton,butwedon'twanttolinkourpackageasadependencyontheISVpackage.

Wewon'tcovertheactualreportdesigninthisrecipe,asreportdesignisbeyondthescopeofthisbook;butwewillcoverallotherareas.

Wehavealreadyaddedthefieldsandeventhandlersinthepriortworecipes;wejustneedtoaddthefieldtothereport.

Page 538: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 539: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Toaddafieldtothereport,followthesesteps:

1. LocatetheSalesConfirmreportintheApplicationExplorer.2. Right-clickonthereportandchooseDuplicateinproject.3. RenamethereporttoConSalesConfirm.4. OpenthereportinthedesignerandexpandtheDatasetsandSalesConformHeaderDSnodes.Then,

lookforthenewextensionfieldintheFieldsnode.Ifthisdoesnotappear,right-clickoneachdatasetandchooseRestore.

5. Youcannowproceedtodesignthereportasperyourrequirements.

Thenextstageisintegratingthereportsothatournewreportisusedinsteadofthestandardreport.Todothis,followthesesteps:

1. WewillneedtoaddaneventhandlertoadelegateexposedbyMicrosoftonthePrintMgmtDocTypeclasscalledgetDefaultREportFormatDelegate.Thefollowingcodedoesthis,thecommentedoutsectionsarethereforreferenceshouldyouwishtoextendotherdocuments:

///<summary>

///AllowstheSSRSReportusedforthePrint

///managementbasedreportstobeoverridden

///</summary>

///<paramname="_docType">ThePrintMgmtDocumentType

///</param>

///<paramname="_result">TheEventHandlerResult</param>

[SubscribesTo(classstr(PrintMgmtDocType),

delegatestr(PrintMgmtDocType,

getDefaultReportFormatDelegate))]

publicstaticvoidDefaultReportFormat(

PrintMgmtDocumentType_docType,

EventHandlerResult_result)

{

switch(_docType)

{

casePrintMgmtDocumentType::SalesOrderConfirmation:

_result.result(

ssrsReportStr(ConSalesConfirm,Report));

break;

casePrintMgmtDocumentType::WHSPickListShippingLoad:

casePrintMgmtDocumentType::WHSPickListShippingShipment:

casePrintMgmtDocumentType::WHSPickListShippingWave:

//_result.result(

//ssrsReportStr(ConPickListShipping,

//Report));

break;

casePrintMgmtDocumentType::WHSPickListProd:

//_result.result(

//ssrsReportStr(ConPickListProduction,

//Report));

break;

casePrintMgmtDocumentType::SalesOrderPackingSlip:

//_result.result(

//ssrsReportStr(ConSalesPackingSlip,

//Report));

break;

casePrintMgmtDocumentType::SalesOrderInvoice:

//_result.result(

//ssrsReportStr(ConSalesInvoice,Report));

break;

Page 540: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

casePrintMgmtDocumentType::SalesFreeTextInvoice:

//_result.result(

//ssrsReportStr(ConFreeTextInvoice,Report));

break;

casePrintMgmtDocumentType::CustAccountStatement:

//_result.result(

//ssrsReportStr(ConCustAccountStatement,

//Report));

break;

casePrintMgmtDocumentType::PurchaseOrderRequisition:

//_result.result(

//ssrsReportStr(ConPurchPurchaseOrder,

//Report));

break;

casePrintMgmtDocumentType::PurchaseOrderReceiptsList:

//_result.result(

//ssrsReportStr(ConPurchReceiptsList,

//Report));

break;

casePrintMgmtDocumentType::CustCollectionLetter:

//_result.result(

//ssrsReportStr(ConCustCollectionJour,

//Report));

break;

}

}

2. Saveandclosethedesignersandbuildtheproject.Youwillneedtodeploythereportsoncethisisdoneinordertotestit.Todothis,right-clickonthereportandchooseDeployreports.

Page 541: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 542: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

Eventhoughthedatasetinthereportisthebasetable,itshowsourextensionfields.Itwillalsoshowallextensionfieldsforthepackagesthatthereportspackagereferences.Theprocessofreportdesignisstraightforwardfromthatpointonwards.

TheintegrationhasbeenwellthoughtoutbyMicrosoftandhas,therefore,exposedadelegatethattheyhandle.Inthemethodthatcallsthisdelegate,thecodedeterminesifithasbeenhandledandwillusetheresultargumentsforthereportname.

Page 543: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 544: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Theremore...Someknowledgeofthedatabasewasrequiredinordertoknowwhichtableswehadtoaddextensionfieldsto,andjustreadingthecodeisnotonlydaunting,butalsoimpractical.Thisexamplewaschosenspecificallyasitisn'teasytofind.Thisrecipeismucheasierwhenthefieldistobeaddedtoaform.WecanseethemenuitemintheURLintheclient,andfromthere,findtheformandthetable.

ThetemporarytablesusedwerefoundsimplybyopeningtheSalesConfirmreportandlookingatthedatasourcesituses.Weknewwhichreportitwas,basedonconsistentnamingconventions.

SalesdocumentsstartwithSales,andarefollowingbythedocumenttype,whichisasfollows:

SalesQuotationSalesConfirmSalesPackingSlipSalesInvoice

PurchasingdocumentsstartwithPurch,andarealsosuffixedwiththedocumenttype:

PurchPurchaseOrderPurchReceiptsListPurchPackingSlipPurchInvoice

ThesedocumentsarecreatedthroughtheFormLetterframework.Thepatternisthateachdocumenthasaheaderandlinestablethatformsthepermanentdata,allowingthereportstobereproducedexactlyastheywerewhenfirstprinted,eveniftheorderhasbeenchangedordeleted.Thesearereferencedasjournals.Thetablesfollowasimilarconvention,asshowninthefollowingtable:

Document Journalheadertable Journallinetable

SalesQuotation CustQuotationJour CustQuotationTrans

SalesConfirm CustConfirmJour CustConfirmTrans

SalesPackingSlip CustPackingSlipJour CustPackingSlipTrans

SalesInvoice CustInvoiceJour CustInvoiceTrans

PurchPurchaseOrder VendPurchOrderJour None

Page 545: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

PurchReceiptsList VendReceiptsListJour VendReceiptsListTrans

PurchPackingSlip VendPackingSlipJour VendPackingSlipTrans

PurchInvoice VendInvoiceJour VendInvoiceTrans

WhenweusedtheCopyeventhandlermethodoptionontheevent,thismerelycreatedavalidmethoddeclarationforuse.Itdoesnotmodifythesourcetableoraddanyreference.Thecodecanthereforebeaddedmanually;itwasjusttosavetime.Thereferenceiscreatedwhentheprojectisbuilt.

Page 546: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 547: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingeventhandlermethodsWehavealreadycreatedaneventhandlerinthepreviousrecipe.

Sofar,wehavecreatedaneventhandlerfordataeventsusingtheDataEventHandlerdecorationandadelegateeventhandlerusingtheSubscribesTodecoration.Wecanalsoaddhandlersdirectlytoanypublicmethod.

Therearetwotypes:apre-eventandapost-eventhandler.OneexampleofwherewemayneedtodothisistheSalesTable.insert()method.Thisdoesn'tcallsuper(),sowecan'tuseadataeventhandler.

TheactualinsertoccurswithintheSalesTableTypeclassintheinsert()method.Ifyouwantaccesstothesalestablerecord,youneedtoaddthehandlertothetableastherecordbeinginsertedisaprivatevariabletotheclass.

Eventhandlerslikethisareusedtointegratespecificsolutions,sothehandlerwillbeinaclassinthespecificpackage.

Page 548: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 549: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyWewilljustneedaDynamics365forOperationsprojectandaclassthatrequirestheeventhandleropeninthedesigner.

Page 550: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 551: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreateapre-eventorpost-eventhandler,followthesesteps:

1. Openthedesignerforthetableorclassthatwewanttoaddahandlerfor,suchastheSalesTabletable.2. Locatethemethodwewishtohandleandright-clickonit:selectCopyeventhandlermethod|Pre-

eventhandler(orPost-eventhandlerasrequired).Thiscreatesamethoddeclarationintothepastebuffer.

3. Pastethemethoddeclarationintothetargetclassforapre-eventhandleronSalesTable.Insert(),whichwillbeasfollows:

///<summary>

///

///</summary>

///<paramname="args"></param>

[PreHandlerFor(tableStr(SalesTable),

tableMethodStr(SalesTable,insert))]

publicstaticvoidSalesTable_Pre_insert(

XppPrePostArgsargs)

{

}

4. Whatwedoatthispointdependsontherequirements;thecommonmethodsinargsareasfollows:

Method Use

booleanexistsArg(str) Doesthehandledmethodhavetheparameter

AnyTypegetArThig(str)

Thisreturnsthemethod'sparametervalueusingtheparameter'sname.ItisreturnedasanAnyTypeobject.

Forexample,ifwearehandlingSalesTine.insert(Boolean_skipMethod=false),wecangetthevalueof_skipMethodusingargs.getArg('_skipMethod').

Thereisnovalidation;wewilluseexistsArgtocheckfirst.Thisisnotanintrinsicfunction,andwecan'tcauseacompilationerrortohighlightanyruntimeerrors.

AnyTypegetArgNum(int) Thisgetsthehandledmethod'sparameterbyusingitspositionfromtheleft.

AnyTypegetThis()

Thisgetstheinstanceoftheobject,inourcase,thecurrentSalesTablerecord.

Thereisnocompilervalidationonthetype,sowemustcheckthismanually.Again,wecan'tuseintrinsicfunctionstoforceacompilationerror.

AnyTypegetReturnValue

Thisisonlyusefulonpost-eventhandlersandgetsthevaluethemethodhasreturned.

Page 552: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

WewouldusuallyusethisinconjunctionwiththesetReturnValuemethod.

setReturnValue(AnyType)Thisisonlyusefulonpost-eventhandlersandletsusoverridethevaluethemethodreturns.

setArg(str,AnyType) Thisallowsustosetthemethod'sparameternamedinthismethod.

setArgNum(int,AnyType) Thisallowsustosetthemethod'sparameterbyitspositionfromtheleft.

5. Oncedone,saveandclosethedesigner.

Page 553: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 554: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Pre-eventandpost-eventhandlersworkinthesamewayastheothereventhandlers,butthesecarryariskofregression.Theeventswehandlethroughthistechniquearenotdelegates,andthedeveloperdidnotspecificallywritethemethodtobehandledinthisway:otherwisetheywouldhavewrittenadelegate.ThereasonwearerestrictedtopublicmethodsisbecausetheseareconsideredasapublicAPI,andwillnotbechangedordeprecatedwithoutaproperprocedure.Protectedmethodscanbechangedwithoutwarning,andwecould,therefore,findthatourcodenolongerworks.

Privatemethodsareprivateforareason:youcan'tguaranteetheinternalclassstate,orhowthesewillbecalled.

Thisalsoreinforcesthatweneedtobecarefulwhenassigningpublic,protected,orprivatetoamethod.Alwaysmakeyourmethodsasprivateaspossible,asthisnotonlyensuresthatyourcodecan'tbecallederroneously,butitalsomakesiteasierforotherdeveloperstouseyourcodecorrectly.

Page 555: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 556: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ExtendingstandardformswithoutcustomizationfootprintAdjustingthelayoutofaform,asanextension,hasbeenmadeveryeasyforus.Thisiscoveredinthefirstpartoftherecipe.Wewillalsocoveranewtechniquetoworkwithformcode.

Page 557: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 558: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyWejustneedaDynamics365forOperationsprojectopen.

Page 559: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 560: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Towriteaformextensionforthesalesorderform,SalesTable,followthesesteps:

1. LocatethedesiredformintheApplicationExplorer,rightclickonitandchooseCreateextension.Thiswilladdanewformextensiontoourproject.

2. Locatethenewformextensioninourproject,andrenameitsoitwillremaingloballyunique,forexample,SalesTable.Con.

3. Opentheformextensioninthedesigner.4. Wecannowdraganyfieldorfieldgroup,includingextensionfieldsthatareavailabletothecurrent

package,tothedesign.5. Wecanalsochoosetochangepropertiesofthecontrolsontheform'sdesign.Therulehereisthat,if

itletsyouchange,itwillwork.

Rememberthatwhilstdoingthis,allchangesshouldbeatthelowestlevelpossibleasthisensuresconsistencyandminimizesmaintenanceeffort.

Thiscoversthedesign,butnotthecode.Wecanright-clickonmostmethodsanduseCopyevent-handlermethodtocreateapre-eventorpost-eventhandler.Thismaysufficeinsomecases,butwecangofurther.IntheNovemberupdate,wecannowcreateextensionclassesthatactasanextensiontotheform'scode.

Thefollowingexampleexplainsoneofthemanybenefitsthatmaynotimmediatelybeobvious.

Tocreateanextensionclassforaform'scode,followthesesteps:

1. Createanewclassthatmustendin_Extension;so,foraSalesTableextension,useConSalesTable_Extension.

Forthistowork,weonlyneedtohavetheExtensionOfdecorationandensurethatthenameendsin_Extension.

2. Theclassdeclarationshouldreadasfollows:

[ExtensionOf(formStr(SalesTable))]

finalclassConSalesTable_Extension

3. Inthisexample,wewilldeterminethecallerrecordbyhandlingtheinitializedeventandusethestoredrecordthatisscopedtotheformwhenweclosetheform.Thecodeforthisisasfollows:

[ExtensionOf(formStr(SalesTable))]

finalclassConSalesTable_Extension

{

PrivateCustTableconCustTable;

[FormEventHandler(formstr(SalesTable),

FormEventType::Initialized)]

publicvoidinitializedFormHandler(xFormRunformRun,

FormEventArgse)

{

Argsargs=formRun.args();

switch(args.dataset())

Page 561: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

{

casetableNum(CustTable):

conCustTable=args.record();

break;

}

}

[FormEventHandler(formstr(SalesTable),

FormEventType::Closing)]

publicvoidinitializedFormHandler(xFormRunformRun,

FormEventArgse)

{

if(conCustTable.RecId!=0)

{

if(FormDataUtil::isFormDataSource(

conCustTable))

{

FormDataUtil::getFormDataSource(

conCustTable).research(true);

}

}

}

}

Wedonotnormallyprefixvariablesinourowncode,butintheprecedingcaseweprefixedCustTablewithCon.Theextensionclassislikeadecoration,andthevariableswecreatemustbeuniqueinthescopeoftheforminstance.

Ifyouwanttowriteamethodtooverrideaform-controlevent,justasamodifiedeventofItemId,wewillperformthefollowingsteps:

1. AddthefollowinglinestotheinitializedFormHandlermethodcreatedearlier:

FormDataObjectitemIdDataObject;

itemIdDataObject=this.SalesLine_DS.object(

fieldNum(SalesLine,ItemId),1);

itemIdDataObject.registerOverrideMethod(

methodStr(FormDataObject,modified),

methodStr(ConSalesTable_Extension,

modifiedItemIdHandler),this);

2. Thenaddtheoverridehandler,asfollows:

///<summary>

///Handlesthemodifiedeventofthe

///ItemIddatasourcefield

///</summary>

///<paramname="itemIdFieldObject"></param>

publicvoidmodifiedItemIdHandler(

FormDataObjectitemIdFieldObject)

{

//writewhatshouldhappenhere

}

FormDataObjecthasthesettingsforthedatasourcefield,suchasAllowEdit.ItalsohasadataSource()methodtofetchtheFormDataSourceobject.Thiswouldbeneededifthemethoddidn'talreadyhaveaccesstotheform'sdatasources,whichitdoes,astheyaredeclaredpublic.

3. Thisisit.Weshouldexperimentandgetusedtothisparadigmbeforeusingitforreal.

Page 562: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 563: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Theformextensionworkslikeanyotherextension;theyaredeltachangesthatareappliedwhenthepackageisdeployed.

Thenewconcepthereistheextensionclass.Thetermextensionisusedspecificallytoavoidconfusionwiththeextendskeyword.Extensionclassesareautomaticallyinstantiatedinplaceofthestandardclassandallowthemtobeaugmented.Thereisnoaccesstoprivatemethods,butwecanaddstatevariablesandaccessthemalongwiththeform'spublicvariablesandmethods.Critically,wedohavefullaccesstothedatasourcesontheform.Theeventhandlerscanbecreatedasinstancemethods,whichgrantsthemaccesstotheextensionclass'svariablesandmethods,butalsothepublicvariablesandmethodsintheclasswecreatedtheextensionfor.

Theclassmustbecreatedasfinal,becauseitisonlyinstantiatedinthecurrentpackage.Therecanbeextensionclassesinotherpackages,whichwecannotaccess,andshouldnotwantto.Thewholepointisforustoknowthatinthispackage,thatform'sclasswillbeinstantiatedasthisclass.

Page 564: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 565: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...Therearemanymoretypesofextensionsthatwecanperform,butallfollowthesametheme.Ensurethatthenamewegiveisnamedcorrectlyandisunique(andcertainlydoesn'thavethesuffixof.extension).

YoucantellwhichobjectscanhaveextensionduetotheApplicationExplorerhavinganodeformthen;forexample,MenuItemsisfollowedbytheMenuItemExtensionsnode.Orsimply,ifyoucanselectCreateextension,youcan!

Developinginthiswayrequiresaslightparadigmshiftinordertomakeitseemnatural.Itseemsthatthosenewtowritingthiswayarethinkingfromacustomization(over-layer)viewpointandthesolutionseemstobemoreofabruteforceworkaroundthanarealsolution.

Forexample,whenwritinganeventhandler,theeventonlyhasaccesstothepublicvariablesandmethodsofthecaller.Wecan,however,usereflectiontogainaccesstothecaller'sprivatevariablesandmethods.Ifwearedoingthis,itusuallymeanswearethinkingaboutthesolutioninthewrongway.ThetoolinginVisualStudioisimprovedwitheachrelease,andtheabilitytomakechangesviaextensionhasincreasedsignificantlyeachtime.

Reflectionisalargetopic,anddiscouragedbymany,asitwillmakeourcodevulnerabletoregressionasMicrosoftreleaseupdates.Formoreinformationonthistopic,pleaseseethefollowinglink:

https://msdn.microsoft.com/en-us/library/f7ykdhsy(v=vs.110).aspx.

AlthoughthepurposeofthisbookistoproviderecipestoextendDynamics365forOperationsthroughthemeansofextension,itisimportanttokeepinmindthereasonforavoidingover-layering:maintainabilityandserviceability.Ifwemakeahackychangetoavoidanover-layer,(likereflectionandinsomecasespre-eventorpost-eventhandlers),wecanactuallymakethecodelessmaintainable.Worsethanthat,thesystemmaycompileandpassyourtestscriptsandstillfailwhendeployedtoproductionaswehavemadeanassumptionabouttheinternalstateofaclass.ThepointIamtryingtomakeistokeepinmindthereasonsweavoidover-layeringstandardcode,whichisn'tjusttoavoidit.Itistorealizethemanybenefitsintermsofeaseofdeployment,andmaintainabilityofcodeasupdatesarereleased.

Page 566: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 567: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Usingaformeventhandlertoreplacealookup

Thisisacommonrequirementofaneventhandler,anddeservesitsownrecipe.PriortoUpdate3inNovember2016,theseeventhandlershadtobeplacedinautilityclass;wecannowhaveaclassthatactsasanextensiontotheform'scode-behind.

Thiswascoveredinthepreviousrecipe,andifyouareusingthistechnique,theeventhandlershouldbewrittentherewithoutthestatickeyword.

Page 568: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 569: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyIdeally,weshouldhaveaformextensionclasswrittenforthis;otherwise,wejustneedaDynamics365forOperationsprojectopen.

Page 570: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 571: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Toaddacustomlookuptoacontrolonastandardform,pleasefollowthesesteps

1. Createanewclassthatcouldbeagenericsalesorderutilityclass,orideallyaformextensionclass(seepreviousrecipeforthis).

Thenamingiskeysothatwecaneasilyfindit.Thereisnoobviouslinkthatwehavedonethis,sonaminganddocumentationiscritical.

2. Thefirstexampleistomakethesalesordercreatedialogusingalookupform.Asstandard(intheNovemberrelease),thelookuponlycontainstheaccountnumberandaccount.Thisexamplebindsthecustomlookuptothecustomeraccount'slookupevent,asfollows:

///<summary>

///Overridethecustomerlookuponthesalesordercreate

///dialoginordertousealookupwiththeaddress

///</summary>

///<paramname="sender">callingcontrol</param>

///<paramname="e">formevents</param>

[FormControlEventHandler(formControlStr(SalesCreateOrder,

SalesTable_CustAccount),

FormControlEventType::Lookup)]

publicstaticvoidSalesTable_CustAccount_OnLookup(

FormControlsender,

FormControlEventArgse)

{

FormStringControlcustAccountCtrl=sender;

CustTableselectedCustomer;

FormRunformRun;

FormcustTableLookupForm;

custTableLookupForm=new

Form(formStr(CustTableLookup));

FormControlCancelableSuperEventArgseventArgs=e;

ArgsformArgs=newArgs();

formArgs=newArgs();

formArgs.name(formStr(CustTableLookup));

formArgs.caller(sender.formRun());

selectedCustomer=

CustTable::find(custAccountCtrl.text());

if(selectedCustomer.RecId!=0)

{

formArgs.lookupRecord(selectedCustomer);

}

formRun=FormAutoLookupFactory::buildLookupFromCustomForm(

custAccountCtrl,

custTableLookupForm,

AbsoluteFieldBinding::construct(

fieldStr(CustTable,AccountNum),

tableStr(CustTable)),

formArgs);

custAccountCtrl.performFormLookup(formRun);

eventArgs.CancelSuperCall();

}

Ifyouwishtocreateyourownlookup,itisalsofine.JustcreateanewformusingLookup-basicpattern.

Page 572: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Let'suseanexamplewherewearewritingacustomproductlookup:

1. Weneedtobindtheselectcontrol(theIDcolumn,suchastheItemIdfieldinourexample).2. Addthedatasourceasusual(orwriteaviewandusethat)andcompletethedesignpattern.When

addingfieldstothegrid,changetheAutoDeclarationpropertyoftheIDcolumntoYes.ThecontrolwillprobablybecalledGrid_ItemIdbydefault,whichisfine.

3. OverridetheinitmethodsowecantelltheformthattheGrid_ItemIdcontrolwillcontainthedatathatwewanttoreturn.Thisisdonewiththefollowingcode:

voidinit()

{

DictFielddictField;

FormStringControlcallerControl;

if(!element.args())

{

throwerror(strfmt("@SYS22862",element.name()));

}

super();

element.selectMode(Grid_ItemId);

}

4. Next,weshouldhandlethefilteringofthecontrolbasedonthecallingcontrol,whichisdonewiththefollowingcode:

publicvoidrun()

{

FormStringControlcallerControl;

booleanfilterLookup=false;

callerControl=SysTableLookup::getCallerStringControl(

element.args());

filterLookup=SysTableLookup::filterLookupPreRun(

callerControl,

Grid_ItemId,

InventTable_DS);

super();

SysTableLookup::filterLookupPostRun(filterLookup,

callerControl.text(),

Grid_ItemId,

InventTable_DS);

}

InventTable_DSisthedefaultdatasourcenameifweaddedtheInventTabletable.Replacethisasrequiredwiththedatasourcenameoftheactualtableused.

5. Weshouldnowcreateamenuitem,andaddittoanappropriatesecurityprivilege.

LookupsinOperationsdon'talwaysuseaformand,infact,theyareusuallyautomaticbasedontheautoLookupfieldgroup.Wecanwritesuchalookupprogrammatically:

1. Createaclass,ConGeneralHandlers,forexample,andaddthefollowingmethod:

Page 573: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

///<summary>

///Performsalookuponthe<c>InventTable</c>table.

///</summary>

///<paramname="_lookupCtrl">

///The<c>FormStringControl</c>controlthatthe

///lookupwillbeattachedto.

///</param>

///<paramname="_itemId">

///Theitemthatisusedtofilterthelookup.

///</param>

publicstaticvoidlookupItemId(

FormStringControl_lookupCtrl,

ItemId_itemId)

{

SysTableLookupsysTableLookup

Queryquery=newQuery();

QueryBuildRangequeryBuildRange;

QueryBuildDataSourcequeryBuildDataSource;

sysTableLookup=SysTableLookup::newParameters(

tableNum(InventTable),

_lookupCtrl)

queryBuildDataSource=query.addDataSource(

tableNum(InventTable));

if(_itemId)

{

queryBuildRange=queryBuildDataSource.addRange(

fieldNum(InventTable,ItemId));

queryBuildRange.value(queryValue(_itemId));

}

sysTableLookup.addSelectionField(

fieldNum(InventTable,ItemId));

sysTableLookup.addLookupMethod(

tableMethodStr(InventTable,

itemName));

sysTableLookup.addLookupMethod(

tableMethodStr(InventTable,

modelGroupId));

sysTableLookup.addLookupMethod(

tableMethodStr(InventTable,

itemGroupId));

sysTableLookup.addLookupField(

fieldNum(InventTable,

ItemType));

sysTableLookup.parmQuery(query);

sysTableLookup.performFormLookup();

}

2. Wemayneedtoreplacethesupercallonthelookupmethodonthefieldwithinadatasource;thisisdonebyright-clickingontheMethodsnodeforthedatasourcefieldinquestionandchoosingOverride|Lookup.Removethesuper()callsoyoudon'tgettwolookups!

3. Touseitasbeforeinaneventhandlerisdoneasfollows:

///<summary>

///Overridetheitemlookuponthesalesorderform

///</summary>

///<paramname="sender">ItemIdcallercontrol</param>

///<paramname="e">argssowecancancelsuper</param>

[FormControlEventHandler(formControlStr(SalesTable,

SalesLine_ItemId),

FormControlEventType::Lookup)]

publicstaticvoidSalesLine_ItemId_OnLookup(

FormControlsender,

FormControlEventArgse)

{

FormStringControlitemIdCtrl;

FormControlCancelableSuperEventArgseventArgs;

eventArgs=e;

Page 574: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

itemIdCtrl=sender;

ConGeneralhandlers::lookupItemId(

itemIdCtrl,

itemIdCtrl.text());

eventArgs.CancelSuperCall();

}

4. ThesetechniquescanbeusedasdesiredtoadjustorreplacethelookupsinDynamics365forOperationswithoutanycustomizationtothestandardcode.

Page 575: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 576: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Thefirstpartistouseacustomform.Thebindingtothelookupeventisdoneusingaspecificeventhandlerforthispurpose.Theseeventshaveoneveryimportantfeature,apartfromallowingustobindtoaformcontrolevent;inthat,theypasstheargumentsobjectasaFormControlEventArgsobject.

WecanthencastthisasaFormControlCancelableSuperEventArgsobject.WhenwecalltheCancelSuperCall()method,ittellsthecallingcontrolnottocallitssuper.Inthecaseofalookupevent,wewouldotherwiseendupwithtwolookups:ournewlookupandthenthestandardlookup.

Apartfromgatheringinformation,theotherkeypartisthatweconstructtheFormRunobjectusingafactory,whichcreatesFormRunsothatitbehavesasalookupandnotjustaform.

Thesecondpartwastocreateourownlookup,whichwaslargelyrepeatingwhatwehavealreadylearnt.Justfollowthepattern.However,thepatterndoesneedalittlehelp,soitunderstandshowtobehaveitself.Thisiswhywehavetooverridetheinitandrunmethods.Evenso,thisisalotlesseffortthaninpriorreleases.

Thethirdpartwastocreatealookupprogrammatically.Thisisoftenpreferableforsimplelookups,andwecouldalsoaddchilddatasourcesandfilterstothequery.Thiswasdoneintwomethods,asitiscommontouselookupslikethisinourowncode.Sothelookupmethodcouldbeusedasthelookupmethodonadatasourcefield.

Page 577: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 578: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingyourownqueryfunctionsQueryfunctionsareusedinuserqueries.Oneoftheissuestheycansolveiswhensubmittingbatchroutines,whereaqueryrangewouldbebasedonthecurrentdate.

OnesuchfunctioniscurrentDate().Thisisusedinaqueryrangeas(currentDate()).Thesystemseestheroundbracketsandknowstolookforaqueryfunction.Wheneverthequeryisexecuted,thesystemwillusethecurrentsystemdate.

InprioreditionsofAX,wewouldaddpublicstaticmethodstotheSysQueryRangeUtilclass.Thiswouldmeananover-layerand,consequently,Microsofthasprovidedawaytoaddnewqueryfunctionswithoutover-layering.

Page 579: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 580: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Tocreateaqueryfunction,followthesesteps:

1. CreateanewclassintheprojectandnameitConQueryRangeFunctions.2. Toaddaqueryfunctiontotheclass,inthiscase,togetthecurrentworkerrecordID,writethe

followinglinesofcode:

[QueryRangeFunction]

publicstaticstrCurrentWorkerNum()

{

RefRecIdcurrentWorkerRecId=

HcmWorkerLookup::currentWorker();

HcmWorkerworker;

if(currentWorkerRecId==0)

{

//ifwereturnandemptystring,itwillmatchall

//records.Soreturnastringthatwillbothcause

//thenorecordstobefoundinthequeryrange,

//butalsotogivetheuseraclueastowhat

//happened.Youshouldcreatethisasalabel,

//withoutspaces,usthefollowingasanexample.

return'#NotFound#';

}

selectPersonnelNumber

fromworker

whereworker.RecId==currentWorkerRecId;

returnworker.PersonnelNumber;

}

YoumaynoticethatittriestoaddAttributetotheend,ifyouuseintelli-type,andthatsomeofthemethodsinSysQueryRangeUtilalsoendinAttribute.Thisistheoldconventionandisnolongerrequired.

3. Thesecondpartofthetrickishowtouseit.Buildtheprojectandopenhttps://usnconeboxax1aos.cloud.onebox.dynamics.com/?cmp=USMF&mi=HcmWorkerListPageinyourbrowser.

4. SelecttheOPTIONSactionpanetab,asshowninthefollowingscreenshot:

Page 581: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

5. ClickonAdvancedFilter/SortandaddarangeforPersonnelnumber,asshowninthefollowingscreenshot:

6. TypethefollowinglineofcodeintotheCriteriacolumnandpressOK:

(ConQueryRangeFunctions::CurrentWorkerNum())

7. Youshouldsee,whenusingtheContosodemodataintheUSMFcompany,thatonlytheuserJuliaFunderburkisshown.

8. IfyouclickontheheadingforPersonnelnumber,ourqueryfunctioncodeisalsolistedasamatchescriterion.Asthesefunctionsareusuallyusedwhensubmittingroutinesorprintingreports,theadvancedfilterdialogismorecommonlyused.

Page 582: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 583: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Whenthepackageisbuilt,theQueryRangeFunctionattributedirectsthecompilertomakethemavailableforuseinquerydialogs.ThesyntaxinthebracketsisnotX++,itisinterpretedatruntimeandjustlookssimilar.Theroundbracketinstructsthecompilertoexpectafieldorfunctionname,andanyfunctionnotinthebaseSysQueryRangeUtilclassrequiresthe(<classname>::<functionname>())format.

Theerrorcheckingislimitedandtheerrorsaren'thelpful.Forexample,ifweforgetoneofthecolons,wewillgetanerrorsuggestingthatarightparenthesisiswrong.Thereisalsonohelptotheuserastohowtousethemfromwithintheuserinterface;so,wemustprovidedocumentationwiththesefunctions.

Apartfromthat,wegetaverypowerfulwaytoprovidetheuserswithsomeverypowerfulfunctionsthattheycanusetosimplifyandautomatetheirprocesses.

Page 584: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 585: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

DataManagement,OData,andOfficeInthischapter,wewillcoverthefollowingrecipes:

CreatingdataentitiesExtendingstandarddataentitiesImportingdatathroughdataimport/exportReading,writing,andupdatingdatathroughOData

Page 586: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 587: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

IntroductionMicrosoftDynamics365forOperationsisacloudsolution,andevenwhenitisavailableon-premise,theobvioushighavailability/disasterrecoverysolutionwouldbetoswitchtoMicrosoftAzure.So,evenwhenoursolutionisdesignedtobeon-premise,weshouldn'twriteourintegrationstoaccesslocalareanetworkresources.

AllintegrationsshouldhaveaserviceendpointthatwillbeaccessedbyDynamics365forOperations.

Tofacilitatewritingintegrationsthatareagnosticofthelocalnetworkresources,MicrosofthasevolvedtheDataImport/ExportFramework(DIXF)inthisrelease,tohelpresolvemanyoftheintegrationsissueswewilloftenface.ItalsoopensupamuchmoreintegratedwayinwhichwecancommunicatewithMicrosoftOffice.

Inthischapterwewillcovertheusageandextensibilityoptionsfordataentities,andalsohowtointeractprogrammaticallywithourdataentitiesthroughOData.

Page 588: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 589: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Creatingadataentity

Inthistask,wewillcreateadataentityforourvehicletable,whichwewillextendinordertodemonstratehowdataentitiescanbeused.WewillalsousethistoallowustomaintainvehicledatathroughtheOfficeadd-inandmakeitapublicODataentity.

Page 590: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 591: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyWewilljustneedtohaveaDynamics365forOperationsprojectopen,andatableforwhichwewanttocreateadataentity.

Page 592: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 593: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreatethedataentity,followthesesteps:

1. Intheproject,addanewitem.WithintheAddNewItemdialog,selectDataModelfromtheleft-handlist,andthenDataEntityfromtheright.

2. EnterConWHSVehicleTableEntityasNameandpressAdd.3. WewillthengetaDataEntityWizarddialogandselectConWHSVehicleTableinthePrimarydatasource

drop-downlist.

Asyouscrolldown,thedrop-downlistcanresize,causinganitemtobeselectedbymistake,itisthereforeeasiertousethePageUpandPageDownkeystolocatethetable.

TheEntitycategoryisnotcorrectbydefault;usethefollowingtabletoselectthecorrectcategory:

Tablegroup Entitycategory

Main,Group Master

Worksheet(alltypes) Document

Transaction(alltypes) Transaction

Parameter Parameter

Reference Reference

4. ThedialogmadeaguessthatConwasaprefixandstrippedthisfromthePublicentitynameandPubliccollectionnamefields;theprefixshouldbeputbacktoavoidthechanceofnamingcollision.

Thedefaultswillcreateapublicinterfaceforaccessbyotherapplications,suchasMicrosoftOffice,astagingtableforusewiththeDataImport/ExportFramework,andsecurityprivilegesinordertocontrolwhohasaccesstothisentity.

5. ClickonNext.

Onthispage,wecanaddrelateddatasourcesandvirtualfields.Inourcase,thisis

Page 594: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

notrequired,andwewillcoverthisoptionintheThere'smore...section.

6. IfwecheckConvertlabelstofieldnames,itwillusethefield'slabelsforthefieldnames.Thisisnotusuallydesirable;thelabelmayneedthecontextoffieldgroupinorderforustoknowwhichfielditrelatesto.Donotcheckthischeckbox.

Agridiscreatedbasedonthetable'sdefinition,whichwillbeusedasthesettingsusedtogeneratethedataentity.Thesesettingsareusuallycorrect;inourcase,thegridisasfollows:

Wecanmakeadditionalfieldsmandatory;however,ifwedecidetouncheckamandatoryfield,wewillgetanerrorunlessitisspecifiedincodebeforetheactualrecordisinsertedorupdated.Thiscanbeusefulwhenthemandatoryfieldisinferredfromanotherfieldinthedataentity.

7. ClickonFinish.

Thewizardhascreatedthefollowingobjectsforus:

Element Description

ConWHSVehicleTableEntity Thisisthedataentity

ConWHSVehicleTableStagingThisisatableusedtostagedatawhenimportingviatheDataImport/ExportFramework

ConWHSVehicleTableEntityMaintain Thisisasecurityprivilegetoallowusfullaccesstothedataentity

ConWHSVehicleTableEntityView Thisisasecurityprivilegetoallowview-onlyaccesstothedataentity

8. Buildtheprojectandsynchronizeitwiththedatabase.9. Openthemainformforthedataentity;inourcase,theVehiclesform,whichcanbeaccesseddirectly

usingthefollowingURL:

https://usnconeboxax1aos.cloud.onebox.dynamics.com/?cmp=usmf&mi=ConWHSVehicleTable

10. Onthetoprightofthescreen,theOfficeiconhasanewoption,OPENINEXCEL,asshowninthe

Page 595: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

followingscreenshot:

11. IfyouhoverthemouseovertheVehicletable(usmf)link,youwillseethatitisourentity,asshowninthefollowingscreenshot:

12. Thisisthepublicentitynamewespecifiedinthewizard,andwecanchangeitbychangingthePublicEntityNamepropertyonthedataentity.

13. ClickontheVehicletable(usmf)link,andthenclickDownloadintheOpeninExceldialog.14. OnceMicrosoftExcelopens,youmaygetthefollowingwarning:

15. ClickonTrustthisadd-in.16. Next,clickonSignin,andsigninusingthesameaccountyouusedforloggingintoDynamics365for

Operations.

Oncesignedin,itwillpopulateasheetwiththedatafromtheConWHSVehicleTabletable,butonlyaddthemandatoryfields.Totesttheentity,weshouldaddafewfields.

17. Intheadd-in,clickonDesignandthenclickontheediticonnexttotheConWHSVehicleTabletable,asshowninthefollowingscreenshot:

Page 596: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

18. Inthenextpage,selectallofthefieldsintheAvailablefieldslistandpresstheAddbuttonthatisjustabovetheSelectedfieldslist.

19. ClickonUpdate,andthenclickYestothewarning.20. ClickonDone,whichtakesusoutofthedesignexperience,andthenpressRefreshtofetchthenew

data.21. Oursheetshouldnowhavethedatafromthevehicletable,asshowninthefollowingscreenshot:

Theheadingsarelabelsinyouruser'slanguage,andtheenumeratedtypesarealsotranslated.

22. Editoneormoreofthefieldsusingtheadd-intoselectthevalueswhentheyhaveadrop-downlistordatepicker.DonotchangetheVehicleIdvalue,butyoucantestthisyourselfinadifferenttesttoseewhathappens.

23. ClickonNewintheadd-ininordertoaddanewvehicle,andcompletethesheetasrequired.Oncedone,theresultshouldbesimilartothefollowingscreenshot:

TheCompanycolumnwasleftblank,andweshouldactuallyremovethiscolumnfromthesheet.Ifyourememberthatthelinkhadthecompanywithinthelink'sname,thisconnectionisboundusingthatcompanyID.

24. Oncedone,clickonPublish;theentitywillberefreshedwiththecompanyIDthatwasactuallyusedwhentherecordsarecreated.

25. Finally,refreshtheVehiclesforminDynamics365foroperations,andyouwillseetherecordswithinthevehicleslistpage.

26. ClosetheExcelworksheet.27. Ideally,wewouldwanttocontrolwhichfieldsareavailable,soopentheConWHSVehicleTableEntitydata

entityinthedesigner.

Thepropertiesareverysimilartothoseofatable,andthenodesinthedesignsharethoseofbothqueriesandtables.Infact,thisiscreatedintheSQLserverdatabaseasaviewand,ifwesynchronizedthedatabase,wecouldviewthedatainSQLServerManagementStudio.

28. AddthefieldsyouwouldliketoseebydefaulttotheAutoReportfieldgroup.29. YoumayalsohavenoticedthattheVehicleGroupIdfielddidnothaveadrop-downlistinExcel,andthe

foreignkeyrelationdoesnothelpinthiscase.Wewillneedacustomlookup,asshowninthe

Page 597: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

followingpieceofcode:

///<summary>

///Acustomlookupforvehiclegroupids

///</summary>

///<paramname="_fields">

///Thisisthefields'metadataprovidedbythe

///officeadd-in

///</param>

///<returns>

///Aserializedlistofvehiclegroupids

///</returns>

[SysODataAction(

'ConWHSVehicleTableEntityVehicleGroupLookup',

false),

SysODataCollectionAttribute('_fields',Types::String),

SysODataFieldLookup(fieldStr(ConWHSVehicleTableEntity,

VehicleGroupId))]

publicstaticstrLookupVehicleGroupId(Array_fields)

{

RefFieldNamevehicleGroupIdFld;

vehicleGroupIdFld=fieldStr(ConWHSVehicleTableEntity,

VehicleGroupId);

//Buildafieldandvaluemapfromthe_fieldsArray

MapfieldMap;

fieldMap=OfficeAppCustomLookupHelper::getFieldMap(

tableStr(ConWHSVehicleTableEntity),_fields);

//Determinethecompanythattheofficeadd-inis

//connectedto,otherwiseitwillreturndatafromDAT

DataAreaIddataAreaId=curExt();

RefFieldNamedataAreaIdFld;

dataAreaIdFld=fieldStr(ConWHSVehicleTableEntity,

DataAreaId);

if(fieldMap.exists(dataAreaIdFld))

{

dataAreaId=fieldMap.lookup(dataAreaIdFld);

}

//Constructtheresultobject,andaddourID

//fieldtothelistasthefirstelementinthearray

OfficeAppCustomLookupListResultresult;

result=newOfficeAppCustomLookupListResult();

result.determinationFields().value(1,

vehicleGroupIdFld);

//declaretheresultStringhere(latest)asitneeds

//tobeinscopeforwhenitisset,andreturnedto

//thecaller

strresultString;

//Checkthatthekeyfieldisinthesuppliedmetadata

if(OfficeAppCustomLookupHelper::fieldsBound(

result.determinationFields(),fieldMap))

{

intcounter=1;

//changetothecompanytheofficeadd-in

//isconnectedto

changecompany(dataAreaId)

{

ConWHSVehicleGroupvehicleGroups;

//Addthevehiclegroupidstothevaluearray

whileselectVehicleGroupId

fromvehicleGroups

orderbyVehicleGroupId

{

result.items().value(counter,

vehicleGroups.VehicleGroupId);

counter++;

}

}

resultString=result.serialize();

}

returnresultString;

}

Page 598: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

30. Rebuildtheprojectandtesttheadd-inagain;youwillgetthefieldsthatyouaddedtothefieldgroupalongwiththedrop-downlistontheVehiclegroupcolumn.

Page 599: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 600: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Whenwecreatedtheentitywithapublicinterface,itactuallycreatesaservicethatofficecommunicateswith.TheExcelfilewedownloadedwasjusttoallowconnectiontothedataentityusingOData.Wearen'treadingrecordsdirectly,therecordsarereadfromDynamics365forOperationsandarewrittenbackwhenwepublishthechanges.

TheauthenticationgoesthroughourMicrosoftOffice365account,andwhenhostedinAzure,theadd-intakescareofthecomplexitiesofthisintegrationforus.Itissecure(anditalsohonorsXDSdatapolicies),yetavailableeverywhere.

Untilwegottowritingthelookup,theprocesswasremarkablyeasy,ifwetakeintoaccounttheresultweachievewithsuchlittleeffort.It,therefore,stoodoutthatwehadtowritequiteacomplicatedmethodforthelookup.

Themethoddoeslookalittledaunting;however,whenbrokendown,itbecomeseasiertounderstand.

ThefirstkeypartofthemethodistheSysODataFieldLookupdecoration,whichishowitknowsforwhichfieldthelookupisbound.

Asweareusingastaticversionofthepattern,the_fieldsparameterprovidesbothmetadataandthevalueofeachfieldinthedataset.ThisisconvertedintothefieldMapmapforeaseofuse.

Wewillneedthisinordertoworkoutwhichcompanyweareworkingin,sowewillreturndatafromthatcompany.Thisdiffersfromotherstaticmethods;inthat,itiscalledwithacompanycontext.ThisisdonebylookingupthevalueofthedataAreaIdfieldasfollows:

dataAreaIdFld=fieldStr(ConWHSVehicleTableEntity,DataAreaId);

if(fieldMap.exists(dataAreaIdFld))

{

dataAreaId=fieldMap.lookup(dataAreaIdFld);

}

Wehavetocheckifitexistsbeforewelookitup;ifnot,itwillthrowanerrorifthekeydoesn'texistinthemap.

Beforeaddingthevaluestothearray,weshouldcheckthattheVehicleGroupIdfieldisbound(isinthe_fieldsarray).WeusedOfficeAppCustomLookupHelper::fieldsBound()forthis.Inourcase,itissemanticallyequivalenttocheckingthatthefieldexistsinthefieldMapmap.

Theactuallookupdataisconstructedbythefollowinglineofcode:

result.items().value(counter,vehicleGroups.VehicleGroupId);

Theresult.items()functionisanarrayorstringvalues.

Toreturnthedata,ithastobeserializedtoastring,whichwasdonebythefollowinglineofcode:

Page 601: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

resultString=result.serialize();

Page 602: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 603: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...Thereareafewspecialmethodsthatareusedwhenwritingadataentity,whichareasfollows:

Method Description

updateEntityDataSource Thisiscalledwhenupdatinganexistingrecord

deleteEntityDataSource Thisiscalledwhendeletingarecord

insertEntityDataSource Thisiscalledwheninsertinganewrecord

initializeEntityDataSource Thisiscalledwhenarecordisinitialized.

Thesemethodsareusedsothatwecanupdaterelatedrecordsthatarenotdirectlyaffectedbythetablesintheentity.AgoodexampleiswhenatableisrelatedtoDirPartyTable.Wecan'tsimplydeletetherelatedpartyrecordinthiscasewhentheparentisdeleted,asitmaybeusedinotherroles.

ThefollowingaresamplemethodsforafictitiousConWHSHaulierTableEntity,whichhasamaindatasource,ConWHSHaulierTable,andachilddatasource,DirPartyBaseEntity.

InthismethodwewillcheckiftheentityistheDirPartyBaseEntitydatasource,andifitis,itexecuteslogictohandletheDirPartyTableandLogisticsPostalAddresstables.Thesetablesarepartofcomplicatedstructures,andthehelperensuresthattheyareinsertedcorrectly:publicbooleaninsertEntityDataSource(DataEntityRuntimeContext_entityCtx,DataEntityDataSourceRuntimeContext_dataSourceCtx){booleanret;

switch(_dataSourceCtx.name()){casedataEntityDataSourceStr(ConWHSHaulierTableEntity,DirPartyBaseEntity):DirPartyBaseEntityHelperpartyHelper;partyHelper=newDirPartyBaseEntityHelper();partyHelper.preInsertEntityDataSource(_entityCtx,_dataSourceCtx,dataEntityDataSourceStr(ConWHSHaulierTableEntity,

Page 604: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

LogisticsPostalAddressBaseEntity));

ret=super(_entityCtx,_dataSourceCtx);

if(ret){partyHelper.postInsertEntityDataSource(_entityCtx,_dataSourceCtx,dataEntityDataSourceStr(ConWHSHaulierTableEntity,LogisticsPostalAddressBaseEntity));}break;default:ret=super(_entityCtx,_dataSourceCtx);}returnret;}

Thefollowingcodehandlesthedeletionlogicandwillcorrectlyhandletheupdatetotheglobaladdressbook(DirPartyTable),removingthelinkscorrectlyforus:publicbooleandeleteEntityDataSource(DataEntityRuntimeContext_entityCtx,DataEntityDataSourceRuntimeContext_dataSourceCtx){booleanret;

switch(_dataSourceCtx.name()){casedataEntityDataSourceStr(ConWHSHaulierTableEntity,DirPartyBaseEntity)):DirPartyBaseEntityHelperpartyHelper;partyHelper=newDirPartyBaseEntityHelper();partyHelper.deleteEntityDataSource(_dataSourceCtx);break;default:ret=super(_entityCtx,_dataSourceCtx);}returnret;}

Thefinalmethodinthissetisthecodetohandlewhathappenswhenrecordsareupdated,thisisidenticaltotheinsertEntityDataSourcemethodexceptthatthemethodiscalledupdateEntityDataSource.

Thefollowingmethodiscalledwhentherecordisinitialized;thecodeinthismethodisusedcorrectlytoinitializetheglobaladdressbookdatastructures:publicvoid

Page 605: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

initializeEntityDataSource(DataEntityRuntimeContext_entityCtx,DataEntityDataSourceRuntimeContext_dataSourceCtx){super(_entityCtx,_dataSourceCtx);

if(_dataSourceCtx.name()==dataEntityDataSourceStr(ConWHSHaulierTableEntity,DirPartyBaseEntity)){//Takescareofmaintainingthereferencetoexisting//partiesifthisrecordprovidesapartynumber.Thisis//because,eventhoughwemaybeinsertingthecustomer//record,thepartymayalreadyexist.DirPartyBaseEntity::initializeDirPartyBaseEntityDataSource(_entityCtx,_dataSourceCtx);}}

Youcanseeexamplesofhowtheseareusedinmanyofthestandardentities;agoodexampleisCustCustomerEntity.

Page 606: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 607: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Seealso

ThefollowinglinksprovidesomeguidanceonrelatedfeaturesandbackgroundtodataentitiesandOData:

AddtemplatestoOpenlinesinExcelmenu(https://ax.help.dynamics.com/en/wiki/add-templates-to-open-lines-in-excel-menu/)OpenlinesinExcelfromjournalsanddocuments(https://ax.help.dynamics.com/en/wiki/open-lines-in-excel-from-journals-and-documents/)CreateOpeninExcelexperiences(https://ax.help.dynamics.com/en/wiki/off101-office-integration-enable-users-to-edit-data-in-excel/)

Theprecedinglinkshowshowtocreatealookupusingapatternthatdiffersfromhowitisusedwithinstandardsoftwareandinthisrecipe.Ichosetousethepatternfromthestandardsoftwareinsteadoftheversioninthisguide.

Officeintegrationtroubleshooting(https://ax.help.dynamics.com/en/wiki/office-integration-troubleshooting/)Securityanddataentities(https://ax.help.dynamics.com/en/wiki/security-and-data-entities/)

Page 608: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 609: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Extendingstandarddataentities

ExtensibilityisbecomingmoreandmorepervasiveinthedevelopmentparadigmofDynamics365forOperations,anditisimportanttobeabletohaveextensibledataentities;otherwise,wewouldhavetowritenewonestobeabletouseafieldweaddedtoatableasanextension.

Inthisexample,wewillcreateanextensionfortheReleasedproductcreationentity,namedEcoResReleasedProductCreationEntity.

Page 610: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 611: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Gettingready

Partofthisrecipeistoaddanextensionfieldsowecanimportdataintoit,sothefirstpartistocreateanextensionfortheInventTabletablewithanewfield.Thisisoptional,butisincludedinordertodemonstratehowthisisdone.

Tofollowthisoptionalstep,createatableextensionfortheInventTabletableandaddanewfieldoftypeNamecalledConWHSAdditionalName.Also,addthistoaformextensionsowecanseetheresults.

Page 612: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 613: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Tocreateadataentityextension,followthesesteps:

1. IntheApplicationExplorer,expandDataModelandthenDataEntities.Right-clickonEcoResReleasedProductCreationEntityandselectCreateextension.

2. Renamethesuffixfrom.Extensionto.ConWHS,oryourpackagename.

AfterexaminingtheDataSourcesnode,wecanseethatitisnotbaseddirectlyonInventTable,butonadataentity.Wewill,therefore,needtocreateanextensionforthisbeforewecanaddthefield.

3. LocateEcoResReleaseProductEntity,createanextension,andrenameitappropriately.4. ExpandtheDataSourcesnodesandthenInventTable,andthenlocatetheextensionfield

(ConWHSAdditionalName).Right-clickonthefieldandchooseCopy.5. CollapsetheDataSourcesnodeandright-clickontheentity'srootFieldsnode.Right-clickonthe

FieldsnodeandselectPaste.

Alternatively,youcanright-clickontheFieldsnode,chooseNew|MappedField,andcompletethepropertysheet.

6. Saveandclosethedataentity.7. OpentheEcoResReleasedProductCreationEntity.ConWHSdataextensioninthedesignerandexpandtheData

SourcesandEcoResReleasedProductEntitynodes.8. LocatetheConWHSAdditionalNamefieldandusecopyandpastetoaddittothedataentity'sFieldsnode.9. SincethiswillbeusedwithintheDataImport/ExportFramework,wewillneedtoaddthefieldto

thestagingtable.Thiscannotbedoneautomaticallyasweareworkingwithanextension--thesystemcan'taddfieldstothestandardtableasthatwouldresultinanover-layer.

10. LocatetheEcoResReleasedProductCreationStagingtableandcreateanextension;nameitappropriately.11. AddtheConWHSAdditionalNamefieldaswedidonInventTable;youcanusecopyandpastetodothis.

WecanworkoutwhichtableisthestagingtablebylookingattheDataManagementStagingpropertyoftheoriginaldataentity,whichisEcoResReleasedProductCreationStaginginthiscase.

12. Forcompleteness,addtheConWHSAdditionalNamefieldtothestagingtablefortheEcoResReleasedProductCreationEntitydataentity.

13. Saveallandbuildtheproject.

YouwillneedtoaddareferencetoDimensionstoyourpackageinorderforthebuildtosucceed.

Page 614: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

14. Finally,synchronizetheprojectwiththedatabase.

Page 615: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 616: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

Thedataentityextensionworksinasimilarwaytoanyotherextension--itstoresadeltachangeinanXMLdefinitionfilethatismergedwiththebaseentitywhentheprojectisbuilt.Shouldwelookattheentitywithintheclient,whichwedointhenextrecipe,wewillseethatthefieldsexistasiftheywerepartoftheentity.

Asitwasamappedfield,wedon'tneedtowriteanyspecialcodeinordertopersistthisthroughthestagingtabletothetargettable.Sincethisdataentityisnotpublic,itcan'tbeusedwiththeOfficeadd-inorthroughOData;itisintendedtobeusedwithintheDataImport/ExportFramework.

Page 617: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 618: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...

Theextentbywhichapplicationobjects(suchasforms,tablesanddataentities)areextensiblewillevolvewitheachrelease.Currently,wecanadddatasources,ranges,relations,fieldgroups,andtablemapstodataentities.Wecannotaddnewmethodsorchangeexistingones,asthiswouldresultinnopracticaldifferencetoanover-layer(exceptitcouldbeworse,asnotoolingwouldexistforconflictmanagement).Instead,wewouldsubscribetothemanyeventdelegates.

Page 619: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 620: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ImportingdatathroughDataImport/ExportFrameworkThisrecipeisusuallyasystemadministrationfunction,butisneededinordertotestourdataentities.Italsogivesusmoreinsightintounderstandinghowourdataentitieswork.

Page 621: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 622: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyThisrecipefollowsonfromthepreviousone,wherewearetestingadataentityextension.

Page 623: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 624: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...ToimportandexportdatausingtheDataImport/ExportFramework,followthesesteps:

1. OpenDynamics365forOperationsinInternetExplorer;usethefollowinglinkonaDevelopmentvirtualmachine:

https://usnconeboxax1aos.cloud.onebox.dynamics.com/?cmp=USMF

2. Thefirststep,aftermakinganychangetothedataentities,istorefreshtheentitylist.OpentheDatamanagementworkspace,selectFrameworkparameters,andclickonRefreshentitylistintheEntitysettingstabpage.Thishappensasynchronously,andyouwillgetamessagewhenitiscomplete;youmayneedtoclickinandoutofformsforthemessagetoappear.

3. Next,wewillneedtocheckthatthefieldsexistandaremappedcorrectlyontheDatamanagementworkspace;clickonDataentities.

4. TypeinReleasedproductsintothefiltercontrolandselectEntity:"Releasedproducts"fromthedrop-downlist.

5. ClickonTargetfieldsandcheckthatthefieldexistsinthelist.Ifnot,youneedtorebuildtheprojectandsynchronizetheprojecttothedatabase.AfullbuildusingDynamics365|Buildmodelsmayberequiredifthispersists.

6. Then,clickonModifytargetmapping.Thegridmaybeempty,andifwehaven'tspecifiedanyspecificmappingpreviously,wecanusetheGeneratemappingbuttontocreatethedefaultfieldmappingforus.

7. Ifwehavespecifiedspecificmappinginthispreviously,clickingonGeneratemappingwilldeleteit.YoucanclickonNewandcompletetheline,orusetheMAPPINGVISUALIZATIONtabpage.Inthisform,dragthefieldfromtheright-handlisttothecorrespondingfieldontheleft.

8. Nowthemappingisdone,let'sexportsomedata.OntheDatamanagementworkspace,clickonExport.

Itmayseemoddtoexportadataentitythatisusedtocreatedata,butthefollowingstepsareusedtocreateanimporttemplateusedlaterintherecipe.

9. Completetheform,asshowninthefollowingtable:

Field Value

Name ReleasedProductCreationExport

Targetdataformat EXCEL

Entityname Releasedproductcreation

Page 625: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Skipstaging Yes

Defaultrefreshtype Fullpushonly

Selectfields Allfields

10. Oncecomplete,clickonAddentity.11. Thiscreatesanewtile.Withthisselected,clickonExportfromthetop-buttonribbon.

Themessagesuggeststhatyourefreshthepage;itmeansthattherefreshicononthetop-rightoftheform,notthebrowserrefreshbutton.

12. Aftersometime,itwillreportthatitsucceeded.ClickonDownloadpackageandsayYestothemessagethatfollows.

13. ThisdownloadsaZIPfile,whichwecanuseasanimporttemplate.14. Copythespreadsheetand,inthecopy,removeallrowsexceptthefirsttwo;aswewillusethefirst

productasatemplatetosavehavingtolookupvalidvalues:weareonlytestingthatitworksfornow.

15. ChangetheItemNumberandProductNumbercolumnstoX1000(anitemnumberthathasnotyetbeenused).

16. FillintheConWHSAdditionalNamecolumnwithsomethinguseful.17. SavetotheDocumentsfolderandclosethespreadsheet.18. GobacktotheDynamics365forOperationsclient,andnavigatebacktotheDatamanagement

workspace.ClickonImport.19. Completethefieldsasfollows:

Field Value

Name ReleasedProductCreationImport

Sourcedataformat EXCEL

Entityname Releasedproductcreation

20. ClickonImport.21. CheckthemappingbyclickingonViewmaponthenewlycreatedtile,andnotethatthemappingis

Page 626: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

doneforus.22. LocatethespreadsheetcreatedearlierandpressOpen.23. ClickonUpload.

24. ThiscantakeawhileonadevelopmentVM,sojustbepatientandclicktheformrefreshbuttoneverysooften.Eventually,youwillgetatileasshowninthefollowingscreenshot:

25. ClickonViewstagingdataandcheckthattheAdditionalnamefieldiscompleted.Ifnot,themappingwasprobablynotcompletedcorrectly.

26. Afterashorttimelater,thetilewillchangetoshowthattherecordsarenowinthetarget.Checkthisbyopeningthereleaseproductsform;checkthatallfieldswerepopulatedcorrectly.

Page 627: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 628: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

ThereisalottotheDataImport/ExportFramework,someofwhichwewillcoverfurtherinthenextrecipe.Inthiscase,wewillcreateasimpleexporttocreateatemplatethatwethenusedtoimportsometestdata.Themappingworkedbasedonnamematching,andthisisusuallythebestmethod,especiallyforloadingtestdata.

Page 629: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 630: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

SeealsoDataentitiesandpackagesframework(https://ax.help.dynamics.com/en/wiki/using-data-entities-and-data-packages/)Dataentitieshomepage(https://ax.help.dynamics.com/en/wiki/data-entities-home-page/)

Page 631: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 632: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Reading,writing,andupdatingdatathroughOData

Inthisexample,wewillcreateasampleODataconsoleapplicationinordertodemonstratehowtoconnectandcommunicatethroughOData.

Inordertostart,youwillneedtocreateanapplicationwithinyourorganization'sAzureID.YouwillneedtheapplicationID--anofficialguideastohowtodothisisavailableathttps://ax.help.dynamics.com/en/wiki/dynamics-ax-7-services-technical-concepts-guide/

Page 633: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 634: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyThefollowingdoesn'thavetobedoneintheDynamics365forOperationsdevelopmentvirtualmachine.However,itwillneedtohaveaccesstotheURL.

Page 635: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 636: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

ToimportandexportdatausingtheDataImport/ExportFramework,followthesesteps:

1. Createanewprojectbut,thistime,chooseVisualC#fromtheTemplatesnodeandthenConsoleApplicationfromtheright.NametheprojectConODataTestandplaceitintheprojectfolderthatwesetupforsourcecontrol.EnsurethatthenamespaceisalsoConODataTest.

2. WewillnowneedtoinstallsomeNuGetpackages.WithinVisualStudio,navigatetoTools|NugetPackageManager|PackageManagerConsole.

"NuGetisthepackagemanagerfortheMicrosoftdevelopmentplatformincluding.NET.TheNuGetclienttoolsprovidetheabilitytoproduceandconsumepackages.TheNuGetGalleryisthecentralpackagerepositoryusedbyallpackageauthorsandconsumers."-(https://www.nuget.org/)

3. InthePackageManagerconsole,typethefollowingcommands:

Install-PackageMicrosoft.Bcl.Build

Install-PackageMicrosoft.Bcl

Install-PackageMicrosoft.Net.Http

Install-PackageMicrosoft.OData.Core-Version6.15.0

Install-PackageSimple.OData.Client-Version4.24.0.1

Install-PackageMicrosoft.Data.OData

Install-PackageMicrosoft.OData.Client

Install-PackageMicrosoft.IdentityModel.Clients.ActiveDirectory

Simple.OData.Clientisaddedasanalternativeandisnotusedinthisexample.Thisiswhythespecific6.15.0versionofMicrosoft.OData.Corewasused.IfSimple.OData.Clientisnotbeingused,theversionneednotbespecified.

4. RestartVisualStudio,otherwise,thebuildwillfailwithoutanymessageastowhy.

5. Next,wewillneedanadd-inforVisualStudioinordertoreadthemetadataandgeneratetypesforus.NavigatetoTools|ExtensionsandUpdates.

6. ClickonOnlineontheleftandtypeODataClientCodeintheSearchVisualStudioGallerytextbox.

7. SelectODatav4ClientCodeGeneratorfromthelistandclickonDownload.

8. Onceinstalled,youwillbeaskedtorestartVisualStudio.

9. Oncerestarted,choosetoaddanewitemtotheproject.SelectODataClientfromthelist.NameitOdataClient.ttandclickonAdd.

Page 637: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Attfileisatransformationfile,anditspurposewillbecomemoreapparentasweprogress.

10. Towardthetopofthisfile,youwillseethefollowingline:

publicconststringMetadataDocumentUri="";

11. Changeitsothatitreadsasfollows:

publicconststringMetadataDocumentUri="https://usnconeboxax1aos.cloud.onebox.dynamics.com/data/$metadata";

12. Checkthatyouhaveaccesstohttps://usnconeboxax1aos.cloud.onebox.dynamics.com/inthebrowserandthatyoucanlogin.Minimize(donotclose)thebrowserandgobacktoVisualStudio.

13. WiththeODataClient.ttfileopeninthecodewindow,clickonSaveorpressCtrl+S.Youwillreceiveasecuritywarning,clickonOKtoproceed.Thiswilltakeafewminutestoproceed,asitisageneratedclientandtypecodeforallpublicentities.

Oncecomplete,youwillseeanewODataClient.csfilenestedundertheODataClient.ttfilewithmanymethods.Thefileisaround40MB,soopeningitwilltakeawhileandisbestavoided.ShouldyoufindthatVisualStudiostartstoperformslowly,ensurethatyouhaveclosedODataClient.ttandODataCilent.csandthenrestartVisualStudio.

14. Wecannowstartwritingthecodeforourtest;addanewclasstotheprojectnamedODataTest.

15. Addthefollowingusingstatementstothetopofthefile:

usingSystem;

usingSystem.Threading.Tasks;

usingMicrosoft.IdentityModel.Clients.ActiveDirectory;

usingMicrosoft.OData.Client;

usingConODataTest.Microsoft.Dynamics.DataEntities;

16. Tosimplifytheprocess,createthefollowdatacontractclassesinordertoaidpassingparameters.Addthemjustbelowtheusingstatements,andabovetheODataTestclassdefinition:

classODataUserContract

{

publicstringuserName;

publicstringpassword;

publicstringdomain;

}

classODataApplicationContract

{

publicstringapplicationId;

publicstringresource;

publicstringresult;

}

publicclassODataRequestContract

{

Page 638: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

publicstringcompany;

}

Wewouldusuallyuseget/setmethodshere,butIusedpublicvariablestosavespaceforthistest.

17. Intheclassbody,declarethefollowingclassvariables:

classODataTest

{

publicconststringOAuthHeader="Authorization";

publicODataUserContractuserContract;

publicODataApplicationContractappContract;

publicODataRequestContractrequest;

stringauthenticationHeader;

publicstringresponse;

TheOAuthHeaderandauthenticationHeadervariablesarekeytotheprocessofauthenticationwithDynamics365forOperations.

18. ThecodetoauthenticatewithAzureADisasfollows:

privateAuthenticationResultGetAuthorization()

{

UriBuilderuri=newUriBuilder("https://login.windows.net/"+userContract.domain);

AuthenticationContextauthenticationContext

=newAuthenticationContext(uri.ToString());

UserPasswordCredentialcredential=new

UserPasswordCredential(

userContract.userName,userContract.password);

Task<AuthenticationResult>task=authenticationContext.AcquireTokenAsync(

appContract.resource,

appContract.applicationId,credential);

task.Wait();

AuthenticationResult

authenticationResult=task.Result;

returnauthenticationResult;

}

19. Now,createapublicmethodthatwillbecalledinordertologon:

publicBooleanAuthenticate()

{

AuthenticationResultauthenticationResult;

try

{

authenticationResult=GetAuthorization();

//thisgetstheauthorizationtoken,this

//mustbesetontheHttpheaderforallrequests

authenticationHeader=

authenticationResult.CreateAuthorizationHeader();

}

catch(Exceptione)

{

response="Authenticationfailed:"+e.Message;

returnfalse;

Page 639: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

}

response="OK";

returntrue;

}

20. EachmethodthatmakesacalltoOperationsmustsetuparesourcesinstance,whichhasaneventhandlerinordertosettheauthenticationkey.Thismethodshouldbewrittenasfollows:

privateResourcesMakeResources()

{

stringentityRootPath=appContract.resource+"/data";

UrioDataUri=newUri(entityRootPath,

UriKind.Absolute);

varresources=newResources(oDataUri);

resources.SendingRequest2+=new

EventHandler<SendingRequest2EventArgs>(

delegate(objectsender,

SendingRequest2EventArgse)

{

//Thiseventhandlerisneededtoset

//theauthenticationcodewegotwhen

//weloggedon.

e.RequestMessage.SetHeader(OAuthHeader,

authenticationHeader);

});

returnresources;

}

21. Thenextthreemethodsaretodemonstratereading,updating,andcreatingrecordsthroughOData:

publicSystem.Collections.ArrayListGetVehicleNameList()

{

System.Collections.ArrayListvehicleNames;

vehicleNames=newSystem.Collections.ArrayList();

varresources=this.MakeResources();

resources.ConWHSVehicleTables.AddQueryOption(

"DataAreaId",request.company);

foreach(varvehicleinresources.ConWHSVehicleTables)

{

vehicleNames.Add(vehicle.Description);

}

returnvehicleNames;

}

publicBooleanUpdateVehicleNames()

{

varresources=this.MakeResources();

resources.ConWHSVehicleTables.AddQueryOption(

"DataAreaId",request.company);

foreach(varvehicleinresources.ConWHSVehicleTables)

{

vehicle.Description=vehicle.VehicleId

+":ODatadidit";

resources.UpdateObject(vehicle);

}

try

{

resources.SaveChanges();

}

catch(Exceptione)

{

response=e.InnerException.Message;

returnfalse;

}

returntrue;

}

publicBooleanCreateNewVehicle(

ConWHSVehicleTable_newVehicle)

{

Page 640: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

varresources=this.MakeResources();

_newVehicle.DataAreaId=request.company;

resources.AddToConWHSVehicleTables(_newVehicle);

try

{

resources.SaveChanges();

}

catch(Exceptione)

{

response=e.InnerException.Message;

returnfalse;

}

returntrue;

}

22. Finally,wecanwriteourmainmethod.OpentheProgram.csfileandensurethatwehavethefollowingusingstatementsatthetop:

usingSystem;

usingConODataTest.Microsoft.Dynamics.DataEntities;

23. Writethemainmethodasfollows(usetheapplicationIDyougeneratedfromyourAzureADapplication):

staticvoidMain(string[]args)

{

ODataApplicationContractappContract;

appContract=newODataApplicationContract();

appContract.resource="https://usnconeboxax1aos.cloud.onebox.dynamics.com";

appContract.applicationId="<yourapplicationId>";

ODataUserContractuserContract=new

ODataUserContract();

Console.WriteLine("UsetheO365accountthatyouusetologintoDynamics365forOperations");

Console.Write("O365Username:");

userContract.userName=Console.ReadLine();

Console.Write("O365Password:");

userContract.password=Console.ReadLine();

Console.WriteLine("Thisisyourtenant,suchasyourdomain.comor<yourtenant>.onmicrosoft.com");

Console.Write("O365Domain:");

userContract.domain=Console.ReadLine();

ODataTesttest=newConODataTest.ODataTest();

test.userContract=userContract;

test.appContract=appContract;

if(!test.Authenticate())

{

Console.WriteLine(test.response);

}

test.request=newConODataTest.ODataRequestContract();

test.request.company="USMF";

System.Collections.ArrayListvehicleNames=test.GetVehicleNameList();

foreach(varvehicleNameinvehicleNames)

{

Console.WriteLine(vehicleName);

}

Console.WriteLine("Changingvehicledescriptions");

test.UpdateVehicleNames();

ConWHSVehicleTablevehicle=newConWHSVehicleTable();

Console.WriteLine("CreateanewVehicle");

Console.Write("VehicleId:");

vehicle.VehicleId=Console.ReadLine();

Console.Write("Vehiclegroup:");

vehicle.VehicleGroupId=Console.ReadLine();

Console.Write("Description:");

vehicle.Description=Console.ReadLine();

Page 641: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

test.CreateNewVehicle(vehicle);

Console.WriteLine("Pressentertocontinue.");

Console.ReadLine();

}

24. Toseewhatisgoingon,addsomebreakpointsandusethedebuggerandrunit(pressF5);don'tstepintocodethatwouldopenODataClient.cs;thiscantakealongtimetoopen.

Page 642: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 643: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Todescribethis,itisbesttostepthroughthekeypartsofthecode.

ThefirstpartwasclickingonsaveontheODataClient.ttfile.ThiscreatedtheclassbyreadingtheURLweenteredinthemetadataDocumentURIvariable.Whenthefileissaved,ittriggersthegenerationofcodeusingmetadatafromDynamics365forOperations.IfyouclickonSave,andthencancelthesecuritywarning,theODataClient.csfilewillbeemptied.

Withinthecodewewrote,thefirstkeypartistheauthentication,whichworksbyauthenticatingwithAzureADandfetchinganauthentication,whichisusedwitheachsubmissionrequest.TheauthorizationcodewasdeterminedintheGetAuthorizationmethod.

ThelogonURIisalwayshttps://login.windows.net/[email protected],theURIwouldbehttps://login.windows.net/contoso.com.

Wecan'tsendausernameandpassworddirectly;wehavetocreateacredentialusingthefollowingcode(fromtheActiveDirectorynamespace):

UserPasswordCredentialcredential=new

UserPasswordCredential(

userContract.userName,userContract.password);

Theauthenticationisperformedbythefollowingcode:

Task<AuthenticationResult>task=authenticationContext.AcquireTokenAsync(

appContract.resource,

appContract.applicationId,credential);

task.Wait();

AuthenticationResultauthenticationResult=task.Result;

ThisdiffersfromthecurrentMicrosoftsamplecode,aswenowhavetouseanasynchronouscall;usingatask,thetask.wait()lineessentiallymakesthecodeexecutesynchronously.ThisisOKforsamplecode,butitismoreusefultouseanasynchronouscallwhenwritingaproductionapplicationaswecancreateamoreuser-responsiveapplication.

TheAuthenticatemethodcallstheGetAuthorizationmethodanddoestwothings.First,wehaveatrycatchstatementtoreturnanicermessagetotheuseruponfailure(wewillpopulatetheresponsepublicvariableandreturnfalse);secondly,thisiswheretheauthenticationHeadervariableisset.This,wewillneedfortherequestswemake.

WithintheProgramclass,intheMainmethod,wewillconstructODataUserContractandcompletethevariablesforusername,password,anddomainusingtheConsole.ReadLine()method.WewillconstructtheODataTestclassandcallAuthenticate().

Onceauthenticated,wecanworkwiththedata.Thethreetestmethods,GetVehicleNameList,UpdateVehicleNames,andCreateNewVehicle,allhaveasimilarpattern.

Page 644: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

EachmethodmakesacalltoMakeResources,whichisdonebythefollowinglinesofcode:

stringentityRootPath=appContract.resource+"/data";

UrioDataUri=newUri(entityRootPath,

UriKind.Absolute);

varresources=newResources(oDataUri);

resources.SendingRequest2+=new

EventHandler<SendingRequest2EventArgs>(

delegate(objectsender,

SendingRequest2EventArgse)

{

e.RequestMessage.SetHeader(OAuthHeader,

authenticationHeader);

});

TheentityRootPathvariableistheDynamics365forOperationsURLplusdata;onthedevelopmentvirtualmachine,thisishttps://usnconeboxax1aos.cloud.onebox.dynamics.com/data.WecanspecifyHTTPheadersontherequest,sowehavetoaddaneventhandler.This,essentially,settheAuthorizationheaderproperty,whichiswhatOAuthHeaderconstantsequatesto.

Eachofthemethodscanthendowhattheyneedto;allrequestsaretousetheresourcesobjectand,wheneachrequestismade,theeventhandlerwillbecalledtosettheauthorizationheaderproperty.

Thefollowinglinesetsthecompany;however,ifweomitthis,itwillusetheuser'sdefaultcompany,whichmaybedesirableinsomecases:

resources.ConWHSVehicleTables.AddQueryOption(

"DataAreaId",

request.company);

ThefirstexamplewastoreadConWHSVehicleTableEntityintoalist,whichwasdoneusingthefollowingcode:

//System.Collections.ArrayListvehicleNames;

foreach(varvehicleinresources.ConWHSVehicleTables)

{

vehicleNames.Add(vehicle.Description);

}

Therequesttotheserverisdonewhenitreachesresources.ConWHSVehicleTables,whichiswhenthedataisactuallyread.ThevehicleNames.Add(...)functionsimplyaddsourchosenfieldtoalist.Youmaywonderwhytheresource'sobjectknowstocallthecollectionConWHSVehicleTablesandthesingletonConWHSVehicleTable.ThisisdeterminedfromthePublicCollectionNameandPublicEntityNamepropertieswespecifiedwhenwecreatedtheConWHSVehicleTableEntitydataentity.

Thenextexamplewashowtoupdatedataandwasdonebywritingthefollowingpieceofcode:

foreach(varvehicleinresources.ConWHSVehicleTables)

{

vehicle.Description=vehicle.VehicleId+

":ODatadidit";

resources.UpdateObject(vehicle);

}

try

{

resources.SaveChanges();

}

catch(Exceptione)

{

response=e.InnerException.Message;

returnfalse;

}

returntrue;

Page 645: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Theresoures.UpdateObject(object)methodrecordsthatwehavemadeachange,butdoesnotwritethisback.Thechangesareactuallysavedbytheresources.SaveChanges()method.ThiswillcallDynamics365forOperation'svalidationlogicfortherecordand,ifthisfails,itwillthrowanexception.Thee.InnerException.MessagesisactuallyJSON,andyoucantraversethisbygivingthemessagebacktotheusers.

Thenextexampleistocreateanewvehicle.Thisisverysimplecode.WewilljustcreateaninstanceoftheConWHSVehicelTableclass,populateitwiththerequiredvalues(VehicleId,DataAreaId,VehicleGroupId,andsoon),andcalltheappropriateAddTomethod.Inthiscase,itisAddToConWHSVehicleTables(ConWHSVehicleTable).

ThereisalotwecandowithOData,andthebestwaytolearnistousethisexample.

Page 646: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 647: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

SeealsoThefollowinglinkcontainssimpleexamplesforOData,Soap,andJSON.Youmaywishtochangethecodetomatchthepatternsinthisrecipeasyouusethem:

Microsoft/Dynamics-AX-Integration(https://github.com/Microsoft/Dynamics-AX-Integration/tree/master/ServiceSamples)Datamanagementandintegrationthroughdataentities(https://ax.help.dynamics.com/en/wiki/data-management-and-integration-through-data-entity/)OData(https://ax.help.dynamics.com/en/wiki/odata-in-dynamics-ax-7/)NuGet(http://www.nuget.org/)

Page 648: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 649: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ConsumingandExposingServicesInthischapter,wewillcoverthefollowingrecipes:

CreatingaserviceConsumingaDynamics365forOperationsSOAPserviceConsumingaDynamics365forOperationsJSONserviceConsuminganexternalservicewithinDynamics365forOperations

Page 650: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 651: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

IntroductionThischapterfocusesonhowtocreateservicesinDynamics365forOperations,andhowtoconsumetheminexternalapplications.ThisdoesnotincludeODataservices,whichwascoveredinthepreviouschapter.

Themethodinwhichservicesarecreatedhasn'tchangedsignificantlyfromDynamicsAX2012(thepriorreleasetoDynamics365forOperations).Thekeydifferenceisthatwebmethodsdon'tneedtobemarkedasanentrypoint.

TheconsumptionofDynamics365forOperations'serviceshaschangedconsiderably.Thismeansthatanycurrentintegrationfromexternalappswillneedchangestothemethodofauthenticationandhowtheinputcontractsarecreated.Havingsaidso,thechangesshouldbeakintoarefactoringexerciseforcustomservicesaccessedthroughSOAP.

TheApplicationIntegrationFramework(AIF)hasbeenremovedinthisrelease,sodocumentservicesarenotavailable;thisisreplacedbyOData.Customservicesarestillavailable,butnolongerrequiretheAIF.TheyareaccessedusingthepathdefinedbythelevelsServiceGroup,Service,andOperation,andwewillseethislaterinthischapter.

CompletelynewtothisreleaseisJSON,anditisthepreferredmethodofinteractionwiththeAPI.JSONmaybenewtomostX++developers,butthereisalotoftechnicalguidanceavailableonlineonhowtouseit.

Finally,wewillconsumeanexternalservicefromwithinDynamics365forOperations.

Page 652: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 653: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Creatingaservice

Therearethreepartstocreatinganewservice:

CreateaclassthatcontainsthebusinesslogicCreateaservicethathasoperationsthatreferenceoperationstotheclass'smethodsCreateaservicegroup

Theservicegroupisacollectionofoneormoreservices,andactsastheservicereferenceshouldweconsumeitwithinVisualStudio.WewillseehowthistranslatestoaURIinthenextrecipe.

Inthisexample,wewillhavetwoserviceoperations:onetogetalistofvehicles,andonetoupdateavehicle'sgroup.TheXMLdocumentationhasbeenomittedtosavespace.

Page 654: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 655: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyWewilljustneedaDynamics365forOperationsvisualstudioprojectopen.

Page 656: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 657: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Tocreatetheservice,followthesesteps:

1. First,createanewclassthatwillholdourservicemethodsandnamethisclassConWHSVehicleServices.2. ThefirstservicewillbetoupdatetheVehicleservicegroup,forwhichwehaveacontractand

processingclassalreadycreated(ConWHSVehicleGroupChangeContract,ConWHSVehicleGroupChange),towritethismethod;writeamethodasfollows:

publicvoidChangeVehicleGroup(

ConWHSVehicleGroupChangeContract_contract)

{

ConWHSVehicleGroupChangechangeGroup=

newConWHSVehicleGroupChange();

changeGroup.Run(_contract);

}

Thiswillwork,buttheconsumerwillnotthankusforthelackoferrorhandling.WecouldeitherreturnaBooleanonsuccessorwriteareturncontract.Areturncontractisthemostusefulwaytoreturntheresultstatus.

3. Toallowameaningfulreply,wewillcreateadatacontractclass.CreateanewclasscalledConWHSMessageContract,andcompleteitasfollows:

[DataContract]

classConWHSMessageContract

{

booleansuccess;

strmessage;

[DataMember]

publicbooleanSuccess(boolean_success=success)

{

success=_success;

returnsuccess;

}

[DataMember]

publicstrMessage(str_message=message)

{

message=_message;

returnmessage;

}

}

4. Nowupdatethemethodsothatitreadsasfollows:

publicConWHSMessageContractchangeVehicleGroup(

ConWHSVehicleGroupChangeContract_contract)

{

ConWHSMessageContractmessage=

newConWHSMessageContract();

ConWHSVehicleGroupChangechangeGroup=

newConWHSVehicleGroupChange();

try

{

updateGroup.contract=_contract;

Page 658: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

if(updateGroup.Validate())

{

updateGroup.Run(_contract);

message.Success(true);

}

else

{

message.Success(false);

//Couldnotupdatevehicle%1tovehicle

//group%2

message.Message(strFmt("@ConWHS:ConWHS53",

_contract.VehicleId(),

_contract.VehicleGroupId()));

}

}

catch

{

message.Success(false);

//Couldnotupdatevehicle%1tovehiclegroup%2

message.Message(strFmt("@ConWHS:ConWHS53",

_contract.VehicleId(),

_contract.VehicleGroupId()));

}

returnmessage;

}

5. Next,let'stryandgetalittleadventurousandreturnalistofvehicles.Inthismethod,wewillreturnalistofcontracts,eachrepresentingavehiclerecord.Wewillfirstneedacontracttostorevehicledata;createoneasfollows:

[DataContract]

classConWHSVehicleTableContract

{

ConWHSVehicleIdvehicleId;

Descriptiondescription;

ConWHSVehRegNumvehRegNum;

ConWHSAcquiredDateacquiredDate;

ConWHSVehicleTypevehicleType;

ConWHSVehicleGroupIdvehicleGroupId;

[DataMember]

publicConWHSVehicleGroupIdVehicleGroupId(

ConWHSVehicleGroupId_vehicleGroupId=

vehicleGroupId)

{

vehicleGroupId=_vehicleGroupId;

returnvehicleGroupId;

}

[DataMember]

publicConWHSAcquiredDateAcquiredDate(

ConWHSAcquiredDate_acquiredDate=acquiredDate)

{

acquiredDate=_acquiredDate;

returnacquiredDate;

}

[DataMember]

publicConWHSVehicleIdvehicleId(

ConWHSVehicleId_vehicleId=vehicleId)

{

vehicleId=_vehicleId;

returnvehicleId;

}

[DataMember]

publicConWHSVehicleTypeVehicleType(

ConWHSVehicleType_vehicleType=vehicleType)

{

vehicleType=_vehicleType;

returnvehicleType;

}

[DataMember]

publicConWHSVehRegNumVehRegNum(

Page 659: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ConWHSVehRegNum_vehRegNum=vehRegNum)

{

vehRegNum=_vehRegNum;

returnvehRegNum;

}

[DataMember]

publicDescriptionDescription(

Description_description=description)

{

description=_description;

returndescription;

}

}

Wecouldsimplyusethetableasthecontract,butusingadatacontractclassallowsusgreatercontroloverthedatapassedbetweenDynamics365forOperationsandtheexternalapplicationthatisusingtheservice.

6. ClosethecodewindowforthecontractclassandopenthecodewindowfortheConWHSVehicleServicesclass.

7. WecannotreturnanarrayofclassesinOperationsinthesamewaythatC#can;instead,weconstructaListandtellthecompilerthetypethatthelistcontains.ThisisdonebytheAifCollectionTypeattributeaddedtothestartofthemethod.Completethemethod,asshowninthefollowingpieceofcode:

[AifCollectionType('return',

Types::Class,

classStr(ConWHSVehicleTableContract))]

publicListGetVehicles()

{

ListvehicleList;

ConWHSVehicleTablevehicles;

ConWHSVehicleTableContractcontract;

vehicleList=newList(Types::Class);

whileselectvehicles

{

contract=newConWHSVehicleTableContract();

contract.vehicleId(vehicles.VehicleId);

contract.vehicleGroupId(vehicles.VehicleGroupId);

contract.vehicleType(vehicles.VehicleType);

contract.vehRegNum(vehicles.VehRegNum);

contract.acquiredDate(vehicles.AcquiredDate);

vehicleList.addEnd(contract);

}

returnvehicleList;

}

8. Wewillnowhaveourtwomethodsthatwillbecomeservicemethods.Tocreatetheservice,addanewitemtotheprojectbychoosingServicesfromtheleft-handlistandServicefromtheright.NametheserviceConWHSVehicleServicesandclickonAdd.

9. SelecttherootConWHSVehicleServicesnodeandsettheClasspropertytoConWHSVehicleServices(theclasswewrote).

10. Enteradescriptionfortheservice,suchasProvidesservicesforvehicles.11. TheExternalNamepropertyshouldbespecifiedasasimplerformoftheservicename.Asitwillbe

withinaservicegroup,wedon'tneedtheprefix.VehicleServicesisappropriateforthisproperty.12. EnteranamespaceintheNamespaceproperty,suchashttp://schemas.contoso.com/ConWHS.TheURLdoes

nothavetoexist,asitisusedasanamespacefortheservice.

Page 660: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

13. Right-clickontheServiceOperationsnodeandselectNewServiceOperation.14. SelectoneofthetwomethodsintheMethodpropertyandmaketheNamepropertythesameasthe

Methodproperty.15. Repeatthisforthenextservicemethod.16. Saveandclosetheservicedesignerwindow.17. Createanewiteminourproject;thistime,ServiceGroupfromtheServiceslist.Nameit

ConWHSServices.18. CreatealabelforContosovehiclemanagementservicesandentertheIDintheDescriptionproperty.19. LocatetheConWHSVehicleServicesserviceintheprojectorApplicationExploreranddragitontothe

ConWHSServicesnodeinthedesigner;thisaddstheservicetotheservicegroup.20. RemovetheConWHSprefixfromtheNamepropertyasthisissuperfluous.21. Saveandclosealldesignersandbuildtheproject.

Page 661: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 662: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...ThefirstnewconceptwashowDynamics365forOperationshandlescollections.InC#,wewouldreturnatypedcollectionoranarray(MyClass[]),andwecouldusetheforeachcommandtoiteratethroughit.

ThisisnotsupportedinX++,sowehavetoreturnaListinstead.Sinceweactuallywanttoreturnatypedcollectiontothecaller,wewillusetheAifCollectionTypeattributetotellthecompilerhowtodothis.

Thenextpartwastocreateaserviceandservicegroup,whichsimplyinstructsthesystemtogeneratepublicservicesexposingthemethodsweaddedtotheservice.

Wewillseehowthisisusedinthenextrecipe.

Page 663: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 664: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ConsumingaDynamics365forOperationsSOAPserviceInthisrecipe,wewillcreateanewC#projecttoconsumetheservicecreatedinthepreviousrecipe.

Beforewestart,weshouldunderstandtheAzureADauthenticationconceptsexplainedintheReading,writing,andupdatingdatathroughODatarecipe,inChapter8,DataManagement,Odata,andOffice.Manyoftheconceptsinthefollowingrecipesextendedtheconceptswecoveredinthischapter.

Inthisexample,wewillcreateaSOAPservicereference.

Page 665: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 666: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyWearecontinuingtheCreatingaservicerecipe,whichmustbecompletedandbuiltbeforewecontinue.

Page 667: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 668: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

ToconsumeaDynamics365forOperationsserviceusingSOAP,followthesesteps:

1. Createanewproject;thistime,chooseVisualC#fromtheTemplatesnodeandthenConsoleApplicationfromtheright.NametheprojectConServiceTestandplaceitintheprojectfoldersthatwesetupforsourcecontrol.EnsurethatthenamespaceisalsoConServiceTest.

2. WewillnowneedtoinstallsomeNuGetpackages.WithinVisualStudio,navigatetoTools|NugetPackageManager|PackageManagerConsole.

3. InthePackageManagerconsole,typethefollowingcommand:

Install-PackageMicrosoft.IdentityModel.Clients.ActiveDirectory

4. Right-clickontheReferencesnodeintheprojectandchooseAddServiceReference.5. Enterhttps://usnconeboxax1aos.cloud.onebox.dynamics.com/soap/services/ConWHSServicesintheAddressfieldand

clickonGo.

ThisistheOperationsURLwith/soap/services/added,followedbytheservicegroupname.

6. ExpandtheConWHSServicesnodeandselectVehicleService.7. ChangetheNamespacefieldtoConWHSandclickonOK.8. CreateanewclasscalledAuthenticate.

Wewillreusethisclass,sothissimplifiestheprocessandallowsustoreusethecodethroughouttheproject.

9. Wewillneedtwoclasses:thecontractclasssowecanpassdatatotheAuthenticateclass,andtheAuthenticateclassitself.

TheconceptsarethesameasdescribedintheReading,writing,andupdatingdatathroughODatarecipeofChapter8,DataManagement,Odata,andOffice;theyarejustseparatedinordertomakethecodereusable.

10. Writethefollowingpieceofcode:

classAuthenticationContract

{

publicstringUserName{get;set;}

publicstringPassword{get;set;}

publicstringDomain{get;set;}

publicstringApplicationId{get;set;}

publicstringResource{get;set;}

publicstringResponse{get;set;}

Page 669: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

}

Thistime,wewillusepropertymethodsinstead,asthismethodallowsmoreflexibility.Forexample,ifweomittheset;argument,thepropertyisread-only:

publicclassAuthenticate

{

publicconststringOAuthHeader="Authorization";

stringbearerkey;

publicAuthenticationContractAuthentication

{get;set;}

publicstringBearerKey

{

get{returnbearerkey;}

}

publicBooleanGetAuthenticationHeader()

{

AuthenticationResultresult;

try

{

result=GetAuthorization();

//Thisgetstheauthorizationtoken,this

//mustbesetontheHttpheaderforall

//requests

bearerkey=

result.CreateAuthorizationHeader();

}

catch(Exceptione)

{

if(e.InnerException!=null)

{

Authentication.Response=

"Authenticationfailed:"+

e.InnerException.Message;

}

else

{

Authentication.Response=

"Authenticationfailed:"+

e.Message;

}

returnfalse;

}

Authentication.Response="OK";

returntrue;

}

publicUserPasswordCredentialGetCredential()

{

stringuri=this.GetSecurityURI();

UserPasswordCredentialcredential;

credential=

newUserPasswordCredential(

Authentication.UserName,

Authentication.Password);

returncredential;

}

publicstringGetSecurityURI()

{

UriBuilderuri;

uri=newUriBuilder(

"https://login.windows.net/"+

Authentication.Domain);

returnuri.ToString();

}

privateAuthenticationResultGetAuthorization()

{

UserPasswordCredentialcredential;

credential=GetCredential();

AuthenticationContextcontext

=newAuthenticationContext(GetSecurityURI());

Page 670: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Task<AuthenticationResult>task=

context.AcquireTokenAsync(

Authentication.Resource.TrimEnd('/'),

Authentication.ApplicationId,

credential);

task.Wait();

returntask.Result;

}

}

11. Youcanclosethecodeeditorforthisclass.12. CreateanewclasscalledSOAPUtil.

Thecodeinthisclasswas'inspired'byasampleutilityprovidedbythisURL:https://github.com/Microsoft/Dynamics-AX-Integration/blob/master/ServiceSamples/SoapUtility/SoapHelper.cs.

13. Settheusingdeclarationstothefollowinglinesofcode:

usingSystem;

usingSystem.Linq;

usingSystem.ServiceModel;

usingSystem.ServiceModel.Channels;

14. Tosimplifytheusage,wewillneedautilityclass.Thisshouldbewrittenasfollows:

publicclassSoapUtil

{

publicconststringOAuthHeader="Authorization";

publicstaticstringGetServiceURI(

string_service,

string_d365OURI)

{

stringserviceName=_service.Split('.').Last();

if(serviceName=="")

{

serviceName=_service;

}

return_d365OURI.TrimEnd('/')+"/soap/services/"

+serviceName;

}

publicstaticEndpointAddressGetEndpointAddress(

string_uri)

{

EndpointAddressaddress;

address=newEndpointAddress(_uri);

returnaddress;

}

publicstaticBindingGetBinding(

EndpointAddress_address)

{

BasicHttpBindingbinding;

binding=newBasicHttpBinding(

BasicHttpSecurityMode.Transport);

//Setbindingtimeoutandotherconfiguration

//settings

binding.ReaderQuotas.MaxStringContentLength=

int.MaxValue;

binding.ReaderQuotas.MaxArrayLength=int.MaxValue;

binding.ReaderQuotas.MaxNameTableCharCount=

int.MaxValue;

Page 671: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

binding.ReceiveTimeout=TimeSpan.MaxValue;

binding.SendTimeout=TimeSpan.MaxValue;

binding.MaxReceivedMessageSize=int.MaxValue;

varhttpsBindingElement=

binding.CreateBindingElements().

OfType<HttpsBindingElement>().FirstOrDefault();

if(httpsBindingElement!=null)

{

//Largestpossibleis100000,otherwisethrows

//anexception

httpsBindingElement.MaxPendingAccepts=10000;

}

varhttpBindingElement=

binding.CreateBindingElements().

OfType<HttpBindingElement>().FirstOrDefault();

if(httpBindingElement!=null)

{

httpBindingElement.MaxPendingAccepts=10000;

}

returnbinding;

}

}

TheGetBindingmethodisthekeyonehere.Wewillauthenticatethroughwhatiscalledabearerkey,whichisimplementedbysettingtheAuthorizationheadervariable;so,wewillwriteabindingmanuallysothatthebindinginApp.Configdoesnotinterfere.

15. ClosethecodeeditorandcreateanewclasscalledUpdateVehicleGroup.16. Settheusingstatementsasfollows:

usingSystem.ServiceModel.Channels;

usingSystem.ServiceModel;

17. Writethefollowingmethod:

publicConWHS.ConWHSMessageContractUpdateSOAP(

AuthenticationContract_authContract,

ConWHS.ConWHSVehicleGroupChangeContract_change)

{

Authenticateauth=newAuthenticate();

auth.Authentication=_authContract;

ConWHS.ConWHSMessageContractmessage;

//Ifwefailtogettheauthorizationbearer

//key,stopandreturntheerrorthrough

//themessagecontract

if(!auth.GetAuthenticationHeader())

{

message=newConWHS.ConWHSMessageContract();

message.Success=false;

message.Message=auth.Authentication.Response;

returnmessage;

}

stringbearerKey=auth.BearerKey;

stringendPoint;

endPoint=SoapUtil.GetServiceURI(

"ConWHSServices",

_authContract.Resource);

EndpointAddressaddress;

address=SoapUtil.GetEndpointAddress(endPoint);

Bindingbinding=SoapUtil.GetBinding(address);

Page 672: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ConWHS.VehicleServiceClientclient;

client=newConWHS.VehicleServiceClient(

binding,address);

ConWHS.CallContextconContext;

conContext=newConWHS.CallContext();

conContext.Company="USMF";

conContext.Language="en-us";

conContext.PartitionKey="initial";

varchannel=client.InnerChannel;

//wedon'tusethecontext,itisusedtoaffect

//thechannelsothatwecansettheoutgoing

//messageproperties

//Usingisusedsothatitisdisposedof

//correctly.

using(OperationContextScopecontext

=newOperationContextScope(channel))

{

//Settheauthenticationbearerkey

HttpRequestMessagePropertyrequestMessage;

requestMessage=newHttpRequestMessageProperty();

requestMessage.Headers[SoapUtil.OAuthHeader]=

bearerKey;

OperationContext.Current.OutgoingMessageProperties[

HttpRequestMessageProperty.Name]=

requestMessage;

//setupthemessage

ConWHS.UpdateVehicleGroupupdate;

update=newConWHS.UpdateVehicleGroup();

update._contract=_change;

update.CallContext=conContext;

ConWHS.UpdateVehicleGroupResponseresponse;

message=newConWHS.ConWHSMessageContract();

response=

((ConWHS.VehicleService)channel).

UpdateVehicleGroup(update);

//theresponsecontainsthecurrentinfolog

//andthereturnresult,whichthereturntype

//wereturnedintheD365Omethod.

message=response.result;

}

returnmessage;

}

18. Let'sseeifitworks.IntheProgram.csfile,writethefollowingpieceofcodeastheMainmethod:

staticvoidMain(string[]args)

{

AuthenticationContractauthContract;

authContract=newAuthenticationContract();

authContract.ApplicationId="<yourapplicationId>";

authContract.Resource="https://usnconeboxax1aos.cloud.onebox.dynamics.com/";

Console.WriteLine("UsetheO365accountthatyouusetologintoDynamics365forOperations");

Console.Write("O365Username:");

authContract.UserName=Console.ReadLine();

Console.Write("O365Password:");

authContract.Password=Console.ReadLine();

Console.WriteLine("Thisisyourtenant,andshouldbeyourdomain,suchasyourdomain.com");

stringdefaultDomain=

authContract.UserName.Split('@').Last<string>();

Console.WriteLine("O365Domain:");

Console.Write("("+defaultDomain+"):");

authContract.Domain=Console.ReadLine();

if(authContract.Domain=="")

{

authContract.Domain=defaultDomain;

}

UpdateVehicleGroupupdate=newUpdateVehicleGroup();

ConWHS.ConWHSVehicleGroupChangeContractchange;

change=newConWHS.ConWHSVehicleGroupChangeContract();

change.VehicleId="X0002";

Page 673: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

change.VehicleGroupId="Leased";

ConWHS.ConWHSMessageContractmessage;

message=update.UpdateSOAP(authContract,change);

if(message.Success)

{

Console.WriteLine("Success!");

}

else

{

Console.WriteLine(message.Message);

}

Console.ReadLine();

}

19. Buildandruntheproject.

Page 674: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 675: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...WhenweaddedServiceReference,VisualStudiocreatedatypeforeachcontracttheservicereferenceuses,andaclientclassinordertointeractwiththeservicesreferencedbyit.ItcorrespondstotheServiceGroupwithinDynamicsAX365forOperations.ThisisalotmorehelpfulthanwhatisprovidedbyJSON,whichiswhythisrecipewaswrittenbeforetheJSONmethod.Inthenextrecipe,wewillseethatwecandeserializeSOAPtypesfromJSON.

TheAuthenticateclassisverysimilartotheclasswewroteinChapter8,DataManagement,Odata,andOffice,justalittlemoreelegant.Wearesimplygettinganauthenticationtoken(knownasabearerkey)thatisusedwhentherequestsaremade.

TheUpdateVehicleGroupsclasshasalittlemoretoit.Thefirstpartistogettheauthorizationcode,whichisjustavariationontheusedforaccessdatathroughOData.Itvariesmorefromthispointon.

Whenweconstructedtheclientwiththecode:client=newConWHS.VehicleServiceClient(binding,address);,weusedabindingthatweconstructedmanually.Wedidthisbecausewedon'twanttheApp.Configbindingstointerfere.ThebindingsinApp.Configaregeneratedautomaticallyforuswhenwecreateorupdatetheservicereference.Inourcase,wedon'twantanythingspecial;wejustwantabasicbinding.TheauthenticationisdonebyspecifyingsettingtheAuthorizationheaderpropertytothebearerkey(authenticationtoken).

ThenextnewpartisalegacyfromAX2012,theCallContextclass.Thiswasusedforsettingthecompany,language,andalsothecredentialstouse.Thisisnolongermandatory,andisfilledinforcompleteness.Partitionisstillactive,butisonlyusedforcertaintestingscenarios:theclientcannolongeraccessanyotherpartitionthan"initial".

Thenextpartlookscomplicated,butit'stheonlywayinwhichwecansettherequestheadervariables.Inthissection,wewillsetupaHttpRequestMessagePropertyinstanceinordertosettheauthorizationheadervariable:

HttpRequestMessagePropertyrequestMessage;

requestMessage=newHttpRequestMessageProperty();

requestMessage.Headers[SoapUtil.OAuthHeader]=bearerKey;

Thisisthenpassedtothecurrentoutgoingmessagepropertieswiththefollowingline:

OperationContext.Current.OutgoingMessageProperties[

HttpRequestMessageProperty.Name]=requestMessage;

Wewillneedtoensurethatthisiscleanedupafterwards,sothisiswhyweenclosedthecodeinthefollowingusingclause:

using(OperationContextScopecontext

=newOperationContextScope(channel))

Thecodethatdoestheactualworkisasfollows:

ConWHS.UpdateVehicleGroupupdate;

Page 676: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

update=newConWHS.UpdateVehicleGroup();

update._contract=_change;

update.CallContext=conContext;

ConWHS.UpdateVehicleGroupResponseresponse;

message=newConWHS.ConWHSMessageContract();

response=((ConWHS.VehicleService)channel).UpdateVehicleGroup(update);

message=response.result;

WithintheConWHSservice,referenceisaclasscalledUpdateVehicleGroup,whichisthenameoftheservicemethodwewrote.Thedeclarationwasasfollows:

publicConWHSMessageContractUpdateVehicleGroup(

ConWHSVehicleGroupChangeContract_contract)

VisualStudiocreatedthisclassbecauseoftheinputparameter.TheclasscontainspropertiesfortheCallContextproperty,whichisalwayscreated,andalsoforthe_contractmethodinputparameter.

Page 677: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 678: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

SeealsoTroubleshootserviceauthentication(https://ax.help.dynamics.com/en/wiki/troubleshooting-service-authentication/).Serviceendpoints(https://ax.help.dynamics.com/en/wiki/dynamics-ax-7-services-technical-concepts-guide/).Thisisalittlegeneric,butitcontainslinkstosomeusefulcodesamples.

Page 679: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 680: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ConsumingaDynamics365forOperationsJSONservice

Inthisrecipe,wewillextendthepreviousC#projecttoconsumetheserviceusingJSON.

TheprimarydifferenceisthatJSONwillnotcreatethecontractandclientclassesforus,wewillneedtowritethem.WewilluseaNuGetpackagetohelpwiththeserializationanddeserializationofC#classestoJSON.

Page 681: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 682: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyWearecontinuingthepreviousrecipe,whichmustbecompletedandbuiltbeforewecontinue.

Page 683: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 684: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

ToconsumeaDynamics365forOperationsserviceusingJSON,followthesesteps:

1. First,let'stakealookatwhatJSONlookslike;thiswillhelptherecipemakemoresenseasweprogress.OpenthefollowingURLusingInternetExplorer:

https://usnconeboxax1aos.cloud.onebox.dynamics.com/api/services/

2. Thiswillaskyoutoopenservices.json;clickonOpen,whichopensafileinVisualStudiothatcontainsalltheservicesexposed.Thefilewillcontainourserviceinthefollowingformat:

{"ServiceGroups":[{"Name":"ConWHSServices"},{"Name":"CuesServiceGroup"}

Now,openthefollowingURLonInternetExplorer:

https://usnconeboxax1aos.cloud.onebox.dynamics.com/api/services/ConWHSServices

3. Thistime,theJSONfileiscalledCONWHSServices.json,andcontainsthefollowing:

{"Services":[{"Name":"VehicleServices"}]}

4. YoucandothesamebyaddingVehicleServicestotheURL,whichwillopenaJSONfilewiththefollowinglinesofcode:

{"Operations":[{"Name":"UpdateVehicleGroup"},{"Name":"GetVehicles"}]}

5. TheJSONfortheGetVehiclesoperationisasfollows:

{"Parameters":[],"Return":{"Name":"return","Type":"ConWHSVehicleTableContract[]"}}

6. TheJSONforUpdateVehicleGroupisshownhere:

{"Parameters":[{"Name":"_contract","Type":"ConWHSVehicleGroupChangeContract"}],"Return":{"Name":"return","Type":"ConWHSMessageContract"}}

Thetakeawayhereistonotethatwehavethreelevels:ServiceGroups,Services,andOperations,whichcorrelatetohowwecreateserviceswithinDynamics365forOperations.

7. OpenthePackageManageconsolefromTools|NugetPackageManager,andtypethefollowingcommand:

Page 685: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Install-PackageNewtonsoft.Json

8. CreateaclassfortheC#classesthatwewillusetodeserializetheJSONintoandnametheclassJsonClient.

9. Setupthefollowingusingdeclarations:

usingSystem;

usingSystem.Collections.Generic;

usingNewtonsoft.Json;

usingSystem.Net;

usingSystem.IO;

10. ChangethenamespacetoConServiceTest.JsonasourclassesmaynotbeuniqueintheConServiceTestnamespace.

11. Removethedefaultclass,sothatweonlyhaveablanklineinsidethenamespacebraces.12. WewillfirstwritetheclassesfortheServiceGroupJSON,whichwas:{"ServiceGroups":

[{"Name":"ConWHSServices"},{etc.}".ThisisrepresentedinC#asfollows:

publicclassServiceGroups

{

[JsonProperty("ServiceGroups")]

publicList<ServiceGroup>ServiceGroupNames{get;set;}

publicclassServiceGroup

{

[JsonProperty("Name")]

publicstringServiceGroupName{get;set;}

}

TheJsonPropertydecorationmapsthemethodtotheJSONproperty.

13. WecancontinuethispatternfortheServicesandOperationslevels,asshowninthefollowingpieceofcode:

publicclassServices

{

[JsonProperty("Services")]

publicList<Service>ServiceNames{get;set;}

}

publicclassService

{

[JsonProperty("Name")]

publicStringName{get;set;}

}

publicclassOperations

{

[JsonProperty("Operations")]

publicList<Operation>OperationNames{get;set;}

}

publicclassOperation

{

[JsonProperty("Name")]

publicstringName{get;set;}

}

Page 686: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

14. Let'swriteaclienttoaccesstheserviceanddeserializethedataintoournewclasses.InourJsonClient.csfile,createanewclasscalledClient.

15. Starttheclasswiththefollowingcode,inordertosetuptheglobalvariablesandconstructor:

publicclassClient

{

stringd365OURI;

Authenticateauth;

publicClient(string_d365OURI,Authenticate_auth)

{

d365OURI=_d365OURI;

auth=_auth;

}

}

16. Next,wewillwritetwohelperfunctions--thefirsttocreatearequestforthesupplieraddress,andtheothertoreadtheresponsefromtherequestintoastring.Thecodeisasfollows:

privateHttpWebRequestCreateRequest(string_address)

{

HttpWebRequestwebRequest;

webRequest=(HttpWebRequest)HttpWebRequest.Create(

_address);

webRequest.Method="POST";

//therequestwillbeempty.

webRequest.ContentLength=0;

webRequest.Headers.Set(JsonUtil.OAuthHeader,

auth.BearerKey);

returnwebRequest;

}

privatestringReadJsonResponse(HttpWebRequest_request)

{

stringjsonString;

using(HttpWebResponsewebResponse=

(HttpWebResponse)_request.GetResponse())

{

using(Streamstream=

webResponse.GetResponseStream())

{

using(StreamReaderreader=new

StreamReader(stream))

{

jsonString=reader.ReadToEnd();

}

}

}

returnjsonString;

}

17. Let'swritethemethodstoreadthemetadata(theservicegroups,services,andoperations).Thecodeforthisisasfollows:

publicServiceGroupsGetServiceGroups()

{

stringserviceGroupAddress=

Json.JsonUtil.GetServiceURI("",d365OURI);

HttpWebRequestwebRequest;

webRequest=

CreateRequest(serviceGroupAddress.TrimEnd('/'));

//Mustoverridethemetadatarequestcalls

//toGETasthisisnotREST

webRequest.Method="GET";

stringjsonString=ReadJsonResponse(webRequest);

ServiceGroupsserviceGroups;

serviceGroups=

Page 687: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

JsonConvert.DeserializeObject<ServiceGroups>(

jsonString);

returnserviceGroups;

}

publicServicesGetServices(string_serviceGroup)

{

stringserviceGroupAddress=Json.JsonUtil.GetServiceURI(_serviceGroup,d365OURI);

HttpWebRequestwebRequest;

webRequest=CreateRequest(serviceGroupAddress);

//Mustoverridethemetadatarequestcalls

//toGETasthisisnotREST

webRequest.Method="GET";

stringjsonString=ReadJsonResponse(webRequest);

Servicesservices;

services=

JsonConvert.DeserializeObject<Services>(

jsonString);

returnservices;

}

publicOperationsGetOperations(

string_serviceGroup,

string_vehicleService)

{

stringservicePath=_serviceGroup.TrimEnd('/')

+"/"+_vehicleService;

stringserviceGroupAddress;

serviceGroupAddress=Json.JsonUtil.GetServiceURI(

servicePath,d365OURI);

HttpWebRequestwebRequest;

webRequest=CreateRequest(serviceGroupAddress);

//Mustoverridethemetadatarequestcalls

//toGETasthisisnotREST

webRequest.Method="GET";

stringjsonString=ReadJsonResponse(webRequest);

Operationsoperations;

operations=

JsonConvert.DeserializeObject<Operations>(

jsonString);

returnoperations;

}

18. Thenextmethodistomakeacalltogetthevehiclelist.Thistime,wearereusingtheConWHS.ConWHSVehicleTableContracttypethatwascreatedinthepreviousrecipe.Thecodeshouldbewrittenasfollows:

publicConWHS.ConWHSVehicleTableContract[]GetVehicles(

string_serviceGroup,

string_service,

string_operation)

{

stringservicePath;

servicePath=_serviceGroup.TrimEnd('/')

+"/"+_service.TrimEnd('/')

+"/"+_operation;

stringserviceGroupAddress;

serviceGroupAddress=Json.JsonUtil.GetServiceURI(

servicePath,d365OURI);

HttpWebRequestwebRequest;

webRequest=CreateRequest(serviceGroupAddress);

stringjsonString=ReadJsonResponse(webRequest);

ConWHS.ConWHSVehicleTableContract[]vehicles;

vehicles=JsonConvert.DeserializeObject

<ConWHS.ConWHSVehicleTableContract[]>(jsonString);

returnvehicles;

}

Page 688: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Youmaywonderhowthiscouldpossiblywork.HowcanthedeserializerpossiblyknowhowtoconverttheJSONfileintoaclasswithouttheJsonPropertydecoration?Thisisbecausethepropertymethodshavethesamenamesasthepropertymethods.

19. Let'stestthisnowandseewhathappens.IntheProgram.csfile,commentouttheSOAPcodefromUpdateVehicleGroupupdate=...andwritethefollowingpieceofcode:

Authenticateauth=newAuthenticate();

auth.Authentication=authContract;

if(auth.GetAuthenticationHeader())

{

Json.Clientclient=newJson.Client(authContract.Resource,auth);

Json.ServiceGroupsserviceGroups;

serviceGroups=client.GetServiceGroups();

foreach(Json.ServiceGroupserviceGroupin

serviceGroups.ServiceGroupNames)

{

Console.WriteLine(serviceGroup.Name);

}

Json.Servicesservices;

services=client.GetServices("ConWHSServices");

foreach(Json.Serviceserviceinservices.ServiceNames)

{

Console.WriteLine(service.Name);

}

Json.Operationsoperations;

operations=client.GetOperations(

"ConWHSServices","VehicleServices");

foreach(Json.Operationoperationin

operations.OperationNames)

{

Console.WriteLine(operation.Name);

}

GetVehiclesgetVehicles=newGetVehicles();

ConWHS.ConWHSVehicleTableContract[]vehicles;

vehicles=getVehicles.GetVehiclesJson(authContract);

foreach(ConWHS.ConWHSVehicleTableContractvehicle

invehicles)

{

Console.WriteLine(vehicle.vehicleId);

}

}

20. Closethecodeeditors,build,andruntheprojecttotestit.

Page 689: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 690: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...TheJSONmethodactuallycarrieslessoverheadintermsofsettingupthecallsthanSOAP.Wedon'tneedtosetupanybindings;wejustneedtosettheAuthorizationheaderproperty,whichiseasilydoneusingthismethod.

TherestisgenericJSON,andthecodewewriteisthesameasifwewerewritingforanyotherapplicationthatisabletousethismethod.

ThedifficultpartissettinguptheC#classesinwhichtodeserializeto,orserializefrom.Itisn'tobviousatfirst,especiallywhenasimplelistofnames,suchasServiceGroups,requirestwoclasses.Thankfully,NewtonsofthavewrittenagreatNuGetpackagethatmeansalotoftheworkisdoneforus.WecanalsocheatalittlebyusingSOAPtogenerateclassesforuse,likewedidfortheGetVehiclesmethod.

Toexplainthisfurther,takethefollowingJSON:

{"Services":[{"Name":"VehicleServices"}]}

TheC#classforthiswasasfollows:

publicclassServices

{

[JsonProperty("Services")]

publicList<Service>ServiceNames{get;set;}

}

publicclassService

{

[JsonProperty("Name")]

publicStringName{get;set;}

}

TheouterpartoftheJSONcontainstheServicesproperty.ThispropertyisalistofNameproperties.WhentheJSONisdeserializedintotheServicesclass,itlooksforapropertythatiseithernamedServicesorhasthe[JsonProperty("Services")]decoration.Inourcase,publicList<Service>ServiceNames{get;set;}hastherequireddecoration.

Thisprocesscontinues.ThedeserializernowcreatesaListoftypeService.ItwilliteratethroughtheJSON,mappingtheNameJSONpropertytotheService.Nameproperty.Inthiscase,theJsonPropertydecorationisnotactuallyrequired,asthepropertynameisthesameasthemethodname.ThefactthatitmatchesonnameisthereasonthatwecanusetheclassescreatedbytheServiceReference.

TherequestisdonebysettinguptheHttpWebRequestobject,whichsetstheAuthorisationheaderpropertytoourbearerkeyandtheMethodpropertytoPOST;thisisthedefaultasmostcallswillbeRESTandthesemustusethePOSTmethod.

Formetadatacalls,wewillsettheMethodpropertytoGET,whichisrequiredfornon-RESTcalls.Wewillthendeserializedirectlyintotheclass.

TheGetVehiclesmethoddeserializesintoanarray;weknewthisbecausetheGetVehicles.jsonfilecontainedthefollowinglinesofcode:

Page 691: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

{"Parameters":[],"Return":{"Name":"return",

"Type":"ConWHSVehicleTableContract[]"}}

Page 692: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 693: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...Let'ssaywewanttopassdatatoaJSONservice,asisrequiredbytheUpdateVehicleGroupsoperation.

OpenthefollowingURLinInternetExplorer:

https://usnconeboxax1aos.cloud.onebox.dynamics.com/api/services/ConWHSServices/VehicleServices/UpdateVehicleGroup

TheJSONfileitopenscontainsthefollowinglinesofcode:

{"Parameters":[{"Name":"_contract","Type":"ConWHSVehicleGroupChangeContract"}],"Return":{"Name":"return","Type":"ConWHSMessageContract"}}

TheactualinputJSONtherequestneedswillfollowthefollowingpattern:

{"_contract":{"VehicleGroupId":"Newvehiclegroup","VehicleId":"X0002","hideVehicleId":0,"parmCallId":"00000000-0000-0000-0000-000000000000","parmSessionIdx":0,"parmSessionLoginDateTime":"0001-01-01T00:00:00"}}

Forthis,wewillneedtoconstructaclasswithaJsonPropertyof_contract;thisisdonebythefollowingclass,whichshouldbeaddedtoJsonClient.cs:

publicclassUpdateVehicleParameter

{

[JsonProperty("_contract")]

publicConWHS.ConWHSVehicleGroupChangeContractContract

{get;set;}

}

TheprecedingclasswillnowserializetotheJSONthattheoperationneeds.

Wewillneedanewmethodinourclientclasstoperformtheupdate;thisisdonebywritingthefollowingpieceofcode:

publicConWHS.ConWHSMessageContractUpdateVehicleGroup(

ConWHS.ConWHSVehicleGroupChangeContract_change,

string_serviceGroup,

string_service,

string_operation)

{

stringservicePath;

servicePath=_serviceGroup.TrimEnd('/')

+"/"+_service.TrimEnd('/')

+"/"+_operation;

stringserviceGroupAddress;

serviceGroupAddress=Json.JsonUtil.GetServiceURI(

servicePath,d365OURI);

HttpWebRequestwebRequest;

webRequest=CreateRequest(serviceGroupAddress);

UpdateVehicleParameterparm;

parm=newJson.Client.UpdateVehicleParameter();

parm.Contract=_change;

stringjsonOutString=JsonConvert.SerializeObject(parm);

webRequest.ContentLength=jsonOutString.Length;

using(Streamstream=webRequest.GetRequestStream())

{

using(StreamWriterwriter=newStreamWriter(stream))

{

writer.Write(jsonOutString);

writer.Flush();

}

}

Page 694: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

stringjsonString=ReadJsonResponse(webRequest);

ConWHS.ConWHSMessageContractmsg;

msg=JsonConvert.DeserializeObject

<ConWHS.ConWHSMessageContract>(jsonString);

returnmsg;

}

Thecomplicatedpartwasvisualizingtheinputparameterasaclassfromthatpointintheprecedingmethodislargelythesameasbefore.WewilljustwritetheJSONstringtothewebrequest'srequeststream.

Now,addanewmethodintheUpdateVehicleGroup.csfileintheUpdateVehicleGroupclass.Themethodshouldreadasfollows:

publicConWHS.ConWHSMessageContractUpdateJSON(

AuthenticationContract_authContract,

ConWHS.ConWHSVehicleGroupChangeContract_change)

{

Authenticateauth=newAuthenticate();

auth.Authentication=_authContract;

ConWHS.ConWHSMessageContractmessage;

if(!auth.GetAuthenticationHeader())

{

message=newConWHS.ConWHSMessageContract();

message.Success=false;

message.Message=auth.Authentication.Response;

returnmessage;

}

Json.Clientclient=newJson.Client(

_authContract.Resource,auth);

message=client.UpdateVehicleGroup(

_change,

"ConWHSServices",

"VehicleServices",

"UpdateVehicleGroup");

returnmessage;

}

Thisistosimplifyusage.Finally,addasectiontotheMainmethodofProgram.cs,justbelowtheexistingJSONtestcode:

ConWHS.ConWHSVehicleGroupChangeContractjsonChange;

jsonChange=newConWHS.ConWHSVehicleGroupChangeContract();

Console.Write("VehicleId:");

jsonChange.VehicleId=Console.ReadLine();

Console.Write("Newvehiclegroup:");

jsonChange.VehicleGroupId=Console.ReadLine();

ConWHS.ConWHSMessageContractjsonMessage;

UpdateVehicleGroupjsonUpdate;

jsonUpdate=newUpdateVehicleGroup();

jsonMessage=jsonUpdate.UpdateJSON(

authContract,jsonChange);

if(jsonMessage.Success)

{

Console.WriteLine("Success!");

}

else

{

Console.WriteLine(jsonMessage.Message);

}

Thisisjustthestart.Youcanexperimentfurther;forexample,deserializingtheDataSetobjectssoyoucanpresentdatadirectlytogridsinyourapps.

Page 695: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 696: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Seealso...NewtonsoftJSONSamples(http://www.newtonsoft.com/JSON/help/html/Samples.htm)

Page 697: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 698: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ConsuminganexternalservicewithinDynamics365forOperationsThetechniquehasn'tchangedsubstantiallysinceDynamicsAX2012.Thekeydifferenceisthatwewillneedtomanuallycraftthebinding.WewillstillneedtocreateaC#projecttoconsumethewebservice,andthenuseitasareferencewithinourDynamics365forOperationsproject.

TheexampleserviceisaweatherserviceprovidedbyWebserviceX.net(www.webservicex.net).Thereisnorecommendationhere;itwassimplythefirstoneinwhenIsearchedforweatherwebservices.Theaimistocreatearecipethatyoucanuseforyourwebservices:thechosenserviceinthiscaseisn'trelevant.

Whenselectingaservicetouse,wemustcheckthelicensetermsandconditions,asnotallarefreetouse;justbecausethelicensetermsaren'tenforced,itdoesnotmeanthatitisfreetouse.

Page 699: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 700: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyWewillneedanexistingDynamics365forOperationsprojectavailable.

Page 701: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 702: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreatetheservicewrapperfortheservice,followthesesteps:

1. CreateanewC#ClassLibraryprojectnamedWeatherService.2. Renametheclass1.csfiletoWeatherService.cs.3. Createanewservicereferencebyright-clickingontheReferencesnodeinSolutionExplorerand

choosingAddServiceReference.

4. Enterhttp://www.webservicex.net/globalweather.asmx?WSDLintheAddressfieldandclickonGo.5. EnterGlobalWeatherintheNamespacefield,andclickonOK.6. OpentheWeatherService.csfileandenterthefollowingpieceofcode:

usingSystem.ServiceModel;

usingWeatherService.GlobalWeather;

namespaceContoso.Weather

{

publicclassWeather

{

publicconststringserviceAddress

="http://www.webservicex.net/globalweather.asmx";

publicstaticstringGetWeatherForCity(

string_country,string_city)

{

//Wecan'tmodifyD365O'sconfigfile,sowe

//needtoaddthebindingmanually

varbinding=newBasicHttpBinding();

binding.MaxReceivedMessageSize=int.MaxValue;

varaddress=

newEndpointAddress(serviceAddress);

GlobalWeatherSoapClientclient=

newGlobalWeatherSoapClient(

binding,address);

returnclient.GetWeather(_city,country);

}

publicstaticstringGetCitiesByCountry(

string_country)

{

//Wecan'tmodifyD365O'sconfigfile,sowe

//needtoaddthebindingmanually

varbinding=newBasicHttpBinding();

binding.MaxReceivedMessageSize=int.MaxValue;

varaddress=new

EndpointAddress(serviceAddress);

GlobalWeatherSoapClientclient=

newGlobalWeatherSoapClient(

binding,address);

returnclient.GetCitiesByCountry(_country);

}

}

}

Thenamespaceshouldbebasedonyourorganization'sconventions,forexample,Contoso.Weather,orContoso.Services.)

7. Saveallandbuildtheproject.8. CopytheDLLfrombin/debug,forexample,C:\Projects\TFS\WeatherService\WeatherService\bin\Debug)tothe

projectfolderoftheDynamics365forOperationsproject(C:\Projects\TFS\ConWHSVehicleManagement\ConWHSVehicleManagement).

9. AddtheDLLtosourcecontrolusingVisualStudio'sSourceControlExplorer.

Page 703: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ThefollowingstepsshowhowtousetheDLLcreatedinthepreviousstepsinordertoaccesstheservicefromwithinDynamics365forOperations:

1. Closethesolution,andopenyourDynamics365forOperationsproject,inourcaseConWHSVehicleManagement.

2. Right-clickontheReferencesnodeandchooseAddreference.3. ClicktheBrowsetab,toselecttheDLLfilethatwecopiedtothisproject'sprojectfolder.Selectthe

DLLandclickonOK.4. CreateanewclassnamedConWHSWeatherService.5. Createtwomethodssowecaneasilyaccesstheservice,asshownhere:

publicstaticstrGetCities(str_country)

{

returnContoso.Weather.Weather::GetCitiesByCountry(

_country);

}

publicstaticstrGetWeather(str_country,str_city)

{

returnContoso.Weather.Weather::GetWeatherForCity(

_country,_city);

}

6. Totestthis,createaformcalledConWHSWeatherTestandusetheDialog-Basicformpattern.7. Right-clickontheMethodsnode,clickonNewFormMethod,andenterthefollowingpieceofcode:

strcityName;

strcountryName;

Notesresult;

publiceditstrCityName(boolean_set,str_cityName)

{

if(_set)

{

cityName=_cityName;

result=ConWHSWeatherService::GetCities(

countryName);

}

returncityName;

}

publiceditstrCountryName(boolean_set,str_countryName)

{

if(_set)

{

countryName=_countryName;

result=ConWHSWeatherService::GetWeather(

countryName,cityName);

}

returncountryName;

}

publicdisplayNotesResult()

{

returnresult;

}

8. ClosethecodeeditorandaddagroupcontrolundertheDesignnode.9. NametheMainFormGroupcontrol,andsettheCaptionpropertyasdesired.10. CreatethreeStringcontrolsasdefinedinthefollowingtable:

Name DataMethod Label MultiLine

Page 704: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CountryNameCtrl CountryName Country No

CityNameCtrl CityName City No

ResultCtrl Result Result Yes

YoumayalsowishtoadjustthewidthandheightofResultCtrl.

11. CompletethepatternbyaddingaButtonGroupcontrolwithtwoCommandbuttonsforOKandCancel.Theyaren'tneededforourtest,buttheformpatternweselectedrequiresthis.

12. SaveandclosetheformdesignerandcreateamenuitemnamedConWHSWeatherTest,labelledTestWeather.13. AddthemenutothePeriodicTaskssubmenuoftheConWHSVehicleManagementmenu.14. Buildtheprojectandtesttheform.

YoumayreceiveanerrorstatingthattheDLLorPDBfilecannotbecopiedtoC:\AOSService\PackagesLocalDirectory\CONWHSVehicleManagement\bin(orsimilarfolder).Inthiscase,stoptheAOSServiceapplicationpoolinInternetInformationServices(IIS)Manager.Youcanstartitoncethebuildhascompleted-therecycleoptionmaynothelpinthiscase.

15. Navigatetothemenuitemandenterthecountryname,suchasUnitedKingdom.16. TheservicecorrectlyreturnsanXMLstringcontainingalistofcities.17. Enterthecity,andyouwillreceiveforecastdata.

Page 705: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 706: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...ThefirstpartoftherecipeisstraightforwardSOAP.Theonlyvarianceisthatwemanuallycraftthebinding.

WhenweaddedtheDLLtoourDynamics365forOperationsproject,itmakesthetypesintheDLLavailabletouse;thisiswhytheIntellisenseworkedwhenwewrotethetwomethodsinX++.

Theformisjustatestbed,nothingreallynewhere.

Whentheprojectisbuilt,itcopiestheDLLtothebinfolderofthepackage.InthecaseoftheConWHSVehicleManagementpackage,itisasfollows:C:\AOSService\PackagesLocalDirectory\CONWHSVehicleManagement\bin

Thiswouldcauseaproblemwhenabuildservertriestobuildtheproject,itwillneedtheDLL.Thisiswhywecopiedittoafolderthatisalreadyinsourcecontrol.Thereareothersolutionstothisbut,inmanycases,wemaynotalwayshavethesamesourcetoaDLL.

Page 707: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 708: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...

IfweputtogetherourknowledgeofJSON,wecouldcreateasolutionwhereweconvertedtheXMLtoJSONandserializedthistoC#classes.Thiswouldmaketheimplementationmucheasiertouse.

Therearemanywebservicesoutthere,ofwhichmanyarefreetouse.Theycaninclude:currencyexchangerates,unitconversions,orevenintegrationwithabot.

Page 709: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 710: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ExtensibilityThroughMetadataandDataDate-EffectivenessInthischapter,wewillcoverthefollowingrecipes:

UsingmetadatafordataaccessUsingInterfacesforextensibilitythroughmetadataMakingdatadate-effective

Page 711: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 712: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

IntroductionInthischapter,thefocuswillbeonprovidingtechniquestomakeoursolutionmoreeasilyextendablebyotherdevelopersandalsotoprovidemorecontroltousers.

Page 713: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 714: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Usingmetadatafordataaccess

AlltablesandfieldswithinDynamics365forOperationshaveanID.ThetableIDcanbeusedtogenerateaninstanceofthetable,andwecanworkwiththattableasifwehaddeclareditasatypeinamethod,makingourcodemoregenericandmoreeasilyextendable.

Inthisexample,wewillwriteadatadefaultingframeworktodemonstratethis.Here,wewillcreateatableandaformthatallowsustostoredefaultsforatable,andaclassthatwillsetthedefaultswhenarecordiscreated.

Page 715: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 716: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Gettingready...Wewillneedasettingsformandtable,alongwithatableforwhichweshallsetthedefaultsfor.Thisexamplewillusethesamplevehiclemanagementsolutiondevelopedduringthecourseofthisbook.

Page 717: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 718: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...First,wewillcreatethesettingstableforthefieldsthatwewanttoallowdefaultsfor.Todoso,followthesesteps:

1. CreateanewtablenamedConWHSVehicleTableDefaults.

Thisparticularsamplewillbefixedtothevehicletable,butyoushouldbeabletoextendthistoworkwithanytableyoudesire.

2. LocatetheExtendeddatatypes(EDT)RefFieldName,andRefFieldLabelintheApplicationExploreranddragthemtotheFieldsnodeofthetable.MaketheRefFieldLabelfieldread-only.

3. DragtheEDTString255totheFieldsnodeandrenamethefieldtoDefaultValueStr.CreatealabelforDefaultvalue,andsettheLabelpropertytoit.

Thepatternforthistypeoftablewouldnormallycontinuebyaddingafieldforeachbasetype,andtheformwoulduseahandlertoshowtherelevantfieldbaseonthefield'sbasetype.Inthiscase,wewillstopatstring,soastonotdetractfromthemainfocusofthisrecipe.

4. Createfieldgroups,asshowninthefollowingtable:

Groupname Label Fields

Overview @ConWHS:OverviewRefFieldName

RefFieldLabel

DefaultValue @SYS40175 DefaultValueStr

5. CreateauniqueindexforRefFieldNameandmakeitthetable'sClusteredindex.6. Completethetableproperties,asshowninthefollowingtable:

Property Fields

LabelNewlabel

Vehicletabledefaults

TitleField1 RefFieldLabel

Page 719: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

TitleField2 DefaultValueStr

CacheLookup EntireTable

ClusteredIndex FieldNameIdx

TableGroup Group

<Trackingfields> Asdesired,itmaybeusefultoknowwholastchangedit.

7. CreateastandardFindandExistmethod,asshowninthefollowingcode:

publicstaticbooleanExist(

RefFieldName_fieldName)

{

ConWHSVehicleTableDefaultstable;

if(_fieldName!='')

{

selectfirstonlyRecIdfromtable

wheretable.RefFieldName==_fieldName;

}

return(table.RecId!=0);

}

publicstaticConWHSVehicleTableDefaultsFind(

RefFieldName_fieldName,

boolean_forUpdate)

{

ConWHSVehicleTableDefaultstable;

if(_fieldName!='')

{

table.selectForUpdate(_forUpdate);

selecttable

wheretable.RefFieldName==_fieldName;

}

returntable;

}

8. TheRefFieldNamefieldwillbeafieldnamefromtheConWHSVehicleTable;sinceweworkwithIDs,itisusefultohaveahelperfunctionthatreturnsthisID.Createthefollowingmethod:

privateRefFieldIdGetFieldId(

RefFieldName_fieldName=this.RefFieldName)

{

returnfieldName2Id(tableNum(ConWHSVehicleTable),

_fieldName);

}

WewillallowthefieldnametobepassedsothatwecangetthefieldIDforanamenotcurrentlyupdatedtothecurrentrecord.

9. Next,createanewmethodthatsetsRefFieldLabelfromthefield'smetadata,asshowninthefollowing

Page 720: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

code:

publicvoidInitFromFieldName()

{

RefFieldIdfieldId=this.GetFieldId();

if(fieldId==0)

{

this.RefFieldName='';

this.RefFieldLabel='';

return;

}

DictFielddField=newDictField(

tableNum(ConWHSVehicleTable),fieldId);

if(dField)

{

this.RefFieldLabel=dField.label();

}

}

10. Now,insertandoverridethemethodformodifiedFieldtocalltheInitFromFieldNamemethodasfollows:

publicvoidmodifiedField(FieldId_fieldId)

{

super(_fieldId);

switch(_fieldId)

{

casefieldNum(ConWHSVehicleTableDefaults,

RefFieldName):

this.InitFromFieldName();

break;

}

}

11. Thefieldselectedmustexist,beoftypeString,andbeeditableoncreate.Tomakethecodeeasiertoread,weshouldcreateacheckfunctionforthis,whichiswrittenasfollows:

publicbooleanCheckRefFieldName(

RefFieldName_fieldName=this.RefFieldName,

boolean_silent=false)

{

booleanok=true;

ErrorMsgmsg='';

RefFieldIdfieldId=this.GetFieldId(_fieldName);

If(fieldId==0)

{

ok=false;

//Field%1doesnotexistintable%2

msg=strFmt("@SYS75684",

this.RefFieldName,

tableStr(ConWHSVehicleTable));

}

DictFielddField=newDictField(

tableNum(ConWHSVehicleTable),fieldId);

if(dField)

{

if(dField.baseType()!=Types::String)

{

ok=false;

//Typeisnotsupported%1

msg=strFmt("@SYS73815",dField.baseType());

}

if(!dField.allowEditOnCreate())

{

ok=false;

//Thefield%1cannotbeselected.

msg=strFmt("@SYS70689",this.RefFieldName);

}

}

If(!ok&&!_silent)

{

Page 721: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

returncheckFailed(msg);

}

returnok;

}

Themethodacceptsthe_silentparameteraswewillwanttousethesamelogictobuildthelookuplater.

12. WewillnowcallthecheckmethodintheprecedingcodetothevalidateFieldformtriggeredevent.OverridethevalidateFieldmethod,asshownhere:

publicbooleanvalidateField(FieldId_fieldIdToCheck)

{

booleanret;

ret=super(_fieldIdToCheck);

switch(_fieldIdToCheck)

{

casefieldNum(ConWHSVehicleTableDefaults,

RefFieldName):

ret=this.CheckRefFieldName();

break;

}

returnret;

}

13. Finally,wewillneedalookupfunctionthatonlyallowstheusertoselectfieldsthatarevalid.Thecodeisasfollows:

publicstaticvoidLookupFieldName(

FormStringControl_control)

{

FormRunformRun;

Argsargs;

Mapfieldmap;

DictTabledTable;

Counteridx;

RefFieldIdfieldId;

ConWHSVehicleTableDefaultsdefaultsRecord;

defaultsRecord=_control.dataSourceObject().cursor();

fieldMap=newMap(Types::String,Types::String);

dTable=newDictTable(tableNum(ConWHSVehicleTable));

for(idx=1;idx<=dTable.fieldCnt();idx++)

{

fieldId=dTable.fieldCnt2Id(idx);

RefFieldNamefieldName=fieldId2Name(dTable.id(),

fieldId);

if(defaultsRecord.CheckRefFieldName(fieldName,

true))

{

fieldMap.insert(fieldName,fieldName);

}

}

args=newArgs(formStr(SysPick));

args.parmEnumType(enumNum(SysPickListType));

args.parmEnum(enum2int(SysPickListType::Simple));

Args.parmObject(fieldMap);

formRun=classfactory.formRunClass(args);

_control.performFormLookup(formRun);

formRun.wait();

}

14. Next,createanewformnamedConWHSVehicleTableDefaultsanddragthenewtabletoitsDataSourcesnode.

Page 722: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

15. CompletethedesignoftheformbasedontheSimpleListandDetails-ListGridformpattern.

16. Completetheformasperthepatternrequirements,usingtheOverviewgroupforNavigationListGroup.Thecompleteddesignshouldlooklikethefollowingscreenshot:

Remembertonamethecontrolsafterthetemplatename,asthismakesthedesigneasiertoworkwith.Thetemplatewillmakethewrongassumptionsaboutwhichcontrolmatchesthetemplatesectionuntilallthetemplatesectionshavematchingcontrols.

17. Wewillnowneedtooverridethelookupforthefieldname,expandtheDataSourcesnode,andlocatetheRefFieldNamefieldundertheConWHSVehicleTableDefaultsdatasource'sFieldsnode.

18. Right-clickonthefield'sMethodsnodeandselectOverride|lookup.19. Inthecodeeditorthatopens,removethelinesuper(_formControl,_filterStr);andreplaceitwiththe

followingline:

ConWHSVehicleTableDefaults::LookupFieldName(_formControl);

20. CreateadisplaymenuitemandaddittotheSetupsubmenuofConWHSVehicleManagement.21. Finally,completetheFormRefpropertyoftheConWHSVehicleTableDefaultstableandbuildtheproject,

includingdatabasesynchronizationaswehaveaddedanewtable.

Wehavenowcompletedthesetuptableandform,wecannowcreateahandlersothatthetable'sdefaultsareset.Todothis,followthesesteps:

1. CreateanewclassnamedConWHSVehicleTableDefaultingEngine.2. Writethefollowinglinesofcodetoinitializetheinstance:

privateConWHSVehicleTablevehicle;

publicConWHSVehicleTableVehicle(

ConWHSVehicleTable_vehicle=vehicle)

{

vehicle=_vehicle;

Page 723: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

returnvehicle;

}

publicstaticConWHSVehicleTableDefaultingEngine

NewFromVehicleTable(ConWHSVehicleTable_vehicle)

{

ConWHSVehicleTableDefaultingEngineengine;

engine=newConWHSVehicleTableDefaultingEngine();

engine.Vehicle(_vehicle);

returnengine;

}

3. Then,writethemethodsthatperformtheupdate,whichareasfollows:

privatevoidProcessDefaults()

{

ConWHSVehicleTableDefaultsdefaults;

whileselectdefaults

{

RefFieldIdfieldId=fieldName2Id(

vehicle.TableId,

defaults.RefFieldName);

if(fieldId)

{

DictFielddField;

dField=newDictField(vehicle.TableId,

fieldId);

switch(dField.baseType())

{

caseTypes::String:

vehicle.(fieldId)=

defaults.DefaultValueStr;

break;

}

if(vehicle.validateField(fieldId))

{

vehicle.modifiedField(fieldId);

}

else

{

switch(dField.baseType())

{

caseTypes::String:

vehicle.(fieldId)='';

break;

}

}

}

}

}

publicbooleanValidate()

{

if(vehicle.RecId!=0)

{

//Cannotdefaultrecordsthatalreadyexist

returncheckFailed("@ConWHS:ConWHS61");

}

returntrue;

}

publicvoidRun()

{

if(!this.Validate())

{

return;

}

this.ProcessDefaults();

}

4. Thefinalmethodisthedataeventhandler,whichwilltriggerwhentherecordisbeinginitialized,anditiswrittenasfollows:

Page 724: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

[DataEventHandlerAttribute(tableStr(ConWHSVehicleTable),

DataEventType::InitializingRecord)]

publicstaticvoidInitValueEventHandler(

Common_record,

DataEventArgs_eventArgs)

{

ConWHSVehicleTableDefaultingEngineengine;

engine=ConWHSVehicleTableDefaultingEngine::

NewFromVehicleTable(_record);

engine.Run();

}

5. Finally,buildandruntheproject.TestthecodebycreatingasetofdefaultsinthenewVehicletabledefaultvaluesform,andthencreateanewvehicleusingtheVehiclesform.

6. TryaddingtheVehiclegroupasadefaultwithavaluethatdoesn'texist;youshouldgetanerrorwhenanewrecordiscreated:Thevalue'invalidvalue'infield'Vehiclegroup'isnotfoundintherelatedtable'Vehiclegroups'.

Page 725: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 726: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Therearetwomainnewtopicsinthisrecipe.Thefirstistousethedictionaryclasses,DictTableandDictField,togainaccesstothetableandfieldproperties.Thiswasusedtobothvalidatethefieldandalsotobuildacustomlookupforfieldsthatcanbeedited.

TheothernewtopicisthatwecanusethefieldIDtoaccessthefieldonarecord;forexample,thecodevehicle.Description="Newdescription";isequivalenttothefollowing:

RefFieldIdfieldId=fieldNum(ConWHSVehicleTable,Description);

vehicle.(fieldId)="Newdescription";

Wehavetobecarefulbecausethefollowingcodewouldcompile,butfailwitharuntimeerror:

RefFieldIdfieldId=fieldNum(ConWHSVehicleTable,Description);

vehicle.(fieldId)=12.345;

ThisiswhyweusedtheDictField.baseType()method.

Youcanalsousethismethodinwhereclauses,butbeverycarefulbeforeyoudothis,inordertoavoidanyunpleasantruntimeerrorsthatmaynotpresentthemselvesatthepointoffailure.

WewillalsocreatealookupusingaMapobjecttocreatecustomlookupsthataren'tbasedondata.

AlthoughthecodecurrentlyonlyhandlesfieldsoftheStringbasetype,itcanbeeasilyextendedtohandleotherbasetypes.YoucouldevenextendthevalidationonConWHSVehicleTableDefaultstocheckifthevaluesarevalid.ThiswouldbedonebycreatingadummyConWHSVehicleTablerecord,populatingthefieldtheuserisaddingadefaultforandcallingthetable'svalidateFieldmethodwiththefieldID.

Tofullyunderstandwhatisgoingon,itisusefultousethedebuggertostepthroughthecode.Extendingthesampleinthisrecipeisalsoagreatwaytobecomecomfortablewithworkingwithmetadata.

Oneofthecommonmodificationsistosetdefaultdates,andwecouldusethathere,too.Thedefaultvaluewouldprobablybeanoffsetfromtoday'sdate,andnotaliteraldate.

Page 727: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 728: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

UsingInterfacesforextensibilitythroughmetadataInterfacesenforcethatallclassesthatimplementthemalsoimplementthemethodsdefinedintheinterface.Thishasallthetraditionalbenefitsassociatedwiththem,butinDynamics365forOperations,wecangofurther.

WeshouldcreateaninterfacecalledMyRunnableIwithamethodcalledRun()andaclassthatimplementsitcalledMyRunningPerson(whichmusthaveamethodcalledRun()).WecanassignaninstanceofMyRunningPersontoavariableoftypeMyRunnableIandcallitsRun()method.Thisallowsgreaterflexibilityandextensibilityofourcode.

However,inDynamics365forOperations,wecancreateapluginframeworkwheretheclasstoinstantiateisconfiguredindata.Wecan,therefore,controlwhichclassgetsinstantiatedbasedonconditionsonlyknownatruntime.

Inordertofocusmoreonthewaythatwecanuseinterfacestocreateapluginpattern,wheretheclasstocallisstoredindata,theexamplewillbesimple.Wewillcreatea'colorable'interfaceandallowtheusertoselectwhichimplementationofthecolorableinterfacewillbeused.Youcouldcombinethisideawiththepreviouschaptertodeterminewhichwebservicetousetogettheweather,forexample.

Page 729: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 730: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Gettingready...Inthisexample,weshallcreateapatternbasedonnewelements,sowewillonlyneedaDynamics365forOperationsprojectopeninVisualStudio.

Page 731: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 732: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Tocreateapluginusinginterfacesthroughmetadata,followthesesteps:

1. First,createtheinterface,whichiscreatedasaclassinitially,usingthefollowinglinesofcode:

publicinterfaceConWHSVehicleColorable

{

publicColorColor()

{

}

}

2. Wewillneedaclassthatimplementsthisinterface.CreateaclassnamedConWHSVehicleGroupColorRed,whichwillsimplyreturnthestringliteralRed,asshownhere:

classConWHSVehicleGroupColorRed

implementsConWHSVehicleColorable

{

publicColorColor()

{

return'Red';

}

}

3. Tokeeplogicawayfromtheformandsimplifyusage,createaclassnamedConWHSVehicleGroupColorSetupForm.

5. Next,createastaticlookupmethod.Thepatternshouldbefamiliarfromthepreviouschapters.Thisisanexampleofcreatingalookupthatisnotbasedondata:

publicstaticvoidLookupColorableClasses(

FormStringControl_classNameControl)

{

FormRunformRun;

Argsargs;

MapclassMap;

ListclassList;

SysDictClassdClass;

ClassIdclassId;

ClassNameclassName;

DictClassimplementationClass;

dClass=newSysDictClass(

classNum(ConWHSVehicleColorable));

classList=dClass.implementedBy();

classMap=newMap(Types::String,Types::String);

ListEnumeratorle;

le=classList.getEnumerator();

while(le.moveNext())

{

classId=le.current();

implementationClass=newDictClass(ClassId);

if(!implementationClass.isInterface())

{

className=classId2Name(classId);

Page 733: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

classMap.insert(className,className);

}

}

args=newArgs(formStr(SysPick));

args.parmEnumType(enumNum(SysPickListType));

args.parmEnum(enum2int(SysPickListType::Simple));

args.parmObject(classMap);

formRun=classfactory.formRunClass(args);

_classNameControl.performFormLookup(formRun);

formRun.wait();

}

ItisOK,inthiscase,tohavethisfunctioninacase-specificclass,astheusageisspecific.Itwouldusuallybebettertocreateautilityclassforthesefunctionsandpasstheinterfaceasamethodparameter.

5. Completetheformhandlerclassusingthestandardpattern,asshowninthefollowingcodesnippet:

FormDataSourcevehicleGroupSetupDS;

protectedvoidsetVehicleGroupSetupDS(FormDataSource_vehicleGroupSetupDS)

{

vehicleGroupSetupDS=_vehicleGroupSetupDS;

}

publicstaticConWHSVehicleGroupColorSetupForm

NewFromFormDS(FormDataSource_vehicleGroupSetupDS)

{

ConWHSVehicleGroupColorSetupFormform=

newConWHSVehicleGroupColorSetupForm();

form.setVehicleGroupSetupDS(_vehicleGroupSetupDS);

returnform;

}

6. Next,createatablenamedConWHSVehicleGroupColorSetupasagrouptable,withthefollowingfields:

Field Label EDT

VehicleGroupId Inherited ConWHSVehicleGroupId

Description Inherited Description

ColorClassName Colorableclass ClassName

7. Ensurethatthetableiscompletedtobestpractices,andensurethefollowing:

TheprimarykeyisVehicleGroupIdColorClassNameismandatoryAfieldgroupiscreatedfortheoverviewgridThestandardFindandExistmethodsarecreated

Page 734: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

8. Writethefollowingstaticmethodonthetable:

publicstaticbooleanImplementsColorable(

ClassName_className)

{

ClassIdclassId;

DictClassdictClass;

classId=className2Id(_className);

if(ClassId==0)

{

returnfalse;

}

dictClass=newDictClass(ClassId);

if(dictClass)

{

Counteridx;

ClassIdtestClassId;

for(idx=1;idx<=dictClass.implementsCnt();

idx++)

{

testClassId=dictClass.implements(idx);

if(testClassId==

classNum(ConWHSVehicleColorable))

{

returntrue;

}

}

}

returnfalse;

}

9. Now,let'svalidatethattheclassselectedimplementstheinterface.OverridevalidateFieldandwritethefollowingcodesnippet:

publicbooleanvalidateField(FieldId_fieldIdToCheck)

{

booleanret;

ret=super(_fieldIdToCheck);

if(ret)

{

switch(_fieldIdToCheck)

{

casefieldNum(ConWHSVehicleGroupColorSetup,

ColorClassName):

If(!ConWHSVehicleGroupColorSetup::

ImplementsColorable(this.ColorClassName))

{

//%1mustimplement%2

ret=checkFailed(strFmt(

"@ConWHS:MustImplement",

this.ColorClassName,

classStr(ConWHSVehicleColorable)));

}

}

}

returnret;

}

10. CreateanewformforthetableusingtheSimpleListpattern,andfollowthepatterntocompletetheform.

11. Completetheformhandlerpatternbyaddingthefollowinglinesofcodetotheform'scode:

ConWHSVehicleGroupColorSetupFormformHandler;

publicvoidinit()

Page 735: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

{

super();

formHandler=ConWHSVehicleGroupColorSetupForm::

NewFromFormDS(ConWHSVehicleGroupColorSetup_DS);

}

12. Toaddthelookup,expandDataSources,ConWHSVehicleGroupColorSetup,andFields.LocatetheColorClassNamefieldandoverridethelookupmethod.Writethefollowinglinesofcode:

publicvoidlookup(FormControl_formControl,str_filterStr)

{

ConWHSVehicleGroupColorSetupForm::

LookupColorableClasses(_formControl);

}

13. Wewillneedaclassthatwillexecuteourcolorableclass,nametheclassConWHSVehicleColorExecute,andaddthefollowingpieceofcode:

ConWHSVehicleGroupIdgroupId;

Colorcolor;

publicColorGetColor()

{

returncolor;

}

publicConWHSVehicleGroupIdParmVehicleGroupId(

ConWHSVehicleGroupId_groupId=groupId)

{

groupId=_groupId;

returngroupId;

}

publicstaticConWHSVehicleColorExecute

newFromVehicleGroupId(ConWHSVehicleGroupId_groupId)

{

ConWHSVehicleColorExecuteexecute;

execute=newConWHSVehicleColorExecute();

execute.ParmVehicleGroupId(_groupId);

returnexecute;

}

publicbooleanCanExecute()

{

If(!ConWHSVehicleGroupColorSetup::Exist(groupId))

{

returnfalse;

}

returntrue;

}

publicvoidRun()

{

If(this.CanExecute())

{

this.execute();

}

}

privatevoidexecute()

{

ConWHSVehicleGroupColorSetupsetup;

setup=ConWHSVehicleGroupColorSetup::Find(groupId);

if(!ConWHSVehicleGroupColorSetup::

ImplementsColorable(setup.ColorClassName))

{

return;

Page 736: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

}

DictClassdictClass;

dictClass=newDictClass(

className2Id(setup.ColorClassName));

if(dictClass)

{

ConWHSVehicleColorablecolorable;

colorable=dictClass.makeObject();

color=colorable.Color();

}

}

14. Let'stestthisbyaddingabuttontooursetupform.OpentheConWHSVehicleGroupColorSetupFormclassandaddthefollowingmethod:

publicvoidTestColor()

{

ConWHSVehicleGroupColorSetupsetup;

setup=vehicleGroupSetupDS.cursor();

ConWHSVehicleColorExecuteexecute;

execute=ConWHSVehicleColorExecute::

newFromVehicleGroupId(setup.VehicleGroupId);

execute.Run();

info(execute.GetColor());

}

15. OpentheConWHSVehicleGroupSetupFormformandwritethefollowingmethod:

privatevoidTestColor()

{

formHandler.TestColor();

}

16. Closethecodeeditorandgobacktotheformdesigner.OntheFormActionPaneControlcontrolthatwecreatedaspartofcompletingtheform'spattern,addanActionPanetab;tothis,addButtonGroupandfinallyaButtoncontrol.

17. Overridetheclickedmethodandwritethefollowinglinesofcode:

publicvoidclicked()

{

super();

element.TestColor();

}

18. CreateamenuitemforourformandaddthistotheSetupmenuforourConWHSVehicleManagement.

19. Saveeverything,buildtheproject,andsynchronizethedatabasewiththeproject.

Page 737: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 738: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Thispatternachievesasafemethodtoallowafunctionalconsultant(orenduser)tochoosethebusinesslogicbasedondata.Thisisverypowerful,andallowsotherpartiestoaddtheirownplugins,whicharetobeusedinsteadofthosesupplied.

Tounderstandwhatisgoingon,thefirstthingtoknowisthatwecanwritethefollowingcode:

ConWHSVehicleColorablecolorable;

colorable=newConWHSVehicleGroupColorRed();

ColormyColor=colorable.Color();

Wecan'tinstantiateaninterface,butwecanassignaninstanceofaclasstoavariabledeclaredtobeaninterface,aslongastheclassimplementstheinterface.Anyerrorswillbehandledbythecompiler.Wewillconsidertheinterfacetobeacodecontract,andtheimplementskeywordbindstheimplementationtotheinterface.

Thereisanalternativetousinganinterface,inthat,wecouldwritethefollowinglinesofcode:

Objectobject;

object=newConWHSVehicleGroupColorRed();

ColormyObjColor=object.Color();

Althoughitwouldwork,inthiscase,thecodeisaterribleidea.Objectshouldalwaysbealastresort,andthiscodeispronetoregressionerrors.Theinterfaceisacontract;theobjecttechniqueallowsyoutowriteanymethodnameyoulike.

Ourexampleistoallowtheclasstobedefinedatruntime.Usingaswitchorattribute-basedconstructorrequiresustohardcodetherelationshipbetweenattributeorotherconditionandtheclasstoconstruct.Wewantausertobeabletodeterminethis.ThisisdoneusingDictClass.NotonlycanweuseDictClasstogaininformationaboutmetadata,butwecanalsousethemtoconstructaninstanceaclass.TheDictClassclassisinstantiatedusingaclass'sID,asshownhere:

DictClassdc=newDictClass(<MyClassId>);

Inourcase,theclassnameisstoredinatable,allowingaclassthatimplementsConWHSVehicleColorabletobeselected.

Therecipecanbebrokendownintosimplesteps,thefirstofwhichistodeterminetheclass'sIDfromthename.ThisIDisanimplementationspecificnumericobjectorelementidentifier,sowemustobtaintheIDthroughafunctioncall.StandardobjectalwayshavethesameIDs,butthisshouldneverbeassumed.Whenwedeployapackagetoanenvironmentforthefirsttime,theIDisset;itwillnotchangeuponsubsequentdeployments.Thisisveryimportantwhenyouconsiderhowextensibleenumerationswork,orifwedecidetostoreIDsinsetuptable--iftheIDchanged,thecodewillfailinunexpectedways.ThecodetodeterminetheobjectIDoftheclassnamethatweenteredinthesetuptableisasfollows:

ClassIdclassId=className2Id(setup.ColorClassName);

WecannowinstantiateavariableoftypeDictClass:

Page 739: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

DictClassdc=newDictClass(classId);

Declareavariablethatisofthesametypeasinterface,ConWHSVehicleColorable:

ConWHSVehicleColorablecolourable=dc.makeObject();

WecannowcalltheColor()method.Themakeobject()methodcreatesaninstanceoftheclassitwasconstructedwith:theclassweenteredinoursetuptable.Shouldweenteraclassthatdoesnotimplementtheinterface,wewillgetanerroratthispoint,eveniftheclasshastheColor()method.Thecodecontainsvalidationondataentryandintheexecutionclass.Thismustbedoneinordertoavoidanyruntimeerrors.

Evenwithvalidationonentryandusage,wecan'tcompletelyavoidregressioninthiscase.Usinganinterface,however,doeshelpusreduceregressionerrors,forexample,itwouldhelpuscatcherrorscausedbyrefactoring.

Page 740: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 741: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Makingdatadate-effective

Dateeffectivetablesallowustocreateanewversionwheneverthedataischanged,andseethedateatanypointintime.Thissoundsgreat,andwillnodoubtfindrequeststomakealltablesdateeffective.Thereisapenalty.Thefirstisthatitbringsalittlecomplexitytotheprocessofdevelopingthetables,reports,anduserinterface.Theotheristhatitcreatesanewrecordeverytime,andcanaffectperformance.Weshouldonlyusethisifwereallyneedit.Greatexamplesincludethehumanresourcetablestoallowhistoryofnamechanges,previousandplannedpositions,addresses,andsoon.

Inourexample,wewillcreateanewtableforvaluessuchasodometerreadingsforavehicle.Wewillcreateatableforthisthatisdateeffectivesothatweonlyrecordanewversionwhenthesekeyfieldsarechanged.

Page 742: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 743: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Gettingready...ThisrecipeassumesthatwehavefollowedthechapterstocreateConWHSVehicleTable,butthepatternbehindthisrecipecanbeappliedtoanyrequirementwhereweneedtorecordthehistoryofchangestoarecord.

Page 744: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 745: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Inthesesteps,wecreateatablethatputstheconceptofdate-effectivityintouse:

1. CreateanewintegerEDTnamedConWHSOdometerwithalabelOdometer.2. CreateanewtablenamedConWHSVehicleTableServiceData.3. DragtheConWHSVehicleIdEDTontotheFieldsnode,andrenameittoVehicleId.Configurethefield's

propertiestobeaprimarykey(cannoteditaftercreationandismandatory).Createaforeignkeyrelationship,butdonotcreateanindex.

4. DragtheConWHSOdometerEDTontotheFieldsnode,andrenametoOdometer.5. Wecannowchoosewhetherourchangescanbeupdateddaily,orforeverychangemade.Thisis

doneusingthetable'sValidTimeStateFieldTypeproperty.Wewanttoallowchangestobemadefrequently,andthateverychangeshouldbesavedasanewversion,sochooseUtcDateTime.

ThiswilladdtwofieldsoftypeUtcDateTimenamedFromDateandToDate.Theseareusedinordertotimestampeachversionoftheapparentrecord.

6. CreateanewprimaryindexnamedVehTimeIdxusingthefollowingsettings:

Property Value

AllowDuplicates No

AlternateKey Yes

ValidTimeStateKey Yes

ValidTimeStateMode NoGap

7. AddtheVehicleIdandFromDatefieldstotheindex.

WewillneedtoaddtheToDatefieldifwesetValidTimeStateModetoGap.

8. Setthetable'sReplacementKeypropertytothenewindex.9. TheFind()methodmustalsobewritteninordertofetchthecurrentversionbydefault,whichisdone

inthefollowingcodesnippet:

publicstaticConWHSVehicleTableServiceDataFind(

ConWHSVehicleId_vehicleId,

ValidFromDateTime_fromDateTime=

DateTimeUtil::utcNow(),

Page 746: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ValidToDateTime_toDateTime=_fromDateTime,

boolean_forUpdate=false)

{

ConWHSVehicleTableServiceDataserviceData;

serviceData.selectForUpdate(_forUpdate);

//thevalidtimestateisusedtoselect

//theversionatapointintime

selectfirstonly

validtimestate(_fromDateTime,_toDateTime)

serviceData

whereserviceData.VehicleId==_vehicleId;

returnserviceData;

}

10. WritetheExist()methodusingthesamepattern.11. Now,let'screateaformsothatwecanseewhathappens.NametheformConWHSVehicleTableServiceData.

SincetheformwillbeopenedfromaVehiclerecord,anditiskeyedontheVehicleIdfield,theformwillusuallybeadialogordetailsform.However,inordertodemonstratemoreclearlywhatisgoingon,wewilluseaSimpleListpatterninstead.

12. ApplytheSimpleListpatternandcompletetheformaccordingtothepattern.13. CreateamenuitemandaddittotheConWHSVehicleTableform.14. Saveeverythingandbuildtheprojectandsynchronizeitwiththedatabase.15. OpenD365OinawebbrowserandopentheVehiclesform.Fromthem,openournewVehicleservice

dataform.ChangetheOdometervalue,whilstsavingtherecordforeachchange.16. OpenSQLServerManagementStudio,pressConnectontheConnecttoServerform,andpressAlt+Nto

createanewquerywindow.Typethefollowing:

UseAxDB

SELECT

VEHICLEID,ODOMETER,

VALIDFROM,VALIDTO

FROMCONWHSVEHICLETABLESERVICEDATA

17. Theresultwillbesomethinglikethis:

VehicleId Odometer ValidFrom ValidTo

V000001 150 2017-01-3113:42:25.000 2017-01-3113:42:39.000

V000001 250 2017-01-3113:42:40.000 2154-12-3123:59:59.000

Itisclearlyworking,butwenowneedtoallowtheusertoseetherecordsasatdifferenttimes.Todothis,wewillneedtoaddDateEffectivenessPaneControllertotheform.

18. OpentheConWHSVehicleTableServiceDataform,andopenclassDeclarationfromtheMethodsnodeandenterthecodepresentinthenextstep.

19. First,wewillneedtoimplementtheIDateEffectivenessPaneCallerinterface,soaltertheclass

Page 747: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

declarationasfollows:

publicclassConWHSVehicleTableServiceData

extendsFormRun

implementsIDateEffectivenessPaneCaller

20. Now,addthefollowingpieceofcode:

DateEffectivenessPaneControllerdePaneController;

//Asrequiredbytheinterface

publicDateEffectivenessPaneController

getDateEffectivenessController()

{

returndePaneController;

}

privatevoidenableAsOfDateButton()

{

dePaneController.setDropDialogButtonEnabled(true);

dePaneController.parmShowAllRecords(true);

}

publicDateEffectivenessPaneController

parmDatePaneController(

DateEffectivenessPaneController_

dePaneController=dePaneController)

{

dePaneController=_dePaneController;

returndePaneController;

}

publicvoidinit()

{

super();

dePaneController=DateEffectivenessPaneController::

constructWithForm(

element,ConWHSVehicleTableServiceData_ds,

false,//showplurallabels

true,//allowshowallrecords

true);//useDateTime

element.enableAsOfDateButton();

}

21. Buildandtesttheformagain,youshouldnowhaveanewAsatdatecontrol.ShouldtheerrorFormwascalledincorrectlybethrown,itisbecausetheIDateEffectivenessPaneCallerinterfacewasnotimplemented.

22. Thiswillsufficefornow,butwecantakeitfurther,andthisisdescribedintheThere'smore...section.

Page 748: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 749: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...WhenD365OconstructstheConWHSVehicleTableServiceDatadatasource,itautomaticallyfilterstherecordstothecurrentversion.Thetransact-SQLthatdoesthisisasfollows:

SELECTT1.VEHICLEID,T1.ODOMETER,T1.VALIDTO,T1.VALIDTOTZID,T1.VALIDFROM,T1.VALIDFROMTZID,T1.RECVERSION,T1.PARTITION,T1.RECIDFROMCONWHSVEHICLETABLESERVICEDATAT1WHERE(((PARTITION=5637144576)AND(DATAAREAID=?))AND((VALIDFROM<=?)AND(VALIDTO>=?)))ORDERBYT1.VEHICLEID,T1.VALIDFROMOPTION(FAST54)

Thishelpsusunderstandwhywemadethepropertychangestothetable.Theindexisclearlyneededforperformance,andthedatasourceusesthepropertiesonthetabletodecideonthequerysenttoSQLServer.

Whenworkingincode,weneedtobesureweareselectingthecorrectrecord.Findmethodsareindispensableinthiscase,anditiscommontowritemultipleFindmethodsforeachtypeofusagescenario.

Weaddedthedateeffectivenesscontrollertoallowtheusertoseeotherversionsontherecord,oreventoshowallrecords.Theshowalloptionshouldonlybeusedwhenweareviewinginagridview,sincetheusercouldn'tseetheotherrecordsanyway.

Toexperimentfurtherwiththis,trycreatingatablethatfollowsthepatternoftheHcmWorkerEnrolledBenefittable.

Page 750: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 751: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...ItwouldbeanicerinterfaceifwecouldhavetheOdometerfieldonthevehicleform,whichwouldrequireustomixanon-dateeffectivetablewithadateeffectivetable.TheHcmWorkertableisagreatexampleofthis.

Toimplementthispattern,wewouldfirstaddConWHSVehicleTableServiceDatatotheConWHSVehicleTableform,joinedtotheConWHSVehicleTabledatasourceasanOuterjoin.ModifyinitValueofConWHSVehicleTableServiceDatainordertodefaultVehicleId.

CreateafieldgroupinasuitableplaceandaddtheOdometerfield.

Sofar,thereisnothingnew.Next,wewillneedtoconstructthedateeffectivenesscontroller,butcontrolthechilddatasources.Thisisdonebysubscribingtodelegatesonthecontroller.Thecodeissimilartothecodeinrecipedescribedpreviously.

Thecodeiswrittenasfollows.Thefirstpartistodeclarethedateeffectivenesscontroller,aswedidbefore:

DateEffectivenessPaneControllerdePaneController;

Next,wewillneedtodeclaretwoeventhandlermethodsthatwewillusetosubscribethecontroller'sevents,whicharedelegates.Wewillneedtofiltertheservicedatarecordbasedonthewhattheuserdidinthedropdialog,andthisisdonebyaddingthefollowingpieceofcode:

///<summary>

///EventHandlerforanApplybutton(clicked)eventinDateEffectivenessPaneController

///</summary>

publicvoiddatePaneController_ApplyClicked()

{

utcdatetimeasOfDateTime;

utcdatetimenowDateTime=DateTimeUtil::utcNow();

if(dePaneController.parmShowAsOfDate()==

DateTimeUtil::date(nowDateTime))

{

asOfDateTime=nowDateTime;

}

else

{

asOfDateTime=DateTimeUtil::newDateTime(

dePaneController.parmShowAsOfDate(),

Global::timeMax(),

DateTimeUtil::getUserPreferredTimeZone());

}

//adjustthequeryfortheouterjoineddatasources

ConWHSVehicleTableServiceData_DS.validTimeStateAutoQuery(

ValidTimeStateAutoQuery::AsOfDate);

ConWHSVehicleTableServiceData_DS.query().

validTimeStateAsOfDateTime(asOfDateTime);

}

///<summary>

///EventHandlerforaCurrentbutton(clicked)eventin

///DateEffectivenessPaneControllerto

///viewthecurrentlyactiveversionrecord.

///</summary>

publicvoiddatePaneController_CurrentClicked()

{

Page 752: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

//adjustthequeryfortheouterjoineddatasources

ConWHSVehicleTableServiceData_DS.validTimeStateAutoQuery(

ValidTimeStateAutoQuery::AsOfDate);

ConWHSVehicleTableServiceData_DS.query().

resetValidTimeStateQueryType();

}

Thenextmethodsareastandardpartofthepatternandarethesameastherecipe:

//Asrequiredbytheinterface

publicDateEffectivenessPaneController

getDateEffectivenessController()

{

returndePaneController;

}

privatevoidenableAsOfDateButton()

{

dePaneController.setDropDialogButtonEnabled(true);

dePaneController.parmShowAllRecords(false);

}

publicDateEffectivenessPaneControllerparmDatePaneController(

DateEffectivenessPaneController_dePaneController=

dePaneController)

{

dePaneController=_dePaneController;

returndePaneController;

}

Theinitmethodissimilartotherecipe,exceptthatwedon'twanttoshowallrecords,andwejustwanttosubscribetothecontroller'sevents.Theinitmethodshouldreadasfollows:

publicvoidinit()

{

super();

dePaneController=

DateEffectivenessPaneController::constructWithForm(

element,ConWHSVehicleTable_ds,

false,//showplurallabels

false,//donotallowshowallrecords

true);//useDateTime

dePaneController.onApplyClicked+=

eventhandler(this.datePaneController_ApplyClicked);

dePaneController.onShowCurrentClicked+=

eventhandler(this.datePaneController_CurrentClicked);

element.enableAsOfDateButton();

}

Finally,adjusttheservicedateformsothatithastheValidFromfields.Thisallowstheformtobeusedtoseethehistoryoftheservicedatatable.

Page 753: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 754: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

UnitTestingInthischapter,wewillcoverthefollowingrecipes:

CreatingaFormAdaptorprojectCreatingaUnitTestprojectCreatingaUnitTestforcodeCreatingatestcasefromataskrecording

Page 755: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 756: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

IntroductionUnittestinghelpsensurethatthecodebothfulfillstherequirement,andfuturechanges(eveninotherpackages)donotcausearegression.Theunittestiswrittenasaseparatepackagethatreferencesthepackageitistesting.IfwefollowTestDrivenDevelopment(TDD),wewillwritethetestsearlyintheprocess(somewouldarguefirst).TDDchangesthewaywethinkwhenwritingcode.Shouldweneedtomakeachangetoaproject,weareforcedtoupdatethetestcasecode(asthetestswillotherwisefail)--thispromotesatest-centricapproachtodevelopment,andnaturallyreducesthetestcycles.Regressioninotherpackagesiscaughtbythebuildprocess;thebuildserverwilldownloadallchecked-incode,performabuild,andthenlookforteststoexecute.Anyteststhatfailarereportedandthebuild--dependingonthebuild'ssetup--willbemarkedasfailed.

Eachpartnerorcustomermayhavetheirownpoliciesforunittesting,somerequirethateverypieceofcodeistested,andotherswillrecommendthatonlykeypartsofthecodearetested.Itiscommonthatthecodeoftheunittestshavethreetimesthecodeofthecodebeingtested,whichmayseemwrongatfirst.Writingtestcasesisaninvestment,thebenefitofwhichisn'talwaysapparentatthetimeofwriting.Thisissometimesbecausetestingwhetherapieceofcodeworksisusuallyprettyeasy,theproblemisthatmanualtestsarenotrepeatable,andusuallyneglectedgecasesbytestingthemainscenario.Thebiggestwin,inmyopinion,isthereductionintheriskofregression.Thisiswhereanapparentminorchange(orahotfix)isapplied,whichaffectsaseeminglyunrelatedpartofthesystem.Thesetypesofregressioncaneasilymakeitintoproduction,sincethetestingproceduresmayonlytestthatpartofthesystem.Thisiscompoundedfurtherbythefactthatanyfixwilltakeatleastadaytodeploy,sincewearenowforcedtogothroughtestandthenproduction.

Thefollowinglinkprovidessomegoodadviceonachievingbalanceintestingsoftware:https://blogs.msdn.microsoft.com/dave_froslie/2016/02/03/achieving-balance-in-testing-software/

Page 757: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 758: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingaFormAdaptorproject

Formadaptorsareusedinordertocreatetestcasesforuserinterfaceevents--theyprovideabridgebetweentheforminteractionsandcode.Theycanbecreatedwithinthemainproject,butthiscreatesalotofneedlessclassesthatwewillrarelywishtosee.

Page 759: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 760: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyOpentheprojectthatweneedformadaptorsfor,which,inourcase,isConWHSVehicleManagement.

Page 761: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 762: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...TocreatetheFormAdapterproject,followthesesteps:

1. SelectDynamics365forOperations|CreatemodelfromthetopmenuandcompletetheCreatemodelformasfollows:

Field Value

Modelname ConWHSVehicleManagementFormAdaptor

Modelpublisher ContosoIT

Layer VAR

Modeldescription FormadaptersfortheConWHSVehicleManagementpackage

Modeldisplayname ConWHSVehicleManagementFormAdaptor

ThesuffixisimportantandshouldalwaysbeFormAdaptor.

2. ClickonNext.3. SelectCreatenewpackageandclickonNext.4. SelectConWHSVehicleManagementandTestEssentialsfromthePackages[Models]listandclickonNext.5. Uncheckthedefaultforcreatingaproject,butleavethedefaultoptionofmakingthenewmodelthe

defaultfornewprojects.ClickonOK.6. Right-clickonthesolutionnodeintheSolutionExplorerandchooseAdd|Newproject....7. IntheNewProjectwindow,ensurethattheDynamics365forOperationstemplateisselectedinthe

leftpane,andOperationsProjectisselectedintheright.EnterConWHSVehicleManagementFormAdaptorasNameandclickonOK.

8. SaveallandcloseVisualStudio.9. IfthepackagewascalledConWHSVehicleManagementFormAdapter,navigatetothefollowingfolderin

WindowsExplorer:

C:\AOSService\PackagesLocalDirectory\ConWHSVehicleManagementFormAdapter\Descriptor

10. OpentheConWHSVehicleManagementFormAdapter.xmlfileinNotepadandaddthehighlightedlineinordertotellthemodelwhichmodelisthesourcetobuildformadapters:

Page 763: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

<AppliedUpdatesxmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays"/>

<Customization>Allow</Customization>

<Description>FormadaptersfortheConWHSVehicleManamentpackage</Description>

<DisplayName>ConWHSVehicleManagementFormAdapter</DisplayName>

<FormAdaptorSourceModel>ConWHSVehicleManagement</FormAdaptorSourceModel>

<Id>895571399</Id>

Donoteditanyotherpartofthisfile.TheoptiontosettheformadaptorsourcemodeldirectlyinVisualStudiomaybeaddedtolaterversions,sothisstepmightnotberequiredinthefuture.

11. OpenVisualStudioandtheConWHSVehicleManagementsolution,thenright-clickontheConWHSVehicleManagementFormAdaptorproject,andchooseProperties.

12. ChangetheGenerateFormAdapterspropertytoTrue.13. Right-clickontheConWHSVehicleManagementFormprojectandchoosePropertiesandchangetheGenerate

FormAdapterspropertytoTrue.14. SelectBuildmodels...fromtheDynamics365menuandbuildbothmodels.15. Thisshouldgenerateandaddaclassforeachform,suffixedwithFormAdaptor.Iftheydon'tappear,you

canlocatethemintheApplicationExplorerandaddthemmanually.

Page 764: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 765: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Wewillcreateanewpackageinordertoseparatetheclassesfromthemainpackage.Thisshouldalwaysbedone,aswecanendupwithalotofautomaticallygeneratedcodethatwouldbeadistraction.ThekeypartofthisprocesswastheXMLtagweenteredintheformadaptormodel'sDescriptorfile--thisiswhattoldthemainpackagethattheformadaptercodeshouldbegeneratedinourformadaptormodel.

ThenextpartwastoturnonGenerateFormAdaptorsinthepropertiesformforbothprojects.Thebuildwillthengenerateformadaptersintoourformadaptorproject.

Wewillusethislaterwhenwecreateaunittestfortheuserinterface.

Page 766: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 767: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingaUnitTestprojectThetestprojectwouldideallybeanewproject(andmodel)insidethepackagewearetesting.Eachpackageshouldhaveonetestproject,and,ifwritinguserinterfacetests,weshouldhaveoneformadaptersalso.

Page 768: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 769: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyOpentheprojectthatweintendtowritetestsfor.

Page 770: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 771: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreatetheunittestproject,followthesesteps:

1. SelectDynamics365forOperations|CreatemodelfromthetopmenuandcompletetheCreatemodelformasfollows:

Field Value

Modelname ConWHSVehicleManagementTest

Modelpublisher ContosoIT

Layer VAR

Modeldescription TestcasesfortheConWHSVehicleManagementpackage

Modeldisplayname ConWHSVehicleManagementTest

ThesuffixisimportantandshouldalwaysbeTest.

2. ClickonNext.3. SelectCreatenewpackageandclickonNext.4. SelectConWHSVehicleManagement,ConWHSVehicleManagementFormAdaptor,ApplicationFoundation,andTestEssentials

fromthePackages[Models]listandclickonNext.

Modelpackagesmayberequired,dependingonthecodeweneedtowrite.

5. Uncheckthedefaultforcreatingaproject,butleavethedefaultoptionofmakingthenewmodelthedefaultfornewprojects.ClickonOK.

6. Right-clickonthesolutionnodeintheSolutionExplorerandchooseAdd|Newproject....7. IntheNewProjectwindow,ensurethattheDynamics365forOperationstemplateisselectedinthe

leftpane,andOperationsProjectisselectedintheright.EnterConWHSVehicleManagementTestasNameandclickonOK.

Page 772: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 773: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

Thiswasbasicallyasimplerversionofthestepswetooktocreatetheformadaptorproject.Thisisdoneaftertheformadaptor,asweneedtoreferenceit;thisprojectwillcontaintestcasesformanuallycraftedunittestsandthosethatwilltesttheuserinterface.

Page 774: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 775: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingaUnitTestcaseforcode

Inthisrecipe,wewillcreateatestcasefortheConWHSVehicleGroupChangeclass,wherewewilltesteachpartofthisclass.Thisincludeswhenitshouldfailandwhenitshouldsucceed.Theprocesswillinvolveprogrammaticallycreatingsometestdatainordertoperformtheupdate.

Page 776: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 777: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyWewilljustneedtheunittestproject,whichwecreatedinthepreviousrecipe,open.Also,onthemainmenu,selectX64fromTest|TestSetting|DefaultProcessorArchitecture.

Page 778: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 779: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Tocreatetheunittestclass,followthesesteps:

1. CreateanewclassandnameitConWHSVehicleGroupChangeTest.Thesuffixisimportant.2. Inthecodeeditor,changethedeclarationsothatitextendsSysTestCase.3. Next,wewillneedsomeconstantsfortestcasesthatweeitherexpecttosucceedorfail;inthiscase,

wewillhavethefollowing:

constConWHSVehicleGroupIdgroupId='%VG01%';

constConWHSVehicleIdvehicleId='%V001%';

conststrnotFound='%ERROR%';

4. Thenextpartistosetupthetestcase,whichisdonebyoverridingthesetUpTestCasemethodwiththefollowingcode:

publicvoidsetUpTestCase()

{

super();

ConWHSVehicleGroupvehGroup;

ttsbegin;

vehGroup.initValue();

vehGroup.VehicleGroupId=groupId;

vehGroup.insert();

ttscommit;

ConWHSVehicleTablevehTable;

ttsbegin;

vehTable.initValue();

vehTable.VehicleId=vehicleId;

vehTable.VehicleGroupId=groupId;

vehTable.insert();

ttscommit;

}

Wecreatedavehiclerecordandavehiclegrouprecordusingtheconstantswedefinedearlier.Wewillexpectourteststofindtheserecordstosucceed.

5. Toreducetheamountofrepetitivecodethatisoftenfoundintestcases,writethefollowingmethod.Thiswillbecalledwithdifferentparametersfromatestmethod.Writethecodeasfollows:

privatevoidvehicleGroupExistTest(

ConWHSVehicleGroupId_groupId,

boolean_shouldBeFound)

{

booleanfound;

found=ConWHSVehicleGroup::Exist(_groupId);

strfoundMsg;

foundMsg="wasnotfound";

if(found)

{

foundMsg="wasfound";

Page 780: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

}

strshouldBeFoundMsg;

shouldBeFoundMsg="shouldbenotbefound";

if(_shouldBeFound)

{

shouldBeFoundMsg="shouldbefound";

}

strmsg=strFmt("Vehiclegroup%1was%2whenit%3",

_groupId,foundMsg,shouldBeFoundMsg);

this.assertEquals(

_shouldBeFound?'Found':'Notfound',

found?'Found':'Notfound',msg);

}

TheassertEqualsmethodwillfailifthefirsttwoparametersarenotthesameandshowthemessage.Ifitsucceeds,nomessagewillbeshown.

6. Wecannowwriteourtestmethod.Thenamingisimportant,itdescribesitisatestandtheelementtowhichitistesting.Thecodeshouldbewrittenasfollows:

[SysTestMethod]

publicvoidtestValidate()

{

this.vehicleGroupExistTest(groupId,false);

this.vehicleGroupExistTest(notFound,true);

}

Wewillalwayswritethetestsothatitwillfailfirst,andthenalteritsothatitwillsucceed.

7. Let'sexecutethetests,andcheckthattheydo,indeed,fail.Todothis,buildtheprojectandchooseTest|Run|AllTests.Theresultshouldbeasshowninthefollowingscreenshot:

8. Changetheparameterssothattheyshouldsucceed;buildandthenclickonRunAll.Theresultshouldbeasfollows:

Page 781: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

9. Dothesametotestthevehicletable.10. Wewilluseasimilarpatternforthevalidatemethodbywritingthefollowingcode:

privatevoidvalidateTest(ConWHSVehicleId_vehicleId,

ConWHSVehicleGroupId_groupId,

boolean_shouldBeValid)

{

ConWHSVehicleGroupChangeContractcontract;

contract.VehicleGroupId(_groupId);

contract.VehicleId(_vehicleId);

ConWHSVehicleGroupChangechange;

change=newConWHSVehicleGroupChange();

change.contract=contract;

booleanvalid;

valid=change.Validate();

strvalidMsg='failed';

if(valid)

{

validMsg='passed';

}

strshouldBeValidMsg;

shouldBeValidMsg='failed';

if(_shouldBeValid)

{

shouldBeValidMsg='passed';

}

strmsg;

msg=strFmt('Vehicle%1,group%2%3validation'+

'whenitshouldhave%4',

_vehicleId,_groupId,

validMsg,shouldBeValidMsg);

this.assertEquals(_shouldBeValid?'Passed':'Failed',

valid?'Passed':'Failed',msg);

}

11. WecannowcompleteourtestValidatemethod,whichshouldreadasfollows:

[SysTestMethod]

publicvoidtestValidate()

{

this.vehicleGroupExistTest(groupId,true);

this.vehicleGroupExistTest(notFound,false);

this.vehicleExistTest(vehicleId,true);

this.vehicleExistTest(notFound,false);

//Testblankvehiclegroup

this.validateTest('','',false);

this.validateTest(vehicleId,'',false);

this.validateTest(notFound,'',false);

//Testinvalidvehiclegroup

this.validateTest('',notFound,false);

this.validateTest(vehicleId,notFound,false);

this.validateTest(notFound,notFound,false);

//Testvalidvehiclegroup

this.validateTest('',groupId,false);

this.validateTest(vehicleId,groupId,true);

this.validateTest(notFound,groupId,false);

Page 782: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

//Testblankvehicle

this.validateTest('','',false);

this.validateTest('',groupId,false);

this.validateTest('',notFound,false);

//Testinvalidvehicle

this.validateTest(notFound,'',false);

this.validateTest(notFound,groupId,false);

this.validateTest(notFound,notFound,false);

//Testvalidvehicle

this.validateTest(vehicleId,'',false);

this.validateTest(vehicleId,groupId,true);

this.validateTest(vehicleId,notFound,false);

}

12. IftheTestExplorerisstillopen,simplyclickonRunAll;thiswillbuildtheprojectforus.Theresultshouldbeasfollows:

Thewarningscomefromthetargetmethod,whereitcorrectlyfailedthevalidationcheck.

13. Asbefore,weshouldwritethevalidationcheckstofailfirst.Youcouldalsoinsertaruntimeerrortoseehowthisishandled.OnthelinethatdefinestheConWHSVehicleGroupChangeContractclass,commentoutthelinethatinstantiatesit.Whenitexecutes,youwillgetaveryverbosemessagestatingNullReferenceException,showingusthateventhesetypesoferrorswillbecaughtthroughunittesting.

14. Youcanthencontinuewritingthetestcasesfortherunmethod,whichshouldbedoneinthesamepatternasthevalidatemethodcheck.Youshouldalsopushyourselfwiththeotherassertmethods.

Page 783: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 784: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Mostofthecodewewroteisrelativelystraightforward.Theinterestingpartishowthesystemdiscoversthetestsandexecutesthem.

ThefirstpartwasthatwereferencedApplicationFrameworkandTestEssentialswhenwecreatedtheproject.ThediscoveryworksbylookingforaclassinthecurrentprojectthatextendsSysTestCaseandformethodsthathavetheSysTestMethodattribute.Thetestmethodmustbepublic,returnvoid,andhavenoparameters.Theyshouldstartwith(oratleastcontain)thewordtest,andreferencethemethodintheclasswearetesting.

Finally,whataboutthedatawecreated?Thetestframeworkwillautomaticallyteardownanydatawecreateduringthetestingsession.Thisoccursbetweentests,sodon'tassumeatestcanusedatacreatedorupdatedinaprevioustestcase.

Page 785: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 786: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Creatingatestcasefromataskrecording

Thispartofthetestistotesttheuserinterfaceinteractions.ThisisdonebycreatingataskrecordingfromwithinDynamics365forOperationsandimportingitintoaprojectinVisualStudio.

Wewilltesttheserviceordercreationlogicbycreatingataskrecording.

Page 787: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 788: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyThiscontinuesfromthepreviousrecipes.

Page 789: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 790: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Tocreateaunittestforthevehicleserviceorder'sform,followthesesteps:

1. WemuststartfromthemainDynamics365forOperationswindow;otherwise,thegeneratedcodemayfail.

2. Onceatthemainmenu,clickonthesettingsicon(thecog)andchooseTaskrecorder.3. ClickonCreaterecordingintheTaskrecordersidebar.4. EnterConWHSServiceOrderTestintheRecordingnamefield,andadescriptionofwhatweexpectto

happen.Thiswillbecometheclassname.5. ClickonStart.6. Navigatetotheformtotest,inourcase,Vehicleserviceorders.7. Createanewserviceorder,andaddtwolines.Thesidebarwillrecordeachinteractionwiththe

form,soitcanpayofftorehearsethisfirsttominimizethenumberofstepsitcreates.

Toaddvalidation,right-clickonthefieldandchooseTaskrecorder|Validate|Currentvalue.

8. Oncedone,presstheStopbuttononthetopleftofthescreen.9. IntheTaskrecordersidebar,clickonSaveasdeveloperrecording.Savethisfilesomewhereyou

canfinditlater.10. GobacktoVisualStudio.11. Tosavetime,right-clickontheConWHSVehicleManagementTestprojectandchooseSetasStartUpProject.12. ChooseImportTaskRecodingfromtheDynamics365|Addinsmenu.

13. UsetheBrowsebuttontofindthetaskrecordingcreatedearlier.EnterthecompanyusedtocreatethetaskrecordingintheCompanyfield.Thefollowingscreenshotisanexample:

IfweusedtheNewProjectmethod,wewouldselecttheConWHSVehicleManagementTestmodelasModel.Whencreatingtheproject,ensurethattheSolutiondrop-downissettoAdd

Page 791: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

tosolution.

14. Thiscreatedaclassnamedafterthetaskrecordingname,which,inourcase,isConWHSServiceOrderTest.15. Inmycase,Iaddedavalidationtothelinenumbercontrol,whichresultedinthefollowingcodein

thesetupDatamethod:

ConWHSVehicleServiceTable_LinesGrid_LineNum=0;

ConWHSVehicleServiceTable_LinesGrid_LineNum1=0;

Thisisclearlyabug,andwecansettheexpectedvaluesbyeditingthiscode.Inmycase,Iwillmakethem1and2,respectively.

16. Ifwebuildtheprojectnow,wewillgetmissingassemblyerrors.Thisisbecausethegeneratedcodereferencesstandardformadaptors.AddareferencetothefollowingpackagestotheConWHSVehicleManagementTestmodel:

ApplicationFoundationFormAdaptorApplicationPlatformFormAdaptorApplicationSuiteApplicationSuiteFormAdaptor

17. Ifwehadn'tcorrectedthelinenumbers,thetestrunwouldcorrectlystatethestatethatthevalidationforthelinenumberfailed.Thiswouldthenneedtobecorrectedincodeandthetestrerun.

Page 792: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 793: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Thisisaverypowerfultool,wherewecanaskconsultantsanduserstocreatethetestcasesbasedonwhattheyexpectforthedevelopertouseasatestcase.

Althoughthereisalotgoingonbehindthescenes,itworksbyreadingtheXMLfilecreatedbythetaskrecording.Wheneachstepisrecording,thesystemstorestheinformationabouttheformandcontrolsinawaythatcanbereferencedincode.Thetaskrecodingimportcreatesaclassthatusestheformadaptorinordertointeractwiththeformprogrammatically,usingthestepsintheXMLfile.

Althoughthiscodeisgenerated,wecaneditit.Inthecaseofvalidations,weusuallyhaveto.Forexample,theconsultantscouldhaveaddedavalidationfortheitemname.Sincewecanonlyusethecurrentvaluetovalidateagainst,wewillneedtoamendthecode.Inthecaseofanitemname,wewouldwritecodetofindtheexpectedvaluebasedontheitemIDtheuserentered.

Sincethesetestscanbeautomatedatthebuildserver,thedatausedtocreatethetestcaseandthatonthebuildserveritselfmustbethesame.Thisisdonebyimportingabackupofthedatabasefromthetestdatabase.Withthetechniquedescribedinthisrecipewecanthencreateintegrationteststhatarerunautomaticallyoneverybuild,thisismuchmorerobustthatrelyingsolelyonend-usertesting.Itisnaturalforustotestwhathaschanged,andthereforeveryeasytomissregressioninareasthathaven'tbeenchanged.Thetaskrecordingisnomoreeffortthatthetestingtheuserswouldhavetodoanyway,andwitharelativelysmalldevelopmenteffortwehavearepeatablesetofteststhatwillalwaysrun,regardlessofthechangemadeinthebuild.

Page 794: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 795: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

AutomatedBuildManagementInthischapter,wewillcoverthefollowingrecipes:

CreatingaVisualStudioBuildAgentQueueSettingupabuildserverManagingbuildoperationsReleasingabuildtoUserAcceptanceTesting

Page 796: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 797: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

IntroductionInthischapter,wewillcoverthestepsrequiredtosetupanduseabuildserver.WetoucheduponsomebenefitswithabuildserverinChapter11,UnitTesting,whereunittestscanbeexecutedtohelpreducetheriskofregression.

Weshallcovertwoscenarios.ThesearecloudhostedcustomerimplementationprojectdeployedviaLCS,andanon-premisebuildserver,whichisequivalenttoanAzureserverhostedunderyourownsubscription.

ShouldtheimplementationbehostedinAzuredeployedthroughaLCScustomerimplementationproject,allweneedtodoissetuptheBuildAgentPoolsandQueuesandthensupplytheparameterstothesetupforminLCS.Theprocessofdeployingabuildmachineiswelldocumentedandwewon'tduplicatethishere,especiallygiventhepaceatwhichupdatestoLCSarebeingmade.Evenifwedon'tsetupabuildservermanually,theinformationmayproveusefulinunderstandingissuesthatmayarisewiththeserver.

TherecipesinthischaptershouldbeusedinconjunctionwithreleasedMicrosoftdocumentation.Theaim(asalways)istoprovidepracticalhands-onguidance,intendedtoaugmentthealreadypublisheddocumentation.

Page 798: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 799: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingaTeamServicesBuildAgentQueue

AgentqueuesactasbridgesbetweenVisualStudioTeamsServicesandthebuildagentthatisinstalledonthebuildserver.Wewillneedanagentqueuebeforeweconfigurethebuildserver.

AgentQueuesbelongtoAgentPools,andgiventhewaythatthebuildserversareprovisionedfromLCS,wewillhaveaone-to-onerelationshipforthis.Thisisbecauseaprojectwilltypicallyhaveitsownbuildserver(whichisnotlimitedtoone)andkeepingthequeuesandpoolsatone-to-onesimplifiesmanagement.ThisisespeciallyimportantforpartnersandISVswhohavemanyprojects.

Page 800: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 801: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyYouwillneedtohavecreatedyourVisualStudioTeamServicessitebeforeyoustartthis.

Page 802: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 803: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

TocreateanAgentQueue,followthesesteps:

1. OpentheVSTSsite,forexample,<yourdomain/tenant>.visualstudio.com.2. Clickonthesettingscogonthetoolbar,andthenAgentPools,asshowninthefollowingscreenshot:

3. OntheAgentsPoolstabpage,clickonNewpool....4. Thenameshouldrelatetotheproject;inmycase,IchoseB05712AX7DevCookBook.Chooseanamethatis

shortandeasytodeterminewhichprojectthepoolisfor.5. UncheckAuto-ProvisionQueuesinallprojectsandclickonOK.

Wewanteachbuildservertohaveitsownagentqueue;ifenabled,itwouldcreatethequeueforallVSTSprojects.

6. ClickonProjectsfromthetopbuttonribbonandselecttheVSTSproject,andthenselectAgentQueuesfromthesettingscogmenu.

7. ClickonNewqueue...andselecttheAgentpoolwecreatedearlier,andthenclickonOK.TheAgentqueuewillhavethesamenameasthepool.

Page 804: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 805: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

TheAgentqueueisusedduringthesetupofthebuildserverinordertoassociatetheagent,whichisinstalledonthebuildserver,withthequeue.Thisway,whenabuildistriggered(manually,orviaacheck-in),itknowswhichservertotriggerthebuildtobuildon.

Page 806: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 807: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Settingupabuildserver

Thebuildserverisaone-boxDynamics365forOperationsvirtualmachine,usuallywithdemodatathatisonlyeverusedtoproducebuilds.Eventhoughithasdata,andseemstohaveanapplicationrunninginIIS,itcannotbeused.

Ifwearecreatingabuildserverforacustomerimplementationproject,mostoftheworkisdoneforus.YouwilljustneedtospecifytheAgentQueuethatwecreatedinthepreviousrecipe.ThisrecipewillfollowtheISVscenariowherewemayinstallthebuildagentourselves.

Page 808: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 809: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyYouwillneedabuildserverVMrunning,withaccesstotheinternet,andtheAgentQueuecreatedagainsttheproject.

Page 810: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 811: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Toconfigurethebuildserver,followthesesteps:

1. OpentheVSTSsiteandselectSecurityfromyouruseroptions(theiconwithyourinitialsorpicture).

2. ClickonAddunderthePersonalAccessTokenstab.3. Enterasuitabledescription,suchasB05712_Agent.4. Settheexpirybasedontheprojectlength,usuallyayearforOperationsprojects;youcanextendthis

shoulditexpire.5. EnsurethatAllScopesisselectedandclickonCreateToken.6. Makesureyoucopythetokenasyouwillnotbeabletoseeitagain!7. OpenaPowerShellprompt(pressWindows+RandtypePowerShell).8. Typethefollowingline:

Cd\DynamicsSDK

9. EnsurethatthecurrentdirectoryisC:\DynamicsSDKandtypethefollowinglinesofcode:

.\SetupBuildAgent.ps1-VSO_ProjectCollectionhttps://<yourDomain>.visualstudio.com/DefaultCollection-VSOAccessToken<youraccesstoken>-AgentNameB05712AX7DevCookBook01-AgentPoolNameB05712AX7DevCookBook

10. Theoutputshouldbeasfollows:

11. OpentheVSTSsite,andcheckthattheagentwasaddedtotheagentqueue;itwillbeshownasthefollowingscreenshot:

12. Westillneedabuilddefinitioncreatedforus,sogobacktoPowerShellandenterthefollowinglinesofcode:

.\BuildEnvironmentReadiness.ps1-VSO_ProjectCollectionhttps://<yourdomain>.visualstudio.com/DefaultCollection-ProjectNameB05712_AX7_DevelopmentCookbook-VSOAccessToken<Youraccesstoken>

Page 812: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

13. OpentheVSTSsiteagainandopenyourproject.SelectBuild&ReleasefromthetopandthenBuildsfromthebuttonribbon,justbelowthemainbuttonribbon.Youshouldseethefollowing:

14. ClickonthethreedotsandchooseClone...aswewillcreateadefinitionforcontinuousintegration:everycheck-inwillperformabuildandrunourtests.

15. Afterafewseconds,thepagewillshowthedefinition,andweshouldnameitbasedontheoriginal,suffixedwithcontinuous.ThedefaultpageistheTaskspage,andwillshowthetasksthatthebuilddefinitionwillprocess.Thisisshowninthefollowingscreenshot:

16. SelecttheTriggerspageandenableContinuousIntegration.Thedefaultsareotherwisecorrect.17. SelecttheOptionspageandchangetheDefaultagentqueuesettingtothecorrectqueue(theonewe

createdearlierandwhichnowcontainsournewagent).18. SelectSavefromtheSave&queueoptionbutton.19. ClickonBuildsagaintoseethelistofbuilddefinitions,thenclickonthethreedotsicononAX7-

BuildMain,and,thistime,selectEdit.20. ClickonVariablesandselect+Addatthebottomofthepage.21. EnterBuild.CleanintheNamecolumnandAllintheValuecolumn.

Page 813: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Thisforcestheagenttoperformacleanbuild.

22. SelectTriggersandenableScheduled.Thisisbasedonyourcompany'sprocedures,andmostcompanieswishtorunacleanbuild(completefetchfromTFS)nightly.Thedefaultscheduleisusuallycorrect.

23. ClickonOptionsandchangetheDefaultagentqueuesettingtothecorrectqueue.24. Saveyourchangesandqueueanewbuild(SaveandQueue)--thiswilltakeanythingfrom15minutes

to2hours,dependingonthehardwaretheVMisrunningon.25. OntheQuerybuildforAX7-BuildMainoption,clickonQueue.26. Youshouldnowseeaconsolewindowshowingtheprogress(veryverboselyasthebuildis

performed).

Page 814: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 815: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Therewerefivepartstothisrecipe:

GettingaPersonalAccessTokenInstallingthebuildagentConfiguringthebuildagentUploadingabuilddefinitiontotheVSTSprojectConfiguringabuildagentforcleanandcontinuousintegration

WecouldhavedownloadedthebuildagentmanuallyfromtheAgentQueueform(youmayhavenoticedtheDownloadagentbutton),andthenconfigureditusingtheagent'sconfig.cmdscript.ThiswouldbeOK,butwewouldn'thavebeenabletodeployabuilddefinitiontotheprojectifwedid.Thenextpart,BuildEnvironmentReadiness.ps1,assumesthattheagentisinstalledinaparticularplaceonthedrive,andwillthereforefailtorun.

Weraneachcommandwithparametersratherthanenteringthematruntimebecausethescriptdoesn'taskfortheoptionalparametersanditwillusedefaults.

TheresultoftheconfigurationisthatwehaveanAgentlinkedtoourAgentqueuethroughitsconfiguration,andtwobuilddefinitionsthatarebothlinkedtotheAgentqueue.

Onceallthesearelinked,andwehavesetupthebuilddefinitions,thebuildserverisreadyforoperation.

Page 816: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 817: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...Weconfiguredbothbuilddefinitionstoexecuteanytestthattheprojectmaycontain,whichisthedefaultbehavior.Iftheprojectdoesnotcontaintests,wewillneedtodisabletheteststepsonthebuilddefinitions.

Todisablethetestexecution,selectBuildsfromtheBuild&ReleasesectionoftheVSTSprojectsite.ClickonthethreedotsiconandselectEdit.YouwillseethelistoftasksontheTaskstabpage.Thetesttasksareasfollows:

TestSetupExecuteTestsTestEnd

Foreachofthesetasks,uncheckEnabledfromtheControlOptionssection.

Ofcourse,wewouldalwayshavetestsforourprojects!

Page 818: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 819: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

SeealsoDevelopertopologydeploymentwithcontinuousbuildandtestautomation:

https://ax.help.dynamics.com/en/wiki/developer-topology-deployment-with-continuous-build-and-test-automation/

DevelopmentandcontinuousdeliveryFAQ:

https://ax.help.dynamics.com/en/wiki/development-and-continuous-delivery-faq/

Page 820: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 821: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ManagingbuildoperationsThisrecipefocussesonwhathappenswhenabuildistriggered,andhowtodealwithsomecommonissues.Wewilltriggerabuildandthenmonitoritsprogress.

Page 822: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 823: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyWemusthaveafullyfunctionalbuildserver,andabuilddefinitionthatwilltriggeroncheck-in.

Page 824: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 825: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tomanagethebuildoperations,followthesesteps:

1. Makeaminorchangetoanycodeinyourprojectandcheck-inthechanges.2. ThenopentheVSTSprojectandselectBuildfromtheBuild&Releasemenu.Youshouldsee

somethinglikethefollowingscreenshot:

3. The#<buildnumber>linkwilltakeustothedetailsofthebuild;clickonthislink.4. ThiswillopenthedetailsofthebuildoperationwiththeConsoleopenbydefault.Thisverbosely

listseverydetailoftheoperationasithappens.Anyerrorsorwarningsarealsolistedhere.5. Oncecomplete,checkforerrorsandlookattheSummarysection(clickontherootoftheprogress

treeontheleft).Youmayfindthatyouwillreceivethefollowingmessage:

EXEC(0,0):Warning:1:22:57PM:NoModelsreturnedbymodelinfoproviderfrommetadata

6. Youmayalsonoticethateventhoughnoerrorwasshown,notestswererun.Theconsolewillprovideanotherclue:

Notestassembliesfoundmatchingthepattern:'C:\DynamicsSDK\VSOAgent\_work\2\Bin\**\*Test*.dll'.

ItislookingforDLLsthatcontainTest!Wecanseethatnamingconventionsareimportant,albeitseeminglyalittleugly;thereisnowaythebuildagentcanseethattheclassintheDLLextendsSysTestCase.

7. Whydiditfail?OpenVisualStudioandselectTeamExplorer.Fromthere,selectSourceControlExplorer.

8. ThefirstcheckistocheckiftheDescriptorfolderisadded;inthefollowingscreenshottheDescriptorfolderisnotlistedundertheConWHSVehicleManagementTestfolder:

Page 826: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

9. WeaddtheDescriptorfileaddedbyright-clickingonthepackage(thefirstConWHSVehicleManagementTestnodeinthiscase)andselectingAddItemstoFolder....AddtheDescriptorfolder,butnothingelse.

Whenhotfixesareapplied,whichwillneedtobeaddedtosourcecontrolsothatthebuildserverbuildsthem,itwillonlyaddthesourcefiles.Youwillmanuallyhavetoaddthemodels'descriptionfile(apackagecanhavemorethanonemodel,andthereisadescriptorforeachmodel).Onlyaddthedescriptorfileforthemodelsthathaveelementsaddedtosourcecontrol.

10. TheotherreasonisthatthemappingofTFSiswrong.Thebuildagentwillassume(andcreate)thestructureTrunk\Main\Metadataanddownloadchangesfromthisfolder.Ifwemappedthemetadatadifferently,thebuildagentwillnotfindanysourcecodetobuild.Dothisbyeditingyourworkspace(itwillusuallybetheservername)andmappingasfollows:

Beforeyoumakethischange,makesureeverythingischecked-inandthatyouhavemovedtheexistingfoldersagainstthe'wrong'foldertothecorrectfolderinTFS(Trunk\Main\Metadata).Failuretodothiswillresultinerrorscheckingcodeinandout,andyoucouldlosecode.

12. Triggerabuildeithermanuallyorviaacheck-in.OpenthebuilddetailsfromtheVSTSsiteandlookattheGetSourcesnode.Youshouldseeentriessimilartothefollowingcodesnippet:

2017-03-02T13:43:47.6277906ZC:\DynamicsSDK\VSOAgent\_work\2\s\Metadata:

2017-03-02T13:43:47.6287911ZGettingConWHSVehicleManagement

2017-03-02T13:43:47.6287911ZGettingConWHSVehicleManagementFormAdaptor

2017-03-02T13:43:47.6297916ZGettingConWHSVehicleManagementTest

2017-03-02T13:43:47.6498150Z

2017-03-02T13:43:47.6507910ZC:\DynamicsSDK\VSOAgent\_work\2\s\Metadata\ConWHSVehicleManagement:

2017-03-02T13:43:47.6517934ZGettingConWHSVehicleManagement

2017-03-02T13:43:47.6517934ZGettingDescriptor

13. Oncethebuildhasfinished,youshouldseethefollowingonthebuild'ssummarypage:

Page 827: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

14. Youcanthenclickonthedetailedreport,usuallyinthecaseoffailures,toviewthedetailedtestresults:

Thedefaultistoshowonlyfailedtests;toviewall,clickontheoutcome(forexample,Failed)nexttothelabelOutcomeandchooseAll.

Page 828: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 829: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Whenthebuildagentisinstalled,itcreatesaTFSworkspacethatishardcodedtobeTrunk\Main\Metadata,theagentassumesamappedserver'slocalpackagesfolder.ItstartsbydownloadingallchangesfromthisfolderinTFStoalocalworkingfolder.Itthenusesthedescriptorfiletodeterminewhattobuild.

Oncethefileshavebeendownloaded,thefollowinghappens:

CheckiftheDynamicsBackupfolderexistsontheCdrive--thiswillbeonaspecificdriveifitisanLCSdeployedbuildserver,andwillnotbeontheCdrive.Ifitdoesnotexist,aSQLbackupismadetotheDynamicsBackup\DatabasesfolderandthelocalpackagesfolderiscopiedtotheDynamicsBackup\Packagesfolder.Ifthefolderexists,theSQLdatabaseisrestoredfromthisbackupandthelocalpackagesfolderisrecreatedfromthisfolder.Thesystemwillthenstartafullbuildofthesystem,andprocesseachstepinthebuilddefinition.MostcallPowerShellscriptsstoredintheC:\DynamcisSDKfolder.Oncecomplete,ituploadsthesourceandresultantdeployablepackagetothebuild.Thiswillthenbeappliedtoourtestserver,andeventuallyproduction.

Itispossible,asatUpdate6ofOperations,toincludeapartner'sorISV'spackagedirectlyonthebuildserver,andtoconfigurethebuildagenttoincludethatpackageinthebuild'sartifacts.

Thisisreferencedinthefollowinglink:

What'sneworchangedinDynamics365forOperationsplatformupdate6(April2017)(https://docs.microsoft.com/en-us/dynamics365/operations/dev-itpro/get-started/whats-new-platform-update-6)Theactualdetailsaretohandatthispoint,andevenwiththisfunctionalityMicrosoftdoencouragethatISVpackagesareinstalledonthedevelopmentmachinesandpushedthroughVSTS.Thisisacomplicatedsubject,asISVsnaturallywanttoprotecttheirintellectualpropertyandcustomersoftendesiretohavesourcecodeinordertoprotecttheirinvestment.

Page 830: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 831: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ReleasingabuildtoUserAcceptanceTesting

Attheendofthebuildprocess,adeployablepackagefilewasuploadedtothebuild.Thisfilecanthenbeappliedtoyouruseracceptancetestorsandboxserver.

Wecanapplythepackagemanuallyonthetestserver,butforLCSdeployedUserAcceptanceTesting(UAT)environments,thisisalwaysdoneviaLCS.Anyreleasetoproductionmustfirstbedeployedtoasandboxserverandmarkedasareleasecandidate.

Page 832: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 833: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyInordertofollowthisrecipe,weneedtohaveanLCSOperationsserverdeployedthroughLCS.

Page 834: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 835: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Toapplythebuildtoatestserver,followthesesteps:

1. OpentheVSTSsite,andthenopenyourprojectandselectExplorerfromBuild&Release.2. Double-clickonthebuildthatyouwishtodeploytoUAT.3. Onthepagethatopens,selectArtifacts,andclickonExploreforthePackagesartifact,asshowninthe

followingscreenshot:

4. IntheArtifactsExplorertab,expandthePackagesnodeandclickonthearrowiconnexttothefilestartingwithAXDeployableRuntime.ClickonDownload.Theicononlyappearswhenyoumovethemouseoverit,asshowninthefollowingscreenshot:

5. DownloadthefileandthenopenLCS(https://lcs.dynamics.com/).6. OpenyourLCSprojectandthenopentheAssetLibrary.Dependingonthetypeofproject,thiswill

beundertheburgericonoratileontheproject.DonotusetheSharedAssetLibrary.7. InAssetlibrary,selectSoftwaredeployablepackagefromtheleft,andthenpresstheplussymbol.In

thedialog,enterthebuildname,(forexample,Build-2017-03-02),adescription,andthenclickonAddafile.

8. LocatethedeployablepackagewejustdownloadedandclickonUpload.Oncecomplete,clickonConfirmontheUploadSoftwaredeployablepackagefiledialog.

9. OpentheenvironmenttowhichthisshouldbedeployedtoandchooseMaintain|Applyupdates.ThiswillshowalistofSoftwaredeployablepackageassets.SelecttheassetandclickonApply.

Theupdateprocesswillstartimmediately.Thiswilltaketheserverofflineandapply

Page 836: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

theupdate.Thiswilltakeseveralhourstocomplete.

10. Aftertestingiscomplete,gobacktothesandboxenvironmentandyouwillbepromptedtoconfirmthattheupdatewassuccessful.Afterthat,gobacktotheAssetlibrary,selectthedeployablepackage,andthenclickonReleasecandidate.Thisissothatitwillbeavailabletobedeployedtotheproductionenvironment.

Page 837: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 838: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

TheapplicationofdeployablepackagesisperformedbyaPowerShellscriptonthetargetserver.ThereareagentsinstalledonLCSdeployedserversthatallowLCStoperformthistask.

Theprocessesareasfollows:

Downloadthedeployablepackagetothetargetserverorservers(testandproductionenvironmentshavemultipleserversandthecomponentsinstalledoneachmayvary)ExtractthedeployablepackageApplytheupdatetotheserverusingthePowerShellscriptsincludedinthepackageAttheendoftheprocess,theservers'servicesarerestarted

Thisprocessisbestused,andis,infact,mandatoryforapplyingupdatestocustomerimplementationenvironments.Youcan'tapplyanupdatetoproductionserversthathasn'tbeenappliedtothesandboxserverthroughLCS.

Page 839: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 840: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ServicingYourEnvironmentInthischapter,wewillcoverthefollowingrecipes:

ApplyingmetadatafixesApplyingbinaryupdatesServicingtheBuildserverServicingtheSandbox-StandardAcceptanceTestenvironment

Page 841: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 842: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

IntroductionThischapterfocusesontheservicesofthevariousenvironmentsusedinacustomerimplementationproject.WewillalsocovertheservicingofenvironmentstypicallyusedbyISVs.

Theimportantpartofthischapteristheprocess.TheactualdetailsofthetaskscanbefoundintheOperationsdocumentation.Therecipesarewrittentohelpthisprocessmakemoresenseastowhy,whichismainlytohelpmanagehowupdatesareappliedandtominimizetheriskofregression.

Forgeneralinformationontheupdateprocessandtheupdatepoliciespleaseseethefollowinglink:Dynamics365forOperationsversionsandupdatepolicy(https://docs.microsoft.com/en-us/dynamics365/operations/dev-itpro/migration-upgrade/versions-update-policy)

Page 843: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 844: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ApplyingmetadatafixesMetadatafixesareupdatestothesourcecodeofOperations.Forover-layeringwewouldusetheTheseareusedtocode-mergeover-layeredcode,buttheyarestillneededonextensionprojectsasthisispartoftheprocesstopushupdatesdownstreamtothebuild,test,andeventually,theproductionenvironment.

Theprocessofapplyingtheupdatesisstraightforward;itistheprocessthatisthemostimportantaspecttotakeawayfromthis.

LCScanseewhichmetadatafixesareavailableforeachenvironmentconnectedtotheproject.Whenworkingonacustomerimplementationproject,usetheSandbox(test)environmentasthereferenceVMtocheckforupdates.WhenworkingoninternalorISVprojects,useareferenceVMhostedinAzureforthis.On-premiseimplementationswillbeconnectedtoLCSandwillbehandledinthesamewayasAzure-basedimplementationprojects.

Thecycleisasfollows:

ChecktheavailabilityofmetadatafixesrequiredagainstareferenceVMorenvironmentDownloadthedesiredfixestoadevelopmentVMApplythehotfixessothattheyareplacedinTFSCheck-insothatabuildistriggered(andotherdeveloperscanbringdownthehotfixesapplied,maintainingthesamecodebaseacrossdevelopers)Deploytheresultantpackagetothetestenvironment(orreferenceVM)

Whenmovingtothelatestupdaterelease,wherenewVMsaredeployedwiththenewupdate,weremovethehotfixesfromsourcecontrolbeforeconnectingthenewdevVMsbacktothesourcecontrolprojectwiththeupgradedcode.Thisprocessmaychange,andthestandarddocumentationcoversthisverywell.Theprocessisdescribedinthefollowinglink:OverviewofmovingtothelatestupdateofDynamics365forOperations(https://docs.microsoft.com/en-us/dynamics365/operations/dev-itpro/migration-upgrade/upgrade-latest-update)

Page 845: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 846: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyYouneedaccesstoanLCSprojectthatallowsthedownloadofmetadatahotfixes.LCSdeployedVMsandCustomerimplementationLCSprojectscandothis.EnsurethatVisualStudioisconnectedtothecorrectTFSproject.

Page 847: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 848: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...ToapplyametadatahotfixtoadevelopmentVM,pleasefollowthesesteps:

1. WithinthedesireddevelopmentVM,openhttps://lcs.dynamics.comandnavigatetoyourimplementationproject(ortheprojectthathostsyourAzurehostedVM).

2. ClickonFulldetailsontheSandbox:Standardacceptancetestenvironment(ortheVMifthisisnotacustomerimplementationproject).

3. Therearetwotilesformetadatahotfixes,whicharecalledX++updatesonthispage:Applicationx++updatesandPlatformx++updates.

Thenumberindicatesthenumberoffixestobeapplied.

4. Clickonthefirsttile.5. Youwillnowseealistofthefixes,andyoucanchoosewhichtoapply.Theyarelinked,whichwill

forceustoaddsetsofhotfixeswhereadependencyhasbeenidentified.ClickonSelectallandpressAdd.

Takenoteofthefixesincludedintheupdate,sothatthesecanbetestedforregressioninprocessofcodethatwehaveextended.

6. ClickonDownloadpackagefromthetopleftofthewindow,andthenDownloadonthenextpage.

Thereisnoneedtoselectthem;thesebuttonsaretoallowyoutoremovefixesfromthedownloadlist.

7. Onthefilethatisdownloaded,right-clickonit,chooseProperties,andunblockthefile.Renamethefilesothatitreflectsthetileusedtodownloadtheupdates,suchasAppXpp20170309.zip,andextractthefilecontents.

8. WithinVisualStudio,selecttheAddins|ApplyhotfixfromtheDynamics365menu.9. SelectApplyMetadataHotfix,andusetheBrowsebuttontoselectthefileextractedfromtheupdate

package.10. TheotherthreetextboxesaretakenfromVisualStudio;checkifthesearecorrectandclickon

Apply.11. Oncecomplete,youwillseemanypendingchangesreadytobecheckedintoTFS.12. Beforecheckingin,weneedtocheckthateachaffectedmodel'sdescriptorfileisalsoadded.Open

SourceControlExplorerfromtheTeamExplorerhometab.13. ExpandTrunk,MainandthenMetadata.14. ThefollowingscreenshotshowsthestructureofmodelsElectronicReportingApplicationandFoundation

sitwithintheApplicationSuitepackage:

Page 849: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

15. Weneedtoensurethatthereisamodeldescriptorfileforbothofthesemodels;ifoneismissing,additbyright-clickingonthepackage'sfolderandchoosingAddItemstoFolder....

16. Addthefilesbydouble-clickingontheDescriptorfolderfromwithintheAddtoSourceControldialogandselectingthemissingfilesforthemodelsalreadyaddedtosourcecontrol.Donotaddanymodeldescriptorfilesformodelsthatarenotaddedtosourcecontrol.Inthefollowingscreenshot,theFoundationUpdateandSCMControlsfilesmustnotbeadded:

17. Onceallrequiredmodeldescriptorfilesareadded,performafullbuildandcheck-inthechanges.Thisbuildwillexecuteouttestscript,whichisespeciallyimportantastheywillhelpusidentifyregressionbeforetheusersstarttesting.

Ifanyover-layeringhasbeendone,thesechangesmustbemergedpriortobuildandcheck-in.

Page 850: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 851: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Theupdateworksbycheckingthelocalpackagesfolderforupdates,andapplyingthosethatarenotyetapplied.Thechangeismadeusingdeltachanges,whichiswhythefilesaresosmall.

Thereasonthatweaddthechangestosourcecontrolissothattheyarepushedtootherdevelopersandalsotothebuild.OnceahotfixhasbeenaddedtoTFS,theresultingdeployablepackageincreasesinsizefromafewmegabytestoaround600MB.Thisisbecausepackagesmustbedeployedintotal.

WehavetomanuallyaddthemodeldescriptorfiletoTFSifwewantthebuildservertobuildthepackage.Thisisusedbythebuildservertoworkoutwhatneedstobebuilt,andwhatwillendupasabuildartefactagainstthebuild.ShouldweaddmodeldescriptorfilesformodelsthatarenotinTFS,errorswillbeproduced;thisissolvedbyremovingthefilefromTFS.

Page 852: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 853: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...Theseupdatescanalsobeaddedusingacommandline,whichisdoneasfollows:

c:\AOSService\PackagesLocalDirectory\bin\SCDPBundleInstall.exe

-packagepath=<path>HotfixPackageBundle.axscdppkg

-metadatastorepath=c:\AOSService\PackagesocalDirectory

-tfsworkspacepath=C:\AOSService\PackagesLocalDirectory

-tfsprojecturi=https://<yourcompanysite>.visualstudio.com

Thisistypedasoneline,andissplitintheprecedingcodesothatitiseasiertoread.

Youmaywonderhowitknowswhichprojectitshoulduse,andyoumayhavethoughtthatVisualStudioautocompletedthisparameterwhenweusedtheGUImethod.

ItdoesthisusingtheworkspacemappingthatwasconfiguredinVisualStudio.ThisiswhatthetfsWorkspacePathparameterdoes--ittellsthecommandtolookupthemappingfromthecurrentTFSworkspace.ThefactthatwespecifythepathsforthelocalpackagesfolderandTFSworkspacepathseparatelyseemstobelegacy,andthesemustalwaysbethesame.

Page 854: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 855: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

SeealsoInstallingametadatahotfix(https://docs.microsoft.com/en-us/dynamics365/operations/dev-itpro/migration-upgrade/install-metadata-hotfix-package)DownloadhotfixesfromLifecycleservices(https://docs.microsoft.com/en-us/dynamics365/operations/dev-itpro/migration-upgrade/download-hotfix-lcs)

Page 856: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 857: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ApplyingbinaryupdatesBinaryupdatescontainreplacementbinaryupdatestothetargetVM.ThebinaryupdatesdownloadedfromLCSaremergeddeployablepackagescontainingupdatestoone(andusually)morepackages.

TheyalsocontainupdatedVisualStudiotooling,whichhastobeinstalledseparately.

Page 858: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 859: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyYouneedaccesstoanLCSprojectthatallowsthedownloadofmetadatahotfixes.LCSdeployedVMsandCustomerimplementationLCSprojectscandothis.

Page 860: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 861: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

ToapplyabinaryhotfixtoadevelopmentVM,followthesesteps:

1. WithinthedesireddevelopmentVM,openhttps://lcs.dynamics.comandnavigatetoyourimplementationproject(ortheprojectthathostsyourAzure-hostedVM).

2. ClickonFulldetailsontheSandbox:Standardacceptancetestenvironment(ortheVMifthisisnotacustomerimplementationproject).

3. Thereisonetileforbinaryupdates,namedBinaryupdates;clickonthisfirsttile.4. Youwillnowseealistofthefixes,andyoucanchoosewhichtoapply.Theyarelinked,whichwill

forceustoaddsetsofhotfixeswhereadependencyhasbeenidentified.ClickonSelectallandselectAdd.

5. ClickonDownloadpackagefromthetopleftofthewindow,andthenDownloadonthenextpage.6. Onthefilethatisdownloaded,right-click,chooseProperties,andunblockthefile.Renamethefile

sothatitreflectsthetileusedtodownloadtheupdates,suchasBin20170309.zip,andextractthefilecontents.

7. WithinVisualStudio,selectAddins|ApplyhotfixfromtheDynamics365menu.SelecttheApplyBinaryHotfixtab,andthenbrowsetothefilebeforeclickingonApply.

8. SelectExtensionandUpdates...fromtheToolsmenu.9. ClickonDynamics365forOperationsVisualStudioToolsandthenclickonUninstall.10. CloseVisualStudio.11. InWindowsExplorer,navigatetothefolderextractedfromthedownloadeddeployablepackaged.12. NavigatethroughtheDevToolsServicefolder,andthenScripts.Double-clickonthe

Microsoft.Dynamics.Framework.Tools.Installer.vsixfile(the.vsixextensionmaynotbevisible)andfollowthepromptstoinstalltheupdatedextension.

Page 862: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 863: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Thebinaryupdatepackageisamergeddeployablepackage,whichusuallycontainsamergedsetofpackagesthatcanbe(technically)applieddirectlytoanyOperationsenvironment.

Thedialog,justlikethemetadatahotfixes,isoptional,andwecanuseacommandlinetoapplytheupdate.

ThisisdoneintheServicingthebuildserverrecipe.

Page 864: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 865: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

SeealsoInstalladeployablepackage(https://ax.help.dynamics.com/en/wiki/installing-deployable-package-in-ax7/)

Page 866: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 867: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Servicingthebuildserver

AlthoughwecouldusethesameprocesstoapplybinaryupdatestothebuildserveraswedidonthedevelopmentVMs(albeitusingthecommandversion),thebuildserverisalittlespecial.

Theprocessofapplyingabuildisthattheenvironment(localpackagesfolderanddata)isrestoredfromabackupandmergedwithobjectsfromTFS.Ifwesimplyappliedtheupdate,thenextbuildwill,effectively,removeit.

Page 868: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 869: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyYouneedaccesstoanLCSprojectthatallowsthedownloadofmetadatahotfixes.LCSdeployedVMsandCustomerimplementationLCSprojectscandothis.

Page 870: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 871: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Toapplybinaryupdatestothebuildserver,followthesesteps:

1. Withinthebuildserver,openhttps://lcs.dynamics.comandnavigatetoyourimplementationproject(ortheprojectthathostsyourAzure-hostedVM).

2. ClickonFulldetailsontheSandbox:Standardacceptancetestenvironment(ortheVMifthisisnotacustomerimplementationproject).DonotusetheDevelopment/buildservertile.

3. ClickontheBinaryupdatestile.4. Inthelistoffixes,clickonSelectallandthenAdd.5. ClickonDownloadpackagefromthetopleftofthewindow,andthenDownloadonthenextpage.6. Onthefilethatisdownloaded,right-click,chooseProperties,andunblockthefile.Renamethefile

sothatitreflectsthetileusedtodownloadtheupdates,suchasBin20170309.zip,andextractthefilecontents.

7. OpenServices(Windows+Randtypeservices.msc).8. Stopthefollowingservices:

WorldWideWebPublishingServiceVSTSAgentMicrosoftDynamicsAXBatchManagementService

9. RenameJ:\AosService\PackagesLocalDirectorytoJ:\AosService\PackagesLocalDirectory_OLD.10. RenameI:\DynamicsBackuptoI:\DynamicsBackup_OLD.11. Openacommandpromptasadministratorandtypethefollowingcommand:

robocopyI:\DynamicsBackup_OLD\PackagesJ:\AosService\PackagesLocalDirectory/E

ThebackupisthebaselinethatisusedtomergewithTFSoneachbuild,soweneedtocopythistoourmainpackagesfolderbeforetheupdateisapplied.

12. Startthefollowingservices:WorldWideWebPublishingServiceMicrosoftDynamicsAXBatchManagementService

13. NavigateusingCDtotherootoftheextractedfiles,forexample,CDC:\Updates\Bin20170309\.14. Runthefollowingcommands:

AXUpdateInstaller.exegenerate-runbookid=Build<YYYYMMDD>

-runbookfile=Build<YYYYMMDD>

-topologyfile=DefaultTopologyData.xml

-servicemodelfile=DefaultServiceModelData.xml

AXUpdateInstaller.exeimport-runbookid=Build<YYYYMMDD>

-runbookfile=Build<YYYYMMDD>

AXUpdateInstaller.exeexecute-runbookid=Build<YYYYMMDD>

Therunbookisstoredinternally,soyoumustuseuniquenamesforeachupdate;weusethefollowingschemas:

Page 872: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Updatetype Namingscheme

Binaryplatformupdate

BinPlatform<YYYYMMDD>Forexample,BinPlat20160219

DonotuseDDMMYYYYorMMDDYYYYasthiscancauseconfusionbetweenthoseintheUSAandoutside.

Binaryapplicationupdate BinApp<YYYYMMDD>

Newbuild(areleaseofyourcode)

Build<YYYYMMDD>:thisdoesn'thavetomatchyourversionnumberingsystem;itjustneedstobeunique.

15. StarttheVSTSAgentserviceandtriggeranewbuild.ThispackagewillthenbeusedtoservicetheSandbox(standardacceptancetest)environment.

16. YoucandeletethefolderswerenamedasOLD.

Page 873: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 874: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

Theapplicationoftheupdateisthesameasbefore;theonlychangeisthatweneedtore-baselinethebuildserver'scleanenvironment.Thebaselineisactuallycreatedwhenthenextbuildruns.Sincewerenamedthebackupfolders,thebuildagentthinksthatthisisanewserverandwillrecreateanewbaselinebackupfromtheupdatedfilesinthelocalpackagesfolder.

Page 875: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 876: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

ServicingtheSandbox-StandardAcceptanceTestenvironment

ThisprocessisnowdoneentirelywithinVisualStudioOnlineandLCS,andwedon'tneedtobeloggedintoaOperationsserver.

Theprocessistotakethelatestbuildfromthebuildserverandmergeitwithabinaryupdatebeforeapplyingittothetestserver.

Page 877: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 878: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyYouneedaccesstoLCSandtheVisualStudioOnlinesitefromwhichthebuildsareexecutedandstored.

Page 879: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 880: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

ToapplytheupdatestotheSandbox-StandardAcceptanceTestenvironment,followthesesteps:

1. NavigatetoyourVisualStudioonlineprojectandlocatethelatestbuild.2. Withinthebuilddetails,clickonArtifactsanddownloadthefilestartingwithAXDeployableRuntime.3. Thesizeofthefilevaries;onceitincludeshotfixestothebaseapplication,itcaninflatetoover600

MB.4. Next,downloadthebinaryupdatestoafolder;thisshouldbetakenfromthedevelopmentserverthat

startedthisprocessinordertoensurethattheupdatesarethesameasappliedtodeveloperandbuildservers.

5. Openhttps://lcs.dynamics.com/.6. OpenAssetlibraryfromthe"burger".7. SelectSoftwaredeployablepackage.8. IfanyaremarkedReleasecandidate,selectthemandclickonNotreleasecandidate.9. Clickonthe+icon.10. NamethepackageasBuild20170306.11. Enterthebuildnumberandversioninthedescription,andselectAOTdeployablepackageasPackage

type.12. Uploadthefile.13. Repeatthisforthebinaryupdate,forexample,Binary20170306.14. SelectbothupdatesandclickonMerge.UsetheMerge20170306pattern,andclickonConfirm.15. Clickontheprojectnameatthetoptoreturntotheprojectdetailspage.16. ClickonFulldetailsontheSandbox:StandardAcceptanceTestoption.17. ClickonMaintainandthenApplyupdates.Selectthemergedpackagefromthelistandclickon

Apply.ThisstartsimmediatelyandallOperationsserviceswillbestopped.YoucanuseMessageonlineuserstowarntheminadvanceofthis.

Page 881: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 882: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Althoughyoucantechnicallyusethecommand-linemethodforthisbyconnectingtotheserverthrougharemotedesktop,thesandboxenvironmentsinvolvedmorethanoneserver.TheLCSmethodappliestheupdatetoallservers.

Thismethoddoestakelonger(severalhoursatpresent)toprocess,sothisshouldbetimedwiththatinmind.

Theactualtechnicalprocessisthesameasthebinaryupdateweusedwhenapplyingtheupdatestothedevelopmentserver.

Page 883: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 884: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...Sometimes,thiscanfail.Itisusuallyafilepermissionissue.LCSfirstdownloadstheupdatetoafolderoneachserver,andthenexecutestherunbookaswedidwhenweservicedthebuildserver.

ThisisusuallyinF:\DeployablePackages.

Insidethefolder,thereisafoldercalledRunBookWorkingFolder;ifyounavigatedownthroughthefolders,finallyexpandingAOSService,youwillseealistofnumberedfolders.

Inthesefoldersarethelogsproducedbytheupdate.Thefailure,ifany,willbeinthelastfolder.Youcanopenthelogsandsearchfortheerrororfailkeywordstohelpdiagnosetheproblem.

Ifitisafileaccessissue,itwasprobablylockedbyaprocess.Locatethefileandsimplyrenameit.Checkthatthefilewascorrectlycreatedoncetheupdatecompletes.

Page 885: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 886: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Servicingproduction

Inordertoapplyanyupdatetolive,thepackagemustfirstbedeployedtotheStandardAcceptanceTestenvironment(LCSenforcesthisrule).Ifthenewupdatepassesuseracceptancetesting,gobacktotheAssetlibraryandlocatethemergedpackage.SelectitandclickonReleasecandidate.

Then,usethesameprocessaswedidforthesandboxservertoapplytheupdate.Thedifferenceinthiscaseisthatitcanbescheduled.SincethisrequiresMicrosoft'sDSE(ateamofengineersthathelpmaintainthecloudenvironments)tobeoncallforthis,theyneednoticewhichiscurrently8hours.Theupdatewindowis5hours,anditwilltake5hours.Itcantakelonger,soplanthiscarefullywiththecustomer.

Page 887: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 888: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

SeealsoUpgradeDynamics365forOperationstothelatestplatformupdate(https://docs.microsoft.com/en-us/dynamics365/operations/dev-itpro/migration-upgrade/upgrade-latest-platform-update)Processforupgradingasandboxenvironment(https://docs.microsoft.com/en-us/dynamics365/operations/dev-itpro/migration-upgrade/upgrade-sandbox-environment)

Page 889: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 890: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

WorkflowDevelopmentInthischapter,wewillcoverthefollowingrecipes:

CreatingaworkflowtypeCreatingaworkflowapprovalCreatingamanualworkflowtaskHookingupworkflowtotheuserinterfaceCreatingasampleworkflowdesign

Page 891: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 892: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

IntroductionWorkflowinMicrosoftDynamics365forOperationshavetwomaintypesofelement,approvalsandtasks,centeredonadocument.Thisisthecenteroftheworkflowwheretasksaretriggeredbasedonwhattheuserdecides.Byadocument,itmeansarecordwithaformthatmaintainsit.Forexample,aNewcustomercreationworkflowwouldbebasedonthecustomertableusingthecustomerdetailsformasthedocument.

Theworkflowdesignercanthenuseconditionsbasedonfieldsanddisplaymethodsonthetablesusedintheworkflowinordertodecidewhathappens.Thissolvesmanyrequirementswhereagreatdealofconfigurabilityisrequired,butcanalsobemisunderstoodandusedinappropriately.ThesubmissionofaworkflowisusuallystartedwiththeuserpressingaSubmitbuttonontheform,whichisthenprocessedwithinaminutebythebatchserver.Theminimumtimeitcantakeforaworkflowtocomplete,iftheconditionsforautomaticcompletionaremet,isthreeminutes:uptooneminuteforsubmission,oneminuteforevaluation,andoneminutepersubsequentworkflowstep.This,therefore,can'tbeusedwhentheuserisexpectedfeedbackaspartofthedataentry.

Inthischapter,theworkflowdesignistocontroltheapprovalofanewvehicle,includingatasktoinspectthevehicleaspartoftheworkflow.

Page 893: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 894: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingaworkflowtypeTheworkflowtypecanbeconsideredatemplateoradocumentdefinition.Theworkflowtypeactslikeanumbrellafortheassociateworkflowelements,suchasapprovalsandtasks.Whenthedesignerstartstodesignaworkflow,theyactuallyselectaworkflowtype,andthedesignsurfacewillallowthemtoselectworkflowelementswehaveassociatedwithit.

Wecreatetheworkflowtypefirstbecausetheworkflowtypecreationtoolingwillcreateelementsusedinotherworkflowelements.Wejustcomebacktothisinordertoaddthemtothelistofsupportedtypesbytheworkflowtype.

Becarefulwiththenaming,asthisprocessautomaticallycreatesmenuitemsandclasses.Theyareallprefixedwiththeworkflowtype'sname.Ifwehaveaclassthatexistsalready,itwilladda1tothename,whichisunpleasant.Forthisreason,themaximumlengthis20charactersforallworkflowtypes,approvals,tasks,andautomatedtasks.

WewillcreateaBaseEnumtopersisttheworkflowstatus.Inthisrecipe,wewillonlyuseoneofthestatusfieldstohandlethestartedandcanceledevents.TheBaseEnumwasdesignedwiththewholeworkflowinmind,whichincludeshandlingthestatusoftheworkflowapproval.

Page 895: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 896: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyThisrecipecanapplytoanyrecordthathasamainformmanagingitsdata--thesearetypicallymainorworksheettabletypes.Inthisexample,wewillusethevehicletable.

Page 897: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 898: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreatetheworkflowtype,followthesesteps:

1. Ifwearecreatingaworkflowforanewmodule,oronethatcurrentlydoesn'thaveaworkflow,weneedtocreateanewworkflowcategory,whichmeansweneedtoaddamoduletoModuleAxapta.LocatetheModuleAxaptaBaseEnumandchooseCreateextensionfromtheright-clickcontextmenu.Renameasusual;changethe.extensionoraddaprefixtomakeitunique.

2. Tocreatetheworkflowcategory,weneedtoaddanewitemtoourprojectandchooseWorkflowCategoryfromtheBusinessprocessandWorkflowlist.EnterthenameConWHSVehicleManagementbeforeclickingonAdd.

3. CompletetheLabelandHelpTextproperties;thesewillbevisibletotheworkflowdesignerintheuserinterface.SettheModulepropertytothemodulename,whichisConWHSinourcase.

4. Beforewecreatetheactualworkflowtypeelement,weneedtocreatesomebaseelementsrequiredbytheworkflowapprovalelement.First,createanewquerycalledConWHSVehWF.Tothisquery,addtheConWHSVehicleTabletable,andsettheDynamicsFieldspropertytoYes.

5. Wecannowgoandgetintocreatingtheworkflowtype,choosetoaddanewitemtotheproject,andselectWorkflowTypefromtheBusinessprocessandWorkflowlist.NamethenewelementConWHSVehWFandclickonAdd.

6. CompletetheWorkflowTypedialogasshowninthefollowingscreenshot:

7. ClickonNext.8. Youwillseetheelementsthatwillbecreated;checkthatnonearesuffixedwith1(whichmeansthat

thepreferredelementnamealreadyexists),andclickonFinish.9. Sinceweusedaprefix,locatingtheelementswillberelativelyeasyforallmenuitems.Complete

theLabelandHelpTextproperties,creatinglabelsasrequiredusingnamedlabelidentifiers.10. Next,opentheConWHSVehWFDocumentclass,theworkflowdocumentclass,andalterasshownbythe

followingcode:

///<summary>

///TheConWHSVehWFDocumentworkflowdocumentclass.

///</summary>

[WorkflowDocIsQueueEnabledAttribute(true,

"@ConWHS:VehicleApproval")]

classConWHSVehWFDocumentextendsWorkflowDocument

{

///<summary>

///Returnsquerynamefortheworkflowdocument.

///</summary>

///<returns>

///Nameofthequery<c>ConWHSVehWF</c>.

///</returns>

publicqueryNamegetQueryName()

{

returnquerystr(ConWHSVehWF);

Page 899: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

}

///<summary>

///Providesdayssinceacquiredonworkflow

///conditioneditorinworkflowconfigurationform.

///</summary>

///<paramname="_companyId">

///Thecompanyonwhichtheworkflowisrunning.

///</param>

///<paramname="_tableId">

///ThetableIDofthetablewhichisassociated

///withtheworkflow.

///</param>

///<paramname="_recId">

///TherecordIDofthetablewhichisassociated

///withtheworkflow.

///</param>

///<returns>

///Thedayssincethevehiclewasacquired.

///</returns>

publicDaysparmDaysSinceAcquired(

CompanyId_companyId,

tableId_tableId,

recId_recId)

{

Daysdays;

if(_tableId==tableNum(ConWHSVehicleTable))

{

ConWHSVehicleTablevehicle;

selectcrosscompanyAcquiredDate

fromvehicle

wherevehicle.DataAreaId==_companyId

&&vehicle.RecId==_recId;

days=vehicle.AcquiredDate-systemDateGet();

}

returndays;

}

}

Weaddedtheparmmethodasanexampleofhowtoadddisplaymethodsthatcanbeusedintheworkflowexpressionbuilderandasaplaceholderintheworkflowtextpresentedtotheuser.

11. TherewillbetwonewActionMenuItemscreated,prefixedwiththeapprovalname,ConWHSVehWF.ThesearesuffixedwithCancelMenuItem,andSubmitMenuItem.Foreachofthese,settheLabelandHelpTextpropertieswithasuitablelabel,forexample:

Menuitem Label HelpText

CancelMenuItem Cancel Cancelthevehicleworkflow

SubmitMenuItem Submit Submitvehicletoworkflow

Createthelabelsusingnamesandnotnumbers,aswewillreusetheselabelsinotherelements.

12. Weneedtohandlethestatechange,sofirstweneedtocreateaBaseEnumname,ConWHSVehApprStatus,withthefollowingelements:

Page 900: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Element Label Description

Draft @SYS75939 Draft--theworkflowhasnotyetbeensubmittedtoworkflow

Waiting Waiting Theworkflowhasbeensubmitted,buthasnotyetbeenallocatedanapprover

Inspection Inspection Thevehicleisbeinginspected

InReview Inreview Theworkflowhasbeenallocatedoneormoreapprovers

Approved Approved Theworkflowhasbeenapproved

Rejected Rejected Theworkflowwasrejectedbytheapprovers

Revise Revised Achangewasrequestedbyanapprover

Theselabelsshouldbecreatedusingnamedlabelidentifiers,aswedidforthemenuitemsaswewillreusethemonotherelements.

13. Usethe@SYS101302label(ApprovalStatus)intheBaseEnum'sLabelproperty.AddthenewBaseEnumtotheConWHSVehicleTabletableasStatus,andaddittotheOverviewfieldgroup.Makethefieldread-only.

14. OpentheConWHSVehWFworkflowtypeandcompletetheLabelandHelpTextfields.Thesearetoassisttheworkflowdesignerinselectingthecorrectworkflowtypewhencreatinganewworkflow.

15. Next,createaclasscalledConWHSVehicleStatusHandler,andwritethefollowingpieceofcode:

classConWHSVehicleStatusHandler

{

///<summary>

///Setsthevehicle'sapproval

///status

///</summary>

///<paramname="_vehicleRecId">

///Thevehiclerecordid

///</param>

///<paramname="_status">

///Thenewstatus

///</param>

publicstaticvoidSetStatus(RefRecId_vehicleRecId,

ConWHSVehApprStatus_status)

{

ConWHSVehicleTablevehicle;

ttsbegin;

selectforupdatevehicle

wherevehicle.RecId==_vehicleRecId;

Page 901: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

if(vehicle.RecId!=0

&&vehicle.Status!=_status)

{

vehicle.Status=_status;

vehicle.update();

}

ttscommit;

}

}

Createcommonselectstatements,suchastheprecedingone,asastaticFindmethod,forexample,FindByRecId.Whenwritinganyselectstatement,checkthatthetablehasasuitableindex.

16. Saveandclosetheclass.17. CreateanewclassnamedConWHSVehWFBaseandaddthefollowingmethod:

publicbooleanValidateContext(WorkflowContext_context)

{

If(_context.parmTableId()!=

tableNum(ConWHSVehicleTable))

{

//Workflowmustbebasedonthevehicletable

throwerror("@ConWHS:ConWHS72");

}

ConWHSVehicleTablevehicle;

selectRecIdfromvehicle

wherevehicle.RecId==_context.parmRecId();

if(vehicle.RecId==0)

{

//Vehiclecannotbefoundfortheworkflowinstance

throwerror("@ConWHS:ConWHS73");

}

returntrue;

}

Wethrowanerrorincaseofavalidationerror,asweneedtheworkflowtostopwithanerrorshoulditfail;theworkflowcannotcontinuewithaninvalidcontext.

18. Next,addamethodsothattheworkflowtype'shandlerclassknowswhetherornotthesupportedelementsactuallyran.Sincethatfinalresultwillbeapprovedorrejected,wecan'tusethesamefieldtostatethattheworkflowiscompleted.Infact,iftheworkflowtypecompleted,butnothingwasdone,thedocumentshouldresetbacktoDraft(notsubmitted).Writethemethodasfollows:

publicbooleanCanCompleteWF(WorkflowContext_context)

{

ConWHSVehicleTablevehicle;

selectRecIdfromvehicle

wherevehicle.RecId==_context.parmRecId();

if(vehicle.RecId!=0)

{

//Codetocheckiftheworkflowcanbecompleted,

//i.e.nothinginprogress

returntrue;

}

returntrue;

}

19. Saveandclosethisclass,andopentheConWHSVehWFEventHandlerclassandaltertheclassdeclarationsothatitextendsConWHSVehWFBase.

20. Addthefollowingmethodsinordertohandletheworkflowtype'sevents:

publicvoidstarted(WorkflowEventArgs_workflowEventArgs)

Page 902: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

{

WorkflowContextcontext;

context=_workflowEventArgs.parmWorkflowContext();

if(this.ValidateContext(context))

{

ConWHSVehicleStatusHandler::SetStatus(

context.parmRecId(),

ConWHSVehApprStatus::Waiting);

}

}

publicvoidcanceled(WorkflowEventArgs_workflowEventArgs)

{

WorkflowContextcontext;

context=_workflowEventArgs.parmWorkflowContext();

if(this.ValidateContext(context))

{

ConWHSVehicleStatusHandler::SetStatus(

context.parmRecId(),

ConWHSVehApprStatus::Draft);

}

}

publicvoidcompleted(WorkflowEventArgs_workflowEventArgs)

{

WorkflowContextcontext;

context=_workflowEventArgs.parmWorkflowContext();

if(this.ValidateContext(context))

{

If(!this.CanCompleteWF(context))

{

ConWHSVehicleStatusHandler::SetStatus(

context.parmRecId(),

ConWHSVehApprStatus::Draft);

}

}

}

ThestatuschangesherearethatwemovefromDrafttoWaitingwhentheworkflowenginestarts,andbacktoDraftifcanceled.Shouldtheworkflowcomplete,butfailtheCanCompleteWFcheck,resetitbacktoDraft.

21. Finally,opentheConWHSVehWFSubmitManagerclassandcompletethemainmethod,asshownhere:

publicstaticvoidmain(Args_args)

{

RefRecIdrecId;

CompanyIdcompanyId;

RefTableIdtableId;

WorkflowCommentcomment;

WorkflowSubmitDialogdialog;

WorkflowVersionTableversion;

recId=_args.record().RecId;

tableId=_args.record().TableId;

companyId=_args.record().DataAreaId;

//Themethodhasnotbeencalledcorrectly.

if(tableId!=tablenum(ConWHSVehicleTable)

||recId==0)

{

throwerror(strfmt("@SYS19306",funcname()));

}

version=

_args.caller().getActiveWorkflowConfiguration();

dialog=WorkflowSubmitDialog::construct(version);

dialog.run();

if(dialog.parmIsClosedOK())

{

comment=dialog.parmWorkflowComment();

Workflow::activateFromWorkflowConfigurationId(

Page 903: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

version.ConfigurationId,

recId,

comment,

NoYes::No);

}

//SettheworkflowstatustoSubmitted.

ConWHSVehicleStatusHandler::SetStatus(

_args.record().RecId,

ConWHSVehApprStatus::Waiting);

if(FormDataUtil::isFormDataSource(_args.record()))

{

FormDataUtil::getFormDataSource(

_args.record()).research(true);

}

_args.caller().updateWorkflowControls();

}

22. OpentheConWHSVehSubmitMenuItemmenuitemandchangetheObjectpropertytoConWHSVehWFSubmitManager.23. Closeallcodeeditorsanddesignersandbuildtheproject.Thecompilerwillhighlightcodewe

forgottohandlebyshowingtheTODOcommentsaswarnings.

Page 904: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 905: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Theworkflowtyperequiredafewelementsbeforewecreatedtheactualworkflowtype.Thedocumentisdefinedbyaquery,whichhasamaintable.Thiscouldbeaqueryofsalesordersandsalesorderlines,wherethesalesorderisthemaintable,andletstheworkflowdesignerusefieldsfromthequerytodefinemessagestotheuser,andalsocontrolhowtheworkflowbehaves.Theworkflowhasspecialapplicationelementtypesforworkflow,whichpointtoclassesthatimplementspecificinterfaces.

Theworkflowtypeisahigherlevelthantheworkflowelements.Workflowelementsarethetasksassignedtotheuser,andtheyhandlestatessuchasReview,Reject,Approve,andsoon.Theworkflowtypeisatahigherlevel,andcontrolswhethertheworkflowisstarted,cancelled,orcompleted.

Itmayseemoddthatwedon'tmaptheworkfloweventtypesdirectlytotheBaseEnumelements.Theworkflowenginedoesn'treadthisfield;itknowswithinitselfthestatusoftheworkflow.Thestatusfieldistoallowustoeasilyreadthestatusoractonaparticularworkflowevent.Forthisreason,wedon'tactuallyneedtohandlealloftheeventsthattheworkflowprovides.

TheConWHSVehWFEventHandlerclasswastiedtotheworkflowtype,andisusedtopersisttheworkflow'sstateinthetargetdocumentrecord--thevehiclerecord,inourcase.

Theparmmethodontheworkflowdocumentclass,ConWHSVehWFDocument,addsacalculatedmemberthatcanbeusedbytheworkflowdesignertoeithermakedecisionsintheworkflowdesign,ordisplayedinmessagestotheusers.Theparmmethodshavetobewrittenwiththesameinputparametersasshownintheexamplemethod,andwearefreetowriteanycodewelike,andreturndataofanybasetypethatcanbeconvertedtoastring,suchasstrings,dates,andBaseEnum.Wecannot,therefore,returntypessuchasrecords,objects,orcontainers.Considerhowthemethodwillperform,asthiswillberunwheneveritneedstobeevaluatedbytheworkflowengine.

Page 906: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 907: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Seealso...Checkoutthefollowinglinksforhelpsettingupworkflowsandforfurtherreading:

Workflowsystemarchitecture(https://docs.microsoft.com/en-us/dynamics365/operations/organization-administration/workflow-system-architecture)Creatingaworkflow(https://docs.microsoft.com/en-us/dynamics365/operations/organization-administration/create-workflow)Overviewoftheworkflowsystem(https://docs.microsoft.com/en-us/dynamics365/operations/organization-administration/overview-workflow-systemandhttps://ax.help.dynamics.com/en/wiki/overview-of-the-workflow-system/)

Workflowelements(https://docs.microsoft.com/en-us/dynamics365/operations/organization-administration/workflow-elements)

Page 908: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 909: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingaworkflowapprovalAworkflowapprovalisanelementthatallowsapprovaltaskstoberouted,whichcanbeapprovedorrejected.Thedesigncanthenusethisoutcomeinordertotriggertasks,orsimplyinformtheuser.Theworkflowapprovalstatusispersistedasafieldonthedocumentrecord(thatis,thevehiclerecordinourcase),inthesamewaythattheworkflowtypedoes.

Asaresultofthis,thereareoftentwofieldsontheworkflow'smaintable,oneforworkflowdocumentstate,andanotherforworkflowelementstate.Insomecases,suchashumanresourceworkflows,theBaseEnumiscombinedintoonefield.Thiscanseemconfusing,butwhentheworkflowstatusfieldisproperlydefined,itsimplifiestheprocess.

Wecannotcreateextensionsforworkflowelements,sowecannotuseworkflowtypescreatedbyotherpartieswithoutcustomization(over-layering).

Page 910: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 911: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyWejustneedtohavecreatedaworkflowtype,orhaveasuitableworkflowtypetoaddtheapprovalto.

Page 912: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 913: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreateaworkflowapproval,followthesesteps:

1. AddanewitemtotheprojectbyselectingBusinessProcessandWorkflowfromtheleft-handlist,andthenWorkflowApprovalfromtheright.EnterConWHSVehApprWFastheNameandclickonAdd.

2. CompletetheWorkflowApprovaldialogasshownhere:

3. ClickonNext.4. Youwillbepresentedwithalloftheelementsthewizardwillcreateforus,remindingusagainwhy

thelimitis20charactersandalsowhythenamingisimportant.ClickonFinish.5. OpenthenewConWHSVehApprWFworkflowapproval,expandtheOutcomesnode,andnotethatthesystem

hasassociatedaworkfloweventhandlerclasswithApprove,Reject,andRequestChange.Tocompletethiselement,completetheLabelandHelpTextpropertiesontherootConWHSVehApprWFnodeelement.Theworkflowdesignerwillneedthistoidentifythecorrectworkflow.

6. TherewillbefivenewActionMenuItemscreated,prefixedwiththewithapprovalname,ConWHSVehApprWF.ThesearesuffixedwithApprove,DelegateMenuItem,Reject,RequestChange,andResubmitMenuItem.Foreachofthese,settheLabelandHelpTextpropertieswithasuitablelabel,forexample:

Menuitem Label Helptext

Approve Approve Approvethenewvehiclerequest

DelegateMenuItem Delegate Delegatethisapprovaltoacolleague

Reject Reject Rejectthenewvehiclerequest

RequestChange Revise Sendtherequestbackforrevision

ResubmitMenuItem Resubmit Resubmitthenewvehiclerequest

Createthelabelsusingnamesandnotnumbers,aswewillreusetheselabelsinotherareas.

Page 914: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Aswellasmenuitems,italsocreatedaneventhandlerclass,whichisnamedbasedontheworkflowapproval,suffixedwithEventHandler.Thisclasswillimplementseveninterfaces,whichenforcethatamethodisimplemented,onepereventtype.

7. Opentheworkeventhandlerclass,ConWHSVehApprWFEventHandler,andaltertheclassdeclarationsothatitextendsConWHSVehWFBase.

8. ThisclassimplementstheWorkflowElementDeniedEventHandlerinterface,eventhoughwechosenottointhecreationdialog;removethisfromthelist.

9. Then,locatethedeniedmethodanddeleteit.10. WenowneedtowritesomecodeforeachmethodthatwasgeneratedforuswithaTODO.Thesample

codetowriteforeachmethodisasfollows:

publicvoidstarted(WorkflowElementEventArgs_workflowElementEventArgs)

{

WorkflowContextcontext;

context=

_workflowElementEventArgs.parmWorkflowContext();

if(this.ValidateContext(context))

{

ConWHSVehicleStatusHandler::SetStatus(

context.parmRecId(),

ConWHSVehApprStatus::InReview);

}

}

11. Followthispatternforeachmethodusingthefollowingtabletodeterminewhichstatustoset:

Element Method

Waiting started

InReview created

Approved completed

Rejected returned

Revise changeRequested

Draft cancelled

12. Forthecreatedmethod,theinputparameterisadifferenttype;simplychangethemethodasfollows:

publicvoidcreated(WorkflowWorkItemsEventArgs_workflowWorkItemsEventArgs)

Page 915: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

{

WorkflowContextcontext;

WorkflowElementEventArgsworkflowArgs;

workflowArgs=

_workflowWorkItemsEventArgs.parmWorkflowElementEventArgs();

context=workflowArgs.parmWorkflowContext();

if(this.ValidateContext(context))

{

ConWHSVehicleStatusHandler::SetStatus(

context.parmRecId(),

ConWHSVehApprStatus::InReview);

}

}

13. Inthepreviousrecipe,wewroteamethodtodetermineiftheworkflowdidanythingthatwasusedtoresettheworkflowshouldnothinghavebeendonewhentheworkflowtypecompleted.OpentheConWHSVehWFBaseclassandalterthemethodasfollows:

publicbooleanCanCompleteWF(WorkflowContext_context)

{

ConWHSVehicleTablevehicle;

selectRecIdfromvehicle

wherevehicle.RecId==_context.parmRecId();

booleancanComplete;

if(vehicle.RecId!=0)

{

switch(vehicle.Status)

{

caseConWHSVehApprStatus::Approved:

caseConWHSVehApprStatus::Rejected:

canComplete=true;

default:

canComplete=false;

}

}

returncanComplete;

}

14. Thefinalpieceofcodetowriteistheresubmissioncode.Atemplatewascreatedforus,soopentheConWHSVehAppWFResubmitActionMgrclass.

15. Inthemainmethod,removetheTODOcommentandwritethefollowingcodesnippet:

publicstaticvoidmain(Args_args)

{

//Themethodhasnotbeencalledcorrectly.

if(_args.record().TableId!=

tablenum(ConWHSVehicleTable)

||_args.record().RecId==0)

{

throwerror(strfmt("@SYS19306",funcname()));

}

//Resubmitthesameworkflow,Workflowhandles

//resubmitaction

WorkflowWorkItemActionManager::main(_args);

//SettheworkflowstatustoSubmitted.

ConWHSVehicleStatusHandler::SetStatus(

_args.record().RecId,

ConWHSVehApprStatus::Waiting);

_args.caller().updateWorkflowControls();

}

16. OpentheConWHSVehApprWFworkflowapproval,selecttheDenyoutcome,andchangetheEnabledpropertytoNo.

17. Finally,opentheworkflowtypeandthenright-clickonSupportedElementsnode.SelectNewWorkflowElementReferenceandsetthepropertiesasfollows:

Page 916: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Field EDT/Enum Description

ElementName ConWHSVehApprWF Thisistheelement'sname

Name ApprovalVehicle Thisisashortversionofthename,prefixedwiththetype

Type Approval Thisistheworkflowelement'stype

18. Saveandcloseallcodeeditorsanddesignersandbuildtheproject.Don'tforgettosynchronize,aswehaveaddedanewfield.

Page 917: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 918: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Theworkflowapprovalissetupwithoutcomes,whicharereferencedtoaneventhandlerclassthatimplementsaninterfaceforeachoutcomeithandles.Eachoutcomeistied,internally,tothatinterface.Whentheoutcomeoccurs,itwillconstructthereferencedeventhandlerclassusingtheinterfaceasthetype.Itthencallstheappropriatemethod.Thispatternofinstantiatingaclassusingtheinterfaceasthetypeiscommonpattern,andwehaveusedthisourselvesinChapter10,ExtensibilityThroughMetadataandDataDate-Effectiveness.

Therearesomeevents(StartedandCancelled,forexample)thataresetontheworkapproval'smainpropertysheet.Allthiswascreatedforuswhenwecreatedtheworkflowapprovalelement.

TheclassthatthecodegeneratedforusimplementsallrequiredinterfaceswithTODOstatementswhereweneedtowritecode.Thecodeisusuallysimple,and,inourcase,wearejustupdatingthevehicle'sstatusfield.Thegeneratedcodewillalwaysimplementallinterfacesthattheworkflowelementcansupport,soitiscommontoremovemethodsandinterfacesfromtheeventhandlerclass.

Page 919: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 920: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Creatingamanualworkflowtask

Amanualtaskisataskthatisassignedtoauserinordertoperformanaction.Theactioncanbeanytask,suchasInspectvehicle,andtheuserwillthenstatethatthetaskwascomplete.

Thisworkflowwillbeusedtoinstructthevehicletobeinspected,andrecordwhetheritwasinspectedinanewfieldonthevehicletable.

Page 921: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 922: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyThisfollowsfromtheCreatingaWorkflowTyperecipe,asweneedaworkflowdocumentclass.

Page 923: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 924: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreatethemanualworkflowtask,followthesesteps:

1. WeneedanewBaseEnumfortheinspectionstatus,asthiswillbeusedbothtoseewhetheravehiclehasbeeninspectedandalsotocontrolthestateoftheworkflowtask;nameitConWHSVehInspStatusandcreatetheelementsasshowninthefollowingtable:

Element Label Description

NotInspected Notinspected Thisvehiclehasnotyetbeeninspected

Waiting Waiting Thisworkflowhasbeensubmitted,buthasnotyetbeenallocatedanapprover

InProgress InProgress Thisworkflowhasbeenallocatedtooneormoreworkerstoperformthetask

Completed Completed Thisworkflowhasbeencompleted

2. CreateanewDateEDTforConWHSVehInspDate,settingthepropertiesasfollows:

Field EDT/Enum Description

Extends TransDate ThisEDTshouldbeusedforalldates.

Label Dateinspected Createanamedlabelforthis,suchas@ConWHS:DateInspected.

HelpText

Thedatetheinspectionwascarriedout

ThisisleftgenericandnottiedtoitseventualimplementationinordertomaketheEDTreusable.Thehelptextdoesnotreferencethevehicleforthisreason.

3. AddthefollowingfieldstothevehicletableandsettheAllowEditandAllowEditOnCreatetoNo:

Field EDT/Enum Description

Page 925: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

InspStatus ConWHSVehInspState ThisisthestatusBaseEnumcreatedinthepreviousstep

InspComment WorkflowComment Thiswillholdthelastnotewhenthetaskiscompleted

InspDate ConWHSVehInspDate Thisisthedateonwhichtheworkflowtaskwascompleted

4. CreateafieldgroupnamedInspectionandsettheLabelpropertytoalabelforInspection.AddthefieldstothisgroupandthenaddthefieldgrouptoasuitableplaceintheConWHSVehicleTableform.

5. Next,let'saddastatushandlerclass;createanewclassname,ConWHSVehicleInspStatusHandler.Createamethodtohandlethestatuschange,andsettheInspCommentandInspDatefieldsfromthemethod'sparameters.Thecodeiswrittenasfollows:

///<summary>

///Handletheinspectiondatechange

///</summary>

///<paramname="_vehicleRecId">

///Therecordidofthevehicle

///</param>

///<paramname="_status">

///Thenewstatus

///</param>

///<paramname="_comment">

///Commentissetwhenthestatus

///iscomplete

///</param>

///<paramname="_inspDate">

///InspDateissetwhenthe

///statusiscomplete

///</param>

publicstaticvoidSetStatus(RefRecId_vehicleRecId,

ConWHSVehInspStatus_status,

WorkflowComment_comment='',

ConWHSVehInspDate_inspDate=dateNull())

{

ConWHSVehicleTablevehicle;

ttsbegin;

selectforupdatevehicle

wherevehicle.RecId==_vehicleRecId;

if(vehicle.RecId!=0)

{

vehicle.InspStatus=_status;

//iftheinspectioniscompleteset

//thecommentandinspectiondatefields

//otherwiseclearthem,astheworkflow

//mayhavebeencancelled.

switch(_status)

{

caseConWHSVehInspStatus::Complete:

vehicle.InspComment=_comment;

vehicle.InspDate=_inspDate;

break;

default:

vehicle.InspComment='';

vehicle.InspDate=dateNull();

}

vehicle.update();

}

ttscommit;

}

6. Againsttheproject,addanewitemandchooseWorkflowTaskfromtheBusinessprocessandWorkflowlist.UsetheConWHSVehWFInspectnameandclickonAdd.

Page 926: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

7. ConfiguretheWorkflowTaskdialogasshowninthefollowingscreenshot:

8. ClickonNext.9. Onthenextpage,chooseCompleteintheTypedrop-downlist,andenterCompleteinthefieldbefore

clickingonAdd.

Youcanaddfurtheroutcomes,whichwillfollowthesamepatternwhenimplemented.

10. ClickonNextandthenFinish.11. Foreachactionmenuitemcreatedbythewizard,completetheLabelandHelpTextproperties.

YoumayrecognizethatthecodegeneratedbythisprocessisverysimilartotheWorkflowapproval.WewillfollowthatpatternagainbyhandlingtherequiredmethodsintheConWHSVehWFInspectEventHandlerclass.

12. Sincewedon'thandleallofthepossibleoutcomes,weshouldonlyimplementtherequiredinterfaces.Also,inordertohaveaccesstotheValidateContextmethod,weshouldextendConWHSVehWFBase.Theclassdeclarationshouldreadasshownhere:

publicfinalclassConWHSVehWFInspectEventHandler

extendsConWHSVehWFBase

implementsWorkflowElementCanceledEventHandler,

WorkflowElementCompletedEventHandler,

WorkflowElementStartedEventHandler,

WorkflowWorkItemsCreatedEventHandler

13. Also,removethemethodslinkedtotheinterfacethatweremoved.Changethestartedmethodasshownhere.Itmaintainsthevehiclestatusandinspectionstatusfields:

publicvoidstarted(

WorkflowElementEventArgs_workflowElementEventArgs)

{

WorkflowContextcontext;

context=

_workflowElementEventArgs.parmWorkflowContext();

if(this.ValidateContext(context))

{

ConWHSVehicleInspStatusHandler::SetStatus(

context.parmRecId(),

ConWHSVehInspStatus::Waiting);

ConWHSVehicleStatusHandler::SetStatus(

context.parmRecId(),

ConWHSVehApprStatus::Inspection);

}

}

14. Thecanceledmethodshouldresetbothstatusfieldsbacktotheirinitialstates:

Page 927: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

publicvoidcanceled(

WorkflowElementEventArgs_workflowElementEventArgs)

{

WorkflowContextcontext;

context=

_workflowElementEventArgs.parmWorkflowContext();

if(this.ValidateContext(context))

{

ConWHSVehicleInspStatusHandler::SetStatus(

context.parmRecId(),

ConWHSVehInspStatus::NotInspected);

ConWHSVehicleStatusHandler::SetStatus(

context.parmRecId(),

ConWHSVehApprStatus::Draft);

}

}

15. Thecompletedmethodneedstogetthecurrentsystemdate,andalsofetchthelastcommentfromtheworkflow.Thisisdoneinthefollowingcode:

publicvoidcompleted(WorkflowElementEventArgs_workflowElementEventArgs)

{

WorkflowContextcontext;

context=

_workflowElementEventArgs.parmWorkflowContext();

WorkflowCorrelationIdcorrelationId;

correlationId=context.parmWorkflowCorrelationId();

WorkflowTrackingTabletrackingTable;

trackingTable=

Workflow::findLastWorkflowTrackingRecord(

correlationId);

WorkflowTrackingCommentTablecommentTable;

commentTable=trackingTable.commentTable();

WorkflowCommentcomment=commentTable.Comment;

Timezonetimezone=

DateTimeUtil::getUserPreferredTimeZone();

if(this.ValidateContext(context))

{

ConWHSVehicleInspStatusHandler::SetStatus(

context.parmRecId(),

ConWHSVehInspStatus::Complete,

comment,

DateTimeUtil::getSystemDate(timezone));

}

}

16. Finally,writethecreatedmethod.Thisiswhenthetaskisassignedtooneormoreusers.Thecodeshouldbewrittenasfollows:

publicvoidcreated(

WorkflowWorkItemsEventArgs_workflowWorkItemsEventArgs)

{

WorkflowContextcontext;

WorkflowElementEventArgsworkflowArgs;

workflowArgs=_workflowWorkItemsEventArgs.

parmWorkflowElementEventArgs();

context=workflowArgs.parmWorkflowContext();

if(this.ValidateContext(context))

{

ConWHSVehicleInspStatusHandler::SetStatus(

context.parmRecId(),

ConWHSVehInspStatus::InProgress);

ConWHSVehicleStatusHandler::SetStatus(

context.parmRecId(),

ConWHSVehApprStatus::Inspection);

}

}

Page 928: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

17. WeshouldalsoupdatetheCanCompletemethodontheConWHSVehWFBaseclass,butwhatwedohereisdependentonwhatwewanttocontrol.Weareindangerofhardcodingabusinessrule,whichisironicallywhatworkflowsaredesignedtoavoid.Asaresultofthis,wejustwanttoensurethatthedocument(vehiclerecord)isalwaysleftinaconsistentstatewhentheworkflowtypecompletes.Thefollowingpieceofcodewillonlyreturnfalseifeithertheapprovalortaskisinprogress:

publicbooleanCanCompleteWF(WorkflowContext_context)

{

ConWHSVehicleTablevehicle;

selectRecIdfromvehicle

wherevehicle.RecId==_context.parmRecId();

booleancanComplete=true;

if(vehicle.RecId!=0)

{

switch(vehicle.Status)

{

caseConWHSVehApprStatus::Revise:

caseConWHSVehApprStatus::Waiting:

caseConWHSVehApprStatus::InReview:

caseConWHSVehApprStatus::Inspection:

canComplete=false;

default:

canComplete=true;

}

switch(vehicle.InspStatus)

{

caseConWHSVehInspStatus::InProgress:

caseConWHSVehInspStatus::Waiting:

canComplete=false;

}

}

returncanComplete;

}

18. Next,completetheConWHSVehApprWFResubmitActionMgrclassasfollows:

publicstaticvoidmain(Args_args)

{

//Themethodhasnotbeencalledcorrectly.

if(_args.record().TableId!=

tablenum(ConWHSVehicleTable)

||_args.record().RecId==0)

{

throwerror(strfmt("@SYS19306",funcname()));

}

//Resubmitthesameworkflow,Workflowhandlesresubmitaction

WorkflowWorkItemActionManager::main(_args);

//SettheworkflowstatustoSubmitted.

ConWHSVehicleInspStatusHandler::SetStatus(

_args.record().RecId,

ConWHSVehInspStatus::Waiting);

_args.caller().updateWorkflowControls();

}

19. Finally,opentheworkflowtypeandthenright-clickontheSupportedElementsnode.SelectNewWorkflowElementReferenceandsetthepropertiesasfollows:

Field EDT/Enum Description

ElementName ConWHSVehWFInspect Thisistheelement'sname

Page 929: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Name TaskInspect Thisisashortversionofthename,prefixedwiththetype

Type Task Thisistheworkflowelement'stype

20. CopyandpastethetasknameintotheElementNameandNameproperties.21. Saveandclosealldesignersandcodeeditorsandbuildtheproject,followedbysynchronizingthe

databasewiththeproject.

Page 930: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 931: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

Theconceptisthesameasfortheworkflowapprovalinthepreviousrecipe.TheWorkflowtaskelementisadefinitionthatthedesignerwillusewhencreatingaworkflow.Thecodewewrotesimplyhandlestheeventsasweneedto.

Thecomplicatedparttounderstandisthestatushandling.Itseemsnaturaltohaveastatusfieldforeachworkflowelement(thetype,approval,andtask),andwiththisparadigm,wewouldbeleftthinkingwhythereisn'tastandardBaseEnumwecouldsimplyuse.Thestatusofthedocument,andthestatusesthedocumentcanbedefinedbyus--whatmakessensetothebusiness,andnotwhatmakessenseincode.Fortheinspectiontask,wewanttoknowifavehicleiswaitinginspection,isinprogress,orifitiscomplete.

Page 932: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 933: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

HookingupaworkflowtotheuserinterfaceThisisthefinalstepindesigningourworkflow,andinvolvessettingapropertyontheformreferencedbythedocumentmenuitem,ConWHSVehicleTable,andaddingtheoptiontodesignworkflowstothemenu.

Page 934: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 935: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyTheminimumweneedtohavecompletedforthisiscreateaworkflowtype.

Page 936: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 937: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Inordertobeabletodesignandprocessworkflows,followthesesteps:

1. ExpandUserinterface,Menuitems,andthenDisplayfromtheApplicationExplorer.LocatedWorkflowConfgurationBasic,right-clickonitandchooseDuplicateinproject.

2. LocatethenewmenuitemintheSolutionexplorer,andrenameittoConWHSWorkflowConfiguration.

Dependingontheversionofthedevelopmenttools,youwillneedtoundothechangetoWorkflowConfigurationBasicCopyandthenaddtherenamedelementtosourcecontrol.

3. OpenthenewmenuiteminthedesignerandsettheEnumParameterpropertytoConWHS,andthencreatelabelsfortheLabelandHelpTextproperties.Thelabelshouldbethemodulename,followedbythewordworkflows,Vehiclemanagementsystemworkflows,forexample.

4. AddthismenuitemtotheSetupsubmenuofourmenu.Thisshouldusuallybethesecondoptioninthelist.

5. OpentheConWHSVehicleTableforminthedesigner.

6. SelecttheDesignnodeoftheformdesignandlocatethefollowingproperties:

Property Value

WorkflowDataSource ConWHSVehicleTable

WorkflowEnabled Yes

WorkflowType ConWHSVehWF

7. Right-clickontheMethodsnodeoftheformandselectOverride|canSubmitToWorkflow.Alterthecodesothatitreadsasfollows:

publicbooleancanSubmitToWorkflow()

{

If(ConWHSVehicleTable.Status==

ConWHSVehApprStatus::Draft)

{

returntrue;

}

returnfalse;

}

Wemayneedthistobemoreelaborateinsomecases,buttheminimumisthatwecan'tallowaworkflowtobesubmittedthatisalreadyinprogress.

Page 938: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

8. Saveandclosealldesignersandbuildtheproject.

Page 939: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 940: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

TheworkflowconfigurationisagenericformthatbuildsbasedontheModuleAxaptaBaseEnum.WelinkedConWHStotheworkflowcategory,whichwasthenlinkedtotheworkflowtype.Thiswill,therefore,allowtheworkflowdesignertocreateandmodifyworkflowsforthismodule.

Theformchangesweresimplytolinktheworkflowtypetotheform,andwhichdatasourceisthedocumentdatasource.Thisisthenusedtoqueryifthereareanyactiveworkflowsforthattype,andwillshowtheoptiontosubmitthevehicleforapprovalifthereisanactivevehicleworkflowdesign.

Page 941: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 942: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Creatingasampleworkflowdesign

Let'stesttheelementswehavecreated.Thefollowingworkflowdesignisonlyintendedtotesttheworkflowwehavecreated,andomitsmanyofthefeaturesthatwewouldnormallyuse.Wewillalsousethesameuserforsubmissionandapproval,andyouwillseethatappeartobewaitingfortheworkflowengineaswetest.Thisseemsaproblematfirstglance,butinreal-lifescenarios,thisisfine.Inpractice,thetasksandapprovalsareperformedbydifferentusersandarenotdoneasaseriesoftasks.Theywillreceiveanotification,andtheycanthenperformthatactionandpasstheballbacktotheworkflowengine.

Page 943: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 944: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyBeforewestart,ensurethattheprojectisbuiltandsynchronizedwiththedatabase.

Page 945: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 946: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreatetheworkflowdesign,followthesesteps:

1. OpenthefollowingURL,andfromthere,openVehiclemanagement|Setup|Vehiclemanagementworkflows:

https://usnconeboxax1aos.cloud.onebox.dynamics.com/?cmp=usmf

YoucannavigatedirectlytotheconfigurationformusingthefollowingURL:https://usnconeboxax1aos.cloud.onebox.dynamics.com/?cmp=usmf&mi=ConWHSWorkflowConfiguration

2. ClickonNEW,andyoushouldseetheworkflowtypeinthelist,andislistedusingtheLabelandHelpTextpropertiesthatweset.

3. Selecttheworkflowtypelink,asshowninthefollowingscreenshot:

4. Youwillthenbeaskedtologin,whichyoushoulddoasthesameuseryouusedtologintoOperations.Youwillthenbepresentedwithanewwindow,withthetwoworkflowelementsthatwewroteandsomeflowcontroloptionsintheleft-handpane.

5. DragtheNewvehicleapprovalworkflowandInspectvehicleelementsontothedesignsurface,asshowninthefollowingscreenshot:

6. AsyourmousehoversovertheStartelement,youwillseesmallhandlesappear--dragoneofthesehandlessothatitconnectstheStartelementtotheInspectvehicle1element.Connectallelements,asshowninthefollowingscreenshot:

Page 947: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

7. SelecttheInspectvehicle1element,andclickonBasicSettingsfromtheactionpane.Configureitasshowninthefollowingscreenshot:

Thetextwithin%symbolswasaddedusingtheInsertplaceholderbutton;clickonthisandyouwillseethatourparmmethodwasaddedandtheparmprefixwasautomaticallyremoved.

8. ClickonAssignmentfromtheleft,andchooseUserfromtheAssignuserstothisworkflowelementlist.SelecttheUsertab,andmanuallyassignthistoAdmin.Thisisthedefaultadministrationuser.Sincewearesimplytestingourcodeworks,wewillassignalltasksandapprovalstoouruser.

9. ClickonClose.10. Double-clickontheNewvehicleapprovalworkflow1elementandclickonBasicSettingsfromthe

actionpane.ChangeNametoNewvehicleapprovalworkflowandclickonClose.

Wewouldnormallyconfigurethenotifications;forexample,wewouldusuallynotifyWorkflowOriginatoriftheapprovalwasrejected.

11. Then,selectStep1,pressBasicSettings,andconfigureasshownhere:

Page 948: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

12. ClickonAssignmentandassignthistoouruseraswedidbefore.PressClose.Thereisabreadcrumbatthetopoftheworkflowdesigner'sdesignsurface,whichshowsWorkflow|Newvehicleapproval.ClickonthewordWorkflowfromthebreadcrumb.

13. Finally,wemustgivesubmissioninstructionstothepersonwhosubmittedthevehicleapproval.Right-clickonanemptyareaoftheworkflowdesigner'sdesignsurfaceandclickonProperties.EntersuitableinstructionsintheSubmissioninstructionsfieldandclickonClose.

14. ClickonSaveandclose,andthentheOKontheSaveworkflowdialog.SelectActivethenewversionontheActivateworkflowdialogandclickonOK.

Let'snowtestiftheworkflowworks:

1. Openthevehicleform,andyoushouldseetheWorkflowbuttonasshowninthefollowingscreenshot:

2. ClickonthebuttonandselectSubmit,whichisthelabelweassignedtotheConWHSVehWFSubmitMenuItemmenuitem.EnteracommentandclickonSubmit.

3. TheoptionsshouldchangetoCancelandViewhistory.YoucanchooseViewhistorytoseetheprogressoftheworkflowengine.Ifthetasksaren'tassignedwithinaminute,checkthattheMicrosoftDynamics365forOperations-BatchManagementServiceWindowsserviceisrunning.AlsocheckthattherearebatchjobsforWorkflowmessageprocessing,Workflowduedataprocessing,andWorkflowline-itemnotifications.Ifnot,openWorkflowinfrastructureconfigurationfromSystemadministration|Workflow,andclickonOK.

4. Afterapproximatelytwominutes,havingpressedtherefreshicon,youshouldseetheComplete,Delegate,andCanceloptions.Thesearetheoptionsfortheinspectiontask.SelectComplete.EnteraninspectioncommentandclickonComplete.Viewthevehicledetailsandwaitforaboutaminute.Clickontherefreshbutton.Youshouldseeinformationsimilartothefollowing:

Page 949: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

5. Afterafurtherminute,refreshtheform,andtheoptionswillchangetotheapprovaloptions.ChooseApprovefromthelist.

Page 950: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 951: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

Theworkflowdesignhasmanyoptionsavailabletous,andisusedtotelltheworkflowenginehowtheapprovalsandtasksshouldbeprocessed.Whenwedevelopworkflows,wedosoasgenericallyaspossibleinordertoleavethebusinesslogictotheworkflowdesigner.Thismeansthatwereducetheneedforchangestothecodeasthebusinessevolves.

Thetestwastohelpdemonstratewhathappenstothestatusfieldsastheworkflowisprocessed.Thisshouldhelpusinourownworkflowdevelopmentinunderstandingthelinkbetweeneventsandstatuschanges.

Page 952: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 953: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

StateMachinesInthischapter,wewillcoverthefollowingrecipes:

CreatingastatemachineCreatingastatemachinehandlerclassUsingmenuitemstocontrolastatemachineHookingupthestatemachinetoaworkflow

Page 954: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 955: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

IntroductionStatemachinesareanewconceptinD365O,andaverywelcomefeature.Previously,thecontrolofstatusfieldswashandcraftedincode,whichcouldbeoftenhardtoreadastherewasnoobviouspatterntofollow;havingsaidthat,wewillalwayslookatasimilarstandardexampletoourcaseandusethatidea.Thisisnotplagiarism,itisgoodpractice.Itisagoodgeneralruletoseekexamplesinstandardcode,asthereisamuchhigherchancethatanotherdeveloperwillunderstandthecodewe'vewritten.

Statemachinesallowustodefineinmetadatahowthestatustransitionsfromaninitialstatetoitsfinalstate.Theserulesarethenenforcedbycodethatthestatemachinewillgenerate.

Thereisarestrictionthough.Theremustbeoneinitialstateandonefinalstate.Whenweareatthefinalstate,thereisnogoingback.Ifwetakethesalesorderstatus,wehavetwofinalstates:InvoicedandCancelled.Thereisanotherreasonwhywewouldn'tuseastatemachineonthistypeofstatus.Thesalesorderstatusisareflectionofactualorderstate;itissystemcontrolled.Statemachinesaredesignedtoenforcedstatuschangelogicwhenthestateisassertedbyauser.

Page 956: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 957: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingastatemachineThisfirstrecipeistocreateastatemachineforvehicleinspection.InChapter14,WorkflowDevelopment,wecreatedaworkflowtaskandaninspectionstatusfield.Inthisrecipe,wewilluseastatemachinetohandletheinspectionstatuschangelogic.

Page 958: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 959: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyWeneedtohaveatablewithastatusfieldwithaninitialandfinalstatus,suchastheInspStatusfieldweaddedtotheConWHSVehicleTabletableinChapter14,WorkflowDevelopment.

Page 960: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 961: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreateastatemachine,followthesesteps:

1. OpenConWHSVehicleTableinthedesigner.Right-clickontheStateMachinesnodeandchooseNewStatemachine.

2. RenamethenewstatemachineInspStateMachineandcompletethepropertiesasshowninthefollowingtable,creatinglabelsfortheDescriptionandLabelproperties:

Property Value

Description Usethistocontroltheinspectionstatus

Label Inspectionstatus

DataField InspStatus

3. Right-clickonthenewstatemachinedefinitionandselectNewState.4. Completethepropertiesofthisstateusingthefollowingtable:

Property Value

EnumValue NotInspected-changethistoWaiting,andthenbackagaintodefaulttheLabelproperty.

Description Thevehiclehasnotyetbeeninspected

Label Notinspected

StateKind Initial

WewillcreateastateforeachelementintheConWHSVehInspStatusBaseEnum,soitisagoodideatocreatedescriptionlabelsinadvanceandjustpastethemin.Usenamedlabelsforthis,notnumeric.IuseasuffixofHT,whichisshortforHelpText,forlabelsthatareusedforbothhelptextanddescriptionsofelements.

Page 962: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

5. Createtheremainingstatesusingthefollowingtableasaguide:

EnumValue Name StateKind Description

Waiting Waiting Intermediate Thevehicleisawaitinginspection

InProgress InProgress Intermediate Thevehicleinspectionisinprogress

Complete Complete Final Thevehicleinspectioniscomplete

6. Theresultshouldlooklikethefollowingscreenshot:

7. Wewillnowneedtotellthestatemachinethetransitionrules.Wewilldefinetherulesasfollows:NotInspectedcanonlytransitiontoWaitingWaitingcanonlytransitiontoInProgressInProgresscantransitiontobothWaitingandCompleteCompleteisthefinalstateandcannottransitionbackwards

8. Again,createlabelsinadvance.Thefollowingtableexplainsthetypeofwordingweshoulduse:

LabelID Label

VehTransWaiting Addtowaitinglist

VehTransWaitingHT Addthevehicletothelistofvehiclesawaitinginspection

VehTransInProgress Startinspection

VehTransInProgressHT Startthevehicleinspectionprocess

Page 963: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

VehTransBackWaiting Reverttowaiting

VehTransBackWaitingHT Placethevehiclebackontothewaitinglist

VehTransComplete Completeinspection

VehTransCompleteHT Complete,andfinalizethevehicleinspection

9. Todothis,right-clickontheNotInspectedstateandselectNewStatetransition.Thistime,theLabelandDescriptionpropertiesdefinetheaction,notthestate.SetthepropertiestodefinethetransitiontotheWaitingstateasfollows:

Property Value

Description @ConWHS:VehTransWaitingHT

Label @ConWHS:VehTransWaiting

Name TransitionToWaiting

TransitionToState Waiting

10. AddanewtransitionstatetotheWaitingStatestateusingthefollowingtable:

Property Value

Description @ConWHS:VehTransInProgressHT

Label @ConWHS:VehTransInProgress

Name TransitionToInProgress

TransitionToState InProgress

Page 964: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

11. Next,addtwotransitionstatestotheInProgressstate.Thefirstistorevertbacktowaiting:

Property Value

Description @ConWHS:VehTransBackWaitingHT

Label @ConWHS:VehTransBackWaiting

Name TransitionToWaiting

TransitionToState Waiting

12. ThesecondstatetoaddtotheInProgressstatecompletesthestatemachine,andshouldbeconfiguredasfollows:

Property Value

Description @ConWHS:VehTransCompleteHT

Label @ConWHS:VehTransComplete

Name TransitionToComplete

TransitionToState Complete

13. Saveyourchanges,theresultshouldlooklikethefollowingscreenshot:

Page 965: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

14. Thefinalstepistoright-clickontheInspStateMachinestatemachine,andclickonGenerate.Thisgeneratesthecodethatwillbeusedtocontroltheinspectionstatusprogression.

IfyougettheerrorGivenkeydoesnotexistinthedictionary,itisbecausethenameofthestatedidnotmatchtheEnumValueproperty.Thismaybechangedinfuturereleasessothatitcanbenameddifferently.

15. Thegeneratedclassesmaynotbeaddedtoyourproject;todoso,locatetheclassesthatstartwithConWHSVehicleTableInspStateMachineanddragthemontotheClassesnodeofyourproject.Donotmodifytheseclasses;theseareshowninthefollowingscreenshot:

Page 966: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 967: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Whatthisprocessactuallydoesisgeneratefourclasses.ThemainclassisnamedConWHSVehicleTableInspStateMachine,whichisaconcatenationofthetable'snameandthestatemachine'sname.Theotherthreeclassesareallprefixedwiththisclass,andallowtypeddatetobepassedtothedelegatesthatwerewrittenintothisclass.

Thefactwehaveastatemachinedoesnotpreventtheuserfrommanuallychangingthestatusfield'svalue.Italsodoesnotstopusfrommanuallychangingthestatusincode.Sotherestrictiononthefinalstatusbeingfinalisonlytruewhenusingthestatemachine.

Therearetwowaysinwhichwecanusethestatemachine:

AttachtoworkfloweventsUsewithmenuitemsaddedtoaform

Wewillexploretheseinthefollowingrecipes.

Page 968: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 969: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

CreatingastatemachinehandlerclassThestatemachineprovidescontroloverthetransitionrules,but,sometimes,wewanttoensurethatothervalidationrulesareobeyedinordertovalidatewhetherthetransitioncanbedone.

ThisisdonebysubscribingtotheTransitiondelegateoftheConWHSVehicleTableInspStateMachineclassthatwasgeneratedbythestatemachine.

ThecodeinthisreciperefactorstheConWHSVehicleInspStatusHandlerclassthatwecreatedinChapter14,WorkflowDevelopment.Thecodewritteninthisrecipewilltieitprogrammaticallytothestatemachine.Shouldyouwishtoattachthestatementtotheworkflowdirectly(whichisagreatidea),thestatuswillbesetbythestatemachine.Therefore,theeventhandlersmustnotsetthestatus.Furthermore,shouldthevalidationwritteninthisrecipefail,wemustensurethattheworkflow'sinternalstatusmatchesthestatemachine'sstatus.Thiscouldbebycancelingtheworkflowbythrowinganerror.

Page 970: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 971: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyWecreatedaclassnamedConWHSVehicleInspStatusHandler;wewillextendthisclasssothatwecanuseitwiththestatemachine.

Page 972: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 973: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Tocreateahandlerclasstoaddfurthervalidationtothestatemachine,followthesesteps:

1. OpentheConWHSVehicleInspStatusHandlerclassandaddthefollowingpieceofcode:

publicConWHSVehInspStatusfromStatus;

publicConWHSVehInspStatustoStatus;

publicConWHSVehicleTablevehicle;

publicbooleanValidate()

{

switch(toStatus)

{

caseConWHSVehInspStatus::Complete:

if(vehicle.InspComment=='')

{

DictFieldfield=newDictField(

tableNum(ConWHSVehicleTable),

fieldNum(ConWHSVehicleTable,

InspComment));

//Thefield%1mustbefilledin"

returncheckFailed(strFmt(

"@SYS110217",

field.label()));

}

break;

}

returntrue;

}

publicvoidrun()

{

if(toStatus==fromStatus)

{

return;

}

if(this.Validate())

{

switch(toStatus)

{

caseConWHSVehInspStatus::Complete:

Timezonetz=DateTimeUtil::

getClientMachineTimeZone();

ConWHSVehInspDateinspDate;

inspDate=DateTimeUtil::getSystemDate(tz);

vehicle.InspDate=inspDate;

break;

}

}

else

{

vehicle.InspStatus=fromStatus;

}

}

Thereisnothingnewabouttheprecedingcode,exceptthatwedon't(andmustnot)callupdateontherecord.Itisjustavalidationclassthatwillstopthetransitionifthecommentisblank.

2. Thecodetotieittothetransitiondelegateisasfollows:

Page 974: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

[SubscribesTo(classStr(ConWHSVehicleTableInspStateMachine),

delegateStr(

ConWHSVehicleTableInspStateMachine,Transition))]

publicstaticvoidHandleTransition(

ConWHSVehicleTableInspStateMachineTransitionEventArgs

_eventArgs)

{

ConWHSVehicleInspStatusHandlerhandler;

handler=newConWHSVehicleInspStatusHandler();

handler.vehicle=_eventArgs.DataEntity();

handler.fromStatus=_eventArgs.ExitState();

handler.toStatus=_eventArgs.EnterState();

handler.Run();

}

Page 975: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 976: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

Whenthestatemachinegeneratedtheclasses,itaddedadelegatethatiscalledwheneverthestatechanges.Thisdelegateiscalledbeforethechangesarecommitted.Thetableispassedbyreference,whichmeansthatwecanrevertthestatusbackwithoutcallingupdate.Ifwedidcallupdate,wecouldcauseconcurrencyissueswithinthestandardcode.

Page 977: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 978: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...

Whenworkingwithahandlerclass,alsobecarefulwithtransactionstate.Wecouldupdatedatainatable,forinstance,amanuallycraftedstatushistorytable.Wecannicelyhandleanypotentialexceptionwithatry...catchstatementwithinourhandlerclass,butwecan'tcontrolwhathappenswhencontrolreturnsbacktothestatemachine.Forexample,ifweupdateahistorytable,butthecodefailslateron,wecouldendupwithanon-durabletransactionifthecodehandlestheexceptionandcontinuestocommitthetransaction.

Page 979: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 980: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Usingmenuitemstocontrolastatemachine

Inthissection,wewillactuallyaddthestatemachinetotheform,sowecanuseit.Usingmenuitemsforthisisaniceconcisewaytocontrolthestatemachine,andfollowstheUIpatternsfoundinotherareas,suchastheprojectsmodule.

Page 981: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 982: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyTheprerequisiteforthisrecipeisthatwehaveatablewithastatemachinethathasbeengenerated.

Page 983: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 984: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...Tocreatethestatemachinemenuitems,followthesesteps:

1. AddanewactionmenuitemtotheprojectnamedConWHSVehInspStatusWaiting.Completethepropertysheetasfollows,intheorderstatedinthefollowingtable:

Property Value

StateMachineDataSource ConWHSVehicleTable

StateMachineTransitionTo Waiting

Label ThelabelyouuseintheWaitingstate'sLabelproperty

HelpText ThelabelyouuseintheWaitingstate'sDescriptionproperty

NeedsRecord Yes

2. Createthemenuitemsfortheremainingstates(InProgressandComplete)followingthesamepattern.3. OpentheConWHSVehicleTableforminthedesigner.4. Undertheform'sDesignnode,expandtheActionPaneHomecontrol,andthenActionPaneActionButtonGroup.

Right-clickonthiscontrolandchooseNew|MenuButton.5. RenamethenewcontrolInspStatusMenuButton.SettheTextandHelpTextpropertiestothesameaswe

usedonthestatemachine,forexample,@ConWHS:InspectionStatusand@ConWHS:InspectionStatusHTrespectively.

6. Then,dragthethreemenuitemsontothismenubutton.SettheDataSourcepropertytoConWHSVehicleTable--thetablethatthestatemachineoperateson.

7. Ifyoucan'taddthemdirectly,dragthemfirstontotheActionPaneActionButtonGroupbuttongroup,andthendragthemfromtheretothecorrectplace.

8. Saveandcloseallcodeeditorsanddesignwindowsandbuildtheproject.

Page 985: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 986: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...Whenwecreatedthemenuitems,thesystemdefaultedmanypropertiesforus.Ifthetableonlyhasonestatemachine,allwehadtodowassetthelabelproperties.Youmaynoticethatitchangesthemenuitem'spropertiessothatitreferencedthestatemachineclassthatwasgeneratedbythetable'sstatemachine.

Whenwetestthebuttons,youcanseethatifwechooseatransitionthatisnotvalid,wegetthiserror:

Wecan'tchangethismessage,asitiscontrolledbyaprotectedmethod,andweshouldn'teditthegeneratedclasses,asthecodechangeswillbelostshouldthestatemachineberegenerated.Thisisalittleodd,asthegeneratedcodedoesgathertheuserfriendlylabelsweadded.

Page 987: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 988: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

HookingupthestatemachinetoaworkflowInthisrecipe,wewillhookupourstatemachinetotheConWHSVehWFInspworkflowtask.

Page 989: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 990: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

GettingreadyWeneedtohaveaworkflowtaskandhavecompletedtherecipesinthischapter.

Page 991: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 992: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howtodoit...

Tohookupthestatemachinetoaworkflowtask,followthesesteps:

1. OpentheConWHSVehWFInspworkflowtask.2. SettheCanceledStateMachinepropertytoInspStateMachine.

Yes,thisisspelledCanceled,butitismorethancompensatedbythecleverwayithasdeterminedthelistofvalidstatemachines.

3. SettheCanceledStateMachineTargetStatepropertytoWaiting;wewon'tbeallowedtouseNotInspected.Duetothisbeingthestatemachine'sinitialstate,itwillletyousetthisvalue,butthestatemachinewillrejectthechange.

4. SettheStartedStateMachinepropertytoInspStateMachine,andtheStartedStateMachineTargetStatepropertytoWaiting.

YouwillalsoneedtoallowtheWaitingstatetotransitiondirectlytoComplete.Don'tforgettoclickonGeneratetoregeneratethestatemachineclass.

5. SelecttheCompletedoutcome,settheStateMachinepropertytoInspStateMachine,andthesetStateMachineTargetStatepropertytoComplete.

6. Sincewenowhavetwowaystosetthestatus,wewill(asashorttermfix)disablethestatusupdatecodethatsetthestatusintheSetStatusmethodoftheConWHSVehInspStatusHandlerclass.OpenthisclassandremovethelinethatsettheInspStatusfield.

WewillupdatethiscodeproperlyintheThere'smore...section.

7. Upontestingthis,wewillfindthatthetaskcompletedsetsthecommentcorrectly,butthestatusdoesn'tchangetocompleted.Thereasonisbecausetheworkfloweventsfirelast,sothestatemachinevalidationrejectedtheupdatebecausethecommentwasnotyetset.Weneedtoupdateourvalidationlogicsothatitonlyrunswhentriggeredfromtheform.AltertheValidatemethodoftheConWHSVehInspStatusHandlerclasssothatitreadsasfollows:

publicbooleanValidate()

{

switch(toStatus)

{

caseConWHSVehInspStatus::Complete:

if(vehicle.InspComment==''

&&FormDataUtil::isFormDataSource(vehicle))

{

//Thefield%1mustbefilledin"

DictFieldfield=newDictField(

tableNum(ConWHSVehicleTable),

Page 993: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

fieldNum(ConWHSVehicleTable,

InspComment));

returncheckFailed(strFmt(

"@SYS110217",

field.label()));

}

break;

}

returntrue;

}

Thehighlightedcodechecksiftherecordbufferisaformdatasource;ifthecodewascalledfromwithinworkflow,thetablewillnotbelinkedtoaform'sdatasource.

8. Buildandtesttheworkflow;allshouldworkcorrectly.

Page 994: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 995: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

Howitworks...

Thechangewehavemadewastosimplytietheeventsontheworkflowtasktoastateofthestatemachine.Thismeansthattheeventhandlermethodswenormallywriteshouldnotupdatethestatus,buttheycanperformactionsthatshouldhappenwhentheeventhappens.

Thestatemachineiscalledbytheworkflowengine,justbeforetheeventsarecalled.Thisiswhywehadtoremovethevalidationonthecomment--thestateischangedbeforethecompletedeventwascalled,whichmeansthatthecommentwasempty.Thereisn'tmuchwecandointhiscasebuttoallowtheworkflowtocontinue.Wecouldusetheworkflowdesignertocheckforthiseventandresubmitthetasktotheuser.

Page 996: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in
Page 997: community.dynamics.com · About the Author Simon Buxton has worked with Dynamics 365 for Operations since its earliest incarnations, starting out as a consultant and developer in

There'smore...Thisseemstogreatlysimplifytheworkflowdevelopment.Wedon'tneedalloftheeventhandlermethods,sincemostofthemonlyupdatetherecord'sstatus.Thereissomethoughtrequired,ifweconsiderthefollowingscenario.

Theworkflowiscancelledbytheuser,whichmeansthatthestatuswillgobacktoWaiting.WechoseWaiting,forwhentheworkflowtaskwascancelled,becausethestatemachinewillthrowanerrorifwetrytosetittotheinitialstate.Theproblemisthatwecan'tchangethestatustothesamestatus;wewillstillgetanerror.Theerrorisnotjustamessagetotheuser;itwillplacetheworkflowinafailedstate,whichwillrequireanadministratortocancel,resubmit,orresumeit.

Theproblemisthatweshouldnothaveascenariowhereauseractioncancauseafailurethatrequiresanadministratortorescuethem;weneedtohandlethiseventualityelegantlywithinourcode.

ThefirstthingwecoulddoisaddaninternalstatetothestatusBaseEnumandtothestatemachine,forexample,"Internalprocessing".Wewouldnotcreateamenuitemforthisasitisonlyforinternaluse.Onthestatemachine,wewouldallowanytransitionfromthisstate;itcantransitionfreelyfromandtothisstate.

ThisisthestateweusefortheCancelledevent.ThismeansthattheworkflowcansetthestatustoWaitingaftertheworkflowwascanceled.

ThenextpartofthechangeswewouldmakewouldbetoremoveallcallstoConWHSVehiInspStatusHandler::SetStatus(...).WewouldwriteanewmethodcalledSetComment,whichiscalledfromtheCompletedeventontheworkflowtaskeventhandlerclass.