Please!… Implement Different Behavior in Production versus Sandbox Dynamics 365 F&O Environments

Write code that behaves differently in production versus sandbox D365 environments.

As a customer/user, it can be a big problem when our customizations or ISV code does certain behavior like calling external web services or sending alert emails. Frequently, there’s some kind of configuration option to change that behavior: for example, a URL is stored in the database and can be changed through the GUI.

But, I can almost guarantee that every customer will be bitten at least once by doing a database refresh and accidentally sending test data out as though it was production data. Many administrators will eventually find themselves with a fairly lengthy checklist when they do a database refresh. For customers trying to do careful testing or frequent training, this can be time consumingt and possibly error-prone, even if automated as much as possible.

As developers, I feel we have an obligation to do more than meet the bare minimum of requirements; we should write code that is as easy as possible for end users, saving them time when possible. It should be a matter of professionalism, if not pride, for us. Therefore, I urge you to write code that can be configured once and will work the same in both sandbox and production environments.

How do we do that?… I recommend using a pattern with the GetUrl() method.

str prodAXUrlPattern = 'myprod.operations.dynamics.com'
str testAXUrlPattern = 'mytest.sandbox.operations.dynamics.com'
str prodWebService = 'production.isvwebservice.com'
str testWebService = 'test.isvwebservice.com'

if (strContains(UrlUtility::GetUrl(), prodAXUrlPattern))
{
MyISVClass::CallWebservice(prodWebService);
}
else if (strContains(UrlUtility::GetUrl(), testAXUrlPattern))
{
MyISVClass::CallWebservice(testWebService);
}
else
{
Throw error('This D365 environment has not been configured.');
}

Implementing something like this will require more work up front, but every administrator will thank you for foolproofing things against against costly mistakes.

Calling an X++ method on a Data Entity through OData

Today I learned of a documented but little-known (well, to me) way to put an X++ method on a data entity, and make it available via an OData call. This is a great way to build more powerful integrations through Flow or other external tools and programs.

I won’t belabor it since it is documented, but something like this:

[SysODataActionAttribute("CalcMaintenanceDuration", true)]
public int CalculateMaintenanceDuration()
{
 //do something
 return 0;
}

…will let you make a method available through an OData call. You’d use a URL that looks something like this to call the method:

https://MYCOMPANY.operations.dynamics.com/data/MYDATAENTITY.CalculateMaintenanceDuration

This needs to be done as a POST request, so you can’t easily test it in Internet Explorer like you can just getting the data entity (which sends you a JSON file full of data). Calling and using OData from outside Dynamics 365 for Finance and Operations is outside the scope of this blog post… and, honestly, outside the scope of my expertise at this point. You’ll want to look at the OData standards (where the methods you can call are usually called “actions”) and/or documentation for your language. Tools like Flow can handle all this in the background for you, just letting you browse available “actions.”

Checking in references to other models

As you might be aware, sometimes you need to add a reference to other models in your own models. You do this by going to Dynamics 365 > Model Management > Update model parameters, selecting your model, and checking the box for the model you want to reference.

Addinf references to a model

For example, here you see references added for FiscalBooks, GeneralLedger, Ledger, and Measurement. (A full discussion of references is too much for this blog post, though!)

I discovered, to my surprise, that even experienced developers sometimes don’t know how to check in these changes. If code that requires the reference is checked in, but the new reference is not, the build breaks with potentially mysterious and hard-to-diagnose errors!

How do you check in changes to references? You need to check in the DESCRIPTOR file for the model. It will be an XML file named after the model, and it will be in \AOSService\PackagesLocalDirectory\<ModelName>\Descriptor; it might excluded by default, and you need to make sure to include it in your changes. For example, if you add a reference to a model named “Czar Extensions Model,” you want to see something like this in your pending changes for the check-in:

Pending changes with Descriptor file

I hope this helps. May all your builds be “green”!

 

Resolving X++ and metadata conflicts when merging code

