Quantcast
Channel: Goshoom.NET Dev Blog
Viewing all 203 articles
Browse latest View live

Data contract serialization from X++

$
0
0

You can decorate classes in AX2012 with DataContractAttribute in much the same way as in .NET with System.Runtime.Serialization.DataContractAttribute. The same is true also for DataMemberAttribute.

This is a simple data contract class in X++:

[DataContractAttribute]
class ItemContract
{
    str name;
 
    [DataMemberAttribute("Name")]
    public str parmName(str _name = name)
    {
        name = _name;
        return name;
    }
}

In .NET, you can serialize data contracts to XML (and deserialize from XML) with DataContractSerializer. What if you want to do the same with data contracts in X++?

I’m not aware of any such serializer in X++. You could write one, but it wouldn’t be a five-minute job. But there is a way how to actually use the .NET serializer.

As you surely know, AX2012 is able to compile X++ types to the Common Intermediate Language (CIL). Other “.NET” languages are compiled to CIL as well, which allows all of them to work together seamlessly. You can save a class written in a different language to a variable, call its methods, inherit from it and so on. (Don’t worry too much about differences between .NET, CLR, CIL, CLI etc. You just need to know that it’s all related to the same thing, usually called .NET).

When AX2012 generates CIL, .NET types for X++ classes and tables are created in Dynamics.Ax.Application assembly. For example, PriceDisc class is turned to Dynamics.Ax.Application.PriceDisc. If a piece of X++ code is executed in a CLR session, CIL types from Dynamics.Ax.Application are transparently used instead of original X++ types.

However, classes decorated by DataContractAttribute won’t be decorated by Dynamics.Ax.Application.DataContractAttribute in CIL. The X++ attribute is actually replaced by System.Runtime.Serialization.DataContractAttribute, therefore you’ll get a proper .NET data contract that can be serialized by DataContractSerializer. That’s likely what happens somewhere in AX kernel when you pass a data contract with parameters from AX to a report server, for instance.

By the way, I really like this approach. People from Microsoft managed to provide an easy way to define data contracts in X++ and still benefit from everything related to data contracts in .NET. Now it looks like an obvious solution, but who would have thought it about before?

Nevertheless what if we want to use the serializer from our own X++ code? First of all, let me introduce a C# method that will do all the serialization. All we have to do is to pass a data contract and we’ll get back the object serialized to XML.

using System;
using System.Runtime.Serialization;
using System.IO;
using System.Xml;
 
namespace Demo
{
    public class ContractSerializer
    {
        public static string Serialize(Object o)
        {
            Type type = o.GetType();
 
            if (!Attribute.IsDefined(type, typeof(DataContractAttribute)))
            {
                throw new ArgumentException(String.Format("{0} is not a data contract", type.FullName));
            }
 
            using (var stringWriter = new StringWriter())
            using (var xmlWriter = new XmlTextWriter(stringWriter) { Formatting = Formatting.Indented })
            {
                new DataContractSerializer(type).WriteObject(xmlWriter, o);
                return stringWriter.GetStringBuilder().ToString();
            }
        }
    }
}

You can put the class to a class library, add it to AOT and deploy it to both client and server.

Then we need to ensure ourselves that our X++ code will run in CIL. We’ll utilize SysOperation framework for that purpose:

class SerializationDemo extends SysOperationServiceController
{
    public static void main(Args args)
    {
        SerializationDemo demo = new SerializationDemo();
 
        demo.parmClassName(classStr(SerializationDemo));
        demo.parmMethodName(methodStr(SerializationDemo, serializeToInfolog));
        demo.parmExecutionMode(SysOperationExecutionMode::Synchronous);
 
        demo.run();
    }
 
    [SysEntryPointAttribute]
    public void serializeToInfolog()
    {
        if (!xSession::isCLRSession())
        {
            throw error("Must run CIL!");
        }
        // Here we'll do the job
    }
}

Do not omit the verification that we’re really in a .NET session. If the code ran in X++, it wouldn’t work, because we can’t simply pass an X++ object to a .NET method. If the code don’t execute in CIL, go to Tools > Options > Development and tick Execute business operations in CIL. Also, don’t forget to generate CIL.

If we’re in a .NET session, we can simply create an instance of a data contract class and send it to our method. Let’s create some data:

private DirPersonInfoData getPersonData()
{
    DirPersonInfoData person = new DirPersonInfoData();
 
    person.parmPersonName("John Doe");
    person.parmPersonPrimaryEmail("john(at)doe.com");
    person.parmPersonUserId("jdoe");
 
    return person;
}

Unfortunately, if you try to pass it directly to the serialization method:

DirPersonInfoData person = this.getPersonData();
Demo.ContractSerializer::Serialize(person);

it will fail with the error “Microsoft.Dynamics.AX.ManagedInterop.Object is not a data contract”.

The problem here is that the generated code uses a proxy class, which is not what we need, because proxy classes are not generated with attributes. Let’s tell AX that we’re working with a .NET object:

DirPersonInfoData person = this.getPersonData();
System.Object clrPerson = CLRInterop::getObjectForAnyType(person);
;
Demo.ContractSerializer::Serialize(clrPerson);

Now let’s put it together and finalize serializeToInfolog() method by adding exception handling and output to infolog:

[SysEntryPointAttribute]
public void serializeToInfolog()
{
    DirPersonInfoData person = this.getPersonData();
    System.Object clrPerson;
    System.Exception ex;
    str serialized;
 
    if (!xSession::isCLRSession())
    {
        throw error("Must run CIL!");
    }
 
    try
    {
        clrPerson = CLRInterop::getObjectForAnyType(person);
        serialized = Demo.ContractSerializer::Serialize(clrPerson);
    }
    catch (Exception::CLRError)
    {
        ex = CLRInterop::getLastException();
        throw error(ex.ToString());
    }
 
    info(serialized);
}

And this is how DirPersonInfoData will be serialized:

