Saturday, March 18, 2017

Development tutorial: SysExtension framework in factory methods where the constructor requires one or more arguments

Background

At the Dynamics 365 for Operations Technical Conference earlier this week, Microsoft announced its plans around overlayering going forward. If you have not heard it yet, here's the tweet I posted on it:
The direct impact of this change is that we should stop using certain patterns when writing new X++ code.

Pattern to avoid

One of these patterns is the implementation of factory methods through a switch block, where depending on an enumeration value (another typical example is table ID) the corresponding sub-class is returned.

First off, it's coupling the base class too tightly with the sub-classes, which it should not be aware of at all.
Secondly, because the application model where the base class is declared might be sealed (e.g, foundation models are already sealed), you would not be able to add additional cases to the switch block, basically locking the application up for any extension scenarios.

So, that's all good and fine, but what can and should we do instead?

Pattern to use

The SysExtension framework is one of the ways of solving this erroneous factory method pattern implementation. 

This has been described in a number of posts already, so I won't repeat here. Please read the below posts instead, if you are unfamiliar with how SysExtension can be used:
In many cases in existing X++ code, the constructor of the class (the new() method) takes one or more arguments. In this case you cannot simply use the SysExtension framework methods described above. 

Here's an artificial example I created to demonstrate this:
  • A base class that takes one string argument in the constructor. This could also be abstract in many cases.
  • Two derived classes that need to be instantiated depending on the value of a NoYesUnchanged enum passed into the construct() method.
public class BaseClassWithArgInConstructor
{
    public str argument;

    
    public void new(str _argument)
    {
        argument = _argument;
    }

    public static BaseClassWithArgInConstructor construct(NoYesUnchanged _factoryType, str _argument)
    {
        // Typical implementation of a construct method
        switch (_factoryType)
        {
            case NoYesUnchanged::No:
                return new DerivedClassWithArgInConstructor_No(_argument);
            case NoYesUnchanged::Yes:
                return new DerivedClassWithArgInConstructor_Yes(_argument);
        }

        return new BaseClassWithArgInConstructor(_argument);
    }
}

public class DerivedClassWithArgInConstructor_No extends BaseClassWithArgInConstructor
{
}

public class DerivedClassWithArgInConstructor_Yes extends BaseClassWithArgInConstructor
{
}

And here's a Runnable class we will use to test our factory method:

class TestInstantiateClassWithArgInConstructor
{        
    public static void main(Args _args)
    {        
        BaseClassWithArgInConstructor instance = BaseClassWithArgInConstructor::construct(NoYesUnchanged::Yes, "someValue");
        setPrefix("Basic implementation with switch block");
        info(classId2Name(classIdGet(instance)));
        info(instance.argument);
    }
}

Running this now would produce the following result:
The right derived class with the correct argument value returned
OK, so to decouple the classes declared above, I created a "factory" attribute, which takes a NoYesUnchanged enum value as input.

public class NoYesUnchangedFactoryAttribute extends SysAttribute
{
    NoYesUnchanged noYesUnchangedValue;

    public void new(NoYesUnchanged _noYesUnchangedValue)
    {
        noYesUnchangedValue = _noYesUnchangedValue;
    }

    public NoYesUnchanged parmNoYesUnchangedValue()
    {
        return noYesUnchangedValue;
    }
}

Let's now decorate the two derived classes and modify the construct() on the base class to be based on the SysExtension framework instead of the switch block:

[NoYesUnchangedFactoryAttribute(NoYesUnchanged::No)]
public class DerivedClassWithArgInConstructor_No extends BaseClassWithArgInConstructor
{
}

[NoYesUnchangedFactoryAttribute(NoYesUnchanged::Yes)]
public class DerivedClassWithArgInConstructor_Yes extends BaseClassWithArgInConstructor
{
}

public class BaseClassWithArgInConstructor
{
    // ...
    public static BaseClassWithArgInConstructor construct(NoYesUnchanged _factoryType, str _argument)
    {
        NoYesUnchangedFactoryAttribute attr = new NoYesUnchangedFactoryAttribute(_factoryType);
        BaseClassWithArgInConstructor instance = SysExtensionAppClassFactory::getClassFromSysAttribute(classStr(BaseClassWithArgInConstructor), attr);

        return instance;
    }
}

Running the test now will however not produce the expected result:
The right derived class is returned but argument is missing
That is because by default the SysExtension framework will instantiate a new instance of the corresponding class (dictClass.makeObject()), which ignores the constructor arguments.

Solution

In order to account for the constructor arguments we need to use an Instantiation strategy, which can then be passed in as the 3rd argument when calling SysExtensionAppClassFactory.

