D365FO. Working with Azure File storage.

Azure Storage - Files

Current version of AX uses Azure Blob storage for various things like document handling, retail CDX files, DIXF and Excel add-in. You can find several blogs explaining how to upload and download files to Blob, SharePoint or temporary storage. However, what about file shares?

Azure File storage implements SMB 3.0 protocol and could be easily mapped to your local computer. You need just a few minutes to create new storage account and mount it, watch this how-to video for details.

To read file from newly created share we can use next code:

using Microsoft.Azure;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.WindowsAzure.Storage.File;

class RunnableClass1
{
    public static void main(Args _args)
    {
        System.IO.MemoryStream memoryStream;

        var storageCredentials = new Microsoft.WindowsAzure.Storage.Auth.StorageCredentials('AzureStorageAccountName', 'AzureStorageAccountKey');

        CloudStorageAccount storageAccount = new Microsoft.WindowsAzure.Storage.CloudStorageAccount(storageCredentials, true);

        CloudFileClient fileClient = storageAccount.CreateCloudFileClient();

        CloudFileShare share = fileClient.GetShareReference('AzureStorageShareName');

        if (share.Exists(null, null))
        {
            CloudFileDirectory rootDir = share.GetRootDirectoryReference();

            CloudFileDirectory fileDir = rootDir.GetDirectoryReference('folder');

            if (fileDir.Exists(null, null))
            {
                CloudFile file = fileDir.GetFileReference('file.txt');

                if (file.Exists(null, null))
                {
                    memoryStream = new System.IO.MemoryStream();
                    file.DownloadToStream(memoryStream, null, null, null);
                }
            }
        }
    }
}

References:

D365O. Trick to pass a value between Pre and Post event handler using XppPrePostArgs.

Recently we came across a scenario where we needed to check if a field has changed after super() in update method of a table. Back in the days of AX 2012 you could easily compare original field’s value with current using orig() method before super() and call necessary logic after.

public void update()
{
    boolean myFieldHasChanged = this.MyField != this.orig().MyField;

    super();

    if (myFieldHasChanged)
    {
        this.doStuff();
    }
}

Now we want to do the same using extensions. We can create Pre and Post event handlers, but they are static, so we need a way to pass a value between them.
First option is to use static field, like it’s done in RunBase extension example

public static class MyTableEventHandler
{
    private static UnknownNoYes myFieldHasChanged;

    [PreHandlerFor(tableStr(MyTable), tableMethodStr(MyTable, update))]
    public static void MyTable_Pre_update(XppPrePostArgs _args)
    {
        MyTable myTable = _args.getThis() as MyTable;

        if (myTable.MyField != myTable.orig().MyField)
        {
            MyTableEventHandler::myFieldHasChanged = UnknownNoYes::Yes;
        }
        else
        {
            MyTableEventHandler::myFieldHasChanged = UnknownNoYes::No;
        }
    }

    [PostHandlerFor(tableStr(MyTable), tableMethodStr(MyTable, update))]
    public static void MyTable_Post_update(XppPrePostArgs _args)
    {
        MyTable myTable = _args.getThis() as MyTable;

        if (MyTableEventHandler::myFieldHasChanged == UnknownNoYes::Yes)
        {
            myTable.doStuff();
        }

        MyTableEventHandler::myFieldHasChanged = UnknownNoYes::Unknown;
    }
}

Another option is to use XppPrePostArgs as a vehicle for new parameter. XppPrePostArgs has collection of parameters under the hood, so nothing stops us to add one more and framework will take care of passing it between Pre and Post event handler!

XppPrePostArgs_collection.jpg

public static class MyTableEventHandler_XppPrePostArgs
{
    const static str myFieldHasChangedArgName = 'myFieldHasChanged';

    [PreHandlerFor(tableStr(MyTable), tableMethodStr(MyTable, update))]
    public static void MyTable_Pre_update(XppPrePostArgs _args)
    {
        MyTable myTable = _args.getThis() as MyTable;

        boolean myFieldHasChanged = myTable.MyField != myTable.orig().MyField;

        <strong>_args.addArg(MyTableEventHandler_XppPrePostArgs::myFieldHasChangedArgName, myFieldHasChanged);</strong>
    }

