MSDyn365FO. How to switch from IIS Express to IIS on development VM.

All new environments starting from PU12 are using IIS Express in debug mode and often due to random issues we have to restart it using “Restart IIS Express” option in the Dynamics 365 menu. There are multiple discussions on community and yammer forums regarding these issues and Joris de Gruyter mentioned that it is possible to switch back to IIS if you have admin access (environment deployed to customer’s or partner’s own Microsoft Azure subscription), but did not explain how 🙂

All we need to do is to edit DynamicsDevConfig.xml file that could be found in ‪K:\AosService\PackagesLocalDirectory\Bin folder (drive letter could be different for your VM). Change RuntimeHostType value from IISExpress to IIS and that’s all.

DynamicsDevConfig

You may find same file under %userprofile%\Documents\Visual Studio 2015\Settings\ , however, Visual Studio rolls back any changes to RuntimeHostType if value is different to value in \AosService\PackagesLocalDirectory\Bin\DynamicsDevConfig file, so don’t try to change it for individual user.

Advertisements

D365FO. “The authentication process was not successful. Please contact your system administrator.” error during Connect to Lifecycle Services setup.

As we know, there are hundreds of task recordings available out of the box in LCS library. Before first use, system administrator should authorize D365FO to access LCS and often it is not possible due to the old bug. When system administrator goes to Settings -> Task recorder -> Play recording as guide -> Open from Lifecycle Services and clicks “Select the Lifecycle Services library” “Connect to Lifecycle Services” dialog pops up.

Connect to Lifecycle Services

There is a link “Click here to connect to Lifecycle Services” and when administrator clicks on it he may receive “The authentication process was not successful. Please contact your system administrator.” error.

It’s is caused by empty value of “LCS.GettingStartedLibrary” parameter in web.config file.

To fix it go to K:\AosService\WebRoot and edit web.config file. Find LCS.GettingStartedLibrary tag and set value to any integer that is greater than 0, save file and restart IIS. Don’t forget to backup web.config before doing any changes.

WebConfigLCSGettingStarted

Probably LCS should put BPM library id there, however, it works with any random positive integer as well. To find BMP project id, go to LCS, open your project and click on “Business process modeler” tile. Then select library that contains task recording.  In the URL of the page grab value of FWK parameter

LCSBPMURL.png

Hopefully, this bug will be fixed soon and we won’t have to play with web.config file anymore.

D365FOE. How to override form data source field lookup method.

A long time ago, I wrote a blog post explaining how to override form data source field methods. I skipped lookup, most popular method, because it did not work and logged a bug with MS hoping that it would be fixed soon. It took more time than I expected, but finally, it works!

Here is a small example how to override it. I’m going to use approach similar to previous post, full example is available on GitHub. Version I’m using is 7.3 PU13.

/// <summary>
/// Handles events raised by <c>SalesTable</c> form.
/// </summary>
public class SalesTableEventHandler
{
    /// <summary>
    /// Post event handler for <c>SalesTable</c> <c>SalesLine</c> Initialized event.
    /// </summary>
    /// <param name=“_sender”></param>
    /// <param name=“_e”></param>
    [FormDataSourceEventHandler(formDataSourceStr(SalesTable, SalesLine), FormDataSourceEventType::Initialized)]
    public static void SalesLine_OnInitialized(FormDataSource _sender, FormDataSourceEventArgs _e)
    {
        var overrides = SalesTableFormExtensionOverrides::construct();

        _sender.object(fieldNum(SalesLine, ItemId)).registerOverrideMethod(methodStr(FormDataObject, lookup),
            methodStr(SalesTableFormExtensionOverrides, ItemId_OnLookup), overrides);
    }
}

/// <summary>
/// Contains methods which are used to override <c>SalesLine</c> data source field methods.
/// </summary>
public class SalesTableFormExtensionOverrides
{
    protected void new()
    {
    }

    /// <summary>
    /// Constructs a new instance of <c>SalesTableFormExtensionOverrides</c> class.
    /// </summary>
    /// <returns>
    /// A <c>SalesTableFormExtensionOverrides</c> class.
    /// </returns>
    public static SalesTableFormExtensionOverrides construct()
    {
        return new SalesTableFormExtensionOverrides();
    }

    /// <summary>
    /// Provides a lookup for the <c>InventTable</c> table
    /// </summary>
    /// <param name = "_callingControl">
    /// The form string control object with which to perform the lookup.
    /// </param>
    public void ItemId_OnLookup(FormStringControl _callingControl)
    {
        SysTableLookup sysTableLookup = SysTableLookup::newParameters(tableNum(InventTable), _callingControl);

        sysTableLookup.addLookupfield(fieldNum(InventTable, ItemId));
        sysTableLookup.addLookupfield(fieldNum(InventTable, NameAlias));

        sysTableLookup.performFormLookup();
    }
}

