HcmWorkerV2

I was asked to investigate why some changes disappeared from Employess form in F&O. If I open Human resources > Workers > Employees and right-click the form, it shows that its name is HcmWorker.

That’s expected. But it’s a lie.

There is a feature called Streamlined employee entry (HcmWorkerV2Feature class) and if it’s enabled, another form (HcmWorkerV2) opens instead of HcmWorker.

Microsoft implemented the logic in init() method of HcmWorker form. If the feature is enabled, HcmWorkerV2 opens and HcmWorker is closed.

if (HcmFeatureStateProvider::isFeatureEnabled(HcmWorkerV2Feature::instance()))
{
    this.redirectToHcmWorkerV2();
    this.lifecycleHelper().exitForm();
}

But the logic that shows Form information isn’t aware of it for some reason; it knows that we’re opening HcmWorker and it believes it’s really been opened.

By the way, if I press F5 and reload the form, Form information starts showing the right form name.

If we want our customizations to work in both cases (with the feature enabled or disabled), we need to change both forms.

By the way, if you want to read more about the new form, here is a documentation page: Streamlined employee navigation and entry.

Method as a parameter (in D365FO)

We usually have methods where the logic is written in code and it just receives some data in parameters. But sometimes it’s benefitial to allow the caller code to define a piece of logic by itself.

For example, I can write code to filter a collection and let consumers decide what condition should be used for the filter. This kind of things is heavily used in LINQ in .NET.

Or I may have a piece of code to select records from database and perform some actions on them, and while the selection logic is the same, the actions that callers may want to perform are unknown to me. Therefore I could create a method that fetches the data and then takes the actual logic as a parameter.

Let me demonstrate it on a simple example. I’ll have a collection of data (a Set object) and I want an easy way to run an action on each of the elements.

Therefore the iteration of elements will be done by my code, but the actual action will be provided as a method parameter.

To make the method easily discoverable, I’ll add it to the Set class as an extension:

[ExtensionOf(classStr(Set))]
public final class My_Set_Extension
{
    public void forEach(System.Delegate _action)
    {
        SetEnumerator enumerator = this.getEnumerator();
        while (enumerator.moveNext())
        {
            System.Object[] parameters = new System.Object[1]();
            parameters.SetValue(enumerator.current(), 0);
            _action.DynamicInvoke(parameters);
        }
    }
}

I’m using a .NET class called System.Delegate as the parameter, because X++ as such doesn’t have any appropriate type. It’s the beauty of the interoperability of X++ with .NET – we can benefit from the whole .NET platform.

The actual iteration of the collection is straightforward. To execute the provided method, we use DynamicInvoke(), which expects an object array with method parameters. We have a single parameter (the element of the collection), therefore we create an array of size 1, put the element there and pass the array to DynamicInvoke().

Before we call our forEach() method, let’s create a method preparing some test data for us:

Set getData()
{
    Set set = new Set(Types::String);
    set.add('Primus');
    set.add('Secundus');
    set.add('Tertius');
    return set;
}

And this will be a method that we want to call for each element:

void reverseAndShow(str _s)
{
    info(strReverse(_s));
}

Because we’ll iterate a collection of strings, the parameter type is str.

Now we need to define a delegate, i.e. the type for our method. The declaration of a delegate in X++ looks like a method declaration, but it’s a very different thing under the hood. We’ll basically get a variable, a type of which is a class (generated under the hood, inheriting from System.Delegate) that can hold references to methods with a particular list of parameters and a return type.

Our method accepts a string and returns void, therefore we need a delegate with the same profile.

delegate void actionDelegate(str _s)
{
}

Now it’s time to make the call:

void run()
{
    this.actionDelegate += eventhandler(this.reverseAndShow);
 
    Set set = this.getData();
    set.forEach(this.actionDelegate);
}

We use the newly created delegate and add the reverseAndShow() method to it by += eventhandler(…).

Then we get the Set with data, call its forEach() method and pass the delegate to it. If you remember, we declared the parameter of forEach() as System.Delegate, which is the parent of what we get from this.actionDelegate.

If you run it, you’ll see that it indeed iterates the Set and calls reverseAndShow() for each element.

A nice feature of delegates is that they can hold references to several methods at once, and invoking the delegate executes all the methods. Let’s add one more method to our solution:

void toUpperAndShow(str _s)
{
    info(strUpr(_s));
}

Then we’ll simply add one more delegate subscription to run() method – no other change is needed.

void run()
{
    this.actionDelegate += eventhandler(this.reverseAndShow);
    this.actionDelegate += eventhandler(this.toUpperAndShow);
 
    Set set = this.getData();
    set.forEach(this.actionDelegate);
}

Now both methods are executed for every element of the collection.

For your convinience, here is a complete class that you can copy and run.

internal final class DelegateDemo
{
    public static void main(Args _args)
    {
        new DelegateDemo().run();
    }
 
    void run()
    {
        this.actionDelegate += eventhandler(this.reverseAndShow);
        this.actionDelegate += eventhandler(this.toUpperAndShow);
 
        Set set = this.getData();
        set.forEach(this.actionDelegate);
    }
 