<DirPersonInfoData xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
                   xmlns="http://schemas.datacontract.org/2004/07/Dynamics.Ax.Application">
    <parmPersonCommunicatorSignIn />
    <parmPersonName>John Doe</parmPersonName>
    <parmPersonPrimaryEmail>john(at)doe.com</parmPersonPrimaryEmail>
    <parmPersonUserId>jdoe</parmPersonUserId>
    <parmWorkerTitle />
</DirPersonInfoData>

The fact that the same X++ code can be executed once in a native AX session and once in a CLR session is very powerful and you usually don’t have to care about how it works. Of course, it may be sometimes confusing. For example, you need to know how your code is executed in a given moment to use the right debugger.

Here I showed how to leverage it even more. The solution works because we can define a type in X++ and use it later as a CLR type. But it also depend on the fact how the CIL generator deals with DataContractAttribute, which is something you can only find by looking into CIL. A CIL decompiler comes handy if you want to play with these things.


Target date not found in work calendar

$
0
0

“Target date not found in work calendar” was an error that was reported to me by a customer using workflow in AX2009. It was easy to find that it was thrown by WorkflowWorkCalendarDueDateProvider.isWorkingDay() but it was far from obvious why.

I tried to update the calendar in approval time limit, although it didn’t seem to be used in that moment (but it did automatically default to a calendar for a previous year):

ApprovalTimeLimit

This seemed to be the core problem – the calendar contained dates for a single year only and anything outside that year caused an error (because, as the error message correctly said, the target date was not found in work calendar).

Changing the calendar helped a little bit, but the workflow failed with the same error a moment later. I found that the calendar ID was saved in every single workflow step (WorkflowStepTable.Duration):

PackedWorkflowStep

 

It seems to me that:

  1. A calendar used in workflow should cover all possible dates (we can add additional dates to the calendar when needed, but we won’t have to ever change the reference to calendar).
  2. If it needs to be changed, the workflow configuration should be dropped and set up again.
  3. But it’s of course possible to update all workflow steps from code:
WorkflowStepTable   step;
WorkflowConfigDate  confDate;
str                 newCalendarId = 'Workflow';
;
ttsbegin;
 
while select forUpdate step
{
    confDate = WorkflowConfigDate::create(step.Duration);
    confDate.parmDailyCalendar(newCalendarId);
    confDate.parmHourCalendar(newCalendarId);
    step.Duration = confDate.pack();
    step.update();
}
 
ttscommit;

Merging concurrent changes in AX with TFS

$
0
0

Suppose you’re an AX developer working in an AX instance shared by few other developers. You want to do some changes that would likely cause issues to your colleagues until they’re finished, therefore you’ve created a separate instance and work there. To simplify the example, assume that you’ve changed only a single method.

If nobody changed it in the meantime, it’s all good – you can simply import the method. But if somebody changed it, importing your code is a bad thing, because you would overwrite somebody else’s code. It doesn’t mean just that a feature would disappear; you would probably remove only a part of a feature and the behavior of the remaining application would be unpredictable.

What you have to do is to merge your changes with the current version of the application. In the simplest case, you want to keep all changes, therefore you’ll add your changes to what’s already there. Sometimes you want to replace a previous change (because, for example, somebody made a quick fix before your more robust solution gets ready). Sometimes you’ll have to implement completely new code to allow concurrent changes to work together. In the worst case, the changes are not compatible and can’t be merged together at all. (This usually reveals a problem with project management.)

My experience indicate that the main problem is not that developers fail to merge code correctly – they often don’t do it at all and overwrite previous changes. Unfortunately you can’t avoid merging if you want to support parallel development on the same objects, nevertheless you CAN ensure that people don’t skip merging.

The answer is obviously a version control system. If you take an object from a repository, modify it and you’re going to commit it, the version control system checks whether there aren’t already conflicting changes. If there are any, you’re not allowed to continue without resolving conflicts.

Let’s take a look at a concrete example with a few pictures. I’m going to simulate two developers working on AX2012 with Team Foundation Server. Each developer has his own AX instance pointing to the same team project and branch.

To make it easier, I used a single instance and I simulated the other developer by adding some change directly in Visual Studio, using a different TFS workspace. I took an existing, empty job and checked it out to both workspaces. In Visual Studio, I typed in // Code added in Visual Studio and checked the change in. In Dynamics AX, my change was // Code added in AX. When committing changes, TFS detects a conflict and shows a dialog where you can see and merge changes:

MergeDialog

This is a very simple example, nevertheless you can see that is shows the changes I’m checking in, the changes done by others in newer versions and the resulting merged code (on the bottom). If there are several conflicts, I can resolve them one by one or simply accept one of versions of the files: ResolveAll

I can change the code in the bottom pane as needed. For example, I decided to merge comments by creating a completely new one:

ManuallyMerged

The code inserted to TFS is the result of this merge, of course:

ViewChanges

You may not like that the dialog uses the format of .xpo files and not a visual tree as in AX, but that’s how AX code and metadata are saved to source control systems and it’s usually better for merging anyway. By the way, .NET code is stored and compared exactly in the same way – as text files; it just has prettier formats. Don’t be too afraid of the .xpo format – it’s simple and you’ll need it in a few other cases, such as when reviewing changes line by line with annotate:

Annotate

It’s also worth mentioning that you may want to have a separate branch for each developer and merge code on branch-level rather than changeset-level. An advantage for merging is that it happens independently on AX, therefore you can use latest tools, regardless what version of TFS is integrated to AX. On the other hand, you usually want to integrate code together (and find any issues caused by that) as soon as possible, which is an argument against too many branches.

Creating AX users in Powershell

$
0
0

If you want to create a user in Dynamics AX 2012, you can simply use New-AXUser cmdlet and other User and role-based security cmdlets. It’s more difficult in AX 2009 and older versions, but you can utilize Business Connector to do the job. Below is an extended function that I wrote for AX 2009. It accepts a path to an exported AX configuration (to know which AX instance to use), a domain, a user name and, if required, the new AX user ID. It also supports -WhatIf and -Confirm parameters.