As you may notice, signature of this method is different to signature of other methods, like validate or jumpRef. They accept FormDataObject as a parameter and lookup() accepts FormStringControl. It looks a bit inconsistent for me and we need to be extra careful here because you can register override method with any signature and get error in the run-time.

D365FOE. Form personalization for developers.

Blog_image_dashboard.jpg

Often in AX one form could be used for different business entities, like customer and vendor external items. And because they use same CustVendExternalItem form under the hood personalization of it affects all entities in the same time. But what if user wants to add new control via personalization only for customer’s and does not want to see it for vendor’s external items?  Now it is possible with a small customization.

In D365 new FormRunConfigurationClass class has been introduced to control form personalization. It saves and restores all changes done by user: new controls added via personalization, columns resize, columns hide, etc. Each form has new property – configurationOwner() that is used by FormRunConfigurationClass to resolve configuration. By default, it is equal to a form name.

To split customer external items and vendor external items form personalization all we need to do is to set configurationOwner()  to overAllModule in the init() method of CustVendExternalItem  form, that is equal to ModuleInventCustVend::Cust for customer and to ModuleInventCustVend::Vend for vendor:

[ExtensionOf(formStr(CustVendExternalItem))]
public final class CustVendExternalItemForm_Extension
{
    public void init()
    {
        //copy-pasted this code from init method because it resolves module after super() and we need it before
        switch (this.args().parmEnum())
        {
            case ModuleInventPurchSalesVendCustGroup::Purch,
                 ModuleInventPurchSalesVendCustGroup::Vend,
                 ModuleInventPurchSalesVendCustGroup::VendGroup:
                 // set personalization owner
                 this.setConfigurationOwner(enum2str(ModuleInventCustVend::Vend));
                break;

            case ModuleInventPurchSalesVendCustGroup::Sales,
                 ModuleInventPurchSalesVendCustGroup::Cust,
                 ModuleInventPurchSalesVendCustGroup::CustGroup :
                 // set personalization owner
                 this.setConfigurationOwner(enum2str(ModuleInventCustVend::Cust));
                break;
        }

        next init();
    }
}

This property should be set before super() of init() method of the form, so if you have controls added in the run-time it won’t affect them. However, FormRunConfigurationClass can load saved personalization for this control as well. Let’s expand our example with new run-time control and load personalization for it:

[ExtensionOf(formStr(CustVendExternalItem))]
public final class CustVendExternalItemForm_Extension
{
    public void init()
    {
        //copy-pasted this code from init method because it resolves module after super() and we need it before
        switch (this.args().parmEnum())
        {
            case ModuleInventPurchSalesVendCustGroup::Purch,
                ModuleInventPurchSalesVendCustGroup::Vend,
                 ModuleInventPurchSalesVendCustGroup::VendGroup:
                this.setConfigurationOwner(enum2str(ModuleInventCustVend::Vend));
                break;

            case ModuleInventPurchSalesVendCustGroup::Sales,
                ModuleInventPurchSalesVendCustGroup::Cust,
                 ModuleInventPurchSalesVendCustGroup::CustGroup :
                this.setConfigurationOwner(enum2str(ModuleInventCustVend::Cust));
                break;
        }

        next init();

        //add runtime control
        FormGridControl gridControl = this.design().controlName(formControlStr(CustVendExternalItem, Grid));
        FormStringControl newControl = gridControl.addControl(FormControlType::String, 'NewControl');
        newControl.label('New control');

        //load personalization for runtime control
 this.configurationHelper().getLoadedConfigurationProfile().applyRuntimePropertiesForControl(this, gridControl, newControl);
    }
}

That’s only two examples of FormRunConfigurationClass usage, but I’m sure that my curious reader will find more.

Advanced cross-reference search.

Cross-references search

Cross-reference is one of the best tools we have in AX that is used by developers daily, however, not everyone is using it for 100%.

Recently, I was asked how to find cross-references for a kernel method that is not defined on a table, because you cannot right click on it 😊   And that’s a good question, often we want to find if there is a call to doUpdate or doInsert somewhere.

In AX 2012 go to Tools -> Cross-reference -> Names and filter by table or method name and then click Used by.

SalesLine.DoUpdate_Names

SalesLine.DoUpdate_UsedBy

Using this form, we can find usage of CRL types as well, for example, Microsoft.Dynamics.IntegrationFramework.  It is defined in AOT under references node, however, from there you cannot find any references.  Names would show you everything!

CLRTypeUsage

What about D365FOE?

As we know, it’s a bit different here. Cross-references data is moved to DYNAMICSXREFDB database.

DYNAMICSXREFDB

And it makes perfect sense because you need to pay for each GB of DB space in Production.

Using next SQL statement, we can find usage of any object. For example, Microsoft.Dynamics.IntegrationFramework :