    Set getData()
    {
        Set set = new Set(Types::String);
        set.add('Primus');
        set.add('Secundus');
        set.add('Tertius');
        return set;
    }
 
    delegate void actionDelegate(str _s)
    {
    }
 
    void reverseAndShow(str _s)
    {
        info(strReverse(_s));
    }
 
    void toUpperAndShow(str _s)
    {
        info(strUpr(_s));
    }
}

Note that DynamicInvoice() is slower than direct method calls. It’s typically not a problem, but you need to keep it in mind if you’re writing performance-critical code that makes a lot of such calls.

The power of well-designed code

My previous blog post reminded me a refactoring that I did on one of my previous projects (in AX 2012).

I needed to adjust a piece of logic and found that it’s implemented as a method with more than two thousand lines. Of course, nobody could understand what’s going on there. Such code in unmaintainable.

After exploring it and comparing parts of the method, I realized that it consists of many almost identical blocks of code. There where select statements with slightly different conditions, otherwise the code was the same. The developer clearly didn’t know how to write the queries dynamically and also didn’t think about extracting the remaining code to a method to be called from each place. When he needed a new condition, he put the current method body to an if block, duplicated the whole method body in the else block and changed a few things in where clauses.

Because no one could understand the method, there were even two more copies of the whole method, when someone needed a slightly different behavior and didn’t dare to adjust this enormous method for another scenario.

The logic was somewhat similar to what I described in my previous blog post. We ran a specific query to find some data and tried to do something with it. If it didn’t give us the expected result, we tried a slightly more generic query and then even a more generic one, and so on. You can imagine it as finding a discount for a particular customer and item, then trying to find it for an item and a group of customers etc. (The real scenario isn’t important and I don’t even remember it well anymore.)

What I did was extracting the logic to a method that took a query as a parameter. Then I created a collection of Query objects and executed them one by one, always checking the result.

The queries were created by methods, each with a name describing the business scenario. Therefore the code creating the list of queries looked like this:

add(this.findByItemAndCustomer());
add(this.findByItemAndCustGroup());
add(this.findByItemOnly());
... approximately ten queries in total ...

Then I easily added my logic and I asked a business consultant to verify that we have the right set of rules and we execute them in the right order. Although he wasn’t familiar with X++, he was pleasantly suprised to learn that he actually could read the code and understand what it does – and therefore review whether it meets business requirements.

So… we got from a method that even no developer could make sense of, to something that even non-programmers can understand.

Dynamic list of methods to execute (in D365FO)

Imagine that you’re running a set of payment matching rules or you’re trying to find discounts based on various criteria.

You’ll try one way to find the data, if it doesn’t bring anything, you try another one, and so on.

A typical implementation looks like this:

found = trySomething();
 
if (!found)
{
    found = trySomethingElse();
}
 
if (!found)
{
    found = tryYetAnotherThing();
}

It’s straightforward and it works reasonable well as long as the list is static. But what if you need more flexibility? For example, maybe you want the caller code to decide which methods should be used (e.g. just those selected by a user) and in which order. Then this approach doesn’t really work.

A classic object-oriented solution would be creating a separate class for each method – each class would represent a single strategy for performing the task. These classes would implement a common interface, which would allow working with them in a polymorphic manner. Then you could easily create a collection of such classes and run them one by one.

This approach is fine and I would recommend it in more complex scenarios, but if the logic is simple, creating an extra class for each method may be cumbersome. Isn’t there any alternative?

A solution may be a collection of delegates. A delegate is basically a reference to a method (or more methods at once, because delegates can be combined together, but that’s not what we need today). In F&O, they’re typically used to allow extensions, but their potential usage is much broader.

Instead of creating a separate class for each method, we’ll create a single class representing a strategy to do something – in this case, to find a discount.

public final class DiscountFinderStrategy
{
    delegate void findDiscountDelegate(ItemId _itemId, DiscountFinderResult _result)
    {
    }
 
    public void execute(ItemId _itemId, DiscountFinderResult _result)
    {
        this.findDiscountDelegate(_itemId, _result);
    }
}

I thought that I could call the delegate directly from other classes, but it doesn’t work. That’s why I added execute() method, which does nothing but calling the delegate.

Here are three methods that we’ll want to use for the price search:

void findDiscountForItem(ItemId _itemId, DiscountFinderResult _result)
{
    info("Finding discount for specific item...");
    if (_itemId == 'aaa')
    {
        _result.parmFound(true);
        _result.parmDiscount(5);
    }
}
 
void findDiscountForItemGroup(ItemId _itemId, DiscountFinderResult _result)
{
    info("Finding discount for item group...");
}
 
void findDefaultDiscount(ItemId _itemId, DiscountFinderResult _result)
{
    info("Finding default discount...");
 
    if (System.DateTime::Now.DayOfWeek == System.DayOfWeek::Monday)
    {
        _result.parmFound(true);
        _result.parmDiscount(1);
    }
}