It automatically adds the user to the admin group, but you easily can change the script to meet your specific requirements.

Function New-AXUser
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    Param(
        [Parameter(Mandatory=$true)]
        [string]$ConfigFile,
        [Parameter(Mandatory=$true)]
        [string] $UserDomain,		
        [Parameter(Mandatory=$true)]
        [string]$UserName,		
        [string]$AXUserId = $UserName.Substring(0, 5)
    )
 
    #region Functions
 
    Function AddToAdminGroup
    {
        $membership = $ax.CreateAxaptaRecord('UserGroupList');
        $membership.set_Field('UserId', $AXUserId);
        $membership.set_Field('GroupId', 'Admin');
        $membership.Insert()
    }
 
    Function AxLogoff
    {
        [void]$ax.Logoff()
    }
 
    Function AxLogon
    {
        try
	{
            [void][Reflection.Assembly]::LoadWithPartialName('Microsoft.Dynamics.BusinessConnectorNet');
            $script:ax = New-Object Microsoft.Dynamics.BusinessConnectorNet.Axapta
            $ax.Logon('', '', '', $ConfigFile)
	}
        catch
	{
            throw 'Logging to AX failed.'
        }
    }
 
    Function CreateUser
    {
        $userManager = $ax.CreateAxaptaObject('AxaptaUserManager');
	if ($userManager.Call('validateDomainUser', $UserDomain, $UserName) -eq $false)
        {
            throw error 'User cannot be created.'
        }
        else
        {
            $userDetails = $userManager.Call('getDomainUser', $UserDomain, $UserName);
            $sid = $userDetails.Call('getUserSid', 0);
            $dispName = $userDetails.Call('getUserName', 0);
 
            $user = $ax.CreateAxaptaRecord('UserInfo');
            $user.ExecuteStmt('select * from %1 where %1.Id == "Admin"')
 
            SetField $user 'Id' $AXUserId
            SetField $user 'NetworkAlias' $UserName
            SetField $user 'NetworkDomain' $UserDomain
            SetField $user 'Name' $dispName
            SetField $user 'Sid' $sid
 
            if ($PSCmdlet.ShouldProcess("Config: $ConfigFile, User ID: $AXUserId"))
            {
                $user.Insert();
                AddToAdminGroup
            }
        }
    }
 
    Function SetField
    {
        Param($axaptaRecord, [string]$fieldName, $value)
 
        $axaptaRecord.set_Field($fieldName, $value);
        Write-Verbose ($fieldName + ": " + $value)
    }
 
    Function ValidateParameters
    {
        if (!(Test-Path $ConfigFile))
        {
            throw "Configuration $ConfigFile doesn't exist"
        }
    }
 
    #endregion
 
    ValidateParameters
    AxLogon
    CreateUser
    AxLogoff
}

Deploying AX 2012 R3 on Azure

$
0
0

The R3 version of Dynamics AX 2012 is finally here and one of its keenly expected features is the deployment on Windows Azure. I haven’t got chance to try it yet, nevertheless I already want to share an idea about how to do it.

You’ll obviously need a Windows Azure subscription. You’ll also need Microsoft Dynamics Lifecycle Services (LCS) where you’ll create a new project (with Product version = Dynamics AX 2012 R3) and link it with the Azure subscription. Then you’ll create a new cloud environment in LCS and deploy it.

More details are available in Deploy Microsoft Dynamics AX 2012 R3 on Azure using Lifecycle Services.

Preventing users from changing session date (AX 2012)

$
0
0

What if you want to prevent users from going to File > Tools > Session date and time in Dynamics AX 2012 and changing the session date?

Usually, every user has access to the form, because the permission is included in System User role. If you go deeper, you’ll find SystemDate entry point in ClientEssentials privilege, which is included in SysServerAXBasicMaintain duty and this duty is assigned to System User role.

Therefore you can simply remove SystemDate entry point from ClientEssentials privilege and assign it only to the privilege/duty/role where you want it.

Access denied: SysOperationServiceController

$
0
0

I unexpectedly got Access denied: SysOperationServiceController when trying to call a method through the SysOperation framework in Dynamics AX 2012. It was just a piece of research code and I didn’t want to bother with my own controller, therefore I created and executed a menu item with the following properties:

Property Value
ObjectType Class
Object SysOperationServiceController
EnumTypeParameter SysOperationExecutionMode
EnumParameter Synchronous

The error message is a bit misleading, so it took me a minute to realize that I forget to define which method to call. Setting the Parameters property to MyClass.MyMethod did the job.

(In)valid conversion

$
0
0

X++ compiler is very benevolent when dealing with types – much more than it should be. It happily accepts code that can’t succeed at run-time, as I’ll will demonstrate in a minute. It’s unfortunate, because some errors could have been detected at compile-time and they aren’t. And that’s not all – the X++ runtime is also more benevolent than CLR (Common Language Runtime), therefore the same code may run successfully as native AX and fail in CLR (after compiling X++ to CIL).

Imagine two X++ classes – a base class and a child class.

class BaseClass
{
    public str name() { return "Base"; }
}
 
class ChildClass extends BaseClass
{
    public str name() { return "Child"; }
    void childOperation() {}
}

You can safely assign an instance of the child class to the parent class variable:

BaseClass base = new ChildClass();

This can’t ever go wrong – ChildClass extends BaseClass and it definitely has all its operations, because it inherits them. If you call base.name(), it will execute the implementation in ChildClass, therefore it will return “Child”. This is called polymorphism and it’s a very important component of object-oriented programming.

Now what if you try to do the opposite – to assign a variable of a general type to a specialized one? The C# compiler, for example, would throw an error (“Cannot implicitly convert ‘BaseClass’ to ‘ChildClass’”). The assignment will work only if base variable actually contains an instance of ChildClass. If it contains BaseClass or some other class extending from it, there could be a missing methods or other behavior, such as in the following example:

BaseClass base = new BaseClass();
ChildClass child = base;
child.childOperation();

