It’s official – AX 7 will support deployment on-premises

Make sure you familiarize yourself with new (upcoming) deployment options for Dynamics 365 for Operations. In short, you’ll be able to run it outside cloud (e.g. on your own hardware) and then either synchronize data to cloud or keep them locally. Of course, not having data in cloud means missing some cloud features such as Power BI. There will be also a change in licensing, because the usual subscription licenses include Azure resources, which you won’t need if you keep everything locally.

This is a huge news for quite a few customers who couldn’t go to cloud because of the distance from nearest Azure data centers, local law or company policies.

JSON-based custom service with parameters (AX 7)

Dynamics 365 for Operations deploys custom web services in two ways: as SOAP-based services and JSON-based services. AX developers are often familiar with SOAP services (which were used in AX 2012), but JSON-based ones are new to them.

One particular challenge is passing arguments to a service. Let’s say we have an Add operation, which can sum two numbers.

public int Add(int a, int b)
{
    return a + b;
}

To call the service, we have to provide values for both arguments, a and b. Because the expected format is JSON (Java Script Object Notation), we have to provide a JSON string describing an object with two properties (a and b) and their values. This is it:

{ a: 2, b: 5 }

Note that names of the properties are important – they must match parameter names in the X++ method.

Because building JSON strings by yourself can be cumbersome (especially with more complex parameters), a better approach is working with objects and leaving conversion to JSON to a serializer.

For example, you can build a simple class,

public class AddContract
{
    public int a { get; set; }
    public int b { get; set; }
}

create an instance with required values and call JsonConvert (from Newtonsoft.Json) to convert it to string:

AddContract contract = new AddContract { a = 2, b = 5 };
string json = JsonConvert.SerializeObject(contract)

If you need AddContract class just at this single place, maybe it’s not worth creating it at all. We can use an anonymous object instead and it will still work the same.

var contract = new { a = 2, b = 5 }; // Anonymous object
string json = JsonConvert.SerializeObject(contract)

When we have the JSON string, we have to send it to the service. The implementation below assumes that you use AuthenticationUtility from the sample solution from Microsoft.

// Prepare HTTP client, including authentication
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(ClientConfiguration.Default.UriString);
client.DefaultRequestHeaders.Add(OAuthHelper.OAuthHeader, OAuthHelper.GetAuthenticationHeader());
 
// Define parameters
var contract = new { a = 2, b = 5 };
 
// Create a request
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post,
                                                    "api/services/MyServiceGroup/MyService/Add");
request.Content = new StringContent(JsonConvert.SerializeObject(contract),
                                    Encoding.UTF8,
                                    "application/json");
 
// Run the service
var result = client.SendAsync(request).Result;
 
// Display result to console
if (result.IsSuccessStatusCode)
{
    Console.WriteLine(result.Content.ReadAsStringAsync().Result);
}
else
{
    Console.WriteLine(result.StatusCode);
}

If you’re using WebRequest instead of HttpClient (as in Microsoft sample code), you can use add parameters to the request in this way:

var contract = new { a = 2, b = 5 };
string json = JsonConvert.SerializeObject(contract);
 
Byte[] byteArray = Encoding.UTF8.GetBytes(json);
 
request.ContentLength = byteArray.Length;
request.ContentType = "application/json";
 
using (Stream dataStream = request.GetRequestStream())
{
    dataStream.Write(byteArray, 0, byteArray.Length);
}

It’s clearly nothing difficult, just make sure that your properties in JSON exactly match parameter names. For example, if parameters of my Add operation were prefixed with underscore (as usual in X++), the JSON string would have to be { _a: 2, _b: 5 }.

AX7 development tools without VM

If you want to run and/or modify AX 7 (Dynamics 365 for Operations), you get a whole virtual environment configured by Microsoft and you either deploy it to Azure or run as a local virtual machine on Hyper-V.

I have a huge laptop with 32 GB RAM and two SSD disks built specifically for running AX 2012 and AX 7 VMs and I also have a few environments in Azure (my MSDN subscription gives me some credit for Azure every month), therefore I have all I need for development. But sometimes I don’t have access to these environments, such as when I travel just with a small laptop, and I would still like to review some X++, verify something when answering questions in forums and so on, and having AX development tools with me would be a great help.

