.Net CoreDevelopmentWPF

Deployment options in .NET Core 3.0

net core 3.0 deyploment logo

.NET Core introduced one very useful deployment option: self-contained application deployment. This means that deployment package contains all the required artifacts to run the application. I can simply copy deploy output to target machine and run it. No runtime prerequisites needed as it is the case with .NET Framework.

Furthermore, .NET Core 3.0 introduced self-contained executables. This means that I can can publish application as a executable and run it without dotnet tool. Before you need to use dotnet run command to start an application. .Net Core 3.0+ also enabled some additional features to reduced size of deployment, reduce startup times and enhance runtime performances.

Enough said, let’s dig in and investigate some deployment options in .NET Core 3.1.

For this experiment, I used simple, empty Windows Presentation Foundation (WPF) .NET Core 3.1 application. Any other type of application can be used also: console, web, other desktop stack (e.g. Windows.Forms). The only difference will be the output: different application types require different dependencies, thus different output.

I want to investigate different deployment options of the .NET Core application, like build times, output sizes, number of files in deploy output, dependencies, etc…

MSBuild command to deploy .NET Core application

I almost always use MSBuild build engine for building .NET application. My experiment in this blog is no exception. I used following command with several different deployment parameters:

Testing script

I created PowerShell script which cleans temporary build items, executes msbuild build target, prepares deploy outputs and performs timing of the deployments. Script output also enables more friendly comparison of the results.

Here is my script. It’s put together on ad-hoc, not perfect, but it fits the purpose.

Output

Let’s start from the end, and first look at my deploy statistic. Later on I will comment all these output results.

Output of the runtime dependent build is the smallest. It’s only 0,17MB in size. And this build & deploy took only 5 seconds. This makes sense: compiler just compiles source code, includes any external dependencies (in my case none) and expects you to have target .NET Core runtime installed on the end-user machine.

All self-contained builds run longer, from 10 sec to 3 minutes, depending on deploy settings. Trimmed & ready-to-run deployment version took even more then 2 minutes to build – and that’s only for a simple WPF .NET Core application. But again, self-contained (platform specific compile & putting all dependencies into deploy), trimming (removing unused code from the app) and ready-to-run option (adding performant native code into output) can be tricky and time consuming. Thus, it makes sense that linking together all required artifacts takes a bit longer compared to simple compile.

Let’s take a look of deploy outputs created by my PowerShell script in more details.

Runtime or framework dependent deployment

This is default option. The output is minimalistic and dependent on .NET Core runtime. If .NET Core is not installed on the target machine, the application will not work.

The output of my simple empty WPF application:

In my experiment, the size of simple WPF app is only about 170kB!!!! Also, application compiles very quickly, because compiler does not need to deal with deployment or linking of any assemblies from runtime. It only compiles source code and produce binaries.

This options is very handy in enterprise or cloud (Azure) environments when I know that runtime is already present.

Benefits of this type of deployment:

  • Deployed applications are small.
  • Applications are sharing runtime, thus memory and disk footprint is small when deploying several applications.
  • Only dependencies not included in runtime needs to be deployed (e.g. 3rd party assemblies, normally included with NuGet packing tool).
  • Deployed applications use .NET assembly file format which is CPU and OS agnostic (https://docs.microsoft.com/en-us/dotnet/standard/assembly/file-format). I only need to target correct .NET Core runtime.
  • It uses latest patched versions of runtime, with all the latest benefits (this could be also dangerous!).

Some drawbacks:

  • If .NET Core runtime is not installed on target environment, then deployed application will not work.
  • It can work on newer runtimes (all patches) as it was compiled against (this is generally a good thing), but application behave strange if target runtime version is different then compile runtime version.
  • If runtime is not present on the end user machine, deployment can be tricky task. By using Installers which checks and deploy all prerequisites and dependencies this can be more professional and more elegant.

In general, this deployment is more or less the same as it is deployment of .NET Framework applications. So, If you do .NET Framework application deployment, this scenario should be familiar to you.

Self-contained executable

When you execute a self-contained deployment, the .NET Core SDK creates a platform-specific binaries. You are not just targeting only .NET Core version (or .NET Standard) but you create platform specific binaries. On short: If I create self-contained console application targeting linux-x64 environment, it will no run on my win10-x64 machine. Even though, let’s say, I target .NET Core 3.0.

Thus, when creating self-contained applications/executables runtime identifier must be set (check RIDs here: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog) .

This deploy output can be simply copied to target device and started. Even more, two or more application can run side by side without worrying about runtimes installed on target machine. This deploy does not need any dependency on preinstall .NET Core runtimes. The bad thing (from packet size perspective) is that all dependencies are included in the deploy. Even more, if I ship next version of my app, it will again contain all the required assemblies.

In my experiment, my demo WPF application is now approx 150MB in size!!!!! The difference between this version and platform dependent deploy is huge. Next listing are files included in self-contained app – just to get a feeling :). Basically whole .NET Core runtime, some target native assemblies and all WPF dependencies are included in deploy.

