Length of Variable Names
May 17th, 2013
For as long as I can remember, variable and function names in C/SIDE have been 30 characters or less. Most of us - especially those with a firm belief in descriptive names - are painfully aware of this limitation. But the days of cryptic, abbreviated names may be behind us.
In an attempt to quickly and easily add a Dutch language layer to the C/SIDE application I’m working on, I exported a complete translation file from a Dutch CRONUS database. When I tried to import the file into my own database, I was confronted with an error message claiming that one of the entries in the file was too long.

Technically speaking, the system is right - the translation entry in question cannot exceed 30 characters (which is what the “L30″ means). But, a little searching on MSDN reveals that variable names in NAV 2013 can now have up to 128 characters. No more excuses for unlegible and/or undescriptive names!
Is anybody aware if Microsoft has released a patch that will cause the IDE to export the correct lengths in translation files? I can’t believe I’m the first person to run into this issue, but there appears to be nothing in the hotfixes list on PartnerSource about this subject.
IWCWMLC#: Unreachable Code
April 5th, 2013
Another post in the I Wish C/AL Was More Like C# series. I realize that some people may accuse me of trying to blame C/SIDE for the consequences of my own, rather chaotic thinking process - if my brain worked in a more structured fashion, none of this compiler cleverness would be required. Then again, given the right project scale and complexity, I think any developer could make the kind of errors that I tend to make, and appreciate a compiler that helps to correct them.
Today’s problem may seem unrealistic at first, especially because the code below is simplified quite extremely, for the sake of the example. Trust me, on a codebase maintained by lots of different developers, some more skilled than others, I have seen this situation happen more than once.
Assume you have the following code in C/SIDE:

Somebody (you?
) has placed an unconditional exit statement above the other code, which means that the //… even more code … (or your MESSAGE statement, for that matter) will never be executed. The same logic in C#…

… would result in a much more helpful compiler warning (also indicated by the green squiggly line below Console), that, if desired, could even be promoted to a compiler error.

Best C/SIDE Error Message Ever…
April 3rd, 2013

IWCWMLC#: Please Don’t Fail Silently
March 24th, 2013
As kriki wrote in his comment on my previous post, C/SIDE and its programming language, C/AL, were probably never quite intended and designed for the scale and complexity of the customisations and extensions that the NAV community has built over time (and still does). Also, I think the field of computer science has since moved further away from development in the sense of “writing code”, and more towards software engineering. In other words, treating the computer as a helpful partner in the development process, rather than a wild animal that needs to be tamed by feeding it the right instructions…
Last time, we spoke about how C# prevents errors by forcing every code path of a function to have a return value (i.e., if the function itself doesn’t return a void). The check takes place at compile-time; your project won’t even build if one or more functions don’t comply with C#’s strict rules. Sadly, not all checks can be carried out before the code is compiled. Sometimes, a check depends on values (e.g. user input, command line arguments) that are simply not known to the compiler, only to the executing run-time. If the .NET run-time detects a problem with such a check, it will throw an exception. As undesirable as these exceptions may be (especially if they are found late in the development process, or even after shipping), I think the general consensus is that they are still better than code that fails without any notification of the error at all. Let’s take a look at a (extremely simplified) example.
The snippet below…

…produces this message - no surprises there:

However, if I accidentally pass in too few parameters (or too many placeholders, depending on your perspective), like so…

…C/SIDE will not raise any run-time errors, and simply substitute a blank value (somehow, I believe that behaviour has changed over time - I expected it to leave the %2 in instead?! Must be getting old.). In other words, it fails silently, and it might take ages for someone to notice this bug:

Now, if we were to do the same thing in C#…

…this would be our first result:

Let’s break the code by removing the second parameter, but leaving the second placeholder intact:

The .NET run-time throws an exception because it cannot find anything to put in placeholder {1}.

