Tuesday, May 28, 2013

Auto-Deploying DLLs and Other Resources - Part 1

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.

8 comments:

  1. Hi Joris, today we deliver an msi file. That is a lot safer and easier. We started using this file deployment but that resulted in a some installation issues. The MSI file can be used for automated rollout to clients and servers.

    ReplyDelete
    Replies
    1. I would disagree that it is easier :-) Also, this would work fine for ISV solutions such as yours, but for customer-specific development where you may have several versions of the DLLs in different environments, and have frequent updates, maintaining the installs on all the clients can be a nightmare. We already have several customers that have difficulty keeping their AX client (kernel) version in sync everywhere after patching.

      Delete
  2. Hey Joris,

    One trick I used previously was to make an empty Visual Studio project. Then have the DLL as a reference to that project. Now the clients will automatically retrieve it along with the empty visual studio project dll.

    Kyle Wascher

    ReplyDelete
    Replies
    1. I'm not sure I understand what you are saying. Referenced DLLs in VS AOT projects don't get deployed, so how does your DLL end up on the client?

      Delete
    2. I guess I forgot to mention the most important step. In the project file you use another ItemGroup with VSProjectOutputFiles

      <ItemGroup>
      <VSProjectOutputFiles Include="$(TargetDir)\Blah.Integration.Core.dll">
      <Visible>false</Visible>
      </VSProjectOutputFiles>
      </ItemGroup>

      Delete
    3. Hmm, that is interesting. I've played with the "Copy Local" setting on the references which made no difference. And I know I've played with the output files for resources. But I've never tried to add a DLL as a resource and then set it as an output file. I'm going to have to try this right away :-)

      Delete
  3. This article was instrumental is coming up with our final solution. Thanks Joris!

    We ended up using the SysFileDeployment in conjunction with AOT Resources to deploy files that were stored in the Model (not on the server file system).

    I blogged about the solution here: http://coffeestain-it.blogspot.ca/2015/05/dynamics-ax-sysfiledeployment-framework.html

    ReplyDelete
    Replies
    1. Glad it was useful, and thanks for linking your take on this here as well.

      Delete