Lösungen
Märkte
Referenzen
Services
Unternehmen
Changing NAV (C/AL) source code through C#

Changing NAV (C/AL) source code through C#

21. Dezember 2016

Changing NAV (C/AL) source code through C#

I know the current hype is about the new dev tooling and absolutely rightfully so. Truly exciting stuff. However I guess a lot of us will be at least partly stuck with C/AL and C/SIDE for the foreseeable future, which means you might still be interested with some not-so-exciting stuff about the old world: How to change C/AL source code through C#.

How does this work? I am not sure if anybody has written about it before and I just missed it or if nobody cared before me, but because I like PowerShell but sometimes it just isn’t the right tool, I started to dig a bit into how to use the dlls referenced by e.g. NavModelTools.ps1 directly from C# code. From that starting point you quickly get to Microsoft.Dynamics.Nav.Model.dll and some interesting classes in there. Because there is no documentation about it in the open, I had to do some digging there as well but as it doesn’t contain too many classes it wasn’t that difficult. First I found a class Nav.Model.IO.Txt.TxtImporter that makes it possible to get to a Nav.MetaModel.ApplicationObject through the well named method ImportFromStream(System.IO.Stream). Nav.MetaModel.ApplicationObject now allows me to get the elements of a C/AL object through GetElements(). Those elements are of type Nav.MetaModel.IElement which contains methods like GetCodeLines() and SetCodeLines(), GetRdlDataLines() and SetRdlDataLines() or GetProperties() and e.g. SetStringProperty(). It also contains ElementTypeInfo of Type Nav.MetaMetaModel.ElementTypeInfo (no typo…).

By now I have probably lost almost all readers but for those still interested: The following few lines of code allow me to read the documentation from a NAV object txt file

class Program
    {
        static void Main(string[] args)
        {
            ApplicationObject myObject = GetNavObj(@"C:\temp\COD1.TXT");
            IElement docu = GetDocu(myObject);
        }
 
        public static IElement GetDocu(ApplicationObject appObj)
        {
            IElement docu = null;
            foreach (IElement element in appObj.GetElements())
            {
                if (element.ElementTypeInfo.ElementType == ElementType.Documentation)
                {
                    docu = element;
                    break;
                }
            }
            return docu;
        }
 
        public static ApplicationObject GetNavObj(string file)
        {
            TxtFileModelInfo tfmi = new TxtFileModelInfo();
            TxtImporter importer = new TxtImporter(tfmi);
            using (var instream = new FileStream(file, FileMode.Open))
            {
                ApplicationObject appObj = importer.ImportFromStream(instream)[0];
                return appObj;
            }
        }
    }

If I add the now trivial export method as follows, I can change lines in the documentation and export it again (in case you missed that, INFOMA will become Axians Infoma by January 1st)

class Program
    {
        static void Main(string[] args)
        {
            ApplicationObject myObject = GetNavObj(@"C:\temp\COD1.TXT");
            IElement docu = GetDocu(myObject);
            List<string> lines = new List<string>();
            lines.AddRange(docu.GetCodeLines().ToArray<string>());
            for (int i = 0; i < lines.Count; i++) {
                string line = lines[i];
                if (line.StartsWith("|Copyright (C) by Infoma GmbH."))
                {
                    lines[i] = "|Copyright (C) by Axians Infoma GmbH.";
                }
            }
            docu.SetCodeLines(lines);
            SetNavObj(@"C:\temp\COD1_adjusted.TXT", myObject);
        }
 
        public static void SetNavObj(string file, ApplicationObject appObj)
        {
            TxtFileModelInfo tfmi = new TxtFileModelInfo();
            TxtExporter export = new TxtExporter(tfmi);
            using (var outstream = new FileStream(file, FileMode.Create))
            {
                export.ExportObject(appObj, outstream);
            }
        }
        ...

I could also search e.g. for all MESSAGEs in all OnOpenPage triggers by adjusting line 14 in the first sample to something like

if (element.ElementTypeInfo.ElementType == ElementType.OnOpenPage)

and line 11 in the second sample to

if (line.Contains("MESSAGE"))

Depending on how desperate you are to get something like true polymorphism in NAV you could use this toolset to implement the very nice idea of an Interface Codeunit, introduced by brilliant Vjeko Babic. You could define your Interface Codeunit and run some checks through the tooling above to make sure the implementing Interfaces actually implement everything needed. Because NAV knows nothing about such things you would then also have to go through all variables and make sure you select the proper implementing Codeunit. A lot of work for what someone from Microsoft described as “syntactic sugar” but that depends on your point of view, I guess.

On not so exotic use cases you might find this interesting if you have to do some refactoring or even creation of new objects with predefined code which isn’t that well supported by NAV. There are a couple of tools out there to help, but somehow at least in my experience they tend to fail in the very specific need I have, so I like to have those tools in my pocket. I truly hope that in the very near future we will have a lot better tooling and maybe even a better language in NAV but until then, this might come in handy.

A very important note: This is by no means supported or even documented by Microsoft to my knowledge. It is just something I came across and worked quite well so far but if you really want to use it, carefully check the results and don’t blame me if it goes wrong…


2 Kommentare zu “Changing NAV (C/AL) source code through C#”

  1. Hi Tobias,

    I used this libraries for reading/writing from/to txt (app objects from NAV), but the problem that I had was FileStream reads only primitive types, so when I wanted to read/write some language specific characters (like some properties) I got special characters instead. I know that StreamReader/StreamWriter had encoding parameter, but still didn’t manage to read/write language specific characters.

    If you have any tips, please write me.

    Tnx


Schreibe einen Kommentar