Let's define that strategy class:

public class InstantiationStrategyForClassWithArg extends SysExtAppClassDefaultInstantiation
{
    str arg;

    public anytype instantiate(SysExtModelElement  _element)
    {
        SysExtModelElementApp   appElement = _element as SysExtModelElementApp;
        Object                  instance;

        if (appElement)
        {
            SysDictClass dictClass = SysDictClass::newName(appElement.parmAppName());
            if (dictClass)
            {
                instance = dictClass.makeObject(arg);
            }
        }

        return instance;
    }

    protected void new(str _arg)
    {
        this.arg = _arg;
    }

    public static InstantiationStrategyForClassWithArg construct(str _arg)
    {
        return new InstantiationStrategyForClassWithArg(_arg);
    }
}

As you can see above, we had to

  • Define a class extending from SysExtAppClassDefaultInstantiation (it's unfortunate that it's not an interface instead). 
  • Declare all of the arguments needed by the corresponding class we plan to construct.
  • Override the instantiate() method, which is being invoked by the SysExtension framework when the times comes
    • In there we create the new object instance of the appElement and, if necessary, pass in any additional arguments, in our case, arg.
Let's now use that in our construct() method:

public class BaseClassWithArgInConstructor
{
    //...
    public static BaseClassWithArgInConstructor construct(NoYesUnchanged _factoryType, str _argument)
    {
        NoYesUnchangedFactoryAttribute attr = new NoYesUnchangedFactoryAttribute(_factoryType);
        BaseClassWithArgInConstructor instance = SysExtensionAppClassFactory::getClassFromSysAttribute(
            classStr(BaseClassWithArgInConstructor), attr, InstantiationStrategyForClassWithArg::construct(_argument));

        return instance;
    }
}

If we now run the test, we will see the following:
The right derived class with the correct argument value returned 

Note

If you modify the attributes/hierarchy after the initial implementation, you might need to clear the cache, and restarting IIS is not enough, since the cache is also persisted to the DB. You can do that by invoking the below static method:

SysExtensionCache::clearAllScopes();

Parting note

There is a problem with the solution described above. The problem is performance.
I will walk you through it, as well as the solution, in my next post.

Friday, March 03, 2017

Announcement: Warehouse Mobile Devices Portal improvements with February update of AX 2012 R3

A monthly cadence AX 2012 R3 update for February has just come out on LCS, and with it a few changes our team has done that deal with overall behavior and performance of WMDP - the Warehouse Mobile Devices Portal used in the Advanced warehousing module.

I want to call them out here, and if you are reading my blog to keep up to date with the Warehousing changes, I strongly encourage you to install the below changes:


  • A functional enhancement, that allows you to start execution of a work order that has some lines awaiting demand replenishment, processing the lines that can be picked already now
  • A performance optimization, that avoids updating certain persisted counters on the wave when work goes through its stages. 
    • This also ensures they do not get out of sync, as they were used to make certain decisions about what is allowed for a work order
    • Can also be downloaded individually as KB3217157
  • An integrity enhancement, that ensures we always are in a valid state in DB, where each service call from WMDP is now executed within a single transaction scope
    • Can also be downloaded individually as KB3210293
    • Can be turned off in code for a selected WHSWorkExecuteDisplay* class, if needed
    • This is a great change ensuring we do not commit any data unless all went well, but might hypothetically impact your new/modified WMDP flows, if you handle your exceptions incorrectly today. If you do find issues with them, please let me know, I am curious to know your specific examples.
  • Various minor enhancements, that ensure WMDP performs well under load
    • Can also be downloaded individually as KB3210293
    • This includes improvements to enable better concurrency in various WMDP scenarios, better error handling on user entry, etc.

Again, install these, try them out, and provide feedback!

There are a lot more enhancements and bug fixes that went into this release, you can read the full list by following the link below:


Thanks!

Thursday, March 02, 2017

Extensible enums: Breaking change for .NET libraries that you need to be aware of

A while back I wrote a blog post describing the Extensible Enums - a new feature that is part of Dynamics 365 for Operations:
http://kashperuk.blogspot.com/2016/09/development-tutorial-extensible-base.html

I explained that when marking an enumeration as extensible, the representation of this enum under the hood (in CLR) changes. Here's the specific quote:
The extensible enums are represented in CLR as both an Enum and a Class, where each enum value is represented as a static readonlyfield. So accessing a specific value from the above enum, say, NumberSeqModule::Invent would under the hood look something like NumberSeqModule_Values.Invent, where Invent is of type NumberSeqModule which is an Enum. It would in turn call into AX to convert the specific named constant "Invent" to its integer enumeration value through a built-in function like the symbol2Value on DictEnum.
Something that was not super clear in the post is that this was actually a breaking change that might impact your .NET solutions relying on one of these base enumerations.