    [PostHandlerFor(tableStr(MyTable), tableMethodStr(MyTable, update))]
    public static void MyTable_Post_update(XppPrePostArgs _args)
    {
        MyTable myTable = _args.getThis() as MyTable;

        <strong>boolean myFieldHasChanged = _args.getArg(MyTableEventHandler_XppPrePostArgs::myFieldHasChangedArgName);
</strong>
        if (myFieldHasChanged)
        {
            myTable.doStuff();
        }
    }
}

Using one of these approaches you should remember that static fields apply to the class, not to instances of the class, so do not mix well with concurrency. Trick with XppPrePostArgs tightly depends on current implementation, that could be changed anytime and comes with no warranty.

To overcome this and other limitations of extensions capabilities Microsoft is introducing Method wrapping and chain of command and I’m pretty sure that we’ll see blogs on this from my MVP fellows soon.

D365O. X++ objects equality.

balance

There are two different kinds of equality: equality by reference and equality by value. I’m not going to explain these basic concepts here, because you can find tons of information in the web, today I want to show how you can implement value based comparison in X++.

By default AX uses reference check. In previous versions we could override equal method of the Object class to implement value based comparison, however, we have to explicitly derive new class from Object, because it was not derived by default and equal was not called by AX (as far as I know). For example, if you wanted to use value equality with collections, like set, you had to workaround using map where key is combination of fields and value is object itself.

With new version of AX we moved to .NET world where X++ is almost a first class citizen. Why almost?  There are bunch of limitations and not implemented features like generics, but we slowly getting there.

Let’s get back to value equality. Now, each class is derived from System.Object. System.Object has Equals and GetHashCode methods used to check equality that we can override. Again, there are lots of info in the web about these two methods and guidelines how to override them, so I’ll jump straight into the example.

I have a new class with two public fields:

class MyClass
{
    public int a;
    public int b;
}

In my test job, I want to store instances of MyClass in a set. Set should contain only instances with unique combination of a and b values.

public static void main(Args _args)
{
    MyClass myClass1 = new MyClass();
    myClass1.a = 5;
    myClass1.b = 10;

    MyClass myClass2 = new MyClass();
    myClass2.a = 5;
    myClass2.b = 10;

    Set set = new Set(Types::Class);

    set.add(myClass1);
    set.add(myClass2);

    info(int2Str(set.elements()));
}

As we already know, AX will add 2 elements into the set because it always performs reference check by default.

Let’s override Equals and GetHashCode using MSDN guidelines

public int GetHashCode()
{
    return this.a ^ this.b;
}

public boolean Equals(System.Object _obj)
{
    if (_obj == null)
    {
        return false;
    }

    if (System.Object::ReferenceEquals(this, _obj))
    {
        return true;
    }

    if (!this.GetType().Equals(_obj.GetType()))
    {
        return false;
    }

    MyClass compareTo = _obj;

    if (compareTo.a == this.a && compareTo.b == this.b)
    {
        return true;
    }
    else
    {
        return false;
    }
}

It’s just an example how “standard” features from .NET could be used in X++. Moving forward, you can expand this example implementing IComparable interface, so new class could be added to any standard .NET collection and be sorted! But it’s a topic for another blog.

D365O. Issues with Microsoft Dynamics App for Office setup.

Recently I’ve helped my colleague to setup Word Templates for D365O. We followed Office integration tutorial on the wiki, but it has few gaps.

First of all, it’s not clear where to find share that contains the Microsoft Dynamics App manifest ( step 1.d). Use base URL for the cloud environment, for example, https://topo00dfa4stbobaos.cloudax.test.dynamics.com/

Second step where we stuck for a while is not so obvious. 4.b says that you need to select Template Designer after loading applets. However, there was nothing to select. To fix this issue you actually need to go to D365O -> System Administration -> Setup -> Office app parameters and on “Registered applets” tab check that there is a record for Microsoft.Dynamics.Platform.Integration.Office.WordDesignerApplet in a grid. 

OfficeAppParameters.jpg

If it’s not there click on “Initialize applet registration” button and AX will auto populate the grid.

 

D365O. How to deploy Demo VM using Visual Studio subscription.