They don’t do anything real; their main purpose is adding messages to infolog, so we can see that they were called.

findDiscountForItem() returns a result for item aaa, therefore subsequent methods won’t be called. We’ll test it later.

The discount in findDefaultDiscount() is there just to make Mondays more bearable for you. 😉

Delegates in CLR can return values and if it was possible in F&O, I could return the result directly from each method. Unfortunately delegates in F&O allow only void return type, most likely because they were designed to be used as events in particular and not as general-purpose delegates. But it’s not a big deal – we can make a workaround by using an object as a parameter and set the values there. That’s the purpose of my DiscountFinderResult class.

public class DiscountFinderResult
{
    boolean found;
    real discount;
 
    public boolean parmFound(boolean _found = found)
    {
        found = _found;
        return found;
    }
 
    public real parmDiscount(real _discount = discount)
    {
        discount = _discount;
        return discount;
    }
}

To call my three methods, I’ll create three instances of DiscountFinderStrategy class and subscribe one method to each.

DiscountFinderStrategy findByItem = new DiscountFinderStrategy();
findByItem.findDiscountDelegate += eventhandler(this.findDiscountForItem);
 
DiscountFinderStrategy findByGroup = new DiscountFinderStrategy();
findByGroup.findDiscountDelegate += eventhandler(this.findDiscountForItemGroup);
 
DiscountFinderStrategy findDefault = new DiscountFinderStrategy();
findDefault.findDiscountDelegate += eventhandler(this.findDefaultDiscount);

By the way, I could add all three methods to a single delegate, but then all of them would execute (unless an exception is raised). That’s not what I want in this case. I want to run once, check the result, run the other one and so on.

Then I need something that will actually execute the methods. I’ve designed DiscountFinder class for this purpose:

public final class DiscountFinder
{
    List strategies = new List(Types::Class);
 
    public void addStrategy(DiscountFinderStrategy _strategy)
    {
        strategies.addEnd(_strategy);
    }
 
    public real findDiscount(str _itemId)
    {
        setPrefix(strFmt("Item %1", _itemId));
 
        DiscountFinderResult result = new DiscountFinderResult();
        ListEnumerator enumerator = strategies.getEnumerator();
        while (enumerator.moveNext())
        {
            DiscountFinderStrategy strategy = enumerator.current() as DiscountFinderStrategy;
            strategy.execute(_itemId, result);
 
            if (result.parmFound())
            {
                return result.parmDiscount();
            }
        }
 
        return 0;
    }
}

It holds an ordered collection of DiscountFinderStrategy objects in strategies variable. Caller code can add them by calling addStrategy() method. findDiscount() iterates the collection, calls execute() method (which runs the delegate and therefore the subscribed method) and checks the result. If a result was found, the execution ends and a discount is returned. If not, the next strategy to find a discount is used.

Finally, here is an example of the caller code, implemented as a runnable class:

internal final class DiscountFinderDemo
{
    public static void main(Args _args)
    {
        new DiscountFinderDemo().run();
    }
 
    void run()
    {
        setPrefix("Discount search");
 
        DiscountFinder discFinder = this.createDiscountFinder();
 
        discFinder.findDiscount('aaa');
        discFinder.findDiscount('bbb');
 
    }
 
    DiscountFinder createDiscountFinder()
    {
        DiscountFinder discFinder = new DiscountFinder();
 
        DiscountFinderStrategy findByItem = new DiscountFinderStrategy();
        findByItem.findDiscountDelegate += eventhandler(this.findDiscountForItem);
        discFinder.addStrategy(findByItem);
 
        DiscountFinderStrategy findByGroup = new DiscountFinderStrategy();
        findByGroup.findDiscountDelegate += eventhandler(this.findDiscountForItemGroup);
        discFinder.addStrategy(findByGroup);
 
        DiscountFinderStrategy findDefault = new DiscountFinderStrategy();
        findDefault.findDiscountDelegate += eventhandler(this.findDefaultDiscount);
        discFinder.addStrategy(findDefault);
 
        return discFinder;
    }
 
    // Here are the three find* methods that we saw earlier
 
    void findDiscountForItem(ItemId _itemId, DiscountFinderResult _result)
    {
        info("Finding discount for specific item...");
        if (_itemId == 'aaa')
        {
            _result.parmFound(true);
            _result.parmDiscount(5);
        }
    }
 
    void findDiscountForItemGroup(ItemId _itemId, DiscountFinderResult _result)
    {
        info("Finding discount for item group...");
    }
 
    void findDefaultDiscount(ItemId _itemId, DiscountFinderResult _result)
    {
        info("Finding default discount...");
 
        if (System.DateTime::Now.DayOfWeek == System.DayOfWeek::Monday)
        {
            _result.parmFound(true);
            _result.parmDiscount(1);
        }
    }
}

And this is the result:

Only one method was called for item aaa, because it managed to find a discount. On the other hand, we had to try all available strategies for item bbb.

This is just one example of how delegates can change the way how you design applications. I’m going to show another one soon.