If you are developing .NET assemblies for use with NAV, then sooner or later you’ll figure out that the new database deployment of add-ins in NAV 2016 is broken.
I’ve just suffered through medieval torture of attempting to have my NAV forget about a database-deployed assembly.
First of all – if you are merely consuming an off-the-shelf assemblies developed by somebody out there, you’ll probably not need to worry at all. However, if you are developing your own assemblies, then sooner or later you’ll find yourself stretched in exactly the same torture rack.
Here’s the scenario. I am developing an assembly that’s used by an NAV vertical product. To simplify deployment, we want to deploy the assembly from the database.
On the face of it, the feature looks lovely: you create a record in the Add-ins table, give it the same name and version as the assembly name and version, pack the assembly into a zip file, and upload that zip file as the resource into that add-in record. And then, when NAV needs that assembly, if it doesn’t find it in the Add-ins folder (and it doesn’t, because I didn’t put it there, duh!) then it finds it in the Add-ins table, and deploys it from there.
But, there is a huge problem with this: once deployed, always deployed. If you remove the Add-in record, and then attempt to use the same assembly from code – it will work.
The crazy thing – if you are inside the development environment, if you attempt to compile an object that references an assembly, then that assembly must either be present in the Add-ins folder, or it must be deployed as a client-side assembly into the %temp%\Microsoft Dynamics NAV\Add-ins\<full_version_number>\<assembly_name_and_version>.
So, my pain started like that. I first wanted to make sure I can deploy my assemblies from database. So I created the Add-in record, verified it works, and deployed it on. Then I removed the Add-in record from the database, closed the development environment, and tried to recompile the object. And it still compiled.
Okay – I thought – it must be the %temp% folder. So I checked the %temp% folder, and indeed I found the assembly sitting in there. I removed it (heck – I removed the whole %temp%\Microsoft Dynamics NAV folder!) and restarted the development environment, and then recompiled expecting an error. But no – the assembly reappered in %temp% folder.
Restarting NAV service tier, didn’t help. Restarting the whole machine didn’t help. Restarting, uninstalling NAV, restarting again, installing NAV again – guess? – didn’t help. Whatever I did, every time I attempted to compile an object that referenced an assembly that was once in the past deployed from the database, it appeared out from nowhere (from all I could tell) in my %temp% folder so my development environment could see it and use it.
Then I uninstalled NAV again, searched through global assembly cache locations, through all program files and ProgramData locations, made sure there was no sign of my assembly anywhere. Then I deleted the NAV database. Then I reinstalled my NAV, and created a new database that didn’t have anything (except standard NAV stuff) in the Add-in table, and then imported my object, and then attempted to recompile the object, and… again. It compiled nicely, and my assembly was again happily sitting in the %temp%\Microsoft Dynamics NAV folder and grinning at me.
At that point I was seriously considering throwing my computer through the window and jumping out together with it.
I knew at this point that there must be some other undocumented location from where an assembly that is ought to be deployed from the database actually being deployed. So I downloaded and ran Process Monitor and then attempted compiling. Of course, the file was again copied to my %temp% folder, but this time I saw from where.
It was from here: C:\Windows\ServiceProfiles\NetworkService\AppData\Local\Temp\Microsoft Dynamics NAV\Add-ins\<version>\<assembly_name>
Well, at that point it kind of made sense, except that it didn’t. Apparently, the Service Tier deployed the file into the temporary folder used by the service account (Network Service, in my case; might be something else in your case) instead of C:\ProgramData\Microsoft\Microsoft Dynamics NAV\<version>\Server\MicrosoftDynamicsNavServer$<instance_name>\ and then temp, assembly, or wherever. The files that should apparently be service (or instance) specific, got deployed machine-wide and in such a way that to figure them out I needed low-level system sniffing tools.
You could say: what’s the big deal? Why do I care where the files are deployed? Should it be – once your assemblies are in the database, and deployed to the client, why would it be a problem if they are not removed?
Well, apart from a ton of scenarios, mostly relatead to development work (a big fat lesson learned for me here is: don’t database-deploy in development setups!) there are runtime deployment issues.
Consider this. You have an assembly that you updated a slight bit. Like, fixed a minor bug in it. Didn’t add a new class, or removed a parameter from a method or something. All is the same, just a bit of code changed inside of a function. You version the file up, but you don’t version the assembly up. That’s how you normally do that. Microsoft does it like that. You don’t version up your assemblies unless absolutely necessary. Versioning an assembly up is a breaking change, even more so with C/AL where updating an assembly reference is a medieval torture in its own right. So, let’s say you don’t version an assembly up, you merely version the file up. Do so – and no way you can get your change propagate to those servers where that assembly (it’s old version actually) was deployed already. Apparently, once an assembly with specific assembly name and version is deployed from the database, updating it in the database (or deleting it from the database) has no effect at all.
All NAV service instances running on a machine where one server instance has deployed it from the database will use the one that was deployed first from any service instance, as long as all service instances share the same service account.
So, the proper practice seems to be to version up the whole assembly, not just the file. However, that’s a very breaking change in C/AL, as I said.
When you update an assembly version, to be able to use the new version from C/AL, you must update every single DotNet variable reference individually, and this means: find the object, go to Globals, or Locals (Locals for every single trigger individually), then for each variable looking up subtype, then looking up assembly, then finding the assembly, then selecting the assembly, then selecting the type. Two variables – two times the chore. Five hundred variables – five hundred times the chore.
Yes, I blogged about a trick how to do it quicker (https://vjeko.com/how-to-update-a-class-or-assembly-reference-in-cal-and-retain-event-trigger-code) but, there is a gotcha! You must first know which objects reference your assembly. If you simply export everything, do search-replace and then import everything, the updated objects are marked in no way. Normally, you’d want to mark the change in the object version, or at the very least in the Modifed flag (so you know which objects to ship from the development onto staging/testing/release/production/whatever).
I normally do this:
- Stop the service tier, exit the development environment.
- Wipe out any trace of the old assembly from Add-ins, %temp%, GAC, or wherever it might be.
- Run the development environment.
- Filter objects to the subset I know contains my .NET references.
- Compile all.
The list of error shows the objects that have invalid references – typically (unless I messed up something else) these are objects that refernce my now-non-existing assembly. Then I export these objects, do my trick, import them back and everything is updated, and I get a chance to mark my objects (because I know exactly which they are).
There are other ways, more best-practicy ways, but the one I described above used to work for me.
Except it didn’t – if I ever deployed my assemblies from the database!
And yet – finding which objects are updated is the least of the problems here. Once you update your references, you must now ship not only new assemblies to other environments You must also ship your new objects to those environments.
Don’t you see an issue already? For a simple fix in a function in an assembly, you suddenly have to introduce a deep breaking change all over your application before your deployments could start using your updated assembly.
Want to take my hint? Don’t do database deployment of .NET assemblies if you can avoid it at all.
However, there are scenarios where you absolutely must use database deployment. And these are when you don’t control the actual machine where your NST is running. These are any kind of third-party hosted or cloud scenarios, where you control the database, but not the infrastructure.
Does Microsoft Dynamics NAV Managed Service for Partners ring the bell?
Well, if you are on Managed Service, the only option to deploy your custom .NET assemblies is to deploy them to the database. And once you do that, if you need a change in your assemblies, then no matter how simple change that may be, you’ll have to:
- Update your assembly version (version up)
- Update all of your C/AL objects with the new reference
- Add the new assembly version to the database and import the new resource file
- Create a new application bacpac
- Deploy a new application version
- Deploy a new application service (or several of them) over the new application version
- Upgrade all existing customers from the old version to the new version
Good luck with that!
And just consider it – no matter how small a change in your assembly (in my case, it was changing the assembly reference for Newtonsoft.Json SpecificVersion property from false to true!) you have to go through all this pain.
It’s a broken feature, if you ask me. One that must be fixed, and the sooner, the better.
Now, how would I fix it? Well, I am definitely going to fix my own database deployment trick (that a few people have pointed out has some shortcomings).
But, Microsoft should definitely do this:
- Assemblies deployed from the database should be stored in ProgramData per service tier, not in \Windows\ServiceProfiles per serrvice account
- When an assembly resource is updated, even if there was no version change, all temporarily deployed copies of the assembly should be invalidated
Two simple changes. Such a big difference.
What do you think?