The child variable here contains an instance of BaseClass, which doesn’t have any childOperation(), therefore the code must fail. That’s why compilers usually don’t allow such code.

Nevertheless this is not the case of X++ compiler – it compiles the code above regardless that it can’t ever work. It will always throw this error: “BaseClass object does not have method ‘childOperation’”.

I have one more extreme example for you:

Object o = new Dialog();
TextIo textIo = o;

You see that we work here with completely unrelated types, but the code is still compilable and runs. The compiler shows the crazy truth – you have a variable of TextIo type containing a Dialog.

Debugger

Don’t believe that an assignment is correct just because the compiler allows it – check types by yourself. In AX 2012, you have is and as operators for this purpose, therefore a better implementation could look like this:

if (base is ChildClass)
{
    child = base;
}

Add a pinch of CIL…

There is one more point to make. If you compile X++ code to CIL, it will run in a different runtime environment (CLR) which is stricter than AX. In our sample code, we saw that AX accepts the assignment on line two and throw an error when calling an non-existing method on line 3:

BaseClass base = new BaseClass();
ChildClass child = base;
child.childOperation();

But if you compile this to CIL and run it in CLR, the assignment itself will fail, because the types are not compatible. You’ll get this error: System.InvalidCastException: Unable to cast object of type ‘Dynamics.Ax.Application.BaseClass’ to type ‘Dynamics.Ax.Application.ChildClass’.

It’s not merely about when you get an error – the following code actually works in X++ but it fails in CIL:

BaseClass base = new BaseClass();
ChildClass child = base;
child.name();

The type of child variable is ChildClass and the compiler allows you to call method of ChildClass. In X++, the assignment (child = base) succeeds and child actually contains an instance of a different type (BaseClass in this case). AX still tries to call the method and if the actual object has a method of the same signature, it gets called. In our example, child.name() returns “Base” – it works, but  the behavior is wrong.

Although the code runs in AX, the assignment is still invalid from CLR perspective and the execution of CIL will fail.

Conclusion

Fortunately, all these things shouldn’t cause you too many troubles if you write your code reasonably. But you may sometimes have incorrect assumptions about the type returned by a method and you get an unexpected type assigned to your variable. Because it doesn’t get detected at this point, both you and the compiler keep the false assumption and you later don’t have a clue why the runtime complains about a missing method. I’ve seen several people struggling with this problem when working with .NET Interop from X++. And the difference between AX runtime and CLR adds some additional complexity.


«Expr» in SSRS reports

$
0
0

Do you like report designs that look like this?

Report

Me neither. It may print the right data, but it’s difficult (= expensive) to maintain.

Fortunately you can easily show something descriptive instead of «Expr». Right-click the value and pick Placeholder Properties…

ContextMenu

and type a description of the placeholder to Label:

LabelSet

The designer will then display the description instead of «Expr»:

Updated

You surely don’t want to go and change all existing reports with this problem, but I encourage you to do it for all new development.

Instrumentation and tracing

$
0
0

Regardless of how hard we try to write flawless software, sooner or later something goes wrong and we must identify where exactly the problem lies (to be able to fix it). One option is using the debugger, but it’s not always possible – we can’t reproduce the problem, for example. Another way is inserting some trace messages into code (= instrumenting the code) and logging them somewhere – it can reveal what code was called, values of certain variables, identity of a user executing some method and so on.

There are quite a few things you may want to do with such trace messages – filter them by priority or origin, save them to various destinations (e.g. text/XML/EventLog), activate/deactivate tracing without rebuilding your application and so on.

The good news is that .NET already contains such a framework and we can use it.

TraceSource in .NET (4.0)

Instead of jumping into details, let’s look at a rather simple example. If you find it interesting, you can learn more in Tracing and Instrumenting Applications.

We’ll need two main components:

  • A trace source. We’ll pass information about interesting events to the source, but the source itself doesn’t know how (or whether) messages will be logged.
  • Trace listeners. Listeners get information from a trace source and log them somewhere.

In your source code, you simply create a named TraceSource instance and use one of its methods to record an event:

System.Diagnostics.TraceSource ts = new System.Diagnostics.TraceSource("Goshoom.DemoApplication");
ts.TraceInformation("Started");

Because we don’t want to create a new TraceSource every time, it’s typically stored in a static field.

Even if we write something into a trace source, nothing is going to happen unless there is a listener. Listeners can be registered in code as well, but the usual approach is using configuration files, because it’s much more flexible. Check out the following fragment of a configuration file:

<system.diagnostics>
  <sources>
    <source name="Goshoom.DemoApplication" switchValue="Information">
      <listeners>
        <add name="TextFile" 
             type="System.Diagnostics.TextWriterTraceListener" 
             initializeData="DemoApplication.log" />
      </listeners>
    </source>
  </sources>
</system.diagnostics>

We identify the trace source by its name (that’s the value we passed to TraceSource’s constructor) and set a switch saying that we’re interested in messages of severity “Information” and higher. It gives you great flexibility – you can, say, log only errors in production environments and switch to more details only when needed and only for the trace source related to the failing component.

Then we add one listener which writes messages received from the trace source to a text file. You can have several listeners for a single source, listeners shared between sources etc. See Trace Listeners on MSDN to learn a bit more about various types of listeners.

Dynamics AX 2012

If you create .NET libraries to be used by AX as part of an AX process itself, the approach is exactly the same – you’ll create and use a trace source in your library and configure listeners in Ax32.exe.config (if the library is used by AX client) or Ax32Serv.exe.config (for AOS). Naturally, you can also listen to events recorded by existing libraries, such as WCF. (It sometimes helps me a lot, just note that you can’t configure diagnostics in endpoint configuration, you have to do that in AOS configuration).

Furthermore, you can use exactly the same approach in X++ via .NET Interop – our example mentioned before as C# is also valid X++ code:

System.Diagnostics.TraceSource ts = new System.Diagnostics.TraceSource("Goshoom.DemoApplication");
ts.TraceInformation("Started");

