First of all – a short disclaimer. On the face of this trick that this post is talking about, you could say “whadda heck, this is the same as the standard database deployment feature” – but you’d be wrong. The difference is huge, and over the next few posts I’ll spend some time to explain in what ways it is different specifically.

So, what do we have there. First is, the Assembly Resolver assembly code. Many of you have asked about the “dll” to put into the Add-ins folder. You can take this C# solution, compile it and you get your assembly that you can put into the Add-ins folder for the development environment. You don’t need it for the service tier (at least not if you are on 2016).

These are the differences from the first version:

There is a shared static instance of the assemblies. This makes sure there is no “memory leak” that Jaap Mosselman has identified. This static instance is a ConcurrentDictionary so it makes sure multiple threads can safely read from it and add to it, and that all sessions of one NST running instance will have one single collection of resolved assemblies. No more memory leaks.

There is still OnResolveAssembly event in C/AL, but it doesn’t fire for all sessions (another issue Jaap identified). It is enough that this event fires in only one session, so the AssemblyResolver class keeps track of all living instances of itself, and when one is disposed, it binds the event to the next session in queue. This makes sure that only one session will receive OnResolveAssembly event and is also thread safe. This makes sure that even if you deploy new assemblies to the database at runtime, you still get them properly loaded by the NST.

C/AL doesn’t attempt to be unnecessarily smart. Last version was doing some crazy .NET interop to compile itself and then bind itself to an instance of System.Object, but then to assign itself to an instance of non-existing self for the purpose of event listening. Crazy, but it worked, however it caused troubles at client-side compilation (C/AL compiler couldn’t check the AssemblyResolver type at compile time). It’s a little less crazy this time, more about this later.

The C/AL code deployed with the solution is now compatible only with 2016, however this dependency is very shallow: it’s the subsciption to the OnAfterCompanyOpen event of Codeunit 1. You simply remove this function and run codeunit 76001 from CompanyOpen trigger in Codeunit 1 and you are good to go. Also, for 2015 and earlier versions, you’ll have to deploy the Assembly Resolver assembly to the Add-ins folder of the NST.

Now, if you run NAV 2016, this functionality has zero footprint on your NST, which makes it particularly useful for deployments in Managed Service – you don’t need to deploy anything to the NST, everything is contained in C/AL.

So, how does it work?

First – it checks whether the AssemblyResolver type is available, and if not, then it installs itself:

This “installs itself” does the following:

It compiles the AssemblyResolver assembly from C# code embedded in C/AL.

It zips the assembly into a zip file.

It creates a line in the Add-ins table for the AssemblyResolver assembly, and imports the zip file created earlier as its resource file.

It cleans up the compiled assembly, zip file, and temporary folder used for this process.

Then, it rechecks if it can load the assembly:

At this point, if everything is okay, NST will locate the assembly in the Add-ins table, and deploy that assembly from the database and dynamically load it. If something is not okay, you get a message about it, and the codeunit exits.

Everything else is already described.

Now, you may think again: what the heck – he is using database deployment and is providing a feature that’s purported to replace it.

As a matter of fact, yes.

Database deployment is – as I said earlier, and as I will show again – not working correctly. I can’t know for a fact exactly what it does at execution level, but I am sure I’ve been able to pretty accurately figure it out from its behavior, and having worked with .NET since 2000 when it was still in beta, you can take my word for it: it’s doing it all wrong. This little assembly that I provide here takes care of all the wrong in there, and fixes it.

The end result is this:

NAV database deployment feature is used to deploy my assembly from the database and load it.

From that moment on, my assembly essentially takes over the functionality that would be done by the NAV database deployment feature.

And the reason why I use database deployment is simple: I want to avoid any kind of deployment of assemblies to the file system. With this little trick (having a self-compilable self-deployable assembly) I take care of that. So, for your live environments, you only need the objects in the application database (which will include the assemblies you upload to the .NET Assembly table) and all of your .NET interop will nicely work, as if assemblies were deployed in the Add-ins folder.