The first manual or automated test to cover this code in C# would trigger the exception, providing the developer with an opportunity to repair the issue before any users (or managers ;-)) are aware of it.
IWCWMLC#: Not all code paths return a value
March 22nd, 2013
Today, the first post in a (potentially long ;-)) series of posts filed under “I Wish C/AL Was More Like C#”.
I like how C# prevents me from making the kind of mistakes that my - slightly chaotic - mind frequently makes: the kind of mistake that’s a lot more difficult to find at run-time than it is at “code-time”. Here’s an example:

The TrimPrefix function can be used to trim off a string from another one, if the latter starts with the former. A simple function that I wrote almost without thinking, while my brain was working on the proverbial bigger picture ;-). My first tests succeeded nicely, but after a while I started to notice blank values in some records. Of course, I had failed to EXIT(Text) in case the prefix is not found, i.e. Text starts with something other than Prefix.
C# is a lot more strict about these things, and would have told me straight away that some of the code paths in my function do not result in a proper return value. This function…

…would lead the compiler to complain about this…
![]()
…which in turn would tell me to make this correction:

I wish this kind of check could be added to C/AL, but I realise doing so would break a lot of existing applications. Perhaps a compiler warning would be nice? If you tell the compiler that your function has a return value, you’d better make sure it does on all code paths!
Consuming NAV Web Services Using PowerShell
February 22nd, 2013
Maybe you just want to test a NAV web service. Maybe you want to periodically carry out an unattended task (Windows Task Scheduler!) in NAV, but you can’t use the NAV Application Server (NAS), e.g. because the task uses .NET Interop, which can only be executed on the NAV Service Tier (NST). Or maybe you are using a version of NAV where the NAS doesn’t even exist anymore.
Whatever the scenario might be, in the past, when confronted with a challenge like this, unaware of any viable alternatives, I used to fire up the old Visual Studio IDE and code my web service calls in C#.
What I like about this approach is that Visual Studio transparently creates the web service proxy for you; what I strongly dislike about it is the fact the C# is a compiled language, meaning that ad-hoc changes will be more time consuming to make, and, even more importantly, we need to deal with two artefacts: the compiled assembly and the source code. Not all customers have a good place to store the latter safely. Wouldn’t it be great if we could do the same thing from, say, a scripting language, so that we we don’t need to deal with a separate set of source code files?
Here’s how to consume a NAV web service from PowerShell. Let’s start with a (very traditional) web service codeunit.

Next, we’ll publish the codeunit as a web service in Web Services page.

In a web browser, my services now look like this. Simply copy the URL for the desired service - we are going to need it in the next step.
Open a Windows PowerShell console, or start the Windows Powershell Integrated Scripting Environment (ISE). All we need to do now is to let PowerShell create the proxy for our web service.
That’s all there is to it - we can now start calling the web service methods.

Surprised to find that the method’s name is not HelloWorld(), but CallHelloWorld()? So was I. It appears to have something to do with a collision of names between the service (which receives its name for the Web Services page in NAV) and the method (whose name matches the function name in the codeunit in which it is implemented). After I renamed the web service in NAV (see below), things looked as expected (even further below).

DotNet Interop, RunOnClient and WithEvents
February 5th, 2013
And it sounded so promising… Using the FileSystemWatcher .NET class from a NAV 2013 page object to detect changes in a file system folder. I had it all figured out: using the right .NET assembly, responding to the FileSystemWatcher’s events and making sure the object runs on the RTC (as opposed to on the Service Tier).

In the OnOpenPage trigger, I instantiated the FileSystemWatcher, told it to monitor my desktop folder (Environment is another .NET Interop variable, also running on the client), and started the event raising mechanism.

The code above, which worked just fine in a Visual Studio console application, does not produce any results in NAV, i.e. does not trigger any of the events below.

