Deploying .NET assemblies to clients and servers in need is no simple affair. In my last post I have explained the problem, and announced the solution.
As promised, in this post I bring you the solution.
To be fully honest, this post only brings the conceptual solution, just a little brain game for you to train your .NET brain muscles a bit. The actual code I’ll deliver in the next post.
Let me start by explaining some theory.
In .NET, assemblies are loaded automatically by .NET runtime. However, for an assembly to be loaded, it must reside either in the Global Assembly Cache (GAC), or in the same directory as the executable that’s attempting to load it. The same directory means exactly that – the same directory, not one directory up or down, or in any system directory or whatever. Same directory.
What happens if an executable attempts to load an assembly, and the assembly does not exist in either GAC or the executable’s directory?
If you say: an error happens, then you are wrong.
When .NET runtime cannot find a required assembly, it raises an event in the application that requested the assembly (more precise, in the application domain that requested it). This event is called AssemblyResolve, and it allows the application to load the assembly from somewhere where .NET framework might not look itself, but the application may expect the assembly to be. This is exactly how NAV knows to look into Add-ins subfolder, and not in some other random folder.
Very nice, this .NET guy, isn’t it?
Yes, and it’s also nice about something else – in .NET events are not one on one, like in C/AL (one event can have exactly one event trigger) but one to many: one event can have many subscribers, and all of these subscribers will be called when event is raised.
So, when an assembly cannot be found, .NET will keep firing the events until one of them returns the resolved assembly. If NAV cannot find the assembly in the Add-ins, .NET may not give up just yet.
In theory, if you could supply an AssemblyResolve event yourself, you could be able to provide the assembly to the .NET Framework even if NAV wasn’t able to locate it itself, and NAV will still happily use it, because it was successfully resolved and loaded by the application domain.
So, can you do this? Well, no, and yes.
If you attempt to subscribe to the AssemblyResolve event of the AppDomain directly from C/AL you’ll soon learn that C/AL does not support .NET events that have a return value. So, the only way to do this is to create your own assembly, add a class to it that subscribes to the AssemblyResolve event, and then either pre-feed it with assemblies, or have it raise a simple C/AL compatible event and notify C/AL when an assembly cannot be resolved.
Finally, last piece of theory is about loading assemblies. In .NET, an assembly can be loaded from file, but also from a byte array. This means that the assembly does not need to be physically present on the disk – it can, for example, be stored in a BLOB field in the database, and then streamed into a byte array, and then loaded dynamically from there. Regardless of how exactly you got your assembly (from file, or from byte array), .NET framework (for all practical purposes as far as NAV goes) does not care.
This means that with that little assembly that subscribes to AssemblyResolve event you could read an assembly from the database, on demand, and provide it to .NET runtime to use and abuse.
And that’s it. With this simple concept and little .NET theory applied to NAV practice, you can solve your DLL hell. Simply upload all your assemblies to the database, and have your assembly resolver take care of the rest.
No worries – I don’t stop here! As promised, I’ll deliver the code as well, just not in this post. I want to tease you a bit, maybe motivate you to attempt to do all this yourself to get a little practice in .NET and learn yourself about possibilities and limitations and how to overcome them.
And then, in my next post I show you my solution. And let me tell you – it won’t be anything like what you thought it would be. It’ll blow your socks off, I’m pretty sure of that. See you tomorrow!
Haha, this is exactly how I designed the NAV Image Library: create an assembly with the image library for each version, embed them in the executable (.Net doesn’t look there) and use the AssemblyResolve event to return the assembly the application is looking for, loaded by a stream.
Very smart to push this to NAV! 😉
Thanks 🙂
Good writing, I do expect there’s going to be a plot twist at the end? 🙂
Kind of 🙂
Pingback: Sorting out the DLL hell, Part 3: The Code - Vjeko.com (a Microsoft Dynamics NAV blog)
Hi,
thanks – this this is really great stuff – but I have a problem:
I compiled, the C# project to get the NavHelper class, I compiled it, signed it and put it into the Add-Ins directory.
Then I imported all Nav objects.
But if I run the ‘Assembly Resolver’ codeunit I get following error:
(translated from german)
An Insamce of .Net Framework-Object could not be loaded
Montage NavHelper.AssemblyResolver, Version=1.0.0.1, Culture=neutral, PublicKeyToken=932f549016510365
I tried google and found that it could help to unblock the DLL what I did as well. But this does not solve my problem.
What I am missing?
BTW:
If all the stuff is installed and running well how do I get access to a function of a DLL I created using C#?
I import my DLL using the .Net assembly page – but then?
How can I access a function within my DLL lets say from a codeunit?
Do I have to add an DotNet Variable?
Thanks for help on this issue!
Hi Thomas,
First, you need to add the compiled DLL into the service tier add-ins folder. It’s found in x:\Program Files\Microsoft Dynamics NAV\x.0\Service\Add-ins. If you put it there, and your service tier still throws you the “An instance of… could not be loaded”, then I really don’t know what’s going on.
After this, you have:
a) For compile time – all your DLLs must reside in the client add-ins folder. It’s in x:\Program Files (x86)\Microsoft Dynamics NAV\x.0\RoleTailored Client\Add-ins.
b) For run time – you import your DLLs into the .NET Assembly page
In essence, my NavHelper class only removes the requirement of deploying assemblies server-side for run time. It does not remove the requirement of deploying them client-side for compile time. Apart from that, everything else applies to NavHelper that applies to regular work with .NET Assemblies in NAV.
Hope this helps.