Dynamics 365 Business Central and .NET add-ins (part 2): why .NET Standard?

As a follow-up to this post, I’ve received some questions related to why we talked about .NET Standard if the future Microsoft’s goal is to use .NET Core on server-side for Business Central (more performant, lightweight, modular, cloud-ready) and if .NET Core is the supported runtime on Azure Functions.

A .NET add-in in Dynamics 365 Business central runs in the context of the service tier. The Dynamics 365 Business Central service tier was previously based on .NET 4.8, so you can run on it every .NET Framework add-ins you had.

But what happens if Microsoft will move the service tier to a full .NET Core runtime? Your .NET Framework add-ins cannot be executed on .NET Core! And the same happens for third-party libraries that you (and Microsoft too) could use.

Why .NET Standard is the recommended choice?

As said in my previous post on this topic, .NET Standard is a specification (not an implementation of .NET!) which defines the set of APIs that all .NET implementations must provide. It addresses the code sharing problem for .NET developers across all platforms by bringing APIs across different environments.

To be more clear, if you have a .NET Standard DLL, you can:

  • Execute it on .NET Framework runtime
  • Execute it on .NET Core runtime

and that’s the magic! Microsoft can continue to work on moving the server stack to .NET Core in different steps during the next months/years and you can continue to use .NET add-ins too.

How can this work? What’s the source of this magic? To explain this, we need to do a bit of “hacking”…

As an example, I’ve created a Visual Studio solution that contains 3 projects:

  1. a class library (DLL) written on .NET Standard 2.0 (here called D365BCNetStandardLibrary). This is my add-in that I want to use on different platforms.
  2. a Console application written with .NET Framework 4.7.1 (here called ConsoleAppNETFramework).
  3. a Console application written on .NET Core 3.1 (here called ConsoleAppNETCore).

The D365BCNetStandardLibrary dll has a simple (and here stupid) method called GetOpenOrders that returns a random number of open orders for a given customer:

The full .NET Framework app (here called ConsoleAppNETFramework) adds a reference to the .NET Standard dll:

and calls its method:

The same is for the .NET Core console application (here called ConsoleAppNETCore). It adds a reference to the .NET Standard library:

and then calls its method:

If I execute the ConsoleAppNETFramework app, it works:

and the same happens if I execute the ConsoleAppNetCore app:

Both apps, that runs on different .NET runtimes, are able to use my .NET Standard library!

How can this happens?

If we use ILDASM.exe (IL Disassembler tool available in the Windows SDK as part of a .NET Framework installation) and we open our .NET Standard library (D365BCNetStandardLibrary.dll file) we can see the content of the library file:

If you click on the class definition, you can see that it extends a [netstandard] object:

and if you inspect the GetOpenOrders method, you can see that all types are provided by .NET standard (netstandard.dll):

This means that our D365BCNetStandard library inherits from a type that comes from netstandard.dll. How can this app working on .NET Framework and .NET Core if we don’t have a reference to this assembly on our project (for example, System.Object in .NET Core is provided by System.Runtime.dll and not by netstandard.dll)?

If you open the C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.14 folder (3.1.14 is the version of the .NET Core framework I have on my machine now) you can see that, together with all the .NET Core libraries, you have also netstandard.dll:

If you open netstandard.dll with ILDASM.exe, this is the content that you will see: EMPTY!

If you click on the manifest link and then you search for System.Object, there’s this definition:

The same if you search for System.Random or all the other types you’re using on your library.

This is the trick for portability: the type definition inside netstandard.dll uses what is called “type forwarding” and redirects the type definition to the right dll for the .NET runtime you’re using. For System.Object, you can see that it redirects to System.Runtime on .NET Core.

The same if you inspect the .NET Framework folder (C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETFramework\v4.7.1\Facades). You can find netstandard.dll and if you check for example System.Object, you can see that it redirects to mscorlib in this case:

This is the secret of the cross-platform portability: when you create a .NET Standard library, the compiler translates the definitions accordingly to your target version of .NET in a fully transparent way.

As a conclusion, I can say the following: if you want to share your common code and libraries across different .NET implementations, .NET Standard is the way to go.

Comment List