Adopting .NET 5

By Hammad Arif , Habib Qureshi
    Advance your knowledge in tech with a Packt subscription

  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Chapter 1: Introducing .NET 5 Features and Capabilities

About this book

.NET 5 is the unification of all .NET technologies in a single framework that can run on all platforms and provide a consistent experience to developers, regardless of the device, operating system (OS), or cloud platform they choose.

By updating to .NET 5, you can build software that can quickly adapt to the rapidly changing demands of modern consumers and stay up to date on the latest technology trends in .NET.

This book provides a comprehensive overview of all the technologies that will form the future landscape of .NET using practical examples based on real-world scenarios, along with best practices to help you migrate from legacy platforms.

You’ll start by learning about Microsoft’s vision and rationale for the unification of the platforms. Then, you’ll cover all the new language enhancements in C# 9. As you advance, you’ll find out how you can align yourself with modern technology trends, focusing on everything from microservices to orchestrated containerized deployments. Finally, you’ll learn how to effectively integrate machine learning in .NET code.

By the end of this .NET book, you’ll have gained a thorough understanding of the .NET 5 platform, together with a readiness to adapt to future .NET release cycles, and you’ll be able to make architectural decisions about porting legacy systems and code bases to a newer platform.

Publication date:
December 2020
Publisher
Packt
Pages
296
ISBN
9781800560567

 

Chapter 1: Introducing .NET 5 Features and Capabilities

This book, as its title suggests, mainly features .NET 5 and all the related best practices that surround any major development processes for professional and enterprise applications. In this first chapter, I will summarize it for you to a great extent so that you get a top-level view at a glance.

We will take a look at how it all started with .NET historically, and through this, we will reflect on why .NET matters and what benefits we can obtain easily when choosing .NET as our primary development platform. Finally, we will see how long its technical support mechanism works for when we adopt .NET 5 as our main development platform.

While we will visit cloud-native apps topics later in this book in Chapter 4, Containerized Microservices Architecture, and Chapter 6, Upgrading On-Prem Applications to the Cloud with .NET 5, in this chapter, we will go deeper into .NET 5 features and the most useful performance improvements that we can leverage in all of our new applications and services. Since .NET 5 has been built on .NET 3.1, we will be revisiting some of the most significant capabilities offered from the previous version that are also still delivered perfectly and, in some cases, with enhanced performance by .NET 5.

In this chapter, we will be looking at the following major topics:

  • Evolution of .NET
  • Discovering the distinguishing factors in .NET versions
  • What is the outlook for jumping to .NET 5?
  • Types of applications developed using .NET
  • What are .NET 5 headliners?
  • Performance improvements
  • .NET release schedule
  • .NET support life cycle

By the end of this chapter, you will be able to see the history, the types of applications that are best built by .NET in certain environments for standalone as well as enterprise products. You will also be able to see and utilize the latest features of .NET and use the performance-oriented improvements directly in your new .NET 5-based applications and services.

 

Technical requirements

This chapter has two examples of code. In order to run them, the following are prerequisites:

  • .NET 5 SDK
  • .NET Framework 4.8
  • .NET Core 3.1
  • .NET 5
  • ab – Apache HTTP server benchmarking tool

More information on Apache Bench can be found here: http://httpd.apache.org/docs/current/programs/ab.html.

Online code for examples can be found at the following GitHub URL: https://github.com/PacktPublishing/Adopting-.NET-5--Architecture-Migration-Best-Practices-and-New-Features/tree/master/Chapter01

 

Evolution of .NET

Microsoft first started working on .NET at the turn of the century and released the first version of .NET around 18 years ago, in 2002. Then, it released its version 1.1 in 2003, version 2.0 in 2005, and so on. It is worth noting that there was no consistency in the release cycle.

Back then, it used to be known as Microsoft .NET Framework, but later, they commonly referred to it as .NET Framework. four years ago, in 2016, it emerged with a big shift vis-à-vis the open source world and with its primary focus on performance and backend applications, going by the name of .NET Core.

.NET Framework initially was a Windows-only platform and, not long after, there were non-Microsoft public domain ports to other Unix-based platforms with the name of Mono. Over time, Mono saw a lot of success with the general .NET Framework. In due course, we saw the emergence and popularity of Xamarin as a .NET-based platform for smartphone app development. Xamarin was based on Mono and was actually developed by the same engineers.

In 2016, Microsoft acquired Xamarin and, in the same year, it also introduced .NET Core as a free, open source, and managed framework for Windows, Linux, and macOS. It was a cross-platform and slim version of .NET Framework. With .NET Core, Microsoft's focus shifted toward open source technologies, embracing the advent of cloud-native development and container-based architecture in platforms other than just Windows. .NET Core 2.0 was released in August 2017, and .NET Core 3.0 in May 2019.

In 2020, Microsoft's vision is One.NET. What this means is that they want to avoid confusion and go fully open source with support for all platforms across all feature sets, in other words, merge .NET Framework and .NET Core. Hence, the next major release after .NET Core 3.1 will be .NET 5. From a developer's perspective, .NET is a cross-platform runtime with a huge class library that contains all the essential elements required to code web, desktop, or mobile applications; platform-specific as well as cloud-native across various tech verticals.

