A busy week

It’s been a busy week, starting with my follow-up visit to my doctor, where I found out I have a hernia. So a lot of my energy this week has been spent just thinking about that and planning for the surgery that I’m likely going to need.

And, at work, we’re in the last stretch of our upgrade from Dynamics AX 2012 RTM to R3. This is a really big update for us, as we have a lot of custom code, so there was a lot of work to do. The final upgrade is scheduled for this weekend, so I’ll have to work on Sunday. And the first week after the upgrade is liable to be a doozy, as various stuff we didn’t catch in testing shakes out.

Meanwhile, the RMA process for my MacBook’s SSD is plodding along. I got the RMA # and the UPS return label this week. I need to print out the label and package up the drive this weekend. I’ve been without my MacBook this week. and have been using my ThinkPad a lot, which is actually a pretty good experience, except that I keep wanting to use Emacs key bindings for a few things, like Ctrl-A and Ctrl-E for beginning of line and end of line. As soon as I get used to using ‘home’ and ‘end’, I’ll probably get the MacBook back up and running, and I’ll have to re-learn the Emacs key bindings.

And I’ve got my TiVo Bolt set up now. I dropped by the cable company office after work yesterday and picked up a CableCARD. I got it installed easy enough, but it took two calls to Optimum support to get it working. I think that “support tech roulette” gave me a clueless rep on the first go-round, and a more experienced one on the second. But both reps were polite and patient. Then it took me a couple of hours to set up the channels and my recordings. I’ve discovered that the channel line-up is a little different when you’re using a CableCARD than when you’re using a box. So I’m going to have to get used to some new channel numbers. The recordings seem to be working out OK too. I’ve got last night’s Daily Show, Nightly Show, and Late Show all on there, ready to watch. But I’ve also just figured out that I needed to re-run the guided setup to get the TiVo to fully recognize the channels that didn’t show up the first time around. Hopefully, that’s the last thing I’ll have to do, and it’ll be nice and stable now, with all the right channels and a fully-populated guide.

fun with credit card expiration dates

I’m doing some work on credit card processing right now. This is all related to my company’s upgrade from Dynamics AX 2012 RTM to 2012 R3. There were major changes to the credit card processing code in R2, and we’ve customized the code from RTM quite a bit, so there’s a lot to do. I’d like to write more about it, but it probably wouldn’t be of any general interest, plus I don’t want to get into any company-specific stuff.

But one very minor thing came up yesterday that was a little bit interesting. I noticed a support ticket from a user who was entering a new card, and wanted to enter an expiration date that wasn’t shown in the drop-down. (It was too far in the future.) This didn’t have anything to do with the upgrade, per se, but it made me curious enough to look at the code in RTM and in R3, to see what was being done. I was curious if maybe we had something in there like this example from The Daily WTF, or something where the upper-limit on the year was hard-coded to a specific year.

Well, it’s not quite that bad, but it is a bit questionable. In RTM, there’s a drop-down that is populated with years from (current year) to (current year + 7). In R3, it’s pretty similar, except that, when editing the expiration date after initial entry, you can go to current year + 8. (That’s probably an off-by-one error, rather than an intentional extension of the range.)

This all made me wonder what a reasonable upper-bound for expiration year would be, which lead me to this Stack Overflow question, which seems to indicate that there’s no agreed-upon maximum, but that it’s probably safe to go with 15-20 years. So Dynamics AX could stand to be be a bit more flexible on that range.

The other thing that bothers me about the way AX handles expiration dates is that (in 2012 RTM) they’re stored in the database as “MMYY”, which makes it difficult to sort and filter out expired cards. I was hoping that would be corrected in R3, but they’ve only changed it to “MM/YYYY” (without any leading zeroes on the month), which has the advantage of being more easily-readable by a human, and also of storing the full 4-digit year, but the disadvantage of being even more difficult to sort on, since the year sometimes starts in the 3rd position and sometimes in the 4th.

And let me end this blog post with a link to my very favorite publicly-documented method in AX, related to credit card processing: CreditCardWizard.codeCoverageFudge. I’m not entirely sure why this method exists in the released product, but I’m guessing it was added to fudge the code coverage numbers in some unit testing metrics. Maybe a certain percentage of code coverage had to be achieved, but there was no easy way to write meaningful tests for the remaining uncovered code, so they just wrote a method that does nothing, and a unit test for it?

AX 2012, the Wizard Wizard, and Origin GUIDs

I had a weird issue in AX recently, and, since I didn’t find much mention of it on the web, I thought I’d write it up for my blog.

A while back, I had used the “Wizard Wizard” to create a wizard form in AX. The name of this tool is kind of silly, but it’s basically correct — it’s a wizard that helps you create the form and class objects necessary to create a standard wizard control in AX. It worked fine, and the resulting wizard was deployed to production a few months ago.