Any ideas, anyone?
On-line help for variables of custom DotNet type?
January 31st, 2013
I have developed and documented a .NET assembly for use from within Dynamics NAV, and I was hoping to be able to assist callers of my API by providing them with context-sensitive on-line help. Does anybody know if NAV 2009R2 or 2013 can display context-sensitive (= F1) help in the Symbol Menu for members of custom .NET types? If so, what are the requirements for the file format, file name and file location of the help file?
P.S.: Running the client with ShowHelpID=1 does not give any indication that this may be supported at all.
[Question also posted on mibuso.com and stackoverflow.com]
On Concentrating INSERTS
January 17th, 2013
Part of the same C/SIDE application as the one described in the previous post was a codeunit that inserts records into two tables based on records in an entire tree of other, mutually 1:n-related tables. Think of the two tables as a summary for the data in the other tables.
In the current implementation, the codeunit contains a function that accepts the “root” of the tree as a parameter, and calls the same function for each of it’s children. That same function in turn calls another function for each of the child’s children, etc. Inserting the summary records was done from each of the functions. In pseudo-code, it looked something like this:
VAR
SummaryRecord
function HandleRootLevel(Root)
{
SummaryRecord.Field1 := "Foo";
SummaryRecord.Field2 := "Baz";
SummaryRecord.INSERT;
foreach Child of Root
{
HandleChildLevel(Child);
}
}
function HandleChildLevel(Child)
{
SummaryRecord.Field1 := "Bar";
SummaryRecord.Field2 := "Qux";
SummaryRecord.INSERT;
foreach Child of Child
{
HandleGrandChildLevel(Child);
}
}
function HandleGrandchildLevel(Grandchild)
{
SummaryRecord.Field1 := "Quux";
SummaryRecord.Field2 := "Quuux";
SummaryRecord.INSERT;
}
In situations like this, I tend to follow a different pattern. Let me show it first, then explain why I do.
function HandleRootLevel(Root)
{
InsertSummaryRecord("Foo", "Baz");
foreach Child of Root
{
HandleChildLevel(Child);
}
}
function HandleChildLevel(Child)
{
InsertSummaryRecord("Bar", "Qux");
foreach Child of Child
{
HandleGrandChildLevel(Child);
}
}
function HandleGrandchildLevel(Grandchild)
{
InsertSummaryRecord("Quux", "Quuux");
}
local function InsertSummaryRecord(Field1Value, Field2Value)
{
VAR
SummaryRecord
SummaryRecord.Field1 := Field1Value;
SummaryRecord.Field2 := Field2Value;
SummaryRecord.INSERT;
}
Not much difference, you may say. Just a function that handles the inserts. Here’s why I think the second snippet is easier to understand and maintain.
- First of all, there’s the DRY principle. That one speaks for itself, I guess. The first snippet contains some duplicate code.
- A call to InsertSummaryRecord tells readers what the code will do, without bothering them with the how, leaving the function a black box until the reader decides she’s interested in what’s inside the function. As a result, I find the second snippet cleaner and more expressive.
- The SummaryRecord variable in InsertSummaryRecord is local, which means it automatically gets initialised every time the function is called, thus making sure that previous INSERTS cannot have any unwanted side effects.
- Whenever a new field is introduced in the SummaryRecord table, just add it as a parameter to InsertSummaryRecord and the compiler will point out (at design-time) what other parts of the code require modification in order to populate the new field.
- Finally, imagine you need to debug the code (particularly, in pre-2013 NAV). Setting a breakpoint on all the codelines that insert SummaryRecords certainly gets easier if there’s just one.
I’m curious to hear what you think! Thanks for reading & responding! ![]()
On the Importance of Separating Business Logic and Presentation Logic
January 16th, 2013
Earlier today, after manually carrying out a bug’s repro scenario for - what seemed like - the tenth time, I decided to automate the preparation steps, so that I could quickly and consistently try a few different scenarios. I didn’t use a real test codeunit (in this sense of the word), although I could have; my idea was to create a simple script that just inserted some records and called some functions on them.
The first few steps were easy, but I quickly ran into a situation where a function would do some things, then display a form, wait for the user to enter certain information and press OK, before carrying on to process the user’s input. The tight entanglement between presentation logic (displaying the form) and business logic (processing user input) left me with two (equally disadvantageous) choices: painstakingly copying only the relevant bits of business logic code to my codeunit, or calling the function as is, and accepting a few manual steps (namely, filling out the form) in my repro script. I opted for the latter, grateful for this excellent reminder of the importance of separating business logic and presentation logic, also (in this case) for testability reasons.
For functions that need to mix e.g. user input with business logic, I think the posting codeunits in NAV show us a great pattern. There’s a “silent”, business-logic-only codeunit for posting, which can be used directly from code. In addition to that, there’s a codeunit that calls our business-logic-only codeunit, after e.g. asking the user for confirmation - this is the codeunit that you typically call from the application’s user interface. Note that the business logic lives in exactly one place (as it should), and, with a bit of dependency injection, should be perfectly equipped for automated testing and other forms of scripting.
C/SIDE Application Pattern: Encapsulate a Temporary Record Buffer in a Codeunit/Override OnFindRecord & OnNextRecord
October 18th, 2012
Intent
In the pre-Query-object-era, if the data you wanted to display on a form (or report) wasn’t readily available in any table, e.g. because the aggregation level of your actual data was lower than what you wanted to user to see, programmatically populating a temporary record buffer was a viable alternative. The object that started the form (or report) in question could prepare the buffered data and run the form “on” that buffer, or populating the buffer could be left to code inside the form itself. My personal preference is a third option, where the buffer is completely encapsulated in a codeunit; the codeunit has the required interface to function as the data source of the form.
Motivation
The integrity of the buffered data is safeguarded by the codeunit; the only way to manipulate the data is through the codeunit’s methods. The form is completely unaware of how the buffer is populated, and the same codeunit could be used from several different objects. Sorting and filtering settings applied on the form get passed to the codeunit, and affect how it returns its data, making the whole process completely transparent to end users.
Implementation
Let’s first define our table. For the sake of this example, I kept my table very simple.
OBJECT Table 90000 Test
{
OBJECT-PROPERTIES
{
Date=03-06-10;
Time=14:05:46;
Modified=Yes;
Version List=;
}
PROPERTIES
{
}
FIELDS
{
{ 1 ; ;Code ;Code10 }
{ 10 ; ;Description ;Text30 }
}
KEYS
{
{ ;Code ;Clustered=Yes }
}
FIELDGROUPS
{
}
CODE
{
BEGIN
END.
}
}
The form, too, is pretty self-explanatory. Note how the OnOpenForm trigger tells the codeunit (described later) to refresh its buffered data. The OnFindRecord and OnNextRecord delegate the actual work to their namesakes in the codeunit. The codeunit variable needs to be a global, since its state needs to be preserved for entire lifespan of the form.
OBJECT Form 90000 Test
{
OBJECT-PROPERTIES
{
Date=03-06-10;
Time=14:20:28;
Modified=Yes;
Version List=;
}
PROPERTIES
{
Width=9790;
Height=6710;
TableBoxID=1100525000;
SourceTable=Table90000;
OnOpenForm=BEGIN
Test.Refresh;
END;
OnFindRecord=BEGIN
EXIT(Test.OnFindRecord(Which, Rec));
END;
OnNextRecord=BEGIN
EXIT(Test.OnNextRecord(Steps, Rec));
END;
}
CONTROLS
{
{ 1100525000;TableBox;220 ;220 ;9350 ;5500 ;HorzGlue=Both;
VertGlue=Both }
{ 1100525001;TextBox;0 ;0 ;1700 ;0 ;ParentControl=1100525000;
InColumn=Yes;
SourceExpr=Code }
{ 1100525002;Label ;0 ;0 ;0 ;0 ;ParentControl=1100525001;
InColumnHeading=Yes }
{ 1100525003;TextBox;0 ;0 ;4400 ;0 ;HorzGlue=Both;
ParentControl=1100525000;
InColumn=Yes;
SourceExpr=Description }
{ 1100525004;Label ;0 ;0 ;0 ;0 ;ParentControl=1100525003;
InColumnHeading=Yes }
{ 1100525005;CommandButton;2530;5940;2200;550;
HorzGlue=Right;
VertGlue=Bottom;
Default=Yes;
PushAction=LookupOK;
InvalidActionAppearance=Hide }
{ 1100525006;CommandButton;4950;5940;2200;550;
HorzGlue=Right;
VertGlue=Bottom;
Cancel=Yes;
PushAction=LookupCancel;
InvalidActionAppearance=Hide }
{ 1100525007;CommandButton;7370;5940;2200;550;
HorzGlue=Right;
VertGlue=Bottom;
PushAction=FormHelp }
}
CODE
{
VAR
Test@1100525000 : Codeunit 90000;
BEGIN
END.
}
}
In this example, the state mentioned above consists exclusively of the contents of the TestBuffer variable in the codeunit. The Refresh method (which, as you may recall, is called from the form’s OnOpenForm trigger), inserts some demo data into the record buffer.
OBJECT Codeunit 90000 Test
{
OBJECT-PROPERTIES
{
Date=03-06-10;
Time=14:18:45;
Modified=Yes;
Version List=;
}
PROPERTIES
{
OnRun=BEGIN
END;
}
CODE
{
VAR
TestBuffer@1100525000 : TEMPORARY Record 90000;
PROCEDURE Refresh@1100525003();
BEGIN
TestBuffer.Code := 'FOO';
TestBuffer.Description := 'Foo';
TestBuffer.INSERT;
TestBuffer.Code := 'BAZ';
TestBuffer.Description := 'Baz';
TestBuffer.INSERT;
END;
PROCEDURE OnFindRecord@1100525000(Which@1100525000 : Text[1024];VAR Test@1100525001 : Record 90000) Found : Boolean;
BEGIN
TestBuffer.COPY(Test);
Found := TestBuffer.FIND(Which);
IF Found THEN
Test := TestBuffer;
END;
PROCEDURE OnNextRecord@1100525001(Steps@1100525000 : Integer;VAR Test@1100525001 : Record 90000) ActualSteps : Integer;
BEGIN
TestBuffer.COPY(Test);
ActualSteps := TestBuffer.NEXT(Steps);
IF ActualSteps <> 0 THEN
Test := TestBuffer;
END;
BEGIN
END.
}
}
Things I Learned at the NavTechDays 2012 in Antwerp (Part II)
October 8th, 2012
Continued from this post. Again - in random order, grouped by subject. Corrections etc. are more than welcome!
Reporting
- Renames in a report’s logical design (column layout) are now automatically propagated to the RDLC.
- Saving as a Word document and the tablix control are some of the new features introduced by Report Viewer 2010.
- Parameters (RDLC) and labels (NAV) to be used for static labels (e.g. non-multi-language). Define field captions and text constants as normal columns. Columns respect the CaptionClass property.
- Server-side printing is now supported. The account under which the NST runs needs sufficient permissions to print to the network printer. Any errors during printing are stored in the Session Event table.
- Tray selection is now supported. Function ID 75 in codeunit 1 allows fine-grained programmatic control of which tray should be selected.
- From the request page, reports can be saved as PDF, Word or Excel. From the About this Report page, you can also save the dataset as XML.
- Request page transformation is still a manual process. No new form transformation tool is shipped with NAV 2013.
- More information on the report upgrade on MSDN. Note to self: find out what TextFormatUpgrad2013.exe does exactly.
Project Management
- The primary reason for recent changes in project management at MDCC was the time it took to ship NAV 2009 R2.
- Since then, features are designed, developed, tested and documented by so called feature crews - small, autonomous, multi-disciplinary teams.
- The so called glide path describes the shape of the chart that displays the remaining work for a release.
- Focus is now on a fixed release rhythm, with a variable scope.
- Internally at MDCC, SCRUM/Kanban (reminded me of Trello) is used with a 2 week cadence.
- Avoiding queues and getting fast feedback is supposed to speed up the development process.
- Instead of a huge overall design, the whole process starts with user stories: something a user can do. A user story is no a specification, but rather the starting point for a discussion within the feature crew.
- Acceptance test driven development - before you start, agree on what is a good enough result.
- IMHO, NAV isn’t particularly forgiving when you need to refactor - asked the session’s presenters for some best practices.
- Theoretically, every nightly build could be shipped. The gated check-in procedure should normally keep most functional and technical errors out of the repository.
- Never carry debt. Not sure how, though? Does this mean that you reevaluate the urgency of any “debt” before including it in the new sprint?
- User assistance (on-line help content) is written in parallel with the software it documents.
- Spike deliverable: invest time getting to know the domain, no required deliverables apart from extra knowledge.
- Developers, testers and PMs work together and are able to temporarily take over each other’s work if necessary.
- Next version of NAV will be codenamed Sicily. Nice to know! What if they run out of Mediterranean islands?
Blocks and deadlocks
- Locks are merely flags; blocks are incompatible combinations of locks (two parties requesting the same kind of access to the same resource at the same time).
- To reduce the probability of conflicting locks, database systems use the smallest granularity possible.
- Keeping track of locks uses a lot of RAM; lock escalation occurs to keep memory usage within acceptable limits.
- NAV previously used the “read uncommitted” isolation level (potential for “dirty reads”); NAV 2013 uses “repeatable reads” ([theoretical] potential for “phantom reads”). “Serializable” is used in case of NAV “hard locking”.
- Normally, in case of deadlocks, the “simplest” victim is selected, i.e. lowest CPU-investment or the least number of data changes.
- Before doing a
DELETEALL, it makes sense to test for ISEMPTY, because it’s possible to (completely pointlessly) lock an empty table. Note to self: shouldn’t the platform take care of this?
DELETEALL, it makes sense to test for ISEMPTY, because it’s possible to (completely pointlessly) lock an empty table. Note to self: shouldn’t the platform take care of this?Things I Learned at the NavTechDays 2012 in Antwerp (Part I)
October 4th, 2012
In random order, grouped by subject. Corrections etc. are more than welcome!
Part II can be found here.
Clients
- Although they appear to fulfil a similar need, the web client and SharePoint client have different target audiences. As an example, the SharePoint client allows users to easily integrate NAV data in all kinds of workflows, and lets them combine NAV data with existing SharePoint data.
STARTSESSIONcan run in a session in the context of another company. As far as I could tell, this goes way beyond whatCHANGECOMPANYcan do, since the other company applies to the entire session, not just a single record variable.- Error messages from sessions started using
STARTSESSIONare stored in the Session Event system table. The job queue uses its own error handling instead. Add “My Job Queue” to your role center to easily monitor your jobs.
Data
- More than any previous version, NAV2013 allows users to combine data from different sources. This makes it even more important than before to follow standard coding schemes, such as ISO codes for countries and currencies.
- In the native database,
SETCURRENTKEYcould be used to select an index that matched the filter criteria, making it faster to retrieve data. Under SQLServer, surprisingly enough,SETCURRENTKEYmight slow things down, in cases where SQL needs to first collect the entire result set prior to sorting it (ORDER BY) and returning it to the service tier. - In NAV 2013,
CALCSUMSis supported for fields that do not exist as a SIFT Index. - The new datastack in NAV2013 is based on ADO instead of ODBC. ADO is maintained by the SQLServer team, and therefore more up-to-date with the latest SQLServer technologies.
- The NST now uses cross-user data caching for all Record API calls. Previously, the data cache was maintained per user. Note that Query results are not cached.
- Permission evaluation now takes place solely at NST level. The NST connects to the database using a single account, which means no more impersonation, no more Synchronize All Logins and no more xp_ndo stored procedures.
- NAV now uses Multiple Active Result Sets (MARS) instead of cursors, making
FINDandFINDSETequally fast. SQLServer now has more freedom to optimize its data retrieval. - Isolation level has been changed from Serializable to Repeatable Read.
- Due to the fact that the average NAV database contains a lot of Code and Text fields, the Unicode conversion is likely to make your database significantly larger.
- Some system tables, such as License Permission, cannot be used as source tables for Queries, possibly because they are virtual (simulated by the run-time) and don’t exist on SQLServer.
- Marks are now converted to a filter and sent to SQLServer, so that a result set based on marks behaves exactly the same as a result set based on filters (in cases where the filter length does not exceed a certain threshold). Previously, the NAV client would keep a list of marked records, which it retrieved one by one, resulting in a potentially differently sorted result set.
- Query objects support FlowFilters, but not all aspects: e.g. ValueIsFilter is not supported.
- The order in which tables appear in a Query object matters: SQLServer processes the query according to the so called nested loop plan, respecting the sequence of tables in the object. In the nested loop plan, dataitem #1 is combined with dataitem #2. The resulting dataset is then combined with dataitem #3. The resulting dataset from that operation is then combined with dataitem #4, etc.
- Buffered inserts (your C/AL code should ignore return value of
INSERTfunction) can seriously improve performance. - Client Monitor has been discontinued and replaced with SQL Callstack Trace (activated from the session list in the debugger). Results are available in the SQLServer Profiler.
INSERTs into system tables such as Object or Company are not longer supported.
- My expections are that the introduction of the query object will strongly decrease the number of cases where we denormalize our datamodel for performance reasons.
- FlowFields now respect security filters.
Application
- Great performance improvements in dimensions. Existing customisations that support dimensions may require some upgrade work, but new ones will likely be easier to develop.
- The absolute numbers are very small, but passing Record parameters by reference (VAR) is about 20x as fast as by value. Reference parameters are merely pointers, whereas passing by value requires the run-time to copy the entire record into the formal parameter. Filed under “nice trivia”…
Debugger and Code Coverage
- Since the debugger now allows developers to debug any NST session, you could even debug a session caught in an infinite loop.
- In the debugger page, placing a breakpoint on the blank line following a function will stop execution at that point, so that you can take a look at the return value. The best thing is that this will work even if the function in question has multiple
EXITstatements! - From what I understood, the “Break on Record Changes” in the debugger’s Break Rules also works for temporary records.
- Using the Code Coverage system table and
CODECOVERAGELOGfunction, automated test systems could measure code coverage.
Testing
- The sequence in which test functions inside a test codeunit are executed is not guaranteed, which is why it’s important to make the test functions mutually independent.
- The
ASSERTERRORkeyword can be used to catch any run-time errors. After that, useGETLASTERRORTEXT(in local language!) orGETLASTERRORCODE(non-localized) to verify the expected error was raised. The value space ofGETLASTERRORCODEis currently unclear to me. - Although it seems to be common practice in unit testing to do as little as you possibly can (e.g. assignments instead of
VALIDATEs,INSERT(FALSE)instead ofINSERT(TRUE)) to reproduce a certain scenario, I’d feel a lot safer carrying out all business logic, to make sure you’re seeing exactly the same as your customer does. - One of the hardest parts of automated testing must be keeping the unit tests in sync with the application under test, as the latter evolves.
- The new page testability features can dramatically improve your code coverage, especially for C/AL code programmed at page level.
- Although Microsoft appears to focus its efforts on unit testing, I feel partners will probably benefit most from page testing: for most add-ons, the interface (UI) doesn’t change as often as the implementation does, thus making page testing the more robust and stable choice.
- Primarily because of the overhead involved in instantiating test pages, page testing is significantly slower than unit testing (which typically only involves executing C/AL).
- Ease of communication between functional and technical specialists (~test scenario authors and test engineers) might be another argument in favour of UI-level testing: it’s easy to describe which steps to take, which input to provide, and which output to expect.
- The TestIsolation property of TestRunner codeunits is really the greatest thing since sliced bread, as it allows you to restore the database to its original state even if the code under test contains
COMMITs.
File Names in NAV: Text[250] vs. Text[260] vs. Text[1024]
October 4th, 2012
When comparing two revisions of an object, I noticed how the developer had changed the data type of global variable FileName (aptly named, i.e. used for storing a file name) from Text[250] to Text[1024]. Even in those rare cases that a file name length should exceed 250 characters, using Text[1024] is clearly overkill - assuming NAV uses the Win32 API for interacting with the file system, the MAX_PATH limitation of 260 characters applies.
P.S.: I tend to use Text[260] even as a hint that a variable is designed to contain a file name.
My MSConnect Suggestion: Importing and exporting translation files from finsql command line in NAV 2013
October 3rd, 2012
I’m curious to hear what you think of the following suggestion, which I submitted on MSConnect. I’d appreciate it a lot if you could add your vote or comment on MS Connect, or, if you don’t have access, in the comments below.
Finsql.exe’s command line in NAV2013 supports a number of additional commands to interact with the Object Designer: CompileObjects, ImportObjects, ExportObjects and DesignObject. For e.g. automated build systems, these commands could compensate nicely for the termination of the IMPORTOBJECTS and EXPORTOBJECTS C/AL functions.
In addition to importing objects, such build systems typically also export and import translation files, for which no elegant solution is currently available. If I recall correctly, Microsoft’s internal tools do support this function, and I believe this feature has, at some point, been part of NAV2013’s scope.
I’m curious to hear what you think of the following suggestion, which I submitted on MSConnect. I’d appreciate it a lot if you could add your vote or comment on MS Connect, or, if you don’t have access, in the comments below.
Ideally, functions that are part of the interface of an object (i.e. functions that render a service to other objects) are global (Local=<No>), whereas any implementation functions (not to be called by other objects) are local (Local=Yes). We try to improve the maintainability and discoverability of our code by making any non-interface function local.
Finding out exactly which functions are local is a tedious job in the current C/AL Editor: keeping the Properties window open while browsing through the functions list, verifying that each function has the right “visibility”.
It would be great if the gray function headers in the C/AL Editor, which now show something like this:
MyLocalFunction()
would show something more similar to the text export format, e.g.
LOCAL MyLocalFunction()
Please note that “modifiers” such as TEMPORARY and VAR are already shown in a similar fashion in these function header bars.
Having Trouble Remembering the Dynamics NAV 2013 Hyperlink Syntax?
September 24th, 2012
If, like me, you don’t use NAV hyperlinks too often, and therefore find yourself looking up the right syntax on MSDN every time you do use them, you may want to take a look at my Dynamics NAV 2013 Hyperlink Builder. It’s an on-line, Javascript-based tool for building valid NAV 2013 hyperlinks. It should look OK in most modern browsers, and (if supported/permitted) will use HTML Local Storage for remembering your selections.
Any feedback in the comments below would be much appreciated!
Tristate
September 13th, 2012
Most values in C/SIDE’s Properties window, that, to the casual observer, may appear to be booleans, are in fact so-called tristate values: in addition to “true” and “false”, they have a third state that, in C/SIDE’s lingo, indicates that the property is set to its default value. Until now, the textual representation of such a default value consisted of the default boolean value, surrounded by angled brackets, like so:<Yes>.
I was a bit surprised to find that, in Page Properties, two adjacent tristate property values appear to have a different way of representing their default state. Either one has No and Yes as possible choices in the dropdown list, but PopulateAllFields displays its default state as <Undefined>.

