How to Migrate from .NET Core 2.2 to .NET Core 3.1: Real Life Project
A developer discusses the process of migrating a large open source project to .NET Core 3.1 and provides tips for best practices with performance analysis.
Join the DZone community and get the full member experience.Join For Free
This article is a logical continuation of upgrading the nopCommerce project - a free open-source CMS for creating online stores. Last time we talked about our experience in migrating nopCommerce from ASP.NET MVC to ASP.NET Core 2.2. In this article, we will look at the migration to .NET Core 3.1.
.NET Core 3.1 will be officially supported until December 2022, so migration is a hot topic right now. If you want to take full advantage of the updated framework, keep up with technological innovations, and the growing global trends then it's time to start the migration.
Challenges to be Solved in the Process of Migration to .Net Core 3.1
For the fast-growing eCommerce project, it is imperative to pay great attention to system performance and security. In the first .NET Core 3.0 review, it was announced that the new version of the framework will be much more fast and productive. Implementing the following .Net Core 3.1 features push nopCommerce to the new level of scalability, performance, and security:
- Tiered compilation allows us to decrease startup time.
- The new built-in high-performance and low-memory support for JSON.
- The endpoint routing introduced in .NET Core 2.2 has been improved. The main advantage is that the route is now determined before running the middleware.
At the time of the release, our expectations were confirmed in practice.
Next, let’s see what exactly affected the nopCommerce performance and how .NET Core evolved since the release of version 2.2.
What's New in .NET Core 3.1?
Let's take a closer look at the .NET Core 3.1 innovations that we use in nopCommerce. You can find the detailed instructions on how to migrate from .NET Core 2.2 to .NET Core 3.1 on the official Microsoft website. In this article we will look at the benefits we receive using these innovations.
In .NET Core 2.1 Generic Host is an addition to the Web Host. It allows you to use tools such as dependency injection (DI) and logging abstractions. .NET Core 3 emphasized the greater compatibility with Generic Host, so you can now use the updated Generic Host Builder instead of Web Host Builder.
This allows us to create any kind of application, from console applications and WPF to web applications, on the same basic hosting paradigm with the same common abstractions.
You can continue to use WebHostBuilder but you need to understand that some types are deprecated in ASP.NET Core 3.1, and may be replaced in the next version.
The idea and possibility of using this feature already existed in the .NET Core 2.0 version, but its capabilities were incomplete. It was only possible to specify the version of the SDK that your application is using.
More flexible SDK version control became available only in .NET Core 3.0 with the introduction of policies such as allowPrerelease and rollForward. The code below illustrates the use of the .NET Core SDK roll-forward policies.
You can see the full version of the application code in our repository on GitHub.
Now you can specify the version from which you can easily build the application, without having to edit global.json every time after the next patch is released. This way you can define the range of functionality that you need based on your requirements.
It will also give you a guarantee that users of your application will run it exactly on those SDK assemblies that you have defined, and not the latest version installed on their server.
ASP.NET Core Module V2
Before .NET Core 2.2, IIS hosted a .NET Core app by executing a Kestrel instance and forwarding requests from IIS to Kestrel by default. Basically IIS acted like a proxy. This works but is slow because there is a double hop from IIS to Kestrel while processing the request. This hosting method is called «OutOfProcess».
.NET Core 2.2 introduced the new "InProcess" hosting model. Instead of forwarding requests to Kestrel, IIS serves requests inside itself. This makes the requests processing much faster because you don't need to forward the request to Kestrel.
However, this was an optional feature and was not used by default.
In .NET Core 3.1, the in-process hosting model is already configured by default, which significantly improves the throughput of ASP.NET Core requests in IIS.
During testing, we recorded a significant increase in system performance with a large number of requests.
Note that running the application on Linux will still use the out-of-process web server, hosting model.
Also, if you host multiple in-process applications on your server, you must ensure that each such application has its pool.
In .NET Core 2.1, routing was done in Middleware (ASP.NET Core MVC middleware) at the end of the HTTP request pipeline. This means that information about the route, such as what controller action will be taken, was not available for the middleware that processed the request before the MVC middleware in the request pipeline.
Starting with .NET Core 2.2, a new endpoint-based routing system was introduced. This routing concept addresses the aforementioned issues.
Endpoint Routing is now built differently in .NET Core 3.1. The routing phase is separated from the call to the endpoint. This way we have two intermediate middlewares:
- EndpointMiddleware - calls the endpoint
- EndpointRoutingMiddleware - this determines which endpoint will be called for each path of the URL request and essentially acts as a routing
The application defines the endpoint to be shipped early in the middleware pipeline. The following middleware can use this information to provide functionality that is not available in the current pipeline configuration.
You can learn more about the code in our repository on GitHub.
С# 8.0 Syntactic Sugar
In addition to updating the .NET Core itself, a new version of C # 8.0 was also released. There are a lot of updates. Some of them are fairly global, others involve cosmetic improvements, giving developers "syntactic sugar".
You can find a detailed description in the documentation.
After we upgraded our nopCommerce application, it was necessary to check how performance increased in our case. For users, this is one of the most important criteria when choosing an eCommerce platform.
Tests were run on Windows 10 (10.0.19041.388), where IIS 10 (10.0.19041.1) acted as a proxy for the Kestrel web server on the same machine. To simulate the high load, we used Apache JMeter, which allows us to simulate a very serious load with many parallel requests. For the test, we have specially prepared a database typical for an average online store.
The number of products was about 50,000 items, the number of product categories was 516 with random nesting, the number of registered users was about 50,000, and the number of orders was about 80,000 with a random inclusion of 1 to 5 products. All this was running by MS SQL Server 2017 (14.0.2014.14).
JMeter ran multiple load tests generating a summary measurement report for each request each time. The averaged results after several runs are presented below.
Test time is about 20% faster (less is better).
The average response time is about 13.7% faster (less is better).
The number of requests per unit of time (throughput) increased by 12.7% (more is better).
We also measured actual memory usage of the application after launch and after a short-term load test. The test was carried out in a few consecutive series. Our goal was to find out how efficiently the application frees the memory. And how the ASP.NET Core module update affected the starting memory consumption.
Let us remind you that we started using the new “InProcess” model.
The results are presented in the diagram above. The in-process model shows more than 1.5 times better efficiency after passing the first load cycle. Further, more efficient use of resources is observed. By the end of the third cycle of load tests, the total memory consumption is more than 2 times more efficient in comparison with out-of-process allocation. This is a very good result.
For comparison, we provide a table with test results including the AspNetCoreModule which we used with .NET Core 2.2. The new module undoubtedly outperforms its predecessor.
The starting memory consumption remained almost unchanged.
The .NET Core platform migration process is still cumbersome and time-consuming. And if you make updates on time and consistently, this will help you to avoid a lot of mistakes and save your time. Many aspects of .NET Core have been improved and you get better performance as a bonus.
In our particular case, we got an average performance increase of 13%. Now requests to the application run faster. This allows you to send more data in less time which makes the application more comfortable to use. Therefore, this is quite a significant increase, considering that you do not actually need to do performance refactoring of your application.
You simply update the framework and get an overall improvement of the platform performance.
It is also important to note that, as always, .NET Core pays a lot of attention to security issues. By the way, since the release of .NET Core 3.1.0, several updates have already been released (the latest version is currently 3.5.1) including security updates.
A very important point is that .NET Core 3.1 is the next LTS version after 2.1, which guarantees us and our customers support and receiving the latest fixes, including security patches. Therefore, we need to move forward with the release of the LTS versions of .NET Core. .NET 5 will be the next global release, and moving your application to .NET Core 3.1 is the best way to prepare for that.
This is a more performant, safe, and more standardized approach to handling JSON objects. We also plan to implement and use as many features as possible that C # 8.0 provides.
Published at DZone with permission of Dmitriy Kulagin. See the original article here.
Opinions expressed by DZone contributors are their own.