I needed to make a change to it recently, though, and when I tried to import it to our testing environment, I got the following error: “Unable to save Form MyWizard. Origin value: {GUID value here} conflicts with element from model store.”

This led me down quite a path, as I haven’t really ever had a reason to dig into origin GUIDs before.
There’s a little bit of history on the origin GUID here and the best explanation I could find on origin GUIDs is here.

So, once I knew a bit about origin GUIDs, I did a little digging, and discovered that the origin GUID for my form was different in each of our environments (dev/TFS, staging, and prod). This should never happen; as the article above states, “this origin will be the same for this element in all installations, environments, versions and variations.” So… huh. How did that happen?

I looked into the TFS history, and discovered that the initial check-in for the form had no origin GUID at all. That line was simply missing from the XPO. So, if I understand things correctly, the lack of an origin GUID field in TFS is likely what caused it to be assigned new and different GUIDs in staging and production. (And, at some point, the field got filled in on my VM and made it into TFS with a completely different GUID.)

I looked into a number of other forms I’d created in the past, and couldn’t find a single instance of this ever happening before. I quickly realized that the one thing that was different about this particular form was that it had been created with the Wizard Wizard. I couldn’t initially find any evidence of an issue with this wizard, but eventually I found this question on Stack Overflow, from someone else who seems to have had the same issue.

So the end result is that I now know that, if I’m going to use the Wizard Wizard, I need to make sure I do something to force the generation of an origin GUID before I check anything in to TFS. (And that Stack Overflow question indicates that the “best practices” checker would likely have flagged this, so I should probably use that more often.)

I couldn’t come up with a particularly good solution to clean up this issue, other than duplicating the form to a new object, and deleting the old form. I briefly considered taking the GUID from prod, and trying to shoehorn it into the other environments, but that seemed like a bad idea, since I’d need to mess with TFS and with the ModelElement table in the database in each environment.

Dynamics AX silliness

How’s this for a post title?

Compare Tool causing a failure, forcing an element restore which results in negating the changes made on the element

Yes, in Dynamics AX, the ERP system I work in every day, using the “compare” tool can destroy your code! Admittedly, it’s an edge case, and it’s not likely to happen terribly often. But still. Compare tools should not actually mess up your code! (Merge tools should, maybe, sometimes. But AX doesn’t even have a merge tool. Don’t get me started…)

Dynamics AX documentation annoyances

So there’s an event I can override, with the following signature:

public void cursorNotify(int _event)

So if I look that up in the documentation, I should be able to see what values I might get for the _event parameter, right? Nope.

It says there’s going to be a table of event IDs, but there is no table.
Go back to the AX 2009 documentation, though, and you can see the table.

And hey, it even has some example code. A lot of the AX 2012 documentation leaves me with the impression that it was automatically generated, and then never reviewed by an actual human. And that they don’t really want to bother updating it or improving it And, sometimes, I feel like I need to vent about that…

Using RecordSortedList in Dynamics AX 2012

Dynamics AX 2012 has a nice class called RecordSortedList, which can be used to create a list of records from a database table. It can be useful when you need to pass a list of records into a method, or return a list of records from a method.

I honestly haven’t used it that often, but I had a case today where I thought it would be perfect. I had a method that currently returns a single record from a table, but that needed to be changed to return a short list of records.

I wrote some code to insert records into the list, then another bit to loop through the list and do something with the records in it. I was puzzled that, while I was definitely inserting multiple records into the list, I was only getting a single record back out. After some trial and error, I discovered that the methods to retrieve records from the list don’t really work right if you fail to explicitly set a sort order on the list. I wasn’t really concerned with sort order, so I didn’t bother setting one at first. Once I set a sort order, everything worked fine.

If you want to see this quirk for yourself, run the test job below with and without the sortOrder() call. As far as I can tell, this isn’t actually documented anywhere, so I thought I’d write up this blog post, as a reminder for myself, and as a resource for anyone else who happens to stumble across this little quirk.

// https://gist.github.com/andyhuey/84495f8a3480d2df31f9
static void AjhTestRSL(Args _args)
{
    CustTable custTable;
    RecordSortedList myList = new RecordSortedList(tableNum(CustTable));
    boolean moreRecs;

    myList.sortOrder(fieldNum(CustTable, AccountNum));

    // create a list
    while select firstOnly10 * from custTable
    {
        myList.ins(custTable);
    }

    // step through the list
    moreRecs = myList.first(custTable);
    while (moreRecs)
    {
        info(custTable.AccountNum);
        moreRecs = myList.next(custTable);
    }
}

Dynamics AX error-handling