One small limitation in multi-tenant environments is that the InstallResolverAssembly function will only execute successfully when ran from a session of a tenant mounted with “allow application database write” setting.

And last, but not least, this works in Managed Service – I had problems making the version that binds itself during the OnAfterCompanyOpen works, but if you avoid binding from there, and load it afterwards, then this works nice.

In our solution we use custom assemblies for Reporting. These assemblies must also be accessible on the client but have to be in the search path for ReportViewer. This can be the GAC or a (named) subfolder in the RoleTailored Client directory.

For easier automatic deployment, we decided to use the second approach to allow automatic user side deployment for these assemblies without the need for a GAC deployment.

This can be accomplished by having a user writable (security!) subfolder in the RTC installation directory and having this path configured for searching in Microsoft.Dynamics.Nav.Client.exe.config in section configuration/runtime/assemblyBinding/probing. Simply add the folder to provatePath there (). Because ReportViewer is launched from RTC process, this path is added to the ReportViewer search path.

Using this approach, you are free to deploy additional DLLs without specific deployments even for Reporting. Keep in mind that the initial deployment has to take care of this config change and making this folder writable for the user.

Well, I had demonstrated something like that at TechDays 2013 (or was it 2014?), it was a little bit different – if a client side requested an assembly that was available on the server side, then server simply serialized the assembly into binary, sent the byte array over to the client, and client stored that into the client add-ins folder. Didn’t do anything of the sort for the reports, though. However – as you point out – it has security implications, so I abandoned the whole idea.

Hi! First of all, thanks for being so active here, trying to solve this, and sorry for me being unresponsive. It has been a busy conference season for me, so I didn’t have much time for maintaining my blog during this period. Anyway, I didn’t update anything on this, nor do I plan to. I create my posts as-is, primarily to share my thoughts and ideas, and I generally don’t have time to maintain that stuff in the long run. I am glad when my stuff helps somebody out there, but I can’t invest much time in maintaining individual things and ideas. This is my hobby and I do these things out of enthusiasm, and I post them, and then move on to other things that interest me in the world of NAV, Azure, and all… Hope you understand it.

When comparing .NET variables, including Enums, you cannot use C/AL comparison operators. To compare .NET variables, you must use the Equals method (of the System.Object type) that all .NET types implement or inherit. So, instead of IF var1 = var2, or IF var1 = var1.EnumValue (in case of an Enum), just write IF var1.Equals(var2), or IF var1.Equals(var1.EnumValue).

I see this mistake often being made or attempted by developers, even though it has been documented inside .NET Interoperability documentation since it was introduced with 2009 R2.

Related

Make sure that you don’t access the Microsoft.Dynamics.NAV JavaScript object before the document ready event fires. If you do so, you might experience problems on Chrome when the user refreshes the browser (F5). It appears that on refresh Chrome loads (and runs) scripts in different order, and depending on how complex scripts included in your project are, your code might get executed before Microsoft’s script is loaded, and it will cause nasty script errors. This occurs only on Chrome on PC.

Related

When you have to format C/AL variables (numbers, dates/times, booleans) for exchange with other apps, call FORMAT(variable,0,9) instead of simply FORMAT(variable). The format 9 formats the variable according to XML standards, and this value can then be interpreted correctly on any system with any regional settings. This is useful also when passing string-formatted values from C/AL to C# or JavaScript.

Related

To check if a BLOB field has a value, you call its HASVALUE function. For example: IF Item.Picture.HASVALUE THEN;

In older versions, earlier than NAV 2009, you had to call CALCFIELDS before you could check HASVALUE, which – if you think of it, did not make much sense. This was changed in NAV 2009, so ever since that version you can check HASVALUE before you decide to call CALCFIELDS first. It makes all the sense – you don’t need to pull up to 2GB of data over just to see if anything is inside.

If you are an old-school guy (or just old, as me), and you CALCFIELDS first, HASVALUE next, maybe it’s time for you to reconsider it.

Rembember – the pattern is: IF Field.HASVALUE THEN Rec.CALCFIELDS(Field);