Self-contained executable, single file

This options is exactly the same as previous one, except I have all files packed in one executable file. Just one executable and my .NET Core WPF app is working!

Self-contained executable, single file, ready-to-run

ReadyToRun option can improve the startup time of your .NET Core application. ReadyToRun is a form of ahead-of-time (AOT) compilation. ReadyToRun assemblies can (regarded to classical) contain IL (intermediate language) and native code. For example, ReadyToRun assembly compiled for .NET Core 3.0 and Linux x64 will only be usable on that specific configuration, because it contains native code that is only running on specific runtime environment.

ReadyToRun option is relative new options in the .NET Core. You can read more about this deployment option here: https://devblogs.microsoft.com/dotnet/announcing-net-core-3-0-preview-6.

In my experiment, I did not noticed any differences with this option enabled. But I only tested simple-empty WPF application. Probably, if application complexity would grow I expect I would noticed some better startup time and memory improvements.

But for now, I would use this option with care.

Self-contained executable, single file, trimmed.

The .NET Core 3.0 come with tool that can reduce the size of apps by analyzing IL and trims down unused code/assemblies. This means that compiled application is statically analyzed (at compile-time) and unused dependencies are removed from application.

This is my output:

As I noticed from my test, build & deploy process is very slow, because identifying and removing of unused dependencies from deploy is not an easy task. But at he end, deploy output is reduced quite a bit- in my case from approx. 150MB to 87MB (approx 42% size reduction). In some cases this is useful, but on the other hand it can be tricky and dangerous. If some runtime type-resolving features are used, e.g. reflection or some other runtime type-resolving mechanisms, trimming can be problematic. In this case application can behave very awkward. Again, trimming deployment option must be used carefully.

If you use reflection or you want IL linker not to exclude some assemblies from, you can explicitly say trimming tool to exclude specific assembly. You can do this by putting TrimmerRootAssembly directive into your *.csproj file.

This way IL linker will not trim out your assembly from deploy output.

Let’s take a look at Trimming and ReadyToRun options together.

Self-contained executable, single file, ready-to-run, trimmed.

This deploy is similar as previous one, except I put ReadyToRun option on. In this case, build time and the deploy size increased drastically. From 87MB to 136MB. It makes sense, because trimming reduced- removed some unused code, but on the other hand ReadyToRun option added some native code back in for performance purposes.

In this case, this is my WPF app deploy output:

Conclusion

.NET Core 3.0+ introduced several new options for application deployment. Having more possibilities to deploy an application is generally a good thing, but it also brings problems if not used correctly.

I will really use this options (trimming and ready-to-run) with care. If I will have enough resources to deploy my app (e.g. enough disk, ability to preinstall runtime, etc…) I will stay on safe side :).

But in some scenarios where size matters -like small devices- trimming could be very handy mechanism to reduce application to a minimal possible size.

My advice: If you are not sure what this deployment options mean, It’s better to stay on safe side and use default deployment option – deploying runtime dependent applications.

If you want to experiment, you can pull source code from here: https://github.com/josipx/Jenx.NetCore3.PublishStrategies

Leave a Reply

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.