I tried it and found that setting it up wasn’t very difficult, as I’m going to show below. Just let me make it clear – I’m talking about having access to Application Explorer, projects, designers and code, not about building and running Dynamics AX. It would be possible, but it would require more effort and my small laptop isn’t even powerful enough to do that. When I want to develop something, I use one of my proper development environment.

First of all, you need a machine with Windows and Visual Studio 2015. To show the process from the beginning on an empty box, I’ll use a virtual machine in Azure based on the image called Visual Studio Enterprise 2015 Update 3 with Universal Windows Tools and Azure SDK 2.9 on Windows 10 Enterprise N (x64).

Files and website

We won’t build the environment from scratch – we’ll copy the application from an existing development environment. Go to an environment you want to copy, find the folder containing packages and WebRoot (most likely J:\AosService), make a copy and put it to your machine. In my example, I’ve put it to c:\AX7.

Find web.config file inside WebRoot folder and update paths according to the new location. Edit > Find and Replace > Quick Replace (or just Ctrl+H) in Visual Studio will help you with it.

Then create a site referring to WebRoot. Because IIS isn’t enabled by default, go to Windows features and add it.

Open IIS manager and add a new website.

Set the name to AOSService, Physical path to the WebRoot folder (C:\AX7\AosService\WebRoot in my case) and Port to something that isn’t already used.

We’re not going to run the website; it’s here just for Visual Studio to locate configuration. If we wanted to actually run it, we would have to pay much more attention to the configuration of both the website and the application pool.

Visual Studio tools

When you have files and the service ready, it’s time to install the Visual Studio extension containing all tools for AX (Application Explorer, project types, the menu and so on). Download a binary update from LCS and extract it to a folder (D:\AllBinary71Update3Updates in my case). Then go to DevToolsService\Scripts, find Microsoft.Dynamics.Framework.Tools.Installer.vsix, double-click it to start the installation and walk through the installation wizard.

If you want to be able to create AX projects (which may be useful), you also have to copy MSBuild target files (which are references by project files) to the expected location. You can do it, for example, by executing the following Powershell script (with the Script folder as the current location).

$dir = New-Item "C:\Program Files (x86)\MSBuild\Microsoft\Dynamics\AX" -Type Directory
cp *.targets $dir

When you run Visual Studio (as administrator), you should be able to open Application Explorer, view code, create AX projects and so on.

But one important thing is missing. When you’re trying to locate an object or you’re familiarizing yourself with the application, you’ll likely use cross references, which we don’t have there in the moment. Let’s fix it.

Cross references

Cross references are stored in a SQL Server database called DYNAMICSXREFDB, therefore go to the source environment, create a full backup of this database and copy the file to your machine. If you have SQL Server installed, connect to it, if not, not an issue – Visual Studio comes with a local DB. You may want to download and install SQL Server Management Studio and then simply connect to a server called (localdb)\MSSQLLocalDB in the same way as if it was a regular SQL Server.

Restore DYNAMICSXREFDB from the file.

Give yourself sufficient permissions to the database, such as making yourself a database owner.

You have to tell Visual Studio where it’ll find the database, therefore open {Documents}\Visual Studio 2015\Settings\DefaultConfig.xml and change the value of CrossReferencesDatabaseName to (localdb)\MSSQLLocalDB (or your database server if you don’t use local DB). Start Visual Studio again and you can start using references as usual.

Although it doesn’t build a full environment with all features, it fulfills its purpose. Now I can easily carry all AX 7 code with me, exploring it on plane, quickly checking code or property names when somebody asks in a forum and so on. My intention isn’t to stop using VMs from Microsoft for development and testing – on machines powerful enough to run AX (don’t forget that the compiler is designed for 16 GB RAM, for example), I can use the VM as well, so configuring the whole environment on my own installation of Windows would be worth the effort. I did this when I couldn’t use the VM and the time spent with setting it up it is already paying off. Documenting it for others took much more time than that. 🙂

Extensible control – HTML/JavaScript

The first part of this mini-tutorial showed how to create necessary classes for an extensible control. We have X++ classes for a control showing maps, we’ve added the control to a form and set a few properties. Now we have to add some HTML and JavaScript to do the job in browser.

Create a text file on disk, set its name to MyMap.htm and put this HTML code inside:

<div id="MyMap" data-dyn-bind="
    visible: $data.Visible,
    sizing: $dyn.layout.sizing($data)"></div>
 
<script src="https://www.google.com/jsapi"></script>
<script src="/resources/scripts/MyMap.js"></script>

It’s very simple. We have a single div representing our control, with appropriate ID. Then we use data-dyn-bind attribute to set some properties based on data from AX. It’s not strictly necessary for this example, but we would have to set some size anyway, so why not to use the right way?

At the bottom, we refer to some JavaScript files. The first one is the usual Google API, the other is a reference to a JavaScript file that we’ll add in a moment.

Create a new resource in your Visual Studio project, name it MyMapHTM and when asked for a file, use the file that you’ve just created.

We also need a resource for JavaScript. Create a file, MyMap.js, with this content:

(function () {
    'use strict';
    $dyn.controls.MyMap = function (data, element) {
        $dyn.ui.Control.apply(this, arguments);
        $dyn.ui.applyDefaults(this, data, $dyn.ui.defaults.MyMap);
    };
 
    $dyn.controls.MyMap.prototype = $dyn.ui.extendPrototype($dyn.ui.Control.prototype, {
        init: function (data, element) {
            var self = this;
 
            $dyn.ui.Control.prototype.init.apply(this, arguments);
 
            google.load("maps", 3, {
                callback: function () {
                    var map = new google.maps.Map(element, {
                            center: { lat: $dyn.value(data.Latitude), lng: $dyn.value(data.Longitude) },
                            zoom: $dyn.value(data.Zoom),
                            mapTypeId: google.maps.MapTypeId.HYBRID
                        });
                    }
            });
        }
    });
})();

If you have an API key, add it to the load() function in this way:

google.load("maps", 3, {
    other_params:"key=Abcdefg12345",
    callback: function () {

It all works for me without any API key, although it’s officially required. If needed, you can easily create a new API key. And you definitely should provide a key if you use this API in production.

I’m not going to explain the JavaScript code in detail. In short, it loads and calls the Google Maps API when our controls loads, it provides a callback function that sets properties of the map. It’s important to notice how we use values of our properties, such as $dyn.value(data.Latitude). These values come from the runtime class, MyMapControl.

As with HTML, create a resource, call it MyMapJS and add MyMap.js there.

Notice that you can edit resource files directly in Visual Studio.

editresource

And we’re done! Build the solution, run the form and you should see a map like this:

formwithmap

If you don’t see anything, make sure you’ve set Height and Width of the control, because my implementation doesn’t provide any minimal size.

If you have some other problem, you can use the debugger in Visual Studio (if it’s related to X++ classes), or press F12 in your browser to review DOM, debug JavaScript and so on.

Note that the control doesn’t merely show a picture; it’s a fully functional Google map – you can scroll, zoom the map or switch to StreetView, for example.

This control is deliberately very simple and doesn’t provide any additional logic, but you could easily utilize the JavaScript API to add pins or routes and do a plenty of other useful stuff. The control also doesn’t accept any input from users, so we didn’t need any binding to datasources nor any commands reacting to user actions, although all these things are possible. It’s always good to start with something simple and complicate things only when basics are clear enough.

Let me show just more one thing that the control already supports – setting properties from code.

Open the form in designer, find the map control (MyMapControl1) and set its AutoDeclaration property of to Yes. Then override form’s init() method and put the following code there:

public void init()
{
    super();
 
    MyMapControl1.parmLatitude(48.9745);
    MyMapControl1.parmLongitude(14.474);
    MyMapControl1.parmZoom(16);
}

When you run the form, you should see a map for coordinates provided in code and not those set in properties in designer. That was easy. 🙂

As you see, creating custom controls isn’t that complicated. The X++ classes we wrote have a few dozen lines of code, but they’re actually very simple; they do little more than just defining our three properties. If needed for your controls, you can put any arbitrary logic there and you’ll be able to manage and debug it as any other X++ code. How difficult is building the front end depends on your proficiency in HTML and especially JavaScript. You obviously can’t build a web UI without any knowledge of web technologies, but on the other hand, you build just a control and not the whole application.

When design robust controls, you should take into account several things not needed for simple examples like this, such as localization and responsive design.

Follow documentation on the wiki (User interface development home page: Control extensibility) to learn more about development of extensible controls.

I think Microsoft made a smart move by designing this framework for extensible control and making it available to everybody. Although it was possible to create custom controls in older versions of AX as well (using ActiveX and managed controls), this is fully integrated (including design-time experience in Visual Studio) and it’s really easy to use. I don’t say it’s completely trivial, but it’s not anything obscure either. I’m looking forward to building more extensible controls myself.

Extensible control – X++ classes

User interface in AX 7 (Dynamics 365 for Operations) is now in web browser, which forced Microsoft to make many fundamental changes. Obviously, they had to rewrite all controls to HTML (plus JavaScript and CSS), but it has many additional consequences. For example, it’s not possible anymore to run X++ code on client; all X++ code runs on server. If something happens in browser, such as you click a button, the browser can still call X++ code on server, but the call over internet is quite slow. Another option is running client-side logic written in JavaScript. In either case, it’s quite different from how older versions of AX worked. On the other hand, it all gives us a plenty of new options.

AX developers usually don’t have to care about any HTML, JavaScript and so on; they deal with controls (such as buttons and grids), both in designer and in X++ code. The whole rendering to HTML, client scripting, communication with server and so on is done by controls themselves, with the help of AX kernel.

That you don’t usually have to care about how control work doesn’t mean that it isn’t useful. It can help you with debugging and exploring capabilities of existing controls, but most importantly it allows you to design new controls for your particular requirements. This is extremely powerful and it’s not too complicated either. Of course, you must know something about HTML and JavaScript, and you’ll find that designing a control that works great in all cases (small resolution, right-to-left languages etc.) isn’t completely trivial, but don’t bother about it right now.

Let’s build a simple control showing a map for given coordinates. I’m not going to dive into much details, explain all capabilities and so on; I want to show a case from beginning to end without any distractions. If you want to build a more useful control, you’ll still have to go to documentation and learn about additional features from there.

First of all, we need a so-called “build class”, which defines how the control behaves at design time in Visual Studio.

Create an X++ class called MyMapControlBuild and paste the following code there:

[FormDesignControlAttribute("My map")]
class MyMapControlBuild extends FormBuildControl
{
    real    latitude;
    real    longitude;
    int     zoom;
 
    [FormDesignPropertyAttribute("Latitude", "Map")]
    public real parmLatitude(real _latitude = latitude)
    {
        if (!prmIsDefault(_latitude))
        {
            latitude = _latitude;
        }
        return latitude;
    }
 
    [FormDesignPropertyAttribute("Longitude", "Map")]
    public real parmLongitude(real _longitude = longitude)
    {
        if (!prmIsDefault(_longitude))
        {
            longitude = _longitude;
        }
        return longitude;
    }
 
    [FormDesignPropertyAttribute("Zoom", "Map")]
    public int parmZoom(int _zoom = zoom)
    {
        if (!prmIsDefault(_zoom))
        {
            zoom = _zoom;
        }
        return zoom;
    }
}

The class inherits from FormBuildControl and has an attribute defining its human-friendly name. It also contains three fields (latitude, longitude and zoom) and corresponding parm* methods, each decorated with FormDesignPropertyAttribute. We’ll see these three properties in the form designer; we’ll be able to set their values and later we’ll use them to show something on the map.

The second argument of FormDesignPropertyAttribute is the category of properties, which seems to be ignored in the moment (but I still think you should use it; hopefully it will become supported later).

Then we need one more class, a so-called “runtime class”. It represents the X++ part of the actual control when rendered in user interface. You could put more logic there, but in this case, we’ll only expose our properties.

Let’s do it in a few steps. Create a new class, MyMapControl, with the following code.

[FormControlAttribute('MyMap', '', classstr(MyMapControlBuild))]
class MyMapControl extends FormTemplateControl
{
    public void new(FormBuildControl _build, FormRun _formRun)
    {
        super(_build, _formRun);
 
        this.setTemplateId('MyMap');
        this.setResourceBundleName('/resources/html/MyMap');
    }
}

Create three FormProperty variables.

FormProperty latitude;
FormProperty longitude;
FormProperty zoom;

For each property, add a parm* method decorated with FormPropertyAttribute. These properties will be available in JavaScript in browser.

[FormPropertyAttribute(FormPropertyKind::Value, "Latitude")]
public real parmLatitude(real _value = latitude.parmValue())
{
    if (!prmIsDefault(_value))
    {
        latitude.setValueOrBinding(_value);
    }
 
    return latitude.parmValue();
}
 
[FormPropertyAttribute(FormPropertyKind::Value, "Longitude")]
public real parmLongitude(real _value = longitude.parmValue())
{
    if (!prmIsDefault(_value))
    {
        longitude.setValueOrBinding(_value);
    }
 
    return longitude.parmValue();
}
 
[FormPropertyAttribute(FormPropertyKind::Value, "Zoom")]
public int parmZoom(int _value = zoom.parmValue())
{
    if (!prmIsDefault(_value))
    {
        zoom.setValueOrBinding(_value);
    }
 
    return zoom.parmValue();
}

We also need to initialize FormProperty objects and associate them with parm* methods. Put this code to the constructor, below super().

latitude = properties.addProperty(methodStr(MyMapControl, parmLatitude), Types::Real);
longitude = properties.addProperty(methodStr(MyMapControl, parmLongitude), Types::Real);
zoom = properties.addProperty(methodStr(MyMapControl, parmZoom), Types::Integer);

The last missing piece is the initialization of the control from the build class. We take values of designer properties and put them into our actual control.

public void applyBuild()
{
    super();
 
    MyMapControlBuild build = this.build();
 
    if (build)
    {
        this.parmLatitude(build.parmLatitude());
        this.parmLongitude(build.parmLongitude());
        this.parmZoom(build.parmZoom());
    }
 
}

Here is the complete class, so you can easily copy and paste the code.

[FormControlAttribute('MyMap', '', classstr(MyMapControlBuild))]
class MyMapControl extends FormTemplateControl
{
    FormProperty latitude;
    FormProperty longitude;
    FormProperty zoom;
 
    public void new(FormBuildControl _build, FormRun _formRun)
    {
        super(_build, _formRun);
 
        this.setTemplateId('MyMap');
        this.setResourceBundleName('/resources/html/MyMap');
 
        latitude = properties.addProperty(methodStr(MyMapControl, parmLatitude), Types::Real);
        longitude = properties.addProperty(methodStr(MyMapControl, parmLongitude), Types::Real);
        zoom = properties.addProperty(methodStr(MyMapControl, parmZoom), Types::Integer);
    }
 
    public void applyBuild()
    {
        super();
 
        MyMapControlBuild build = this.build();
 
        if (build)
        {
            this.parmLatitude(build.parmLatitude());
            this.parmLongitude(build.parmLongitude());
            this.parmZoom(build.parmZoom());
        }
    }
 
    [FormPropertyAttribute(FormPropertyKind::Value, "Latitude")]
    public real parmLatitude(real _value = latitude.parmValue())
    {
        if (!prmIsDefault(_value))
        {
            latitude.setValueOrBinding(_value);
        }
 
        return latitude.parmValue();
    }
 
    [FormPropertyAttribute(FormPropertyKind::Value, "Longitude")]
    public real parmLongitude(real _value = longitude.parmValue())
    {
        if (!prmIsDefault(_value))
        {
            longitude.setValueOrBinding(_value);
        }
 
        return longitude.parmValue();
    }
 
    [FormPropertyAttribute(FormPropertyKind::Value, "Zoom")]
    public int parmZoom(int _value = zoom.parmValue())
    {
        if (!prmIsDefault(_value))
        {
            zoom.setValueOrBinding(_value);
        }
 
        return zoom.parmValue();
    }
}

Build the solution, create a new form and add the new control, My map (the name comes from FormDesignControlAttribute).

Control selection

We can’t successfully run the form yet, because we still haven’t defined how the control should render, but we can work with the control in designer, set its properties, possibly override its methods and so on.

designer

If you open properties, you’ll see our three custom properties (Latitude, Longitude and Zoom), together with many other properties common to all controls (such as Visible). I would expect to see a new group of properties, Map, but it’s not how it works in the moment.

Fill in your favorite GPS coordinates, a zoom level and the required size of the control.

properties

This is all for now, we’ll add HTML and JavaScript in the next blog post.