If X++ code doesn’t run in CIL, there is a performance penalty when invoking calls via .NET Interop. It’s nothing you have to care about if you do few calls, but it may cause troubles if it’s used in huge loops, for instance.

Infolog

Infolog messages created in AX are also automatically passed to a trace source and you can use usual techniques to log them. If you look at the MSDN article about propagating infolog messages to Event Log, you will recognize the pattern described before:

<configuration>
  <system.diagnostics>
    <trace autoflush="true"/>
    <sources>
      <source name="Microsoft.Dynamics.Kernel.Client.DiagnosticLog-Infolog"
              switchValue="Information">
        <listeners>
          <add name="EventLog" 
               type="System.Diagnostics.EventLogTraceListener" 
               initializeData="Dynamics Infolog"/>
          <add name="TextFile" 
               type="System.Diagnostics.TextWriterTraceListener" 
               initializeData="DynamicsInfologTrace.log" 
               traceOutputOptions="DateTime"/>
        </listeners>
      </source>
    </sources>
  </system.diagnostics>
</configuration>

We have two listeners there (writing messages to Event Log and to a text file) for messages received from Microsoft.Dynamics.Kernel.Client.DiagnosticLog-Infolog trace source.

Let’s try another type of listener – XmlWriterTraceListener:

<add initializeData="infolog.svclog" type="System.Diagnostics.XmlWriterTraceListener"
    name="xmlListener" traceOutputOptions="DateTime, Callstack" />

You can open such files in any text or XML editor, but there is one more option – Service Trace Viewer. I intentionally used .svclog extension instead of .xml, because .svclog files are associated with Service Trace Viewer. If you don’t have it installed, you can get it as part of Windows SDK. See how it looks like:

Service Viewer - Infolog

It has a lot of great features regarding visualization, filtering, opening several trace files in the same time (e.g. client and AOS or multiple AOSes) and so on. You may not appreciate that too much when working with infolog messages, but it’s extremely useful when things start to be a bit more complicated.

I selected Callstack as one of output options – let’s look at what we get from AX:

at System.Environment.GetStackTrace(Exception e, Boolean needFileInfo)
at System.Environment.get_StackTrace()
at System.Diagnostics.TraceEventCache.get_Callstack()
at System.Diagnostics.XmlWriterTraceListener.WriteFooter(TraceEventCache eventCache)
at System.Diagnostics.XmlWriterTraceListener.TraceEvent(TraceEventCache eventCache, String source, TraceEventType eventType, Int32 id, String message)
at System.Diagnostics.TraceSource.TraceEvent(TraceEventType eventType, Int32 id, String message)
at Microsoft.Dynamics.Kernel.Client.DiagnosticLog.AddInfologMessage(TraceEventType eventType, Int32 errorType, String message)
at Microsoft.Dynamics.Kernel.Client.DiagnosticLogWrapper.AddInfologMessage(DiagnosticLogWrapper* , Int32 errorType, Char* message)
at System.Windows.Forms.UnsafeNativeMethods.CallWindowProc(IntPtr wndProc, IntPtr hWnd, Int32 msg, IntPtr wParam, IntPtr lParam)
at System.Windows.Forms.NativeWindow.DefWndProc(Message& m)
at Microsoft.Dynamics.Kernel.Client.NativeWindowWithEvents.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at _wWinMainCRTStartup()

If you expected an X++ call stack, this must be a huge disappointment. All what we get is a sequence of .NET architecture calls that as some point call X++ (CallWindowProc()). Although it makes a good sense, it renders the log much less useful than it usually would be. Normally we want to know where exactly an event occured.

We can’t change the call stack generated by .NET – it’s even correct, it’s just not what we want. But we can make a workaround – we can create an additional trace message containing the X++ call stack. We’ll make following changes to the Info class:

classDeclaration
{
    System.Diagnostics.TraceSource ts;
    …
}
 
new
{super();
    ts = new System.Diagnostics.TraceSource("Microsoft.Dynamics.Kernel.Client.DiagnosticLog-Infolog");
    …
}
 