Programs in .NET can be developed with a variety of programming languages such as C++, Visual Basic, and Fortran (as F#), with C# being the most popular. C# is a feature-rich, object-oriented programming language initially based on the syntax of C/C++ and Java.

Any language used for programming in .NET meets a set of minimum requirements in terms of using the .NET class libraries. These requirements are called the Common Language Specification (CLS). They share a Common Type System (CTS), such as basic data types (integers and strings). CLS and CTS are also a part of the Common Language Infrastructure (CLI). CLI is an open specification that was actually developed by Microsoft and then standardized by ISO as well as ECMA.

.NET programs are compiled to a processor-independent intermediate language (IL). When .NET-based apps run on a particular platform, IL is compiled into a native code of the processor by a just-in-time (JIT) compiler. JIT compilation is implemented by the platform-specific Common Language Runtime (CLR). Code written in .NET is known as managed code since the actual machine language is translated by the CLR.

In every .NET program, CLR provides garbage collection services. It determines whether a memory block is no longer referenced by any variable in the application and will free up that fragment of memory as and when required.

.NET Core uses consistent and well-defined API models, written in what is called.NET Standard, which makes it portable and usable to many .NET applications. In this way, the same .NET library can be used in multiple platforms and in multiple languages. If you build the .NET Standard version as the target for the assembly of your .NET library instead of .NET Framework or .NET Core, then your library will be able to use both .NET Framework and .NET Core.

Now that we have covered a bit of the history regarding .NET Framework and .NET Core, let's look at the last big part of this chapter – learning about the distinguishing factors in .NET versions.

 

Discovering the distinguishing factors in .NET versions

First of all, the biggest positive point for .NET Core over .NET Framework is that .NET Core is open source. Being open source is a huge plus point as it gets a look-over from critics all over the world as well as providing a lot of valuable feedback and improvements to the open source code. On other hand, .NET Core is newer and was rewritten for cross-platform intentions from the outset, which implies that it is meant to be portable, faster, scalable, and more performant with a lesser memory footprint.

Let's see the core advantages of .NET Framework and .NET Core over one another.

Understanding the advantages of one over the other

Remember that with the advent of .NET Core, .NET Framework is not dead. In the first chapter, we saw the support life cycle for both runtimes and that .NET Framework will still be supported for many years to come. Since it is going to stay, it does have some important aspects that render it preferable to .NET Core under certain conditions.

.NET Core has a heavy focus on microservices and cloud-native application development, and therefore is better in terms of memory and performance when using a containerized form, especially with Docker. .NET Core enables much simpler and smoother usage of side-by-side execution of the same code with different .NET Core versions in addition to its popular self-contained deployment feature.

Let's now have a quick and concise look at the benefits of one runtime over another.

Benefits of using .NET Framework as the main part of your technology stack

Although it sounds odd talking about .NET Framework, version 4.8 was released in 2019, the same year as .NET Core 3.1. Highlighting this reminds us that the tech is new and not irrelevant. Here, therefore, we see the benefits offered by .NET Framework over the other versions:

  • .NET Framework has a very long support cycle compared to .NET Core. As a big enterprise that is not a software company, you do not upgrade your tech stack every 2 or 3 years, which is the case for .NET Core to remain in support.
  • .NET Framework is shipped with Windows and is supported as long as the Windows OS version is supported. This means that the applications do not need to worry about the runtime installations and losing support.
  • If your application is stable and does not require big changes, you are better off upgrading to the latest .NET Framework version and to the latest patch level, as it could be costly to catch up and stay up to date with .NET Core versions.
  • If your existing app or services are using extensive and exclusive features (such as hosting WCF) of .NET Framework, then it is suggested to stay with .NET Framework but upgrade to the latest version. This may lead to a lot of efforts due to the number of changes required.
  • You would need to stay with .NET Framework if your app has a heavy dependency on the use of third-party components or it uses certain NuGet packages that are only available for .NET Framework.
  • If your app uses certain platform-specific features that are not available with .NET Core, then you have no choice to switch until you migrate those platform specific features; or wait until those become available with the latest .NET version.
  • If your app's code is dependent on Code Access Security (CAS), then it will have to stay with .NET Framework because .NET Core does not support it. The CAS feature is not even supported after C# 7.0.
  • There are more and more reference implementations, technical documentation, and resources available for .NET Framework compared to .NET Core.
  • .NET Core often has breaking changes or changes that render previous .NET Core versions obsolete, which means that it would require code changes in order to stay with the latest version, so staying with .NET Framework means fewer code changes.

Benefits of using .NET Core as the main part of your technology stack

Here, we see the benefits of .NET Core compared with other versions:

  • It is a cross-platform technology.
  • It is a container-aware technology.
  • It is rewritten instead of modifying.NET Framework, is built for performance, and has a smaller memory footprint, thereby making it more suited to containers.
  • With this, Entity Framework Core was also introduced, which is better than Entity Framework as it has better support for mappings and query-level performance.
  • .NET Core is shipped independently of Windows and has a clearer and more consistent release cycle.

Initially, .NET Core was built with a focus on server-side applications, but later it evolved into all popular types of applications. However, .NET Core increased its popularity and became a widely adopted framework from .NET Core 3.0. We will now see the additional aspects incorporated by .NET Core 3.0, up to .NET 5, on top of .NET Core.

Benefits of using .NET 5 onward as the main part of your technology stack

Here, we examine the benefits of .NET 5 over other versions:

  • Starting with .NET Core 3, support for WinForms and WPF was included so that the modern Windows desktop apps can be built using .NET Core and .NET 5 has further improved its support.
  • Starting with .NET 5, Xamarin evolved into .NET MAUI, and so now Android, as well as the iOS app, can also be built with the consistent platform and tooling.
  • .NET 5 supports ARM64 Linux flavors, hence the extended support of edge computing for IoT.
  • .NET 5 reduced the memory footprint even further, as well as the disc size, with improvements in app trimming, which has been furthered with container size optimization.
  • .NET 5 introduced C# 9, which added a lot of new features (which we will talk more about in the next chapter).
  • .NET 5 has introduced further performance improvements on top of the already world-class performance by .NET Core 3 in many areas.

In this section, we learned about the main offerings of the specific .NET platform as well as saw the benefits of one over the other and, in particular, what value we can add when we adopt the .NET 5 technology stack. In the next section, we will see why we are working with .NET 5.

 

What is the outlook for jumping to .NET 5?

It is not just the case that .NET 5 is the latest tech in the Microsoft and open source world. We see that adopting .NET 5 adds a lot of value as well. .NET 5 has many features that are forward-looking, such as not being tied just to the Windows platform, being container-aware, and supporting lots of cloud-native workloads. It has even added the superior support of technologies from .NET Framework, including WinForms, yet .NET 5 still does not support a number of technologies, including WebForms, WCF hosting, and Windows Workflow (WF).

This means that if you are not starting a new application, but instead, are thinking about upgrading your existing applications or services, then it could also be the case that your application depends on some libraries or NuGet packages that are not yet available on .NET Core 3.1 or .NET 5. As a strategy, you may look for alternative technologies or wait until the vendor releases the newer versions specifically for .NET 5.

In all such cases, we will dive deeper in the later chapters of this book, where we will focus on the migration topics along with the relevant situations and the example codes that will help you in making better decisions on migration topics.

Tip for WCF and WF applications

Around May 2019, there was a kick-off for two community-based open source software (OSS) projects on GitHub that are intended to bring WCF and WF support to .NET Core.

Check out Scott Hunter's blog post to see where Core WCF and Core WF fits in with the .NET 5 roadmap: Supporting the community with WF and WCF OSS projects:https://devblogs.microsoft.com/dotnet/supporting-the-community-with-wf-and-wcf-oss-projects/.

Now that we've understood the history and the advantages of .NET 5, let's check out the type of applications it can create.

 

Types of applications developed using .NET

.NET Core is a versatile framework that can be used to build many types of applications, including the following:

  • Web apps
  • Mobile apps
  • Desktop apps
  • IoT apps
  • Cloud-native services
  • Machine learning

Let's look at each one separately in this section.

Web applications

ASP.NET Core is the main framework for building web applications. It is a core component of the .NET Core ecosystem. Using the MVC architecture helps to build web apps as well as REST APIs. Razor is also a framework part of ASP.NET Core that assists in building dynamic web pages using C# and TypeScript.

Note

WebForms are no longer supported and the recommended alternative is ASP.NET Core Blazor.

Blazor helps in building an interactive client-side web UI using C# instead of JavaScript. It is therefore sharing the server-side and the client-side app logic in .NET, which in certain instances may not be the ideal approach.

In the API domain, besides REST APIs, .NET Core dived deeper into the open source world in communication protocols using a compact and performance-oriented RPC mechanism with gRPC technology. gRPC support is also present in desktop applications, Windows, Linux, and containers. .NET Core also still supports the capability to call the SOAP services via WCF tech, but does not provide hosting as server-side SOAP services. Note that gRPC is now a preferred technology over WCF.

Mobile development

.NET Core also offers mobile application development. The name of the feature set is Xamarin, which is a set of tools and libraries, enabling you to build cross-platform mobile apps. Developers can build native apps for iOS, Android, Windows, and even for macOS.

With .NET 5, Xamarin Forms are convoluted into .NET MAUI, which is open source and present on GitHub at https://github.com/dotnet/maui. With .NET MAUI, development is simplified by providing a single stack that supports all of these platforms: Android, iOS, macOS, and Windows. It provides a single project developer experience that can target multiple platforms and devices.

Desktop applications

With .NET Core 3.0, Microsoft made available the biggest improvements as regards Windows desktop development using Windows Forms and Windows Presentation Foundation (WPF). It also supports the Windows UI XAML Library (WinUI), which was introduced back then along with the Universal Windows Platform (UWP).

New boilerplate WPF or WinForms apps can be simply created using the CLI:

dotnet new wpf
dotnet new winforms p

Internet of Things

Edge computation is increasingly being utilized on a daily basis, from households and water pipes to cars, airplane engines, and what not. .NET Core also provides extensive support for IoT development via Azure infrastructure products, as well as via the UWP framework for IoT devices running Windows 10 IoT Core.

It also offers support for ARM64 on Linux, and ARM64 Docker images are also available. .NET Core has even added GPIO support for the Raspberry Pi.

Cloud-native development and microservices

All of the application deployment types available on Azure are achievable via .NET Core technologies. Various serverless, event streaming, containerization, and many other applications besides are directly supported in cross-platform flavors.

.NET Core fully supports and promotes microservice architecture-based development. It has full support for Windows, as well as Linux Docker containers, to implement microservices and fully adapts Kubernetes as a container orchestrator. It supports and promotes point-to-point communication via the gRPC protocol, as well as out-of-sync communication via various messaging patterns. We will dive deep into these topics in later chapters.

Machine learning

.NET Core provides support to execute machine learning via ML.NET, which is an open source and cross-platform machine learning framework. ML.NET allows you to create custom machine learning models, and train and build them using C# or F# for a number of machine learning scenarios. We will visit this interesting technology in more depth later in this book.

Visual Studio solution templates for .NET

Visual Studio 2019 provides some useful solution templates that generate the boilerplate code for some of the most common application development. They are shown here basically to highlight how straightforward it is to start .NET based development. Note that these solution templates are applicable to both .NET Core and .NET 5.

Here, I will provide you with a quick glimpse into the most popular solution templates for .NET Core-based projects that are readily available with Visual Studio 2019. Note that for all of these solutions, we can easily change the .NET SDK version by going into the project properties. It can be changed for any version of .NET Core and .NET 5 and so on.

Following are the solution templates for the web applications:

Figure 1.1 – Solution templates for web applications

Figure 1.1 – Solution templates for web applications

For desktop applications, we have the following templates:

Figure 1.2 – Solution templates for desktop applications

Figure 1.2 – Solution templates for desktop applications

With this, we can see how easy it is to generate the most popular application types from Visual Studio using .NET Core both for desktop and web application types. Note that in the project properties, you have the option to change the runtime to .NET 5, but the templates still name them as .NET Core.

Next, we will look at the major highlights of the single .NET, in other words, .NET 5.

 

What are the .NET 5 headliners?

Like I said earlier, .NET 5 is primarily the next release after .NET Core 3.1. The version number of 5 was chosen to avoid confusion with .NET Framework 4.x and .NET Core versions. .NET 5 is now the main implementation as a single .NET version stream for all the future releases of .NET. It will be released as .NET 5, .NET 6, .NET 7, and so on, since it has merged the two streams of .NET Framework and .NET Core into one. Therefore, .NET 5 supports more types of apps and platforms than .NET Core or .NET Framework.

Having an edge over .NET Core 3.1 and .NET Framework, .NET 5 has made considerable improvements in many areas, including the following:

  • Single-file, self-contained app: This feature has been improved further, as provided earlier by .NET 3.1.
  • App trimming and container size optimization: The size of the app has been reduced by trimming the types used inside the .NET libraries and reducing the size of the runtime binaries for containers just to what is essential.
  • C# compiler enhancements and C# 9 as well as F# 5.0: Various new features have been added and performance improved further.
  • Windows ARM64: ARM64 platform-specific optimizations in the .NET libraries.
  • Performance improvements: Applied in many areas, including text, JSON, tasks, and more.

We have now learned the purpose of .NET and seen its main headliners. We will dive deep into various performance improvements and C# 9 features to further increase our knowledge and command over .NET 5 in the next main section. For now, let's get to know some more about some of its major features.

Discovering self-contained applications

To run .NET programs on a certain setup (such as a bare metal machine, VM, or container), .NET runtime is required. Prior to .NET Core 3.0, all the previous versions were dependent on the runtime pre-present in the setup in which it executes. .NET Core 3.0 comes with support known as self-contained executables, which enable you to publish applications as a single executable for a given supported platform such as Linux, Windows, 32-bit, or 64-bit.

In order to simulate the creation of a self-contained app, I will use the .NET 5 SDK. To see the version of dotnet SDK, we run the following command:

C:\Users\Habib\source\repos\Adopting-.NET-5--Architecture-Migration-Best-Practices-and-New-Features\code\Chapter01>dotnet –version 5.0.100-rc.2.20479.15

For demonstration purposes, I will create a sample console app via the command line:

C:\Users\Habib\source\repos\Adopting-.NET-5--Architecture-Migration-Best-Practices-and-New-Features\code\Chapter01>dotnet new console -o myscapp

Then, the command to generate the self-contained executable (for Linux 64-bit) would look like this:

C:\Users\Habib\source\repos\Adopting-.NET-5--Architecture-Migration-Best-Practices-and-New-Features\code\Chapter01>dotnet publish -c release --self-contained --runtime linux-x64 -o c:\apps\myscapp_lx

Executing dir C:\apps\myscapp_lx shows me something in terms of which end part is something like this, with 186 files and around 75 MB of disk space:

...25/09/2020  23:06            16.776 WindowsBase.dll              186 File(s)     75.306.803 bytes                2 Dir(s)  709.082.009.600 bytes free

In order to generate a self-contained executable in a single .exe file (for a Windows 64-bit platform), the command would be as follows:

C:\Users\Habib\source\repos\Adopting-.NET-5--Architecture-Migration-Best-Practices-and-New-Features\code\Chapter01\myscapp>dotnet publish -c release --self-contained --runtime win-x64 /p:PublishSingleFile=true -o c:\apps\myscapp_win
Microsoft (R) Build Engine version 16.8.0-preview-20475-05+aed5e7ed0 for .NET Copyright (C) Microsoft Corporation. All rights reserved.
  Determining projects to restore...  Restored C:\Users\Habib\source\repos\Adopting-.NET-5--Architecture-Migration-Best-Practices-and-New-Features\code\Chapter01\myscapp\myscapp.csproj (in 36,23 sec).
  You are using a preview version of .NET. See: https://aka.ms/dotnet-core-preview
  myscapp -> C:\Users\Habib\source\repos\Adopting-.NET-5--Architecture-Migration-Best-Practices-and-New-Features\code\Chapter01\myscapp\bin\release\net5.0\win-x64\myscapp.dll   myscapp -> c:\apps\myscapp_win\

I have the following output after running this command with the .NET 5 SDK:

Figure 1.3 – .NET 5 generating a self-contained app in a single .exe file

Figure 1.3 – .NET 5 generating a self-contained app in a single .exe file

This generated six files instead of 180 plus files, and the main .exe file is around 50 MB on my machine using the .NET 5 RC2 SDK, and this file contains within itself all the required libraries from the framework and the runtime.

Additionally, .NET also provides a feature to generate the trimmed version of the self-contained executables. What this means is that the output will only contain the required assemblies that are used by the application code and avoid including any other runtime libraries that are not used. If the code is using reflection, the .NET assembly linker does not know about this dynamic behavior and therefore it could not include those types and the libraries that are required and expected by the code that uses the reflection at runtime. Therefore, you need to indicate the linker in relation to any of the types needed by reflection in the specified libraries.

A simple command line to generate the trimmed self-contained version would look like this:

C:\Users\Habib\source\repos\Adopting-.NET-5--Architecture-Migration-Best-Practices-and-New-Features\code\Chapter01\myscapp>dotnet publish -c release --self-contained --runtime win-x64 /p:PublishSingleFile=true /p:PublishTrimmed=true -o c:\apps\myscapp_win_2t

Running this command will generate the following output:

Figure 1.4 – .NET 5 generating a self-contained trimmed app in a single .exe file

Figure 1.4 – .NET 5 generating a self-contained trimmed app in a single .exe file

Comparing the generated output files with our previous command line without using the trim option, which generated around 50 MB, this one generated around 11 MB of .exe files, and this contained all of the required .NET 5 runtime and framework libraries!

While single file and trimming might be regarded as a cosmetic change, the self-contained option is really a safeguarding boundary for your application so that it can run safely and in a stable manner with other applications using any other .NET runtime, especially when your app is not running in the container but in a shared environment.

.NET 5 app trimming optimizations

In .NET 5, trimming has been further optimized where the assembly linker goes deep inside the assemblies and removes even the types and members that are not used in all of the referenced assemblies. This is known as member-level trimming.

Member-level trimming can be enabled by passing -p:PublishTrimmed=True and -p:TrimMode=Link to the dotnet publish command.

In .NET 5, in order to give hints to the trimmer (known as the ILLink) for dynamic code such as reflection, a set of attributes has been added that enables the code to be annotated, and so should be included by the ILLink.

One more feature that is being added to .NET 5 allows us to conditionally remove code from applications using the feature switches. Feature switches remove the features provided by the framework, which even reduces the size of the .NET runtime.

Tip

Remember, when you trim your app, that it is necessary to perform exhaustive end-to-end testing of the published version of the application. The testing of a trimmed app should be carried out externally rather than by unit tests or code within the app, as the published code is only the trimmed output.

ReadyToRun

Another feature that has been introduced, starting with .NET Core 3.0 and above, is ReadyToRun (R2R). R2R is a kind of ahead-of-time (AOT) compilation. It improves the startup time of your application by natively compiling your application assemblies in R2R format at build time so that JIT is not required to be done when assemblies are executed the first time.

R2R has the most significant effect in terms of startup time. Later, there should be little difference once the process is already warmed up.

Developing programs for .NET

To develop .NET programs, a fully fledged IDE is not required. The CLI tools provided, specifically the dotnet command, are enough to compile and build the programs. You can also use free, open source, and cross-platform Visual Studio Code, or Express, which is a free version of Visual Studio. For .NET Core 3 and .NET 5, you will at least require Visual Studio 2019.

You can also use Visual Studio Code Remote Development, which enables you to use a container, VM, remote machine, or the Windows Subsystem for Linux (WSL) as a fully fledged development environment. This allows your development machine to be different to your deployment machine. Your remote host (container, VM), which is used for building the application, is fully configured and streamlined according to the app requirements, and hence you would no longer want to modify your development machine. Binaries resulting from the source code are not even required on your local machine, which is why you can perform remote debugging on your remote host.

From May 2019, Microsoft introduced Visual Studio Codespaces, which provides Azure-powered, managed, on-demand development environments. You can work in these environments, which differ from your development machines, via Visual Studio Code or Visual Studio 2019, with certain extensions installed in your IDE.

You would need to use codespaces in these situations, where you may like to try out new tech, a new tool, or a different technology stack, without disrupting your own primary development environment, or setting these development environments locally would simply take too much time before you can even try it out. Unless we are ready to brick our fine-tuned development machine, we would mostly hesitate trying out a new stack, such as Node.js, Android development, or Python. This is where codespaces come in.

Head on over to the following link for more information:

https://code.visualstudio.com/blogs/2019/05/02/remote-development.

Next, we will be understanding performance improvements and checking how does it work in different .NET versions.

 

Performance improvements

Starting from .NET Core, since it was rewritten separately to .NET Framework, the main focus of .NET was performance, meaning performance was one of the most important features of the platform and, ever since then, each .NET version has enhanced performance further in every aspect of the platform.

Why performance is so important?

For any sizeable application, naturally, performance is a major area where non-functional improvements take place. Once all the processes and algorithms specific to the applications have been enhanced, there is not so much left to change that can add further performance enhancements. In such situations, analysis is required for every bit of code in minute detail. If performance improves by even just 0.001%, this may have a significant impact.

Let's have a few examples to build the context of how important performance improvements are that automatically affect the business and, hence, impact the functional part of the application.

According to independent statistics accumulated, as well as our own years of experience related to our online digital life that has become so normal these days, we can make the following general observations:

  • More than 70% of customers who are dissatisfied with the performance of the web store are less likely to buy again and would rather seek alternatives next time.
  • 1 out of 5 customers are likely to forego a purchase if the shopping cart and checkout process are very slow; some may even have a mistrust of the process.
  • If a website is performing poorly, then no matter what type of services it provides, 30% of users would have a negative impression.
  • Half of users expect the website to load in 2 seconds, especially on smartphones, and would leave the site if it takes more than 5 seconds to load.
  • Amazon calculated that they would lose $1.6 billion every year if they slowed down by even just 1 second.
  • Customers have a general perception that if it is fast, it must be professional!
  • The rise of cloud computing means per-second billing of on-demand compute and memory resource consumption.

    Performance stats by Skilled

    Look here for stats from Skilled on how page speed affects e-commerce:https://skilled.co/resources/speed-affects-website-infographic/.

Seeing these real-world examples, we realize how vital performance improvements are for any online business presence. Even a second's improvement in the response time of a web application can generate significant profit!

These are the kinds of use cases as to why Microsoft has taken .NET to another level by focusing on improvements in every area and to every bit of code.

This means that if the underlying engine of the application code is supercharged, the application automatically reaps the performance benefits and, hence, the return on investments (ROI) gets increased as the company invests time and money in migrating to .NET 5.

Let's now see what are the main areas that have been improved in various versions of .NET, up to .NET 5, that are key to a faster application!

Performance improvements in .NET versions

Let's now look in depth at where .NET has improved performance, starting with .NET Core and going through to .NET 5. What we will do is examine the key focal points as regards improvements in .NET Core version by version. This will give us a good picture of how and where core performance improvements take place and how we automatically accrue the benefit of it in our applications.

By way of a general understanding based on the work of Microsoft and open source developers, all the newer versions of .NET Core (and now .NET from version 5.0) are built on top of previous versions, which means either they improve on top of existing performance tweaks or they even go back and increase performance gains, which, in the previous version, were improved in a slightly different manner. Further improvements in a newer version of an already improved feature are made possible due to the two main components:

  • The first is following the availability of new C# language features (from C# version 7.3+), along with its improved compiler, which generates better code, for example, by utilizing techniques that take into account the best use case and also common scenarios.
  • The second is owing to improvements in JITTER, which generates more efficient machine code by reducing the total number of instructions and using intrinsic instructions.

From the very first version of .NET Core, performance started to improve, and this held true for each version that followed. Let's visit the major improvement areas in order of the released versions.

Performance improvements in .NET Core

Performance improvements in .NET Core (for versions 1.0–2.1) initially focused on the following areas:

  • Collections are faster as well as LINQ.
  • Compression (specifically, Deflate Stream).
  • Cryptography and math (specially BigInteger).
  • Serialization (BinaryFormatter improved in .NET Core 2.0, but generally it is recommended to use other types of serialization).
  • Text processing (improvement in RegEx, UrlDecoding – a minimum of 50% faster).
  • Improvements in strings (Equals, IndexOf, IndexOfAny, Split, and Concat).
  • Asynchronous file streaming (CopyToAsync is 46% faster).
  • Networking (especially sockets, improvements in asynchronous read, write, and copy operations in NetworkStream operations as well as SslStream).
  • Concurrency (threadpool, synchronization, especially SpinLock and Lazy<T>).
  • JIT (devirtualization and often method inlining)

Performance improvements in .NET Core 3

Now, let's check major improvement areas in .NET Core version 3:

  • Various improvements associated with manipulating data in memory, especially with span and memory.
  • Improvements in arrays and strings (based on the use of spans and their vectorization optimizations).
  • Collections are faster (Dictionary, Queue, BitArray, and SortedSet are around 50% faster than in .NET Core 2.1).
  • System.Decimal has been overhauled and is much faster compared with .NET Core 2.0.
  • Threading (tasks and async are faster, timers are optimized).
  • Networking (an even faster SslSteam, HttpClient: a larger buffer size, thereby reducing system calls to transfer data).
  • JIT's direct access to newer and compound CPU instructions.
  • Many I/O improvements that are at least 25% faster than .NET Core 2.1.
  • Improvements in the interop, which itself is used by .NET (SafeHandle 50% faster).
  • Improvements in the Garbage Collector (GC) (set memory limits, and more container-aware).

Performance improvements in .NET 5

Finally let's overview the major performance improvements areas in .NET 5 that are on top of .NET Core's aforementioned improvements:

  • Garabage Collection (GC): GC has a process of marking the items that are in use. Server GC allocates one thread per core for the collection process. When one thread has finished marking all the items, it will continue to work on marking the items that have not yet been completed by other threads. In this way, it speeds up the overall collection process.

    GC is optimized to decommit the Gen0 and Gen1 segments upon which it can return the allocated memory pages back to the operating system.

    Improvements in the GC's scalability on machines with a higher number of cores reduces memory resets in low-memory situations.

    Movement in some of the code, such as sorting primitives from C code into C#, also helped in managing the runtime and in the further development of APIs, as well as in helping to reduce the GC Pause, which ultimately improved the performance of the GC as well as the application. GC Pause is a pause time, which means how long the GC must pause the runtime in order to perform its work.

  • Hardware Intrinsics: .NET Core 3.0 added lots of hardware intrinsics that allow JIT to enable C# code to directly target CPU instructions, such as SSE4 AVX. .NET 5.0 also added a lot more intrinsics specific to ARM64.
  • Text Processing: char.IsWhiteSpace has been improved, thereby requiring a smaller number of CPU instructions and less branching. Improvements in this area have also facilitated improvements in lots of string methods, such as Trim().

    char.ToUpperInvariant is 50% faster than .NET 3.1, while Int.ToString is also 50% faster.

    Further improvements have been made in terms of encoding, for example, Encoding.UTF8.GetString.

  • Regular Expressions: RegEx has seen more than a 70% performance improvement compared to .NET Core 3.1 in various cases.
  • Collections: Lots of improvements have been made to Dictionary, especially in the lookups and utilizing the ref returns to avoid a second lookup, as the user would pick up the value when obtaining the keys with the computed hash.

    Similar upgrades applied to ConcurrentDictionary as well.

    Hashset wasn't optimized previously, as was Dictionary, but now it is optimized on a similar algorithm to Dictionary, meaning it is much faster than .NET FW 4.8 and even .NET Core 3.1.

    Iteration on ImmutableArray is optimized by inlining the GetEnumerator and further JIT optimization, and hence the iteration in .NET 5 is almost 4 times faster than 3.1.

    BitArray is a specialized collection that was also optimized by the open source non-Microsoft developer by utilizing the hardware intrinsics using AVX2 and SSE2 advanced CPU instructions.

  • LINQ: Improvements in .NET 5 for LINQ made OrderBy 15% and SkipLast 50% faster than 3.1.
  • Networking: Socket improvements were made to the Linux platform for faster asynchronous I/O with epoll and a smaller number of threads. A number of improvements to the Socket Connect and Bind methods, along with underlying improvements, made them even faster than .NET Core 3.1.

    SocketsHttpHandler and Date format validation in the header is optimized, giving us more performance gains.

    HTTP/2 code is optimized as it was mostly functional in 3.1, but performance improvements took place in 5.0, which makes it perform twice as fast and consume almost half of the memory in certain scenarios.

  • JSON: A number of improvements have been made to the System.Text.JSON library for .NET 5, especially for JsonSerializer, which makes it more than 50% faster than 3.1 and means it consumes much less memory.
  • Kestrel: Kestrel is a web server included with .NET SDK. This web server is specifically designed to serve APIs built on .NET Core and .NET 5. It should be noted that as a result of performance improvements in the areas of reduced allocations in HTTP/2, along with the higher use of Span and improvements in GC, this has given a significant boost to the Kestrel implementation, which is included with .NET 5. These especially have a direct impact when serving gRPC-based APIs as well as REST APIs.

Wow! That was quite an impressive number of performance improvements and these, too, applied version by version to every level of the code. This many optimizations applied to such types of projects across this time duration is not something normally observed.All of this is the result of a number of expert Microsoft and other developers working on an open source project and we all reap the benefits from this in terms of our individual productivity.

Let's now look at a couple of examples to run the code and compare performance.

Let's do some benchmarking

In the previous section, we have seen a number of improvements applied to a plethora of feature sets. We have also seen that so far; .NET 5 is a superset of all the performance improvements and is the fastest .NET version out there.

At the end of this chapter, I have placed the links to the article where Microsoft has specified a number of benchmarks for each of the aspects they talked about. Like me, you can also run them very easily. Out of those, I will pick one and present it here, as well as the complete code, so that you can repeat the experiment on your own machine.

Benchmarking API performance between .NET versions

In the first benchmarking program, I will pick one of the benchmarking examples similar to the Microsoft Blog post for demonstration purposes and will also show the results as they ran on my machine. We will benchmark this with .NET Framework 4.8, .NET Core 3.1, and .NET 5.0:

  1. To begin, first of all, this is a tiny project setup with only one Program.cs file required as a console application. In this, we use the NuGet package used for all types of .NET benchmarking: benchmarkdotnet. At the time of writing, I used version 0.12.1.
  2. To execute the benchmark, just use this simple .csproj file:
    Figure 1.5 – The csproj file for the benchmark project

    Figure 1.5 – The csproj file for the benchmark project

    For our example, I am using here this Program.cs file, while in the code repository for this book, I have included a couple more that you can also use to test out quickly:

    Figure 1.6 – The Program.cs file for the benchmark project

    Figure 1.6 – The Program.cs file for the benchmark project

    On my machine, I have .NET Framework 4.8, .NET Core 3.1, and .NET 5 RC2 installed. So, in order to run these benchmarks successfully on your machine, please ensure that you install them too.

  3. Typing this command generates the following output:
    C:\>dotnet –version
    5.0.100-rc.2.20479.15

    This confirms that .NET 5.0 is the dominant SDK on my machine and which, if required, is changeable via the global.json file.

    Tip

    Placing this global.json file on the directory changes the default dotnet SDK for all of the sub-directories. For example, I place the following content in my global.json file to change the default SDK/compiler for all subprojects to .NET Core 3.1:

    {

        "sdk": {

            "version": "3.1.402"

        }

    }

  4. I placed the two files as mentioned earlier into the following folder: C:\Users\Habib\source\repos\Benchmarks.
  5. Then, I run the following command to execute the benchmark:
    dotnet run -c Release -f net48 --runtimes net48 netcoreapp3.1 netcoreapp5.0 --filter *Program*

    The command is giving an instruction to build and run the code in the current directory using the entry point from Program and generate and execute three .exe files, one each for .NET Framework 4.8, .NET Core 3.1, and .NET 5.0.

  6. Running this command on my machine now, I get the following result, which I will paste here for our reference. Remember that results may vary depending on the kind of machine on which we execute our benchmark. I am including here only the last part of the execution output, which is the summary of the benchmarking execution:
Figure 1.7 – Output of the benchmark project

Figure 1.7 – Output of the benchmark project

Notice here the mean execution time in nanoseconds and, in the last column, the number of bytes allocated to each execution of the test case.

From the final output, it can be clearly seen that .NET 5 outperforms both .NET Core 3.1 and .NET Framework 4.8 by a wide margin. With this, let's now move on to our second benchmarking program.

Benchmarking a tiny Web API using .NET Core 3.1 and .NET 5.0

In the first benchmarking example, we saw a performance comparison of one .NET API: IndexOfAny on the byte array, and how .NET 5 was the best of all. In this second example, we will create a very tiny Web API project, in fact, a default one from the dotnet scaffolding. We are then going to run it on localhost and call it 50,000 times and see the performance statistics in a manner that is almost akin to blackbox performance testing.

Note that this is not a realistic test in terms of a practical application, but it can give us an overall impression of the general performance of the same code using .NET Core 3.1 versus .NET 5.0 without applying any tweaks.

Setting up the benchmark

To begin our tiny benchmark, we first need to set up a few things. Here, I am listing them in short and easy steps:

  1. First, we set up our project structure so that the instructions run perfectly for all readers. So, first of all, my directory hierarchy looks like this:
    C:\Users\Habib\source\repos\Adopting-.NET-5--Architecture-Migration-Best-Practices-and-New-Features\code\Chapter01
  2. Now, using the global.json file as mentioned in the previous tip, I set my dotnet SDK version to be .NET Core 3.1 by placing the global.json file in the Chapter01 folder.
  3. Then, I execute the following commands:
    dotnet –version
    dotnet new webapi -o dummyapi31

    The first command is just to verify that the SDK is .NET Core 3.1, and the second command creates the default Web API project.

  4. After that, just edit this file on Notepad++:
    C:\Users\Habib\source\repos\Adopting-.NET-5--Architecture-Migration-Best-Practices-and-New-Features/blob/master/Chapter01/dummyapi31/Controllers/WeatherForecastController.cs
  5. Then, add the following lines to the Get() method:
    Figure 1.8 – Code fragment to change in the default Web API project

    Figure 1.8 – Code fragment to change in the default Web API project

  6. After this, execute the following commands to run the Web API project:
    cd dummyapi31 dotnet run

    And I have the following output:

    info: Microsoft.Hosting.Lifetime[0]      Now listening on: https://localhost:5001 info: Microsoft.Hosting.Lifetime[0]      Now listening on: http://localhost:5000 info: Microsoft.Hosting.Lifetime[0]      Application started. Press Ctrl+C to shut down.info: Microsoft.Hosting.Lifetime[0]      Hosting environment: Development info: Microsoft.Hosting.Lifetime[0]      Content root path: C:\Users\Habib\source\repos\Adopting-.NET-5--Architecture-Migration-Best-Practices-and-New-Features\code\Chapter01\dummyapi31
  7. Similarly, I do the same thing for the executable based on .NET 5.0. First, I disable the global.json file by renaming it, then I execute dotnet –version to verify that the active SDK is now .NET 5.0, and then I run the same command to create the Web API project with the name dotnet new webapi -o dummyapi50. I then edit WeatherForecastController in exactly the same way and then execute dotnet run to get the following output:
    C:\Users\Habib\source\repos\Adopting-.NET-5--Architecture-Migration-Best-Practices-and-New-Features\code\Chapter01\dummyapi50>dotnet run Building...info: Microsoft.Hosting.Lifetime[0]      Now listening on: https://localhost:5001 info: Microsoft.Hosting.Lifetime[0]      Now listening on: http://localhost:5000 info: Microsoft.Hosting.Lifetime[0]      Application started. Press Ctrl+C to shut down.info: Microsoft.Hosting.Lifetime[0]      Hosting environment: Development info: Microsoft.Hosting.Lifetime[0]      Content root path:C:\Users\Habib\source\repos\Adopting-.NET-5--Architecture-Migration-Best-Practices-and-New-Features\code\Chapter01\dummyapi50

Now that we've set up the benchmark correctly, let's see how to execute it.

Executing the benchmark

Since our Web API projects are now set up, we are going to use the simple benchmarking application named Apache Bench. This is free and open source and the binary is available from within the Apache installer package:

  1. I use Windows, so I downloaded the ZIP file installation via the following link: http://httpd.apache.org/docs/current/platform/windows.html#down. This downloaded the httpd-2.4.46-o111h-x86-vc15.zip file to my machine and, from this ZIP file, I just extracted one file, ab.exe, which is all I need.
  2. I use the following configuration with Apache Bench to send the requests to our Web API project with 10 concurrent threads and a total of 50,000 requests:
    ab -n 50000 -c 10 http://localhost:5000/WeatherForecast
  3. Now, executing this against our dummyapi31 project, which is .NET Core 3.1-based, generates the following output:
Figure 1.9 – Apache Bench output for the .NET Core 3.1-based Web API

Figure 1.9 – Apache Bench output for the .NET Core 3.1-based Web API

And executing the same ab command against our dummyapi50 project, which is .NET 5.0-based, generates the following output:

Figure 1.10 – Apache Bench output for the .NET Core 5.0-based Web API

Figure 1.10 – Apache Bench output for the .NET Core 5.0-based Web API

Note that I run the Apache Bench for each .NET version 5 times with no gaps, or say with a gap of a maximum of 1 second in between the calls, and obtained the statistics from the fifth run, meaning I ran 50,000 requests 5 times and got the results just from the fifth run. This was done in order to also see whether there is some kind of caching that is applied as well as a GC function that might happen in between as well.

From the results, we can definitely see the result that .NET 5.0 is faster than .NET Core 3.1. Even though the improvement is marginal, 0.2 to 0.5% faster, this tiny Web API is not meant to indicate a huge difference, but for a sizeable project, it demonstrates the capabilities in a very simple way that you can benchmark your own project against the two runtimes almost without modifying the code and see the performance gains by yourself in the real application.

Here, we can see that although the difference in performance is small, .NET 5 still performs faster in a tiny application with the very few APIs and features used.

Ending with these two benchmarking examples, we finalize the .NET performance topic. We have observed the performance improvements from various .NET versions down to the latest .NET 5 and noticed how .NET 5 encompasses all the improvements achieved by the previous .NET versions already baked into it.

The fact that the use of .NET 5 automatically improves the performance of the application without the need to change the code ought to be appreciated. For example, if the application was built using .NET Core 3.1 and is now compiled using .NET 5, it receives an additional performance boost.

The version of Kestrel that is included with .NET 5 already exhibits a significant performance improvement in terms of serving the APIs. Therefore, the same application code that was previously compiled with the older .NET version and is now compiled with .NET 5 and served by Kestrel does automatically get the better performance.

Hence, the effort of migrating the application to .NET 5 automatically adds a performance boost as an additional benefit without having to change the code with the purpose of improving performance.

Now that we have covered performance improvements and understood how it works in different .NET versions, let's look at the release schedule for .NET.

 

.NET release schedule

From now on, as well as in the future, .NET will be the only name carried forward by Microsoft when talking about .NET Core or .NET 5, so we will now use the dominant name from here on, .NET, when talking in general terms. We will highlight .NET 5 when the topic is specifically related to .NET 5.

We learned earlier that .NET has a consistent schedule and is independent of Windows release cycles and Windows updates. Let's have a quick look at what the schedule looks like:

Figure 1.11 – .NET release schedule

Figure 1.11 – .NET release schedule

With this, we realize that .NET has a very predictable schedule that a company can rely on. We see that .NET 5 gets released in November 2020, and .NET 6 in November 2021 – meaning that every year there is a new major release in November, and that every even-numbered release is a long-term support (LTS) where it is supported for 3 years.

What does industry highlight in relation to .NET?

With all the features and forward-looking technologies provided by .NET with full cloud-based modern application support, we have some really promising statistics from the industry side. Let's see what some of the top stats say:

•  On GitHub, .NET Core is one of the highest velocity open source projects.

•  On GitHub, C# is the fifth most popular language.

•  In 2019/2020, .NET Core was the most loved framework on StackOverFlow.

•  In terms of professional careers, .NET is the second most popular technology skill required by the hot jobs on LinkedIn.

We learned that it is with .NET 5 that the .NET Framework and .NET Core worlds are merged into a single .NET offering. We also realized that each technology platform's distinguishing factors are made clearly visible, meaning that it helps technology leaders make well-informed decisions. We also see now that the single version of .NET has consistent release dates provided in advance that enterprises can depend on. Next, we will visit the .NET Support policies for both .NET Framework and .NET Core.

 

.NET support life cycle

The support life cycle starts when a product is released and ends when it is no longer supported. End of support means that from this date, Microsoft will no longer provide fixes, updates, or online technical assistance. It is vital to keep your release updated before you reach this date. If Microsoft support is ended, you do not receive any security or critical updates, which may make your system vulnerable and open to malicious software.

.NET Framework support

.NET Framework is defined as a core component of the Windows OS, meaning its support is tied to the Windows life cycle. Every Windows OS as a product has a life cycle that starts when a product is released and ends when it is no longer supported.

For example, Windows 10 March feature updates are supported for the next 18 months, and September feature updates are supported for the next 30 months from the date of the release.

At the time of writing of this book,.NET Framework 4.8 is the latest version of .NET Framework that will continue to be distributed with future releases of Windows.

It will continue to be supported as long as it is installed on a supported version of Windows OS.

Tip

Support dates for all of the Microsoft products can be seen here:https://docs.microsoft.com/en-us/lifecycle/products/.

With this, we learned that there will be no newer versions of .NET Framework, but that it will be continuously supported as long as the underlying Windows OS is supported by Microsoft.

.NET Core support

Talking about .NET Core support includes .NET Core, ASP.NET Core, and Entity Framework Core. The .NET Core support life cycle offers support for each release. It has two kinds of release and, hence, two basic support models:

  • Long-term support (LTS) releases: These are designed for long-term support. They include the most stable features and components and require fewer updates. These releases could be regarded as a preferred choice for big enterprises, where stability while staying up to date is the first choice.

    LTS releases are supported for 3 years following initial release.

  • Current releases: These include the latest features, but they could change in the future and would potentially make your application unstable. These could be considered a good choice for active application development, but you need to upgrade more often to a later .NET Core release to stay in support.

    Current releases are supported for 3 months after each subsequent release.

Although both types of release receive critical fixes for security and reliability throughout their entire life cycle, you must stay up to date with the latest patches to remain qualified for support.

There is also a support case known as a maintenance cycle, offered in the last stage of the life cycle. During this cycle, a given release will only receive security updates. The maintenance time length is just 3 months for the current release, and 1 year for the LTS release.

.NET Core release life cycles

The following link has a table that keeps track of release dates and end-of-support dates for .NET Core versions: https://dotnet.microsoft.com/platform/support/policy/dotnet-core.

 

Summary

We have covered a brief introduction to the history of .NET Framework and .NET Core and now know the fundamentals of the framework, which are the essential first steps for a solid development foundation and architecture. We also learned the main features and capabilities of .NET 5 by comparing them directly with the capabilities of .NET Framework and .NET Core and we understood the benefits of one over the other. We also learned what the release schedule for .NET looks like from now on and what information is provided to us in the form of industry statistics, all of this designed to enable us to adopt the right technology at the right time and with the planned landscape already inline with our own application architecture and life cycle.

We saw the different types of applications that can be quickly and easily developed with it. We visited the support life cycle for both .NET Framework and .NET Core, which envisage us to architect and design the application software well in advance, with a better and long-term view of our own application product life cycle.

Lastly, we dived into performance improvements and some of the statistics by again doing a comparison with some of the previous versions of .NET. Since .NET 5 is a superset of all of the previous .NET Core versions with added features, it therefore already incorporates all of the previous performance gains applied to the earlier versions, which is why we also overviewed the performance improvements of the previous .NET Core versions and, finally, the specific improvements in relation to .NET 5.

I hope you have enjoyed learning the features of .NET Core and .NET 5 as much as I did and are now looking forward to the next portion of the book, which focuses on design and architecture.

About the Authors

  • Hammad Arif

    Hammad Arif is a passionate IT leader and speaker with over 16 years' experience in architecture, design, and implementing cutting-edge solutions to address business opportunities for enterprise applications. He earned his bachelor's degree in computer science and has previously worked as a solutions architect on Microsoft platforms.

    Based in Sydney, Hammad is currently leading a software development team that builds solutions on Azure and AWS platforms that serves all the top-tier financial institutions in Australia.

    Browse publications by this author
  • Habib Qureshi

    Habib Qureshi is an integration architect and lead developer with almost two decades of professional experience in the software industry. He has worked with start-ups and enterprises, successfully delivering high-quality solutions in an agile manner. He has experience in multiple countries and cultures with on-site, off-site, and remote teams. He is a go-getter and teams always look up to him for technical reviews and solutions.

    Browse publications by this author
Adopting .NET 5
Unlock this book and the full library for FREE
Start free trial