Many of us have Visual Studio subscription (formerly called MSDN subscription) and there is monthly Azure credit coming with it. It could be used to spin up D365O demo VM, but the whole process is not straightforward. I would describe it step by step today.

  1. Activate monthly Azure credit. You will get an email with subscription id after completion.ActivateSubscription.png
  2.  Go to Azure portal where you can check details of new subscription.AzurePortal.jpgAzurePortalSubscription.jpg
  3. Different types of subscriptions give you different amount, enterprise is the best (but costs a lot) and gives you 150$ monthly credit, that is enough to run D12V2 VM for 235 hours per month.
  4. With new subscription you get new tenant. Now we need to activate D365 trial on this tenant. That’s a mandatory step for ARM onboarding. Existing customers can follow steps on the customer source  to request access via email. Partners can find details on the partner source , you have 2 options there: request new tenant with trial or activate trial on existing tenant. We will activate trial because we already have a tenant with subscription, but it’s possible to transfer subscription to new tenant as well.
  5. To activate trial on existing tenant
    1. Create new user .AzureActiveDirectoryAddUser.jpg
    2. Make new user Owner of the subscriptionAzureSubscriptionOwner.jpgAzureSubscriptionOwnerNew.jpg
    3. Make new user Global admin of Active Directory.AzureActiveDirectoryUserRole.jpg
    4. Follow a link to trial offer from partner source article. You need to login using credentials of newly created user.ActivateTrial.jpgconfirmtrial
  6. No we are ready to create new LCS project. Go to LCS and login using user created on step 4. LCSNewProject.jpg
  7. Fill all the details required: Version of AX, project name and methodology.LCSNewProjectDetails.jpg
  8. Setup connection to Azure.LCSMicrosoftAzureSettings.jpg
  9. In the organizations list you will see tenants of project users. You can invite users from different tenants to a project and you will see their organization in the list as well.  lcsnewprojectauthorizeCurrent user should be administrator of the tenant to complete authorization.LCSNewProjectAuthorization.jpgLCSNewProjectAuthorizationComplete.jpg
  10. Add Dynamics Deployment Services [wsfed-enabled] to the subscriptionAddDynamicsDeploymentServices.jpg
  11. Setup Azure connector. Check “Configure to use Azure Resource Manager” checkbox or you won’t be able to deploy any VM starting from Update 2. Azure Subscription Id and tenant name could be found on Azure portal.LCSSetupAzureConnector.jpgLCSSetupAzureConnector2.jpgLCSSetupAzureConnector3.jpg
  12. For ARM deployments we don’t need to download any certificate, so just click “Next” on this step.lcssetupazureconnector4
  13. Select Azure region. Please note that not all regions are available for Visual Studio Subscriptions. If you select wrong region you will have to create new connector with another region because all deployments would fail.LCSSetupAzureConnectorRegion.jpg
  14. Deploy new Demo VMLCSDeployNewVM.jpgLCSDeployNewVM2.jpgLCSDeployNewVM3.jpg
  15. Enter VM name and go to Advance settingsLCSDeployNewVM5.jpg
  16. Select demo data package or “None” for blank environment. Here you could select demo data from ISV partners as well if they shared it with you.LCSDeployNewVM6.jpg
  17. Select number of disks. Select 7 if you want to scale it down to D12V2 later, otherwise you would have to manually delete them, that is not an easy task, because it’s not possible to change size if target VM has different disks configuration. LCSDeployNewVM7.jpg
  18. Select GER configuration to be deployed. You can deploy any later from assets library if you missed this step.LCSDeployNewVM8.jpg
  19. Select VM size. Different sizes have different hardware configuration and costs. To find costs you can use Pricing calculator. I prefer to use D13V2 and D12V2 because I think they have best  price–performance ratio.LCSDeployNewVM9.jpg

That’s all. Usually deployment takes up to 5 hours. Don’t forget to setup auto shutdown to save your costs!

Links

Filter OData entity by enum field.

As you may know, logic apps do not have any triggers available for Dynamics365 for Operations. So we have to use recurrence trigger to read data periodically from a data entity and obviously we want to filter a query. This is pretty straightforward with string or integer fields, but not with enums.  Under the hood AX enums are enumeration in OData.

For example, I have new entity “RentalInvoices” and I want to filter it by AXCRMTransferStatus field and select only “pending” records.

Designer view:

LogicAppODataFilterDesignerView.png

Code view:

LogicAppODataFilterCodeView.png