I haven’t written a useful blog post about Dynamics AX in a while, so here’s something I came across this week that might be interesting. I was trying to troubleshoot a problem with a fairly complicated process that I’ve somehow wound up being in charge of. In digging through the code, I found several instance of this pattern:

try {
  ttsBegin;
  // do some stuff involving database reads and writes
  ttsCommit;
}
catch {
  ttsAbort;
}

…and that’s it. If something goes wrong, abort everything and keep quiet about it! (I guess I should call that an anti-pattern rather than a pattern.) I think this will still display any error messages to the end-user, but of course there’s no guarantee that the end-user will tell anyone.

Well, I highly suspect that things may be going wrong somewhere in one of these try blocks, given what I’m seeing in the database, so I wanted to add some logging, at least, to the catch blocks.

In AX, error messages generally get thrown into the infolog, which is a nice little mechanism for queuing up messages for the end-user to see. So, what I’d want to do in the catch block is grab any messages from the infolog and e-mail them to myself. I looked around for other code that was doing this, and found a few instances of something like this:

s = infolog.text();

…then ‘s’ would be emailed to someone, or saved off to a log file. This looked good, but I wanted to check it first, so I wrote a little test job to try it. Well, infolog.text() really only returns the first line from the log, so that’s probably not what I want. (I’m not sure if the programmer who wrote the code I was looking at only wanted the first line, or if he just didn’t realize that infolog.text() only returns one line.) So I dug a little deeper, and found that:

c = infolog.infologData();

will get the entire contents of the infolog into container ‘c’. But it also clears the infolog, so the user doesn’t see the error messages, so that’s not good.
Digging some more, I found that:

c = infolog.copy(1, infologLine());

will copy the entire infolog to a container, without clearing it, so the user can still see it. So then I tried the usual con2str() method to convert the container to a string I could email to myself. But, it turns out that it’s a structured container that can’t be converted to string that easily. So then I found info::infoCon2Str(), which parses out the infolog container structure to string, with the parts delimited by pound signs. So to break that back up into lines I can replace # with \n and off I go.

I managed to get that all into a one-liner that looks like this:

s = strReplace(info::infoCon2Str(infolog.copy(1, infologLine())), '#', '\n');

Not bad, right? Not perfect either, and you can get a more nicely formatted string out of it by writing a little utility method to parse out the container into a friendlier format. (See this blog post for an example. Look at Martin Drab’s comment below the post for a useful code snippet.)

AX 2012 list pages: missing the obvious

I spent an embarrassingly long time today trying to solve a problem in AX that was pretty simple, once someone pointed out the obvious answer to me. Just in case anyone else is looking for the same thing (and for the amusement value), I thought I’d write it up.

A list page in AX 2012 always has a drop-down in the upper right, allowing you to filter the grid by one of a number of fields. I was asked to add a new field to the list of available fields. This didn’t seem like it should be a big deal. Now, I haven’t done much work specifically with list pages. But they’re still basically AX forms, and I’ve done plenty with “regular” AX forms. Going into the list page form definition, I couldn’t find anything that looked like a control for that drop-down. It just seemed to appear magically. I found a blog post explaining how an individual user can add a new field to the drop-down, but nothing on how a programmer could add a field to it for all users.

Until someone pointed out to me that the list of fields in the drop-down corresponded exactly to the list of fields in the grid below it. So the drop-down is basically just a way to filter on any individual field in the grid. So the answer, of course, was just to add the field to the grid.

Oh, and there’s one other oddball thing about list pages that I figured out a few weeks ago, after a similarly long amount of time banging my head against the wall. List pages can be used by both EP (Enterprise Portal) and via the regular AX client. So if you need to, for instance, override the “clicked()” method on a button on a list page, you need to change the display target from “auto” to “client” before AX will let you do that. (See this blog post for details.) I guess this isn’t a good idea if you’re using EP, but we’re not, so, in my case, it’s OK.

base 36 conversion

I haven’t posted any code on here in a while, so here’s a quick little bit of code that might be useful to somebody. I had to write some code this week to do conversion from base 10 to base 36 and back, in Dynamics AX. So I just took some simple code from the Wikipedia article on base 36 and converted it to X++. Nothing fancy. The code below is all in a job, but I’ll probably put it in a utility class when I actually implement it.

