Public Profile
  • AuthenticationUtility as NuGet package

    AuthenticationUtility from ServiceSamples on GitHub (https://github.com/Microsoft/Dynamics-AX-Integration/tree/master/ServiceSamples/AuthenticationUtility) is very useful; it allows developers not to worry about the boilerplate authentication code.

    Turning it to a NuGet package would allow easy adding to new projects (now it requires knowing about the samples and copying the utility from there), add versioning, allow maintaining at a single place (unlike when everybody just copies a certain version of the source code), improve discoverability, enforce consistency and so on.

    Obviously it requires some refactoring, because currently setting parameters (such as ActiveDirectoryClientAppId) requires changing code of the utility itself. Developers must be able to provide such values as parameters from outside the authentication library.

  • Open Designer from Solution Explorer with Shift+F7

    Open Designer functionality should be bound to command View.ViewDesigner, which has the default shortcut Shift+F7. The shortcut is ignored by Solution Explorer when working with AX7 projects, although it works fine in Application Explorer and also with other projects types (e.g. WPF) in Solution Explorer.

  • DEV VMs with up-to-date cross references

    Please rebuild cross references on the DEV VM before making it available. They currently contain references to modules that aren't included at the box (unit tests) and don't contain xRefs that should be there. For example, I had to rebuild the application to get "Find event handlers" working.

  • Allow deploying DEV VMs to DevTest Labs

    Azure DevTest labs (https://azure.microsoft.com/en-us/services/devtest-lab/) are intended for exactly this purpose; they simplify management of Dev/Test VMs, allow stopping and starting VMs on schedule (without writing any code) and so on.

  • Improvements of "Find references"

    Evaluation of cross-references is very important with such a large code base (for understanding the current solution, for impact analysis and so on). Unfortunately the experience is worse than in previous versions of AX; here are some suggestions:

    1. Allow filtering and sorting.
    2. Add filtering options similar to those in Application Explorer (by type, model etc.).
    3. When I get a list of references, I often want to look at where some references are used. Please make it possible directly from the same window. For example, I could click on a reference and use "Find reference on it". Or I could be able to expand the node and see a hierarchy of references.
    4. Re-introduce "Reference" field, such as the information where a field is written into. I know it was dropped consciously, but it was really useful. If the standard "Find Symbol Results" doesn't support things like this, it's probably time to move from it.

    By the way, when you later decide to switch to SQL Server 2017, wouldn't DynamicsXRefDB form a nice graph database?

  • Hide non-public classes in Application Explorer

    Allow hiding non-public classes in Application Explorer.
    For example, I want to hide UserGuidClass, because I can't use it anyway. It's internal, therefore it can only be used from inside its package, but I can't add anything to the package, because it doesn't allow customizations.
    It's not very important in this moment, because there are just a few such classes (no legacy code could use this feature), but it will change as new code utilizes internal classes for information hiding.

  • Support for NuGet packages in X++ projects

    NuGet packages are extremely useful for adding and updating dependencies to .NET projects, especially in complicated cases. It would makes sense to be able to add NuGet packages to X++ projects in the same way as to C# projects, for instance.

    I can recognize several scenarios:
    - Traditional NuGet packages with compiled code and other resources, such as resource files and images. For example, let's say I want to add WindowsAzure.ServiceBus to my X++ project. It should support all existing packages with appropriate requirements (version of .NET Framework). It must resolve all packages, copy files to the modelstore and add appropriate references to AOT.
    - Packages including source code / metadata ("content"). For example, the package will add an X++ class with a default implementation that can be changed by developers as needed. NuGet supports this scenario. It could also support source code transformations - I don't have any good scenario for it in the moment, but making sure that this NuGet feature doesn't get broken will give us some extra options.
    - Deployable packages created from X++ code. For example, an open-source extensible control (in its own model+package) could be distributed as a NuGet package, therefore everybody would be able to add it to their solutions and receive updates from the same source. Installation scripts of the NuGet package would install the deployable package in the usual way. What needs to be addressed are requirements such as a specific minimum version of AX platform.

    The implementation should utilize the existing integration to Visual Studio whenever possible, such as respecting custom package sources (Options > NuGet Package Manager > Package Sources).

  • Expose more information about deadlocks

    Environment monitoring on LCS offers a view to raw logs called "All deadlocks in the system", but the data shown there isn't sufficient for proper analysis. For instance, it shows just a single query, not all queries participating in the deadlock. It also doesn't contain information about which resources were locked etc.

    Maybe you could include deadlock graphs (.xdl) for download.

  • Include form name in raw logs

    This suggestion is about raw logs ("Slow queries") in LCS environment monitoring.

    In some cases, raw logs show that a query originated from a form, but the stack trace doesn't mention which form it was, which greatly decreases its value. Sure, we sometimes can find the query by cross-references or just understanding the business domain, but getting the information straight away would be much better.

    I don't suggest any technical solution, because I don't know how it works under the hood.

    Below is a real-life example of such a stack trace.

    =====

    at [AosKernel.dll]Microsoft.Dynamics.Ax.MSIL.Interop.GetManagedCallStack(basic_string\,std::allocator >* )
    at [AosKernel.dll]Microsoft.Dynamics.Ax.Services.AOSAppDomainHelper.callGetManagedCallStack(basic_string\,std::allocator >* )
    at callGetManagedCallStack(basic_string\,std::allocator >* )
    at cqlClass.callEx(cqlClass* , Char* , interpret* )
    at cqlClass.callEx(cqlClass* , Char* , interpret* )
    at [AosKernel.dll]Microsoft.Dynamics.Ax.MSIL.cqlClassIL.Call(IntPtr c, String methodName, Object[] parameters, Type[] types, Object[] varargs, Type[] varargsTypes)
    at [Microsoft.Dynamics.AX.Server.Core.dll]Microsoft.Dynamics.Ax.Xpp.XppObjectBase.Call(String methodName, Object[] parameters, Type[] types, Object[] varargs)
    at [Microsoft.Dynamics.AX.Xpp.Support.dll]Dynamics.AX.Application.NativeQueryRunImplementation.next()
    at [Microsoft.Dynamics.AX.Xpp.Support.dll]Dynamics.AX.Application.QueryRun.next()
    at QueryRun::next(Object , Object[] , Boolean& )
    at [Microsoft.Dynamics.AX.Xpp.Support.dll]Microsoft.Dynamics.Ax.Xpp.ReflectionCallHelper.MakeInstanceCall(Object instance, String MethodName, Object[] parameters)
    at callILClassMethod(interpret* ip, Char* method, UInt32 chdl, cqlClass* c)
    at cqlClass.call(cqlClass* , Char* , Int32* , interpret* )
    at cqlClass.call(cqlClass* , Char* , Int32* , interpret* )
    at [AosKernel.dll]Microsoft.Dynamics.AX.DataAccess.Kernel.NativeCqlRecordCollection.LoadInitialData(Int32& rc)
    at Microsoft.Dynamics.AX.DataAccess.Kernel.Proxy.CIRecordCollection.LoadInitialData(CIRecordCollection* , Int32* rc)
    at cqlClass.callEx(cqlClass* , Char* , interpret* )
    at cqlClass.callEx(cqlClass* , Char* , interpret* )
    at [AosKernel.dll]Microsoft.Dynamics.Ax.MSIL.cqlClassIL.Call(IntPtr c, String methodName, Object[] parameters, Type[] types, Object[] varargs, Type[] varargsTypes)
    at [Microsoft.Dynamics.AX.Server.Core.dll]Microsoft.Dynamics.Ax.Xpp.XppObjectBase.Call(String methodName, Object[] parameters, Type[] types, Object[] varargs)
    at [Microsoft.Dynamics.AX.FormsEngine.dll]Dynamics.AX.Application.FormDataSource.executeQuery()
    at FormDataSource::executeQuery(Object , Object[] , Boolean& )
    at [Microsoft.Dynamics.AX.Xpp.Support.dll]Microsoft.Dynamics.Ax.Xpp.ReflectionCallHelper.MakeInstanceCall(Object instance, String MethodName, Object[] parameters)
    at callILClassMethod(interpret* ip, Char* method, UInt32 chdl, cqlClass* c)
    at cqlClass.callEx(cqlClass* , Char* , interpret* )
    at cqlClass.callEx(cqlClass* , Char* , interpret* )
    at [AosKernel.dll]Microsoft.Dynamics.Ax.MSIL.cqlClassIL.Call(IntPtr c, String methodName, Object[] parameters, Type[] types, Object[] varargs, Type[] varargsTypes)
    at [Microsoft.Dynamics.AX.Server.Core.dll]Microsoft.Dynamics.Ax.Xpp.XppObjectBase.Call(String methodName, Object[] parameters, Type[] types, Object[] varargs)
    at [Microsoft.Dynamics.AX.FormsEngine.dll]Dynamics.AX.Application.FormDataSource.research()
    at [Dynamics.AX.ApplicationFoundation.dll]Dynamics.AX.Application.FilterManagerControl.`SetSorts(List dataSourceFilterExpressionList, String ascending, String clearSorts, String controlName, String sortLabel, Boolean @ascending_IsDefaultSet, Boolean @clearSorts_IsDefaultSet, Boolean @controlName_IsDefaultSet, Boolean @sortLabel_IsDefaultSet) in xppSource://Source/ApplicationFoundation\AxClass_FilterManagerControl.xpp:line 226
    at [Dynamics.AX.ApplicationFoundation.dll]Dynamics.AX.Application.FilterManagerControl.FilterManagerControlCoCHelper.`SetSorts(FilterManagerControl instance, List arg0, String arg1, String arg2, String arg3, String arg4, Boolean , Boolean , Boolean , Boolean )
    at [Dynamics.AX.ApplicationFoundation.dll]Dynamics.AX.Application.FilterManagerControl.SetSorts(List dataSourceFilterExpressionList, String ascending, String clearSorts, String controlName, String sortLabel, Boolean @ascending_IsDefaultSet, Boolean @clearSorts_IsDefaultSet, Boolean @controlName_IsDefaultSet, Boolean @sortLabel_IsDefaultSet)
    at [Dynamics.AX.ApplicationFoundation.dll]Dynamics.AX.Application.FilterManagerControl.SetSorts(List dataSourceFilterExpressionList, String ascending, String clearSorts, String controlName, String sortLabel)
    at FilterManagerControl::SetSorts(Object , Object[] , Boolean& )
    at [Microsoft.Dynamics.AX.Xpp.Support.dll]Microsoft.Dynamics.Ax.Xpp.ReflectionCallHelper.MakeInstanceCall(Object instance, String MethodName, Object[] parameters)
    at [Microsoft.Dynamics.AX.Xpp.Support.dll]Microsoft.Dynamics.Ax.Xpp.DictClass.Callobject(String _methodName, XppObjectBase _Called, Object[] varArgs)
    at [Dynamics.AX.ApplicationPlatform.dll]Dynamics.AX.Application.FormControlUtil.`invokeCommandWithSerializedArgs(FormControl _control, String _commandName, Object[] _arguments, Object[] _argumentTypes) in xppSource://Source/ApplicationPlatform\AxClass_FormControlUtil.xpp:line 160
    at [Dynamics.AX.ApplicationPlatform.dll]Dynamics.AX.Application.FormControlUtil.FormControlUtilCoCHelper.`invokeCommandWithSerializedArgs(FormControlUtil instance, FormControl arg0, String arg1, Object[] arg2, Object[] arg3)
    at [Dynamics.AX.ApplicationPlatform.dll]Dynamics.AX.Application.FormControlUtil.invokeCommandWithSerializedArgs(FormControl _control, String _commandName, Object[] _arguments, Object[] _argumentTypes)
    at FormControlUtil::invokeCommandWithSerializedArgs(Object[] , Boolean& )
    at [Microsoft.Dynamics.AX.Xpp.Support.dll]Microsoft.Dynamics.Ax.Xpp.ReflectionCallHelper.MakeStaticCall(Type type, String MethodName, Object[] parameters)
    at callILClassMethod(interpret* ip, Char* method, UInt32 chdl, cqlClass* c)
    at [AosKernel.dll]Microsoft.Dynamics.Client.ServerForm.Interactions.InteractionHandler.InvokeCommand(CommandInteraction interaction, CommandCallbackInteraction& result)
    at [AosKernel.dll]Microsoft.Dynamics.Client.ServerForm.Interactions.InteractionHandler.HandleCommandInteraction(CommandInteraction interaction, List`1 outboundInteractions)
    at [AosKernel.dll]Microsoft.Dynamics.Client.ServerForm.Interactions.InteractionHandler.HandleInteractions(IInteractionChannel interactionChannel)
    at [AosKernel.dll]Microsoft.Dynamics.Client.ServerForm.Interactions.InteractionManager.ProcessMessages(Object stateinfo)
    at [mscorlib.dll]System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
    at [mscorlib.dll]System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
    at [mscorlib.dll]System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
    at [mscorlib.dll]System.Threading.ThreadPoolWorkQueue.Dispatch()

  • Read-only form data sources

    When we add a joined data source to a form (to display some related data), the system will try to save it together with the parent record. To avoid it, we need to overwite write() method (to do nothing) and validateWrite() (to always return true). A typical example is InventDim data source in SalesTable form.

    Junior developers always struggle with it, because it's counter-intuitive.


    If we could simply set a data source as read-only, it'd be all much easier to do and understand.