Exception handling in PU31

In 2018, I wrote the blog post Throwing managed exceptions from X++ in D365FO, where I pondered upon how throwing proper exceptions objects in X++ would be beneficial. This is still true. I also showed a proof of concept how it can be done despite the fact that X++ doesn’t directly support it. But this has changed! Platform Update 31 has introduced the possibility to throw managed (CLR) exceptions directly with the throw statement.

Therefore you can now do things like this right from X++:

throw new System.ArgumentException('A value must be set', 'FromDate');

Then you can react to this particular type of exception and you’ll also get a lot of context, such as which argument is wrong. For example:

System.ArgumentException argEx;
catch (argEx)
    warning(strFmt("Please provide a valid value for the parameter '%1'.", argEx.ParamName));

If you want to define your own exception class (e.g. FieldEmptyException from my previous blog post), you still can’t do it in X++ – you need a C# project or something. And it’s probably not going to change. But it shouldn’t be a big problem, because working with C# projects in D365FO is very easy.

PU31 added one more ability of throw. Imagine that you want to log an exception, but you don’t want to handle it. You catch it, log it and then you can throw another exception. But this new exception won’t have the same properties as the original one, e.g. its stack trace will show that it was thrown from your catch clause. To rethrow the same exception, use throw without any argument. Like this:

catch (ex)

Compare records in code

When saving a record, I had to check which fields had changed and react in a special way if only certain fields (and not any other) changed their value.

I could iterate all fields and compare their values in two table buffers (the original and the updated one), but I thought that D365FO might already have such logic. And indeed it has: in VersioningCompareRecordVersions class. (It exists in AX 2012 too and maybe even in older versions.)

The following example prepares two slightly different records, compares them and shows which fields differ:

// Prepare records to compare
PurchLine origLine;
PurchLine modifiedLine;
select firstonly origLine;
modifiedLine.PurchQty += 1;
modifiedLine.Name += " updated";
// Compare records
var comparer = VersioningCompareRecordVersions::newTableId(tableNum(PurchLine));
container changes = comparer.packChangedFields(VersioningChangeType::Updated, origLine, modifiedLine);
// Show modified fields
for (int i = 1; i <= conLen(changes); i++)
    container changedField = conpeek(changes, i);
    FieldId fieldId = conpeek(changedField, VersioningCompareRecordVersions::posRelatedFieldId());
    info(fieldId2name(tableNum(PurchLine), fieldId));

There is more what you can do with VersioningCompareRecordVersions, and there are other related classes as well (such as VersioningComparePurchLine), but this is a good starting point.

Thoughts on element prefix vs. suffix

When I read Evaldas Landauskas’s blog post Development guidelines: Prefix Vs. Suffix, I thought I would write a comment below the post and share a few ideas, but then I decided that it’d be better to write my own blog post. Here I have a much better control over formatting and the content will be more visible than if it’s hidden in a mere comment.

I’m not going to dispute the argument that prefixes are better; I’ve seen teams using various approaches and I’m fine with most of them. On my current projects, we also use prefixes for most things. But I want to add a few more things to consider.

The statement that you can’t search objects by suffix because you can’t handle those ending with _Extension is underestimation of regular expressions. For example, if I want to find elements ending with either Xyz or Xyz_Extension, I can use Xyz(_Extension)?$. Is it complicated? Yes, a bit. Is it impossible? Definitely not.

Nevertheless are we doing the right thing in the first place? Isn’t our goal to find elements in our model? If so, searching by a part of name doesn’t really match our intention. We should model:”XYZ project” instead.

Finding all elements starting with a company prefix is nice, but it’s not what people usually need. A more common task is finding extensions of an existing element, such as a table. I think this should be really easy, because it’s both common and important, but we lack really good tools for that. As a workaround, some people choose starting names of extension classes with the name of the original object, which allows them to see all extensions together. We can use regular expressions again to deal with prefixes as well, such as searching for something like type:class SalesTable.*_Extension$, but any solution based on name simply isn’t good enough, in my opinion. And references aren’t great for this purpose either. But we have to live with what we have, or to build better tools.