Could PopulateAllFields’ data type somehow be subtly different from LinksAllowed’s?
Subtle Improvements in NAV2013: Variable Data Type Order
September 7th, 2012
Seasoned NAV developers probably don’t even notice anymore how odd and unintuitive the order of variable data types in NAV 2009 R2 is:

NAV2013 (Beta Refresh) lists the data types in alphabetical order:

(And yes, I know that virtually everybody selects a data type by typing the first few characters of its name, instead of selecting it from the list – that’s not the point here!
)
See You at the Nav TechDays 2012
August 18th, 2012
I hear there are still tickets for the NAV TechDays 2012 in Antwerp (Belgium) on 27 and 28 September. Get yours while you can - this is an event you don’t want to miss! The subjects of the very in-depth sessions, as copied from the website:
- NAV on Azure
- What’s new for data access in NAV2013
- Web client & NAV portal framework
- Dynamics NAV 2013 Reporting Story
- New server capabilities
- Scrum introduction (and how it’s used by the Microsoft Dynamics NAV team)
- What is new in RTC (Filter variables,Grouping and Grid layout)
- C/AL coding for performance in NAV2013
- Writing C/AL unit tests efficiently with help of new tools
- Best practices in development and design patterns
- Web Services Black Belt – tips & tricks from the (battle)field
- Understanding Blocks & Deadlocks – Theory and Practice
Oh, and did I mention it’s also a great networking opportunity?
See you in Antwerp!

