Dynamics AX Blog - Dynamics AX 2012 - sysoperation-framework - Microsoft Dynamics AX (Axapta) - Seite 4
In den letzten Jahren, in denen ich mich fast hauptsächlich mit der Entwicklung im Umfeld von Microsoft Dynamics AX (vormals Axapta) beschäftigt habe, ist das eine oder andere Code-Fragment entstanden, von dem ich mir vorstellen könnte, daß es auch für andere AX-Entwickler ganz nützlich sein könnte. Aber auch Tips und Tricks zu dem mächtigen ERP-System werde ich in dieser Kategorie präsentieren.
RSS-Feed dieser KategorieRSS-Feed dieser Version
AX 2012: SysOperation-Framework: Häufig verwendete Attribute
24.03.2013Microsoft Dynamics AX (Axapta)
|
AX 2012: SysOperation-Framework: FileName-Methoden einbinden
23.12.2012Microsoft Dynamics AX (Axapta)
Wenn man in früheren Versionen von Dynamics AX eine Klasse auf Basis des RunBase-Frameworks im Einsatz hatte und dabei in einem Dialog ein Feld für einen Dateienamen verwendet hat, konnte man über spezielle filename...()-Methoden beispielsweise die gewünschte Dateierweiterung oder den Speicherort steuern.
|
AX 2012: SysOperation-Framework: Ein eigenes Formular als Dialog verwenden
07.12.2012Microsoft Dynamics AX (Axapta)
|
AX 2012: Beispiel für die Verwendung des SysOperation-Frameworks
04.11.2012Microsoft Dynamics AX (Axapta)
Das SysOperation-Framework ist eine Neuerung in Dynamics AX 2012 und soll das RunBasebatch-Framework ersetzen. In der MSDN sind zahlreiche Dokumentationen und Whitepaper über dieses Framework zu finden, welche ich unbedingt empfehle zu lesen. Der folgende Beitrag enthält ein bewusst einfach gehaltenes Beispiel, wie man in AX 2012 Programmlogik auf Basis dieses Framework einsetzen kann. Das Beispiel besteht dabei aus insgesamt vier Klassen:
Data Contract ClassEine Data Contract Class besteht im Grunde genommen lediglich aus Accessor-Methoden (parm...-Methoden) und ist dadurch gekennzeichnet, daß in der classDeclaration() das Attribute DataContractAttribute verwendet wird. Für alle Accessor-Methoden des Data Contracts werden vom SysOperation-Framework beim Aufruf entsprechende Dialogfelder generiert. DIe weiteren im Beispiel verwendeten Attribute SysOperationContractProcessingAttribute bzw. SysOperationGroupAttribute verknüpfen zum Einen den Data Contract mit einem UI Builder und zum Anderen wird - neben der standardmässig immer generierten Feldgruppe namens Parameter - eine weitere Feldgruppe mit dem internen Namen DemoGroup. [ DataContractAttribute ,SysOperationContractProcessingAttribute(classStr(TutorialSysOperationUIBuilder)) ,SysOperationGroupAttribute('DemoGroup', 'For demonstration purpose only', '2') ] class TutorialSysOperationDataContract implements SysOperationValidatable, SysOperationInitializable { str qryStr; FilenameSave filenameSave; TransDate dialogDate; CustAccount custAccount; } Accessor-Methoden eines Data Contracts müssen immer über das Attribute DataMemberAttribute gekennzeichnet werden. Das Attribute SysOperationDisplayOrderAttribute legt die Reihenfolge der Felder im Dialog fest und mit Hilfe des Attributes SysOperationGroupMemberAttribute wird gesteuert, daß das Feld innerhalb der in der classDeclaration() definierten Feldgruppe eingeordnet wird. [ DataMemberAttribute ,SysOperationDisplayOrderAttribute('2') ,SysOperationGroupMemberAttribute('DemoGroup') ] public CustAccount parmCustAccount(CustAccount _custAccount = custAccount) { custAccount = _custAccount; return custAccount; } Die Accessor-Methode für das Datum unterscheidet sich im groben nur durch ein weiteres verwendetes Attribute SysOperationLabelAttribute mit Hilfe dessen das Label des Dialogfeldes festgelegt wird. Das hier anzugebende Datum ist im übrigen lediglich zu Demonstrationszwecken implementiert und hat keinen erwähnenswerten Einfluss auf die Programmlogik. [ DataMemberAttribute ,SysOperationDisplayOrderAttribute('3') ,SysOperationGroupMemberAttribute('DemoGroup') ,SysOperationLabelAttribute(literalStr("@SYS128676")) ] public TransDate parmDialogDate(TransDate _dialogDate = dialogDate) { dialogDate = _dialogDate; return dialogDate; } In der Accessor-Methode für den Dateinamen wird über das Attribute SysOperationDisplayOrderAttribute festgelegt, daß das Feld das Erste im Dialog sein soll. [ DataMemberAttribute ,SysOperationDisplayOrderAttribute('1') ] public FilenameSave parmFilenameSave(FilenameSave _filenameSave = filenameSave) { filenameSave = _filenameSave; return filenameSave; } Eine Besonderheit unter den im Beispiel verwendeten parm-Methoden stellt die folgende Methode dar. Weil innerhalb von Accessor-Methoden nicht jeder in AX zur Verfügung stehender Datentyp verwendet werden kann, muss ein Query beispielsweise in einen String umgewandelt und übergeben werden. Das SysOperation-Framework stellt dafür eigene Methoden zur Verfügung. [ DataMemberAttribute ,AifQueryTypeAttribute('_qryStr', '') ] public str parmQuery(str _qryStr = qryStr) { qryStr = _qryStr; return qryStr; } Die Methode validate() steht nur zur Verfügung, wenn in der classDeclaration() des Data Contracts die Klasse SysOperationValidatable eingebunden wurde. Dann funktioniert sie ähnlich, wie im RunBaseBatch-Framework. public boolean validate() { boolean ret; ret = true; // Simple validation example if(this.parmDialogDate() && this.parmDialogDate() < systemDateGet()) { ret = checkFailed(strFmt("'%1' has to be greater/equal than %2.", "@SYS128676", systemDateGet())); } if(!this.parmFilenameSave()) { ret = checkFailed(strFmt("Field '%1' must be filled in.", extendedTypeId2pname(extendedTypeName2Id(identifierStr(FileNameSave))) )); } return ret; } Die Methode initialize() steht nur zur Verfügung, wenn in der classDeclaration() des Data Contracts die Klasse SysOperationInitalize eingebunden wurde. Diese Methode wird nur aufgerufen, wenn keine Nutzungsdaten vorhanden oder verwendet werden. public void initialize() { dialogDate = systemDateGet() - 365; }
Service ClassDiese - von SysOperationServicebase abgeleitete - Klasse, enthält die eigentliche Logik des Beispiels. class TutorialSysOperationService extends SysOperationServiceBase { } Genauergesagt ist die Logik in der Methode runService() enthalten. Die Besonderheit dieser Methode ist, daß sie als einzigen Parameter den Data Contract erhält und daß das Attribute SysEntryPointAttribute gesetzt ist. Der Name der Methode hingegen bleibt dem Entwickler überlassen, die Methode wird über den Namen in der Methode newFromArgs() des Service Controllers referenziert. Die im Beispiel enthaltene Methode enthält keine aufregende Logik, sie erstellt ledigich eine einfache Text-Datei und schreibt in diese einige Werte aus den über den Query übergebenen Ausgangsrechnungen. [SysEntryPointAttribute(true)] public void runService(TutorialSysOperationDataContract _dataContract) { Query query = new Query(SysOperationHelper::base64Decode(_dataContract.parmQuery())); QueryRun queryRun; CustInvoiceJour custInvoiceJour; TextIo textIo; FileIOPermission fileIOPermission; #file if(_dataContract.parmFilenameSave()) { // Assert permission. fileIOPermission = new FileIOPermission(_dataContract.parmFilenameSave() , #io_write); fileIOPermission.assert(); // If the test file already exists, delete it. if (WinAPIServer::fileExists(_dataContract.parmFilenameSave())) { WinAPIServer::deleteFile(_dataContract.parmFilenameSave()); } textIo = new TextIo(_dataContract.parmFilenameSave(), #io_write); if( !textIo) { throw error('File creation failed.'); } textIo.write(strFmt("--- %1 ---", _dataContract.parmDialogDate())); } // Do service really need to call validate one more time? framework should do it instead! if (!_dataContract.validate()) { // Service should always revalidate parameters throw error("@SYS326740"); } queryRun = new QueryRun(query); while(queryRun.next()) { custInvoiceJour = queryRun.get(tableNum(CustInvoiceJour)); if(textIo) { textIo.write(strFmt("Invoice: %1;CustAccount: %2;Currency: %3", custInvoiceJour.InvoiceId, custInvoiceJour.OrderAccount, custInvoiceJour.custTable_OrderAccount().Currency)); } } }
Controller ClassEine Controller Class muss von SysOperationServiceController abgeleitet sein. class TutorialSysOperationServiceController extends SysOperationServiceController { } Die Methode newFromArgs() ist eine selbst erstellte Methode, die dazu dient, die Klasse zu instanziieren. Dabei wird die Klasse und die als Service aufzurufende Methode festgelegt. Gleichzeitig wird über die Methode parmArgs() des Frameworks der Aufrufer für einen evtl. späteren notwendigen Zugriff gespeichert. public static TutorialSysOperationServiceController newFromArgs(Args _args) { TutorialSysOperationServiceController controller; controller = new TutorialSysOperationServiceController(classStr(TutorialSysOperationService), methodStr(TutorialSysOperationService, runService)); controller.parmArgs(_args); return controller; } Die initQuery() dient dazu, den Query für den Dialog zu initalisieren. Diese Methode wird später von der Methode initializeServiceParameter() aufgerufen. Erwähnenswert ist in Verbindung mit dem Query die Klasse SysOperationHelper, die u.a. Methoden zur Verfügung stellt, um einen Query in einen String zu konvertieren und umgekehrt. Dies ist notwendig, da der Data Contract nur mit "einfachen" Datentypen arbeiten kann. public static Query initQuery(TutorialSysOperationDataContract _dataContract) { Query query; QueryBuildDataSource queryBuildDataSource; QueryBuildRange queryBuildRangeInvoiceDate; ; query = new Query(); queryBuildDataSource = query.addDataSource(tableNum(CustInvoiceJour)); // Build ranges for SELECT-Button SysQuery::findOrCreateRange(queryBuildDataSource, fieldNum(CustInvoiceJour, OrderAccount)); SysQuery::findOrCreateRange(queryBuildDataSource, fieldNum(CustInvoiceJour, InvoiceAccount)); // Add locked Range queryBuildRangeInvoiceDate = query.dataSourceTable(tableNum(CustInvoiceJour)).addRange(fieldNum(CustInvoiceJour, InvoiceDate)); queryBuildRangeInvoiceDate.value("01.01.2010.."); queryBuildRangeInvoiceDate.status(RangeStatus::Locked); // Add sort fields query.dataSourceTable(tableNum(CustInvoiceJour)).addSortField(fieldNum(CustInvoiceJour, InvoiceDate), SortOrder::Ascending); _dataContract.parmQuery(SysOperationHelper::base64Encode(query.pack())); return query; } Wie zuvor angekündigt, verwende ich die Methode initializeServiceParameter() um den Query des Dialoges über die Methode initQuery() zu initalisieren. Weiters wird an dieser Stelle ein weiterer Parameter mit Werten befüllt. protected Object initializeServiceParameter(DictMethod dictMethod, int parameterIndex) { Object ret; TutorialSysOperationDataContract tutorialSysOperationDataContract; ret = super(dictMethod, parameterIndex); if(ret is TutorialSysOperationDataContract) { tutorialSysOperationDataContract = ret; TutorialSysOperationServiceController::initQuery(ret); tutorialSysOperationDataContract.parmDialogDate(systemDateGet()); // Overridden by SysLastValue, if exists } return ret; } Die main()-Methode ist bereits aus dem RunBaseBatch-Framework hinreichend bekannt. Sie wird immer dann aufgerufen, wenn die Controller Class über ein MenuItem aufgerufen wird. In dieser Methode wird - über die oben erwähnte Methode newFromArgs() - die Klasse instanziiert. Weiters wird über parmExecutionMode() der Ausführungsmodus festgelegt und schließlich über startOperation() der eigentliche Aufruf des Service gestartet. Diese Methode retouniert übrigens einen Enum-Wert vom Typ SysOperationStartResult mit Hilfe dessen man abfragen kann, ob das Service beispielsweise sofort oder im Stapel gestartet wurde oder ob der Dialog vom Benutzer abgebrochen wurde. public static void main(Args _args) { TutorialSysOperationServiceController controller; TutorialSysOperationDataContract dataContract; SysOperationStartResult sysOperationStartResult; if (!_args) { throw error("@SYS25407"); } controller = TutorialSysOperationServiceController::newFromArgs(_args); controller.parmExecutionMode(SysOperationExecutionMode::Synchronous); setPrefix(controller.caption()); sysOperationStartResult = controller.startOperation(); if(sysOperationStartResult == SysOperationStartResult::Started) { dataContract = controller.getDataContractObject(); if(dataContract is TutorialSysOperationDataContract) { info(strFmt("File %1 sucessfully written.", dataContract.parmFilenameSave())); } } }
UI Builder ClassEine User Interface Builder Class ist dadurch gekennzeichnet, daß sie von SysOperationAutomaticUIBuilder abgeleitet ist. Eine solche Klasse kann dazu verwendet werden, den vom Framework automatisch generierten Dialog um eigene Logik zu erweitern. Die Verknüpfung zwischen Data Contract und UI Builder erfolgt dabei über das Attribute SysOperationContractProcessingAttribute in der classDeclaration() des Data Contracts. class TutorialSysOperationUIBuilder extends SysOperationAutomaticUIBuilder { DialogField df_custAccount; } Ein Ziel unserer UI Builder Class ist, das Lookup-Formular des Feldes für das Debitorenkonto zu übersteuern. Dazu erstellen wird eine neue Methode beliebigen Namens - im Beispiel custAccount_lookup() - und befüllen diese mit dem gewünschten Programmcode. Im Grunde genommen unterscheidet sich die Methode nicht von anderen, ähnlichen Lookup-Methoden. Das einzige worauf man achten muß ist, daß die Methode die gleichen Parameter enthält, wie die gleichwertige Methode eines Formulares. public void custAccount_lookup(FormStringControl _control) { SysTableLookup sysTableLookup; Query query = new Query(); query.addDataSource(tableNum(CustTable)); SysQuery::findOrCreateRange(query.dataSourceTable(tableNum(CustTable)), fieldNum(CustTable, Currency)).value(queryValue(CompanyInfo::standardCurrency())); sysTableLookup = SysTableLookup::newParameters(tableNum(CustTable),_control); sysTableLookup.addLookupfield(fieldNum(CustTable,AccountNum),true); sysTableLookup.addLookupMethod(tableMethodStr(CustTable, name)); sysTableLookup.addLookupfield(fieldNum(CustTable,Currency)); sysTableLookup.parmQuery(query); sysTableLookup.performFormLookup(); } Die Methode postBuild() ist ein idealer Platz, um die in der classDeclaration() deklarierte Variable vom Typ DialogField über bindInfo().getDialogField() mit Leben zu befüllen. Auch ist die Methode gut dazu geeignet, die Eigenschaften von Dialogfelder abhängig vom Aufrufer zu übersteuern. Im Beispiel wird dazu die Methode parmArgs() des Service Controllers abgefragt. public void postBuild() { super(); // get references to dialog controls after creation df_custAccount = this.bindInfo().getDialogField(this.dataContractObject(), methodStr(TutorialSysOperationDataContract, parmCustAccount)); // Initalize dialog field from calling args if(this.controller().parmArgs() && this.controller().parmArgs().record() && this.controller().parmArgs().dataset() == tableNum(CustTable)) { df_custAccount.value(this.controller().parmArgs().record().(fieldNum(CustTable, AccountNum))); df_custAccount.allowEdit(false); } } In der Methode postRun() kann man nun das Dialogfeld mit der oben beschriebenen Lookup-Methode verbinden. public void postRun() { super(); // override methods df_custAccount.registerOverrideMethod( methodStr(FormStringControl, lookup), methodStr(TutorialSysOperationUIBuilder, custAccount_lookup), this); }
Menu ItemAls letzten Schrittt gilt es nun ein MenuItem für die Service Controller Class zu erstellen. Ruft man dieses MenuItem auf, so sollte sich das oben beschriebene Beispiel wie folgt im Client darstellen: |
AX 2012: SysOperation-Framework: Werte eines Data Contracts prüfen
02.11.2012Microsoft Dynamics AX (Axapta)
Wenn man in einem Data Contract die Klasse SysOperationValidatable über den Befehl implements einbindet, kann man die dadurch zur Verfügung stehende Methode validate() einbinden. Mit Hilfe dieser Methode können bereits bei der Eingabe von Werten im - beispielsweise vom SysOperation-Framework - generierten Dialog Prüfungen durchführen. Die Methode funktioniert ähnlich wie die gleichnamige Methode des RunBaseBatch-Frameworks. [DataContractAttribute]
class TutorialSysOperationDataContract implements SysOperationValidatable { date dialogDate; }
[DataMemberAttribute]
public TransDate parmDialogDate(TransDate _dialogDate = dialogDate) { dialogDate = _dialogDate; return dialogDate; }
public boolean validate()
{ boolean ret; ret = true; // Simple validation example if(this.parmDialogDate() && this.parmDialogDate() < systemDateGet()) { ret = checkFailed(strFmt("'%1' has to be greater/equal than %2.", "@SYS128676", systemDateGet())); } return ret; }
|
AX 2012: SysOperation-Framework: Werte eines Data Contracts initialisierenWenn man in einem Data Contract die Klasse SysOperationInitializable über den Befehl implements einbindet, kann man die dadurch zur Verfügung stehende Methode initialize() einbinden. Mit Hilfe dieser Methode kann man Variablen innerhalb des Data contracts initialisieren. Diese Methode wird allerdings nur aufgerufen, solange keine Nutzungsdaten gefunden werden bzw. diese nicht aktiviert sind. [DataContractAttribute]
class TutorialSysOperationDataContract implements SysOperationInitializable { date dialogDate; }
[DataMemberAttribute]
public TransDate parmDialogDate(TransDate _dialogDate = dialogDate) { dialogDate = _dialogDate; return dialogDate; }
public void initialize()
{ dialogDate = systemDateGet() - 365; }
|
AX 2012: SysOperation-Framework: Titel des Dialoges ändern
20.10.2012Microsoft Dynamics AX (Axapta)
Als Titel eines vom SysOperation-Framework generierten Dialoges wird in der Regel der Label des aufrufenden MenuItems angezeigt. Möchte man diesen Titel bewusst übersteuern, so muss man in der Service Controller Class (=abgeleitet von SysOperationServiceController) die nachstehenden beiden Methoden einbinden/überschreiben: public LabelType parmDialogCaption(LabelType _dialogCaption = '')
{ // Base class is setup to get the caption from the // the class name or from the menu item. Reroute it // to the caption override. return this.caption(); }
public ClassDescription caption()
{ ClassDescription ret; ret = "Tutorial-SysOperation-Framework"; return ret; }
|
|
|
|
|
|
|
Attribute des Sysoperation-Frameworks werden in Dynamics AX 2012 über Klassen abgebildet, die von SysAttribute abgeleitet sind. Solche Klassen bestehen oft nur aus eine new()-Methode, über welche die Parameter des Attributes gesteuert werden können, sowie aus einer Accessor-Methode je Parameter.