Problem statement

As part of enabling further extensibility in the application for the next release, we have made a number of additional base enums extensible.

Let's take enum TMSFeeType as an example. Assume we have made it extensible in X++. That means that in our C# project, where we use this enum, we will no longer be able to access it from Dynamics.AX.Application namespace by name, like so:

switch (accessorialFeeType)
{
    case TMSFeeType.Flat:
        // Do something
        break;

    case TMSFeeType.PerUOM:
        // Do something else
        break;
    // etc.
}

If you navigate to its definition, you will notice that the enum declaration is empty:

namespace Dynamics.AX.Application
{
    public enum TMSFeeType
    {
    }
}

The proper way to use the enum that is extensible is to reference the above mentioned class suffixed with _Values, which lives in the Dynamics.AX.Application.ExtensibleEnumValues namespace, like so:

if (accessorialFeeType == TMSFeeType_Values.Flat)
{
    // Do something
}
else if (accessorialFeeType == TMSFeeType_Values.PerUOM)
{
    // Do something else
}

Note: Because the values are now determined at runtime by going to the AOS and asking for the correct integer value of this enum, they cannot be used in a switch/case block, which expects constant expressions known at compile-time.

What's next

Obviously, this situation is not great. 
Let's hope that Microsoft will think of a good way to address this going forward.

Question to you

That leads to a question - how many of you actually have .NET libraries relying on application code in Dynamics 365 for Operations and might be impacted by us making some of the enums extensible in the next major release?

Thursday, January 19, 2017

Warehouse Mobile Devices Portal - Now as an App for your mobile device

Our team has been working on an application that would eventually replace the existing web-site based Warehouse Mobile Devices Portal (WMDP), as we see the trend of warehouses relying more and more on phone-based devices with added scanner capabilities (Honeywell Dolphin 75e, for example).

We are happy to announce that the app is now publicly available.
Installation and configuration instructions,  as well as download links, can be found on the Dynamics 365 for Operations wiki website:
https://ax.help.dynamics.com/en/wiki/install-and-configure-dynamics-365-for-operations-warehousing/

The app works on Windows 10 devices, as well as Android devices. iOS is not supported, since such devices are generally not used in warehouses due to high cost and some technical limitations.

The app only works with Dynamics 365 for Operations, so older releases like AX 2012 R3 can only use the old WMDP web site.

The app is an alternative front-end to all the business logic and screen generation logic in X++, so does not contain any business logic inside, similar to the old WMDP.

As you will see, we have re-worked the user interface, focusing on showing as few controls as possible at once, adding cards for showing grouped data like on-hand info, work list, etc. A special keyboard to entering quantities, support for scanning, etc.

Here is how the app looks on a Windows Phone:

Log in screen for new WMDP app


We are very excited to get this out into your hands and are looking for feedback. So don't be shy :)

Update 2017-01-20: Read a much more detailed description from Markus on the SCM blog:
https://blogs.msdn.microsoft.com/dynamicsaxscm/2017/01/20/announcing-dynamics-365-for-operations-warehousing/

Wednesday, December 28, 2016

Configuration tip: Warehouse mobile devices portal Native application in Azure Active Directory

Setting up the Warehouse mobile devices portal is a relatively simple process, and is described in detail on the corresponding wiki:
https://ax.help.dynamics.com/en/wiki/warehouse-mobile-devices-portal-for-microsoft-dynamics-ax/

However, now and again I would get complaints around the authorization process not working properly, or asking for interactive login, both of which shouldn't be necessary for the authorization method used by WMDP.

So in this post I would like to give a small configuration tip that prevents the below error message:

Error: "The user or administrator has not consented to use the application with ID ''. Send an interactive authorization request for this user and resource.

The reason this error is shown is because the Native application setup in Azure for WMDP has access to resources that require the Azure admin to consent to that first.

WMDP app is configured to allow access to AAD resources
As you can see, some of the resources WMDP app has access to require prior Admin consent.

Recommendation: 

When configuring the Native application for WMDP in Azure, only give it permissions for the Microsoft Dynamics ERP resources.

This will ensure you don't get unexpected errors when trying to connect to WMDP.



Happy Holidays to all of you!

Stay tuned for more posts next year and subscribe if you haven't already to get updates on the latest tips and tutorials for Dynamics 365 for Operations aka Dynamics AX aka Axapta :)