In my article on .NET Assembly Deployment in AX 2012 we reviewed how assemblies are deployed for Visual Studio projects as well as the CIL generate for X++. However, there are several scenarios one can think of where you want to deploy files outside of that. For example, you are releasing a DLL but don't want to provide the source, in which case you can't add the VS Project to the AOT. Other scenarios are files not related to code execution, for example icons or other resource files. In this article we'll look at a framework in AX that supports doing this, and it has actually existed for multiple versions of AX already: SysFileDeployer.
Let's start with a scenario. We have a .NET assembly (DLL) we need for use on the client side. We could optionally copy this DLL file into every user's client/bin folder, but that's not very convenient. If we need to make an update, we'll need to update all the clients as well. So, we want to auto-deploy these files to the client. Additionally, the question is WHERE do we put the files on the client side? Putting it in the client/bin would be one option, but there's a few potential issues. For example, what if the user doesn't have write privileges to that folder? (it's in program files after all). For auto-deploying VS projects, AX has created a VSAssemblies folder in each user's directory, and AX actually looks there to load DLL files. So we can exploit that and put our DLLs there as well. I'll go with that in this example, but of course you're free to do what you want.
Second decision is, where do we put the files to begin with? The best way in my opinion is the share/include folder on the AOS. Each AOS bin directory has an Application\Share\Include folder which already contains some images and other things to be shared. For example, my default AOS "AX60" has those files in C:\Program Files\Microsoft Dynamics AX\60\Server\AX60\bin\Application\Share\Include . We'll have the AOS load the files from there, and transfer them to the user's AppData\Local\Microsoft\Dynamics AX\VSAssemblies folder.
To start off, I'll create a new X++ project called FileDeployer and add some of the existing AX classes in there. I'll add classes SysFileDeployment, SysFileDeploymentDLL, SysFileDeploymentFile and SysFileDeployer.
Now, if we debug this framework (for example, put a breakpoint in the MAIN method of the SysFileDeployer class and restart your AX client) we can figure out how this works. Unfortunately, you'll soon figure out that this framework has an issue right from the start - but of course nothing we can't fix. Anyway, the SysFileDeploy class a static method called "filesAndVersions" which will get a list of classes (that have to inherit from SysFileDeployment) that will tell this framework which files we wish to deploy. Obviously that will be the first thing we need to customize. Next, it will loop over that list of classes, instantiate each class and call the "getServerVersion" method. The end result is it returns the list of classes with the version on the server side. This method will be called from the "isUpTodate" method on the file deployer class, then it creates an instance of each class again - this time on the client side, sets the server version it got earlier, then calls the "isClientUpdated" method. The idea is that the isClientUpdated method actually checks the version on the client, and compares it with the server version that was retrieved earlier. It all makes sense. Then from the main method in the file deployer it will call the run method on each file deployment class if it determind one file was out of date.
So a few issues here. One, if one file needs to be updated, it seems to be downloading all of them. I don't think that's a big issue considering these files are typically not large (and if they are, you may need to reconsider how you're deploying these). The biggest issue though is the check for the parmUpdate() method in that main method. It's basically checking a stored version from SysLastValue. So any time files are updated, that flag is set to true and stored for next time. Unfortunately, the check for that flag in the main() method is at the beginning of the IF statement, meaning this thing will only run once in its lifetime, to then never run again. Without customizing this framework, the easiest thing I could think of to get around this (in AX 2012 anyway, you're stuck with customizing in AX 2009) is to add our "isUpdated" logic as handlers to the parmUpToDate method and change the return value if we need to update.
If anyone has any better ideas or solutions to this issue, please let me know (put in comments or contact me).
Alright, in the next article we'll start the code.