In the near future, Microsoft will seal off customizations/overlayering in Dynamics 365 for Operations. Once that happens, and VAR/ISVs catch up, you won’t have the same problems with conflicts while merging code from various sources (including Microsoft’s own X++ hotfixes). Until then, though… I found the process of properly resolving conflicts to be not completely intuitive, and ended up doing unnecessary manual work and comparison for a while. Here’s how to do it the right way (also the easier way), with a little-known secret or two to help.

You probably already know that after merging in new models, you need to go to Dynamics 365 > Addins > Create project from conflicts in Visual Studio to check for conflicts.

Resolve Conflicts 0a

After doing so, you need to select which model to look for conflicts in. Yes, you need to do them one at a time. Yes, the dialog disappears after each check, making it a hassle to use if you are juggling models from a bunch of different ISVs.

Resolve Conflicts 0b

To save time, be smart about which models you check. You only need to check third party “customization” models, not “extension” models. (Comment if you know of a way conflicts can appear in extension models!) If you’re savvy, you might be able to limit it further, if you know who is changing which objects. But play it safe; you don’t want to miss any conflicts!

Assuming you actually hit a conflict… it’s going to need to create a project. I like to use a naming scheme like ModelNameConflicts_YYYYMMDD to make it easy to clean up later.

The project will be created, and it will contain any and all objects that have conflicts. Here’s where it got confusing for me; intuitively, I thought I’d want to right-click the object and choose “Compare and Merge Code” or something. Nope! Start out by just opening the object.

Resolve Conflicts 1

Once you open it, it won’t be obvious where the conflicts are. They’re marked, but if it’s a big complex object, it might be hard to find them.

The secret?… put “cf:” in the search bar and hit enter.

Resolve Conflicts 2

The object will be filtered to show you the conflicts. (See the little red double arrows icon?)

What you do next depends on whether the conflict is in metadata or in code. It’s not that different, though. One at a time, right click each object, and choose either “Resolve property conflicts” or “Resolve code conflicts.”

If it’s a property conflict:

Resolve Conflicts 3

If you are resolving a property conflict, you’ll get a GUI that you hopefully find intuitive.

Resolve Conflicts 4

If it’s a code conflict:

Resolve Conflicts 5

If you are resolving a code conflict, you’ll be given a DIFF tool to do the merge. This post is not meant to fully explain code merges, but it might be intuitive for you anyway, even if you aren’t already familiar with the concept.

Resolve Conflicts 6

Whether you’ve done a property merge or a code merge, you’ll be able to mark the conflict as resolved, so it won’t come up if you do another check for conflicts.

However, re-merging models might cause the conflict to pop up again, if your ISVs (or whomever you are merging from) do not integrate the merged changes. So, be prepared to re-check in the future, and possibly correct the same conflicts repeatedly!

Catch CLR errors in X++ in Dynamics 365 for Operations

Although there are useful posts out there on catching CLR errors in X++ for older versions of Dynamics AX, as of this writing, I couldn’t find one that address the current version. It took me a little experimentation and trial-and-error to figure out how to do it; in particular, to actually return a useful error message, instead of just a generic CLR error. Below is the pattern I use. For simplicity, I’m leaving out some of the other types of CATCH you might use, as well as other detail irrelevant to the pattern.

As usual, I would greatly appreciate comments on whether this works for you, and if you know of any improvements.

System.Exception e;
InteropPermission interopPermission;
try
{
interopPermission = new InteropPermission(InteropKind::ComInterop);
interopPermission.assert();
// REST OF CODE THAT MIGHT GENERATE A CLR ERROR
}
catch (Exception::CLRError)
{
e = CLRInterop::getLastException();
if (e != null)
{
e = e.InnerException;
throw error(e.ToString());
//…or whatever you want to do with that error text
}
else
{
throw error("null CLR error caught blah blah blah");
}
}
catch
{
//other error handling
}
finally
{
CodeAccessPermission::revertAssert();
}