// https://gist.github.com/andyhuey/5c2404b65939b5fccab8
static void AjhBase36Test(Args _args)
{
    // ajh 2014-05-07: 611.23
    // adapted from http://en.wikipedia.org/wiki/Base_36.
    // note: handles non-negative integers only.
    #define.NBASE(36)
    #define.CLIST("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")

    int64 base36_decode(str base36_input)
    {
        int64 result = 0;
        real pow = 0;
        int i, pos;
        str c;

        for (i = strLen(base36_input); i > 0; i--)
        {
            c = subStr(base36_input, i, 1);
            pos = strScan(#CLIST, c, 1, #NBASE) - 1;
            if (pos == -1)
                return pos;  // error
            result += pos * power(#NBASE, pow);
            pow++;
        }
        return result;
    }
    str base36_encode(int64 input_num)
    {
        int64 work_num = input_num;
        str result = "";
        int digitidx;
        str c;

        do {
            digitidx = int642int(work_num mod #NBASE);
            c = subStr(#CLIST, digitidx + 1, 1);
            result += c;
            work_num = work_num div #NBASE;
        } while (work_num > 0);
        return strReverse(result);
    }

    print base36_decode("");
    print base36_decode("0");
    print base36_decode("A"); // 10
    print base36_decode("7PS"); // 10,000
    print base36_decode("255S"); // 100,000
    print base36_decode("!@#$"); // error
    print base36_encode(0);
    print base36_encode(123);
    print base36_encode(10000);
    print base36_encode(100000);
    pause;
}

checking user roles in AX 2012

It’s been a while since I’ve posted anything related to Dynamics AX / X++, so I thought I’d write up something I stumbled across recently. I had created a custom form, with a number of buttons on it. Two of the buttons needed to be available only to users in a certain role.

Well, first, I should point out that this can be done without any code. See here and here for information on that. And there are good reasons to do it this way, in many cases.

But there are also some good reasons to do this in code. It allows you to document what you’re doing and why, and it gives you more flexibility than just doing it through properties in the AOT. In my case, the business rules around this didn’t really fit into the permissions available in the AOT (Read, Update, Create, and Delete), so while I could have picked one of those and used it, it wouldn’t have accurately reflected the actual use case.

So I first wanted to find a method in X++ that would tell me if a given user was in a given role. I’m familiar with Roles.IsUserInRole from the .NET Framework, and have used it frequently in the context of ASP.NET sites using custom membership providers. So I looked for something similar in AX. That led me to the SysUserManagement Class.

I wound up writing a utility method that made use of this class:

// https://gist.github.com/andyhuey/9326912
/// <summary>
/// return true is the specified user is in any of the roles in the roleNames container.
/// </summary>
/// <param name="axUserId">
/// AX user id, e.g. curUserId()
/// </param>
/// <param name="roleNames">
/// container of role names to check. (use role NAME, not label.)
/// </param>
/// <returns>
/// true if user is in ANY of the specified roles.
/// </returns>
/// <remarks>
///
/// </remarks>

public static boolean isUserInRole(UserId axUserId, container roleNames)
{
    SysUserManagement userManagement = new SysUserManagement();
    List roleList = userManagement.getRolesForUser(axUserId);
    ListEnumerator listEnum = null;
    boolean isInRole = false;
    str roleStr = '';

    if (!roleList)
        return false;

    listEnum = roleList.getEnumerator();
    while (listEnum.moveNext())
    {
        if (conFind(roleNames, listEnum.current()))
            return true;
    }

    return false;
}

It worked fine on my VM, when logged in under my own ID. But, after deployment, it quickly became apparent that X++ code running under a normal user account (without SYSADMIN rights) can’t call methods in the SysUserManagement class. Now, there’s nothing I can see in the documentation that indicates that, but I should of course have tested my code under a normal user account.

So I rewrote my code to access the appropriate role-related tables directly, and it turns out that a normal user can do that, no problem:

// https://gist.github.com/andyhuey/9326939
/// <summary>
/// return true is the specified user is in any of the roles in the roleNames container.
/// </summary>
/// <param name="axUserId">
/// AX user id, e.g. curUserId()
/// </param>
/// <param name="roleNames">
/// container of role names to check. (use role NAME, not label.)
/// </param>
/// <returns>
/// true if user is in ANY of the specified roles.
/// </returns>
/// <remarks>
/// ajh 2014-02-12: previous method req'd admin perm. to run. Doh!
/// </remarks>

public static boolean isUserInRole(UserId axUserId, container roleNames)
{
    SecurityUserRole securityUserRole;
    SecurityRole securityRole;

    while select AotName from securityRole
    join securityUserRole
    where securityUserRole.User == axUserId
    && securityUserRole.SecurityRole == securityRole.RecId
    {
        if (conFind(roleNames, securityRole.AotName))
            return true;
    }
    return false;
}

So I guess the lesson here is to always test your code under a normal user account, and not to assume that the MSDN page for a given AX class will tell you everything you need to know about that class.

And, as with a lot of stuff in AX, I have a feeling that I’m still doing this “the wrong way”, even though my code works and is fairly simple. I’m guessing that, a year from now, I’ll have figured out that there’s a better way to do this.