Auf dem Weg in die Cloud entwickelt sich Microsoft Dynamics NAV von einer grauen Maus zu einem modernen Enterprise Resource Planning System (ERP-System) mit ansprechendem Look & Feel. Schau dir an, wie Add-Ins in Dynamics NAV fit werden für’s Web!
Am 1. Dezember 2017 wurde die Version 2018 von Microsoft Dynamics NAV veröffentlicht, die mit dem Nachfolger Dynamics 365 „Tenerife“ unaufhaltsam in die Cloud führt – über den steinigen Weg des Technologiewechsels und allen Herausforderungen, die mit diesem Wechsel verbunden sind.
Der Weg ins Web setzt eine 3-Tier Architektur voraus. Für Dynamics NAV bedeutet dies: Lösungen, die im Role Tailored Client (RTC) einfach über clientseitige Dynamic Link Libraries (DLL) umsetzbar waren, müssen nun zwingend auch im Web laufen. Und das heißt: viel Arbeit für die NAV Entwickler!
Fit für’s Web: Add-Ins in Dynamics NAV
Am Beispiel eines Add-In im NAV RTC soll gezeigt werden, wie NAV für das Web fit gemacht werden kann. Beliebt ist die Funktion Dateien per Drag & Drop an Belege anzuheften, wobei eine beliebige Datei im RTC oder im Browser auf einem definierten Feld fallengelassen, automatisch auf den Dynamics NAV Server hochgeladen und in der Datenbank abgelegt wird.
Technisch sieht dies grob so aus:
Um webtauglich zu sein, kommuniziert Dynamics NAV über das „NAV Extensibility Framework“ mit JavaScript und umgekehrt. Aber genug der Theorie, ran ans Werk!
Vorbereitung der Entwicklungsumgebung
Schritt 1: In Visual Studio wird ein Projekt zur Entwicklung einer Klassenbibliothek angelegt.
Schritt 2: Für den JavaScript-Bereich wird eine Ordner-Struktur benötigt. In einem Add-In-Ordner wird jeweils ein Ordner angelegt für Ressourcen, für Skripte und für Stylesheets inkl. entsprechender Dateien:
- Der Ordner „Image“ enthält ein Bild für die Oberfläche.
- Der Ordner „Script“ enthält den Code. Zur Entwicklung wird TypeScript verwendet, daher ist eine .ts Datei vorhanden, die von Visual Studio automatisch in eine .js Datei umgewandelt wird (TypeScript Compiler).
- Im Ordner „StyleSheet“ liegt eine .css Datei zur Gestaltung der Oberfläche.
Schritt 3: Eine Manifest.xml wird angelegt, die das Add-In deklarativ in XML beschreibt.
Schritt 4: Abschließend benötigen wir eine Signierung für die Assembly.
Wenn die Projektmappe ungefähr so aussieht wie auf dem Bild oben, kann es eigentlich schon losgehen. Diese 3 „Postbuild-Maßnahmen“ vereinfachen aber die Entwicklung:
- Ausgabe des Signaturtoken, der benötigt wird, um das Add-In in NAV zu registieren. Dazu kann ein PowerShell Skript genutzt werden.
- Erstellung einer Zip-Datei, mit der Add-In-Ordnerstruktur.
- Die erstellte DLL wird in den Add-In-Ordner von NAV kopiert.
Der C# Part
Zunächst verweisen wir über „using“ auf das Microsoft Dynamics Extensibility Framework.
Dann beschreiben wir über ein Interface alle in NAV zu nutzenden Methoden und Events. Wir benötigen ein Event zur Initialisierung sowie ein Event zum Fallenlassen einer oder mehrerer Dateien (Drop).
Dem Drop-Event geben wir noch unsere Datei zusammen mit einigen Metadaten mit. Der Parameter „data“ beinhaltet dabei die Datei. Diese wird webtauglich als Base64 String übertragen, daher wird hier der Typ „String“ verwendet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Dynamics.Framework.UI.Extensibility; namespace DragAndDrop2NAV_2018 { public class DragAndDrop2NAV_2018 { public delegate void FileDropEventHandler(string data, string filename, string type, decimal size); [ControlAddInExport("DragAndDrop2NAV_2018")] public interface IDropAreaControlAddIn { [ApplicationVisible] event ApplicationEventHandler ControlAddInReady; [ApplicationVisible] event FileDropEventHandler FileDrop; } } } |
Manifest.xml, der Einstiegspunkt
Weiter geht es mit der Manifest.xml, die als Einstiegspunkt dient. Die Manifest.xml wird so gefüllt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <?xml version="1.0" encoding="utf-8"?> <Manifest> <Resources> <Script>DropArea.js</Script> <StyleSheet>DropArea.css</StyleSheet> <Image>DropIcon.png</Image> </Resources> <ScriptUrls> <ScriptUrl>http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.min.js</ScriptUrl> </ScriptUrls> <Script> <![CDATA[ $(document).ready(function() { initializeControlAddIn('controlAddIn', Microsoft.Dynamics.NAV.GetImageResource('DropIcon.png')); Microsoft.Dynamics.NAV.InvokeExtensibilityMethod('ControlAddInReady', null); }); ]]> </Script> <RequestedHeight>55</RequestedHeight> <RequestedWidth>172</RequestedWidth> <VerticalStretch>true</VerticalStretch> <HorizontalStretch>true</HorizontalStretch> </Manifest> |
- Im Knoten „Resources“ liegen die Referenzen auf unsere zuvor angelegten Dateien.
- Der Knoten „Script“ dient als Einstiegspunkt für das Add-In.
- Mit „initializeControlAddIn“ rufen wir JavaScript Code zur Initialisierung des Add-In auf.
- Mit „InvokeExtensibilityMethod“ wird NAV angerufen und der Trigger „ControlAddInReady“ ausgeführt.
- Im unteren Bereich wird noch die Größe festglegt.
Etwas für die Optik: Die CSS Datei
Die CSS Datei enthält ein paar Eigenschaften zur Verschönerung des Add-Ins.
Wir ziehen einen Rahmen um die Drop-Fläche und erstellen einen Hover-Effekt, um dem Benutzer zu signalisieren, dass er die Fläche getroffen hat und die Datei loslassen kann.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #droparea { border: none; vertical-align: middle; font-family: "Segoe UI","Segoe WP",Segoe,device-segoe,Tahoma,Helvetica,Arial,sans-serif; font-size: smaller; border: 1px dashed #333; } #droparea.hover { border: 1px dashed #333; background-color: #d1e8f8; } |
Der TypeScript Code
Zunächst legen wir die Klassen „Upload“ und „UploadQueue“ an.
Upload beinhaltet die Datei mit einigen Metadaten.
Um gleichzeitig mehrere Dateien hochladen zu können, legen wir den Upload in einer Warteschlange ab.
Die dort abgelegten Uploads werden über die Methode „StartSendData“ in einen Base64 String gewandelt und über das Extensibility Framework an NAV gesendet.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | declare var Microsoft; declare var Dynamics; declare var NAV; class Upload { name: string; file: File; data: string; length: number; totalLength: number; constructor(public newName, public newFile) { this.name = newName; this.file = newFile; this.data = null; this.length = 0; this.totalLength = 0; } GetFileExtension() { return this.name.substring(this.name.lastIndexOf("."), this.name.length); } } class UploadQueue { uploadQueue: Array<Upload>; uploadInProgress: boolean; constructor() { this.uploadQueue = new Array<Upload>(); } StartSendData() { for (var i = 0; i < this.uploadQueue.length; i++) { var upload: Upload = this.uploadQueue[i]; var reader: FileReader = new FileReader(); reader.onloadend = function (event: ProgressEvent) { var data: string = (<any>event.target).result; if (data != null) { this.upload.data = this.result.toString().split("base64,")[1]; var size = upload.file.size / 1048576; Microsoft.Dynamics.NAV.InvokeExtensibilityMethod('FileDrop', [this.upload.data, this.upload.file.name, this.upload.GetFileExtension(), size], false); } } reader.readAsDataURL(upload.file); reader.upload = upload; } } Push(upload: Upload) { this.uploadQueue.push(upload); } } |
Die Methode „initializeControlAddIn“ initialisiert unser Add-In. Hier ist ein entsprechender HTML Code hinterlegt, in dem auch das Bild eingebettet ist.
Weiterhin wird über die Methode „pageLoaded“ das Drop Event registriert.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | function initializeControlAddIn(id: string, imageUrl: string) { var controlAddIn = document.getElementById(id); var imageTag: string = ""; if (imageUrl != "") { imageTag = '<img src="' + imageUrl + '">'; } controlAddIn.innerHTML = '<div id="droparea">' + '<table><tr><td>Ziehen Sie die zu verknüpfende Datei auf dieses Logo!</td><td>' + imageTag + '</td></tr></table></div>'; pageLoaded(); } function pageLoaded() { var dropArea: HTMLElement = document.getElementById('droparea'); dropArea.ondragleave = onDragLeave; dropArea.ondragend = onDragEnd; dropArea.ondragover = onDragOver; dropArea.ondrop = onDrop; } function onDragLeave(e: DragEvent) { this.className = ''; return false; } function onDragEnd(e: DragEvent) { this.className = ''; return false; } function onDragOver(e: DragEvent) { this.className = 'hover'; e.preventDefault(); } var uploadQueue: UploadQueue; |
Wenn Dateien auf das Add-In fallengelassen werden, werden diese in der Warteschlange abgelegt und anschließend gesendet.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | function onDrop(e: DragEvent) { uploadQueue = new UploadQueue(); this.className = 'drop'; e.preventDefault(); try { var type: string = e.dataTransfer.types[0]; if (type === "Text") { var content: string = e.dataTransfer.getData(type); alert(content); } else if (type === "Files") { var files: FileList = e.dataTransfer.files; for (var i: number = 0, file: File; file = files[i]; i++) { try { var upload = new Upload(file.name, file); uploadQueue.Push(upload); } catch (ex) { alert(ex.message); } } uploadQueue.StartSendData(); } } catch (ex) { alert(ex.message); } return false; } |
Der NAV Part
In NAV benötigen wir eine Tabelle, in der wir ankommende Dateien inkl. der Metadaten ablegen und speichern.
Die eigentliche Datei wird in einem BLOB Feld gespeichert.
Anschließend erstellen wir eine Page, auf der wir das Add-In anzeigen möchten.
Hier bietet sich eine Page vom Typ CardPart an, die wir als FactBox nutzen können.
In der Eigenschaft „ControlAddIn“ des Feldes „AddIn“ hinterlegen wir das Add-In.
Jetzt sollten im C/AL Editor auch die zuvor definierten Events als Trigger erscheinen:
Der Trigger „FileDrop“ nimmt eine Datei nach einem Drop Event in JavaScript entgegen.
Die Metadaten werden zunächst in den entsprechenden Feldern abgelegt.
Anschließend wird die ankommende Datei im Base64 String Format in NAV wieder decodiert (Funktion: „Base64Decode“) und im BLOB Feld abgelegt.
Wenn das Add-In jetzt in NAV registriert ist, kann es getestet werden.
Beim Start der Page sollte das erstellte Add-In angezeigt werden:
Wenn man eine Datei fallen lässt, wird diese aufgenommen und nach NAV übertragen.
Das Add-In kann jetzt z. B. als Klassiker zum Anhängen von Dateien an Belege benutzt werden.
Das Add-In kann man noch aufhübschen und mit vielen Ideen erweitern.
Viel Spaß beim Nachbauen, Implementieren und Weiterentwickeln!