Changing the topic, I’m also not sure where this statement came from: “We should follow Microsoft’s pattern and suffix it as well.”. As far as I know, Microsoft doesn’t address vendor prefixes/suffixes at all and general naming conventions say that “A subject area specific application object is prefixed with the name of the subject area the object belongs to, for example Cust*, Invent*, Ledger*, Proj*, Vend*”. Therefore names like RevRecAmountPercentList look correct to me.

We surely can find many places where Microsoft isn’t following best practices, just note that SalesFormLetter extending FormLetterServiceController is a special case. It used to extend FormLetter class, but it has changed when SysOperation framework was introduced. One could argue that the name should have been FormLetter_Sales instead of SalesFormLetter, but naming conventions say that it can be the case and not that it must.

I would like to thank Evaldas for giving me something to think and write about. 🙂

Security API

The product that used to be called Microsoft Dynamics 365 for Finance and Operations (I truly don’t know how I should call it these days) allows defining security elements both by developers (delivered together with code) and by power users though GUI (stored in database). You may have a need to work with security objects in code and you want to get information from both sources. An API providing a unified view would be handy.

I’m not going to cover it in detail, but let me show you an example: an iteration of security privileges and getting information about security permissions contained in privileges:

using Microsoft.Dynamics.AX.Security.Management.Domain;
class SecurityApiDemo
    public static void main(Args _args)
        var privilegesRepo = SysSecurity::GetSecurityRepository().Privileges;
        var privEnumerator = privilegesRepo.LoadAll().GetEnumerator();
        while (privEnumerator.MoveNext())
            Privilege privilege = privEnumerator.Current;
            setPrefix(strFmt("Privilege %1", privilege.Name));
            var grantEnumerator = privilege.ActionMenuItemGrants.GetEnumerator();
            while (grantEnumerator.MoveNext())
                MenuItemGrant grant = grantEnumerator.Current;
                info(strFmt("%1 (%2)", grant.Name, grant.Grant.ToString()));

Apart from ActionMenuItemGrants, there are also DisplayMenuItemGrants, OutputMenuItemGrants, DataEntityGrants, ServiceOperationGrants and DataModelGrants.

Beta exam MB-500

Today I took the beta exam MB-500: Microsoft Dynamics 365: Finance and Operations Apps Developer; I think it’s going to be made generally available on January. I was already planning to take it when I received an email with a significant discount, therefore I had no reason to hesitate.

They didn’t show me my score – they’ll do it „within two weeks after the exam’s live publication date“. But I don’t think I have to worry…

I usually dislike many exam questions – they’re often vaguely formulated, outdated, off the exam topic, testing things that are easy to test but irrelevant and so on.

Questions of this exam are firmly on topic – I don’t remember any where I would question whether the topic itself is relevant. But there are surely things to improve.

Here a few examples:

All answers of one question mention an action in GUI which simply doesn’t make sense, so technically neither answer is correct. It’s clear that the actual question was about something else, but it may be confusing and it doesn’t look very professional.

In one lab, the ALM process described there doesn’t make a good sense to me and it goes directly against Microsoft best practices, therefore I don’t think it’s a good idea to put it there. Then you must answer questions not based on how things should be done in practice, but what fulfills the artificial restrictions – and hope that authors of the exams didn’t forget about them when defining “correct” answers.

In one question, I was asked to implement a very common piece of code, but the options didn’t include any of the ways how it’s normally done. What I assume is the right answer is technically correct, but nobody would ever do it and I don’t believe that many people remember the method – I didn’t it. I’m convinced that this isn’t what exams should test.

I also found some basics mistakes in code, such as incompatible types that would cause failure already on compilation, or answers that can be correct or not depending on context (which wasn’t provided).

After taking an exam, there is time for evaluation and I was keen to cover all these points in detail there. But the time allocated was completely insufficient; then it simply kicks you off. And when I got a warning popup that I had only one minute left, the UI became unresponsive and I was unable to complete at least the thing I writing at the moment. Effectively, I got even one less minute that intended.

It sounds pretty strange to me – you invite somebody to take a beta exam and give you feedback, but they you make providing detailed feedback impossible. The time slot for evaluation should be longer – even for regular exams, but especially for beta exams that should be all about collecting feedback. And a bit more review of technical correctness wouldn’t harm either.