private void addTraceCallstack(str _txt)
{
    str msg = _txt + " - Callstack";
    container c = xSession::xppCallStack();
    str path;
    int i;
    boolean found;
    #AOT
 
    for (i = 1; i <= conLen(c); i += 2)
    {
        path = conPeek(c, i);
        path = substr(path, strfind(path,'\\',1,strlen(path)), strlen(path));
 
        if (found)
        {
            msg += strFmt("\n%1 [%2]", conPeek(c, i), conPeek(c, i+1));
        }
        else
        {
            if (path == #ClassesPath+'\\'+classstr(Global)+'\\'+staticmethodstr(Global, info) ||
                path == #ClassesPath+'\\'+classstr(Global)+'\\'+staticmethodstr(Global, error)||
                path == #ClassesPath+'\\'+classstr(Global)+'\\'+staticmethodstr(Global, warning) ||
                path == #ClassesPath+'\\'+classstr(Global)+'\\'+staticmethodstr(Global, checkFailed))
            {
                found = true;
            }
        }
    }
 
    ts.TraceEvent(System.Diagnostics.TraceEventType::Verbose, 0, msg);
}
 
Exception add()
{
    Exception ex;
    …
    //return super(_exception, (buildprefix?getprefix():'')+_txt);
    ex = super(_exception, (buildprefix?getprefix():'')+_txt);
    this.addTraceCallstack(_txt);
    return ex;
}

We’ve created a new TraceSource instance with the same name as used for standard infologs, which is completely valid. In addTraceCallstack(), we get X++ callstack, drop calls related to infolog handling, format the message and log it as a trace event of type Verbose. This method is triggered by add(), after processing the infolog message itself.

Any infolog message logged through the tracing infrastructure will be followed by another massage with X++ call stack.

Infolog - Callstack You’ll have to set switchValue in configuration file to Verbose. You could also log the call stack with the same severity as the original message – it’s all up to you.

If you don’t have enough, you can also check some new features for tracing in .NET 4.5.

Index fragmentation on xRef tables

$
0
0

Building cross-references for code in Dynamics AX creates millions of records in xRef tables. It has also very negative impact on fragmentation of database indexes.

This is an example of indexes on xRefReferences table from a customer’s environment after rebuilding all cross references.

Before

And here the same set of indexes a few minutes later after a rebuilding indexes:

After

If an index is heavily fragmented, it’s much slower to scan it and you’ll notice performance degradation of all queries using the index. It’s not unusual that opening Add-ins in the context menu in AOT takes several seconds just because of a simple check to xRef tables (to see whether cross-references exist for the given object). And because the index can’t be used effectively, of course.

It’s should be obvious that having right indexes isn’t sufficient – they also have to be in a good shape.

It’s wise to have a scheduled job to maintain fragmentation and statistics of all indexes, nevertheless you may also want to rebuild indexes for xRef tables immediately after updating cross references.

Issue Search

$
0
0

Issue Search is obviously not the coolest part of Microsoft Dynamics Lifecycle Services, but it’s very useful and I would like to show you a few tricks.

If you’ve never heard about Issues Search before, it’s a relatively simple web page where you can find hot fixes released for AX 2012 and even issues currently being investigated. This is how the main page looks like:SearchResults

You can see that there is a text box for search input, some filters and results. Notice that you can filter by version of AX (you sometimes need to open Advanced search and adjust this setting to get what you want), that different states of issues have different colors and that you can directly see KB number and the release date (if applicable).

If you click on an issue, you’ll see a bit more details and most importantly you’ll be able to download released hotfixes.

IssueDetails

Often you can even see changes done by Microsoft in X++ code, which allows you to quickly review the solution and impact on your application (by using View changes or clicking on a particular affected object).

CodeChanges

If you’re familiar with Team Foundation Server, you probably know that this web-based code comparison is a standard feature, so it’s not something unique to Dynamics Lifecycle Services. But it’s unusual to see such a level of transparency from software vendors and I must appreciate that Microsoft decided to share all these details with us.

Now if you return to the main page, you can see the following suggestion in the input box: Enter a keyword, KB number or AOT object path ($\ObjectType\Object or $\ObjectType\Object#element, e.g. $\Classes\Tax#post). If you suspect that something is wrong in a specific object or method, you can easily find all related fixes:

preRunModifyContract

I also noticed that in AX 2012 R3 you can open Issue Search for a given object directly from AOT.

AOTContextMenu

Just note that this currently works only for root elements, not for individual methods.

I never enjoyed searching for hotfixes on PartnerSource; Issue Search is so much better. I don’t say it’s perfect, but it’s evolving, together with the rest of Dynamics Lifecycle Services. And you can help it to evolve by sending some feedback to the product team – just use the smiling icon in the top right corner. Smile

I can tell you from my experience that they don’t ignore feedback (which, of course, doesn’t necessarily mean that your favorite feature will get on the top of their backlog).

Stakeholder license for Visual Studio Online

$
0
0

Brian Harry announced some upcoming changes in licensing of Visual Studio Online (and later for Team Foundation Server too). Everybody should be delighted by the new Stakeholder license, which will be completely free. It will have following permissions:

  • Full read/write/create on all work items
  • Create, run and save (to “My Queries”) work item queries
  • View project and team home pages
  • Access to the backlog, including add and update (but no ability to reprioritize the work)
  • Ability to receive work item alerts

That’s a lot of things that people can do without any cost, isn’t it? Now everybody will be able to participate in a VS Online / TFS project without any special licensing requirements.

Compile backwards

$
0
0

Today I noticed that Type hierarchy browser in AX 2012 R3 allows you to compile types backwards:

HierarchyBrowserCompile

Context menu in AOT supports only forward compilation.

It’s possible that the feature is there for some time and it just took me long time to notice it, because I normally don’t compile code from Type hierarchy browser.

Operand types are not compatible

$
0
0

I managed to produce a really confusing error in AX 2012. Look at the following piece of code:

MyExtendedDataType a, b;
a = b;

Pretty boring, isn’t it? There is nothing to fail… except that I got a compilation error: Operand types are not compatible with the operator. What?

Actually it become quite clear as soon as I simplified my code to this trivial example. There is clearly nothing wrong with my code, it must have something to do with the data type.

To make a long story short, I created a new extended data type of type enum and forgot to fill in the EnumType property. Linking the EDT to an enum instantly solved the problem.

But beware – the compiler won’t detect many other uses of this incorrectly-defined type. For example, MyExtendedDataType a = NoYes::Yes; is compilable X++ code even if MyExtendedDataType has empty EnumType.


XML DocType in X++

$
0
0

Let’s say that we want to use X++ to create an XML file with the following DocType definition:

<!DOCTYPE MyType SYSTEM "http://www.validome.org/check/test.dtd">

AX has Xml* classes (e.g. XmlDocument) for such purpose, but let’s build it with .NET classes first (you’ll see why in a moment). This is X++ code calling .NET classes through .NET Interop:

System.Xml.XmlDocument xmlDoc = new System.Xml.XmlDocument();
xmlDoc.AppendChild(xmlDoc.CreateDocumentType('MyType', null, 'http://www.validome.org/check/test.dtd', null));
info(xmlDoc.get_OuterXml());

The output is exactly what we want.

Now let’s try to rewrite it with native X++ classes:

XmlDocument xmlDoc = new XMLDocument();
xmlDoc.appendChild(xmlDoc.createDocumentType('MyType', '', 'http://www.validome.org/check/test.dtd', ''));
info(xmlDoc.xml());

Notice that it’s very similar to the previous example, but we had to replace null values with empty strings (”), because X++ doesn’t support null-valued strings.

The output is not correct in this case:

<!DOCTYPE MyType PUBLIC "" "http://www.validome.org/check/test.dtd"[]>

We can easily prove that the empty strings are to blame by rewriting our .NET-based example to use empty strings instead of nulls:

System.Xml.XmlDocument xmlDoc = new System.Xml.XmlDocument();
xmlDoc.AppendChild(xmlDoc.CreateDocumentType('MyType', '', 'http://www.validome.org/check/test.dtd', ''));
info(xmlDoc.get_OuterXml());

The output is identical to what we get from X++.

Let me explain what’s going on.

If you provide a value for publicId (the second parameter), the PUBLIC keyword is used, followed by the URI provided in publicId. That applies to empty URI as well, so we get:

<!DOCTYPE MyType PUBLIC "" (…)>

If you want to get SYSTEM keyword, publicId must be null, not “”. The fourth parameter, internalSubset, is a similar case.

Even if you aren’t familiar with .NET at all, you likely understand the difference between a null reference (x = null) and an empty object (x = new Object()). Unlike in X++, strings in .NET are objects (instances of System.String class) and therefore they can either refer to a value or be without any value at all (= null).

Now what we can do to set null values to XmlDocument.createDocumentType()? Well… nothing. All parameters are X++ strings and null isn’t a valid value for them. It would have to be designed differently, e.g. using strings wrapped in objects or providing an additional flag to say that the value should be ignored.

The workaround is simple – you can use .NET classes (from System.Xml namespace) as in the first code snippet.

Tested in AX 2012 R3.

Powershell management cmdlets

$
0
0

I love Powershell and I use it for many tasks such as managing AOS services and deploying AX applications. Nevertheless I rarely do much work regarding Windows servers, because there is always somebody who does that as a part of his own job. But now I’m preparing few private machines for my research and development and it’s great how management cmdlets make it easier. Just a few examples:

Rename-Computer Dev1
Install-WindowsFeature Hyper-V -IncludeManagementTools
Restart-Computer

Lovely!

Creating sales orders via AIF in AX2012

$
0
0

I was asked for an example how to create a sales order through AIF document services (SalesSalesOrderService). Here is my minimalist variant in C# (for AX2012 and demo data):

var line = new AxdEntity_SalesLine()
{
    ItemId = "D0001",
    SalesQty = 42,
    SalesUnit = "ea"
};

var order = new AxdEntity_SalesTable()
{
    CustAccount = "US-003",
    PurchOrderFormNum = "xyz",
    ReceiptDateRequested = DateTime.Now.Date,
    SalesLine = new AxdEntity_SalesLine[] { line }
};

var orderList   = new AxdEntity_SalesTable[] { order };
var callContext = new CallContext() { Company = "USMF" };
var client      = new SalesOrderServiceClient();

try
{
    client.create(callContext, orderList);
    client.Close();
}
catch
{
    client.Abort();
    throw;
}

It doesn’t have to work for you out of the box (for example, you may have additional fields required by the contract), but it should give you an idea how it looks like, without adding unnecessary complexity.

If you’re not familiar with AIF document services, you may want to look at AX 2012 Documentation Resources for AIF and Services.

Windows Azure Virtual Network with Active Directory

$
0
0

First of all, why would you want to create virtual machines in Windows Azure? There may be many reasons; let’s mention just a few of them:

  1. You don’t need any new hardware or adding more pressure on existing hardware.
  2. Creating virtual machines is very easy – you can do it in just a few clicks.
  3. You can easily build systems with several machines, for example if you need to test a clustered environment.
  4. You can easily change allocated resources, e.g. you may add additional processor cores and RAM for the time of compilation.

By the way, if you’re considering putting a production environment of Dynamics AX to Azure, you may want to wait for AX 2012 R3, where deployment to Windows Azure will be officially supported (as announced on Convergence 2013 EMEA).

In many cases, you’ll also need Active Directory (installation of Dynamics AX falls into this category). You could use your existing domain even in Windows Azure and that’s something what you probably want to do with production systems. But creating a new domain (or even several domains) is useful for many development and testing scenarios. And what’s equally important – you will get your own domain where you can do everything you want, which is typically (and fortunately) not the case with your real company domain. You may want to add new machines to a domain, create new users to test permissions, create users for AX lifecycle services, testing Group Policy and so and so on.

Before you start, you’ll need an Azure account. If you have an MSDN subscription, you’ll get monthly credit for Azure services – that’s the best way to start. Otherwise you can always use the free one-month trial.

Creating a network with Active Directory domain is relatively easy, it just requires several steps.

First of all, log into the Azure management portal and create a new virtual network. You can use Network Services > Virtual Network > Quick Create or Custom Create.NewVirtualNetwork800

Then create a new virtual machine and configure it as a domain controller. Use Install a new Active Directory forest in Windows Azure as a reference.

Create a new virtual machine from gallery (choose a template with Windows Server OS, of course).

NewVM800

Don’t forget to assign it to the newly created virtual network.

VMConfig2

Log into the new machine and configure it as a Domain Controller.

By the way, I used merely the Small VM size (1 core, 1.75 GB RAM).

When the domain controller is ready, go to the configuration of your virtual network and set the Domain Controller machine as a DNS server (use its Internal IP Address that you can find on VM’s Dashboard). You can use some additional DNS servers, if you want:

DNS400

The last step is creating a virtual machine (machines) for your real work (don’t forget to link it to the virtual network) and adding it to your new domain as usual.

If you’re in doubt which size to choose for Dynamics AX 2012, I’m using the Large one (4 cores, 7 GB RAM). You can always change it if you find it unsuitable for your purposes.

Data contract serialization from X++

$
0
0

You can decorate classes in AX2012 with DataContractAttribute in much the same way as in .NET with System.Runtime.Serialization.DataContractAttribute. The same is true also for DataMemberAttribute.

This is a simple data contract class in X++:

[DataContractAttribute]
class ItemContract
{
    str name;

    [DataMemberAttribute("Name")]
    public str parmName(str _name = name)
    {
        name = _name;
        return name;
    }
}

In .NET, you can serialize data contracts to XML (and deserialize from XML) with DataContractSerializer. What if you want to do the same with data contracts in X++?

I’m not aware of any such serializer in X++. You could write one, but it wouldn’t be a five-minute job. But there is a way how to actually use the .NET serializer.

As you surely know, AX2012 is able to compile X++ types to the Common Intermediate Language (CIL). Other “.NET” languages are compiled to CIL as well, which allows all of them to work together seamlessly. You can save a class written in a different language to a variable, call its methods, inherit from it and so on. (Don’t worry too much about differences between .NET, CLR, CIL, CLI etc. You just need to know that it’s all related to the same thing, usually called .NET).

When AX2012 generates CIL, .NET types for X++ classes and tables are created in Dynamics.Ax.Application assembly. For example, PriceDisc class is turned to Dynamics.Ax.Application.PriceDisc. If a piece of X++ code is executed in a CLR session, CIL types from Dynamics.Ax.Application are transparently used instead of original X++ types.

However, classes decorated by DataContractAttribute won’t be decorated by Dynamics.Ax.Application.DataContractAttribute in CIL. The X++ attribute is actually replaced by System.Runtime.Serialization.DataContractAttribute, therefore you’ll get a proper .NET data contract that can be serialized by DataContractSerializer. That’s likely what happens somewhere in AX kernel when you pass a data contract with parameters from AX to a report server, for instance.

By the way, I really like this approach. People from Microsoft managed to provide an easy way to define data contracts in X++ and still benefit from everything related to data contracts in .NET. Now it looks like an obvious solution, but who would have thought it about before?

Nevertheless what if we want to use the serializer from our own X++ code? First of all, let me introduce a C# method that will do all the serialization. All we have to do is to pass a data contract and we’ll get back the object serialized to XML.

using System;
using System.Runtime.Serialization;
using System.IO;
using System.Xml;

namespace Demo
{
    public class ContractSerializer
    {
        public static string Serialize(Object o)
        {
            Type type = o.GetType();

            if (!Attribute.IsDefined(type, typeof(DataContractAttribute)))
            {
                throw new ArgumentException(String.Format("{0} is not a data contract", type.FullName));
            }

            using (var stringWriter = new StringWriter())
            using (var xmlWriter = new XmlTextWriter(stringWriter) { Formatting = Formatting.Indented })
            {
                new DataContractSerializer(type).WriteObject(xmlWriter, o);
                return stringWriter.GetStringBuilder().ToString();
            }
        }
    }
}

You can put the class to a class library, add it to AOT and deploy it to both client and server.

Then we need to ensure ourselves that our X++ code will run in CIL. We’ll utilize SysOperation framework for that purpose:

class SerializationDemo extends SysOperationServiceController
{
    public static void main(Args args)
    {
        SerializationDemo demo = new SerializationDemo();

        demo.parmClassName(classStr(SerializationDemo));
        demo.parmMethodName(methodStr(SerializationDemo, serializeToInfolog));
        demo.parmExecutionMode(SysOperationExecutionMode::Synchronous);

        demo.run();
    }

    [SysEntryPointAttribute]
    public void serializeToInfolog()
    {
        if (!xSession::isCLRSession())
        {
            throw error("Must run CIL!");
        }
        // Here we'll do the job
    }
}

Do not omit the verification that we’re really in a .NET session. If the code ran in X++, it wouldn’t work, because we can’t simply pass an X++ object to a .NET method. If the code don’t execute in CIL, go to Tools > Options > Development and tick Execute business operations in CIL. Also, don’t forget to generate CIL.

If we’re in a .NET session, we can simply create an instance of a data contract class and send it to our method. Let’s create some data:

private DirPersonInfoData getPersonData()
{
    DirPersonInfoData person = new DirPersonInfoData();

    person.parmPersonName("John Doe");
    person.parmPersonPrimaryEmail("john(at)doe.com");
    person.parmPersonUserId("jdoe");

    return person;
}

Unfortunately, if you try to pass it directly to the serialization method:

DirPersonInfoData person = this.getPersonData();
Demo.ContractSerializer::Serialize(person);

it will fail with the error “Microsoft.Dynamics.AX.ManagedInterop.Object is not a data contract”.

The problem here is that the generated code uses a proxy class, which is not what we need, because proxy classes are not generated with attributes. Let’s tell AX that we’re working with a .NET object:

DirPersonInfoData person = this.getPersonData();
System.Object clrPerson = CLRInterop::getObjectForAnyType(person);
;
Demo.ContractSerializer::Serialize(clrPerson);

Now let’s put it together and finalize serializeToInfolog() method by adding exception handling and output to infolog:

[SysEntryPointAttribute]
public void serializeToInfolog()
{
    DirPersonInfoData person = this.getPersonData();
    System.Object clrPerson;
    System.Exception ex;
    str serialized;

    if (!xSession::isCLRSession())
    {
        throw error("Must run CIL!");
    }

    try
    {
        clrPerson = CLRInterop::getObjectForAnyType(person);
        serialized = Demo.ContractSerializer::Serialize(clrPerson);
    }
    catch (Exception::CLRError)
    {
        ex = CLRInterop::getLastException();
        throw error(ex.ToString());
    }

    info(serialized);
}

And this is how DirPersonInfoData will be serialized:

<DirPersonInfoData xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
                   xmlns="http://schemas.datacontract.org/2004/07/Dynamics.Ax.Application">
    <parmPersonCommunicatorSignIn />
    <parmPersonName>John Doe</parmPersonName>
    <parmPersonPrimaryEmail>john(at)doe.com</parmPersonPrimaryEmail>
    <parmPersonUserId>jdoe</parmPersonUserId>
    <parmWorkerTitle />
</DirPersonInfoData>

The fact that the same X++ code can be executed once in a native AX session and once in a CLR session is very powerful and you usually don’t have to care about how it works. Of course, it may be sometimes confusing. For example, you need to know how your code is executed in a given moment to use the right debugger.

Here I showed how to leverage it even more. The solution works because we can define a type in X++ and use it later as a CLR type. But it also depend on the fact how the CIL generator deals with DataContractAttribute, which is something you can only find by looking into CIL. A CIL decompiler comes handy if you want to play with these things.

Viewing all 203 articles
Browse latest View live