SELECT
	sourceName.[Path] as sourcePath,
	targetName.[Path] as targetPath,
	[Kind],
	[Line],
	[Column],
	sourceModule.[Module] as sourceModule,
	targetModule.[Module] as targetModule
	FROM dbo.[References]
	INNER JOIN dbo.[Names] sourceName
		ON dbo.[References].[SourceId] = sourceName.[Id]
	INNER JOIN dbo.[Modules] sourceModule
		ON sourceName.[ModuleId] = sourceModule.[Id]
	INNER JOIN dbo.[Names] targetName
		ON dbo.[References].[TargetId] = targetName.[Id]
	INNER JOIN dbo.[Modules] targetModule
		ON targetName.[ModuleId] = targetModule.[Id]
	WHERE targetName.[Path] like '%Microsoft.Dynamics.IntegrationFramework%'

CLRTypeUsage_D365FOE

Where Kind is:

    /// <summary>
    /// Types of Cross References
    /// </summary>
    public enum CrossReferenceKind
    {
        /// <summary>
        /// Type not specified. Used for queries
        /// </summary>
        Any = 0,

        /// <summary>
        /// Indicates that the reference is a Method Call
        /// </summary>
        MethodCall = 1,

        /// <summary>
        /// Type reference
        /// Indicated that the type is used (variable and field declaration, attributes, function return type, etc)
        /// </summary>
        TypeReference = 2,

        /// <summary>
        /// Interface implementation
        /// Indicates that the source entity is implementing this interface
        /// </summary>
        InterfaceImplementation = 3,

        /// <summary>
        /// Class Extended
        /// Indicates that the source entity is extending this class or interface
        /// </summary>
        ClassExtended = 4,

        /// <summary>
        /// Test Call
        /// Indicates that the source entity (test) directly or indirectly calls an application method.
        /// </summary>
        TestCall = 5,

        /// <summary>
        /// Property
        /// Indicates that the source entity has a certain property.
        /// </summary>
        Property = 6,

        /// <summary>
        /// Attribute reference
        /// Indicated that an Attribute is used
        /// </summary>
        Attribute = 7,

        /// <summary>
        /// Test Helper Call
        /// Indicates that the source entity is a test helper.
        /// </summary>
        TestHelperCall = 8,

        /// <summary>
        /// Metadata or code Tag reference
        /// Indicates that the source tag is used on a metadata element, class or a method or a line of code.
        /// </summary>
        Tag = 9,
    }

Let’s try doUpdate:

DoUpdate_UsedBy_D365FOE

As we can see, result is different to AX 2012, where we could search for an individual table, now all Common methods have reference to Common.

 

D365FOE. FormHasMethod extension for form extension methods.

Recently I have seen multiple people asking how to check if form has method added by extension at run-time.  For form methods we can use Global::formHasMethod but it does not work with form extensions. I advised people to use new metadata API to do this and finally community user Axaptus wrote the code!

I tweaked it a bit to ignore method’s name case as AX does and to exclude private methods. Also I used it to extend standard formHasMethod method

/// <summary>
/// The class <c>Global_Extension</c> contains extension methods for the <c>Global</c> class.
/// </summary>
[ExtensionOf(classStr(Global))]
public static final class Global_Extension
{
    static boolean formHasMethod(FormRun fr, IdentifierName methodName)
    {
        boolean ret = next formHasMethod(fr, methodName);

        if (!ret)
        {
            ret = Global::formExtensionHasMethod_IM(fr, methodName);
        }

        return ret;
    }

    private static boolean formExtensionHasMethod_IM(FormRun _formRun, IdentifierName _methodName)
    {
        if (!_formRun || !_methodName)
        {
            return false;
        }

        try
        {
            System.Object[] extensions = Microsoft.Dynamics.Ax.Xpp.ExtensionClassSupport::GetExtensionsOnType(_formRun.GetType(), true);

            if (extensions)
            {
                System.Type    formRunExtensionType;
                System.Reflection.MethodInfo    methodInfo;
                
                //extension methods are always static
                var  bindingFlags = BindingFlags::Public | BindingFlags::Static | BindingFlags::IgnoreCase;

                for (int i = 0; i < extensions.Length; i++)
                {
                    formRunExtensionType = extensions.GetValue(i);

                    var methodsInfo = formRunExtensionType.GetMethods(bindingFlags);

                    for (int n = 0; n < methodsInfo.get_Length(); n++)
                    {
                        methodInfo = methodsInfo.getValue(n);
                        if (methodInfo.Name == _methodName)
                        {
                            return true;
                        }
                    }
                }
            }
        }
        catch (Exception::CLRError)
        {
            error(CLRInterop::getLastException().ToString());
        }

        return false;
    }
}

Extending standard method has its pros and cons. From one side it will slow down execution of standard code that calls it when method does not exist, but it’s a rare case. From another side it allows you to reuse standard code without changing it and it could be handy in various places where AX looks for a method on a form.

Source code is available on GitHub