Migrate hệ thống ASP.NET Core 2.2 lên 3.1

Bài viết này mình đúc kết lại sau khi migrate toàn bộ hệ thống TEDU hiện tại từ .NET Core 2.2 lên 3.1 chia sẻ lại để mọi người cùng trao đổi. Có một điều mình thấy khác biệt khi migrate một hệ thống nhỏ lên thì rất đơn giản vì chỉ cần làm theo guide của Microsoft là xong. Nhưng với hệ thống dự án thật của các bạn thì không chỉ như vậy, nó sẽ dùng rất nhiều các thư viện của bên thứ 3 nên việc migrate nó sẽ gặp những vấn đề khác nhau. Tuy nhiên vấn đề mình thấy phải chỉnh nhiều nhất là phần Breaking changes  của Entity Framework Core từ 2.2 lên 3.1.

Đầu tiên chúng ta phải hiểu từ 2.2 lên 3.1 nó khác nhau gì? Chính xác là lên từ 2.x lên 3.x thì khác nhiều, nhưng 2.1 lên 2.2 hay 3.0 lên 3.1 thì lại không có gì vì nó là minor version. Tức theo quy tắc đặt tên version thì số đầu tiên 2.x hay 3.x là Major version tức version quan trọng. Thường là thay đổi lớn hoặc breaking changes, còn các chấm sau như 2.1 lên 2.2 hay 3.0 lên 3.1 chỉ là minor nên việc nâng cấp sẽ dễ hơn nhiều. Hầu như chỉ cần change phiên bản của Target Framework là xong.

Những điểm mới trong .NET Core 3.x

Đầu tiên trước khi làm bất cứ một việc liên quan đến nâng cấp version nào các bạn cần đọc xem có sự khác nhau gì? Có cái nào breaking changes không? Tức là các thay đổi làm hỏng các tính năng hiện tại:

- Danh sách các breaking changes: https://docs.microsoft.com/en-us/dotnet/core/compatibility/2.2-3.1

- Điểm mới trong ASP.NET Core 3.0: https://docs.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-3.0

- Điểm mới trong ASP.NET Core 3.1: https://docs.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-3.1

- Điểm mới trong Entity Framework Core 3.1: https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/index

Các breaking changes trong EF 3.1: https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes

Vì chúng ta không nâng cấp lên 3.0 mà lên thẳng 3.1 nên chúng ta chỉ cần tập trung lên 3.0 là sẽ lên được 3.1, các bạn có thể đọc tài liệu chính thức nhưng mình tóm gọn lại thì ASP.NET Core 3.1 nó có những cái mới như sau:

  • Loại bỏ  không cần thêm Microsoft.AspNetCore.App vì nó được mặc định thêm vào shared framework.
  • Thư viện Blazor là một thư viện xây dựng frontend trên C#
  • Chuẩn giao tiếp nội bộ cho service với gRPC
  • Cải tiến SignalR
  • Thêm cơ chế JSON Serialize sẵn tốc độ cao hơn NewtonSoft
  • Tách nhỏ MVC ra theo nhu cầu, thay vì lúc nào cũng phải thêm cả MVC vào thì bạn có thể thêm vào project riêng Controller cho Web API hoặc Controller kèm View cho Web App. Sẽ giúp tối ưu hơn.
  • Cải tiến performance
  • Cải tiến Generic Host thay vì WebHost như bản cũ

Các bước nâng cấp

Tôi chỉ follow theo guide chính thức của Microsoft: https://docs.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.1&tabs=visual-studio

Bước 1: Chuẩn bị cài đặt .NET Core 3.1 SDK và Global tools

Để phát triển được chúng ta cần cài bộ SDK (Software Development Toolkit) phiên bản 3.1.100 LTS trên trang chủ: https://dotnet.microsoft.com/download/dotnet-core/3.1.

Sau đó chúng ta cần cài đặt bộ command tool cho máy tính bằng câu lệnh

dotnet tool install --global dotnet-ef --version 3.1.0

Đây là bộ công cụ để thực hiện các câu lệnh run trên dotnet. Ví dụ để kiểm tra xem máy đó đang có những bộ SDK phiên bản nào bạn run trên CMD:

dotnet --list-sdks

Tương tự như vậy chúng ta có --list-runtimes là danh sách phiên bản .NET Runtime chạy trên máy. Ở máy phát triển chúng ta cần cài SDK để phát triển còn trên server triển khai chúng ta chỉ cần cài bản Runtime để chạy thôi nhé.

Các bạn có thể chọn download Runtime cho Run App  và Build App thì chọn SDK:

Thêm chú ý nữa là với .NET Core 3.x bạn bắt buộc phải cài Visual Studio 2019 chứ không dùng được Visual Studio 2017 nhé.

Bước 2: Thay đổi version của toàn bộ các Project

Tiếp theo bạn cần mở mã nguồn ra với một Editor cụ thể như Visual Studio hoặc Visual Studio Code. Trên Mac bạn cũng có thể dùng Visual Studio for Mac tuy nhiên trên Mac mình thấy dùng VS Code khá là ngon còn Visual Studio for Mac thì hơi chán. Sau khi mở các bạn đơn giản là chỉ cần change toàn bộ phiên bản .NET Core từ 2.2 lên 3.1 cho toàn bộ các Project (file .csproj) --> <TargetFramework> thành netcoreapp3.1 trong solution của bạn rồi build lại.

Sau khi build thì chắc chắn là có lỗi và cảnh báo rồi, theo kinh nghiêm của mình các bạn cứ xem kỹ lỗi và cảnh báo thường là có những plugin chưa tương thích trên .NET Core 3.1 hoặc có những thư viện bị obsolete tức là bị báo cũ sẽ cần phải thay thế thì chúng ta cũng có gợi ý thôi. Mình follow triệt để theo guide này nhé: https://docs.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.1&tabs=visual-studio

Bước 3: Loại bỏ các package không cần thiết nữa

Trong ASP.NET Core 3.x trở lên thì các thư viện như Microsoft.AspNetCore.App hay Microsoft.AspNetCore.Razor.Design được tích hợp sẵn vào shared framework nên không cần thiết phải thêm vào kiểu package nữa. Follow theo: https://docs.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.1&tabs=visual-studio#remove-obsolete-package-references

Như vậy project sẽ bỏ đi kiểu như sau:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App"/>
    <PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" />
  </ItemGroup>

</Project>

Lên 3.1 sẽ đổi thành:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

</Project>

Một số gói đã không cần khai báo trong 3.x:

Bước 4: Thay đổi version các package

Còn các gói khác không phải thì chỉ cần đưa lên phiên bản 3.1 hết ví dụ như:

<PackageReference Include="Microsoft.AspNetCore.Authentication.Facebook" Version="3.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="3.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.0">
  <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  <PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="3.1.0" />

Với Entity Framework Core trên các project Class Library tôi cũng lên 3.1.0 hết:

<ItemGroup>
        <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.0" />
        <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="3.1.0" />
        <PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.0" />
        <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
        <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0">
            <PrivateAssets>all</PrivateAssets>
            <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
        </PackageReference>
        <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.0" />
    </ItemGroup>

Và đương nhiên là breaking change lớn nhất trong EF Core 3.x là không tự động chuyển sang Client Evaluation cho các câu lệnh không biên dịch sang SQL được mà bạn phải làm tường minh tức phải gọi ToList hoặc AsEnumerable. Nên nếu câu query nào mà bị sai là sẽ chết ngay với lỗi là Cannot translate expresion.

Sau khi thay đổi hết các gói các bạn có thể build lại để nó update lên, nếu build thấy lỗi nhớ vào từng project xoá thư mục obj đi rồi run dotnet restore để có thể bị cache các gói cũ nhé.

Bước 5: Thay đổi một số thư viện

Follow: https://docs.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.1&tabs=visual-studio#startup-changes

Một số thư viện trong ASP.NET Core 3.1 đã thay đổi để phù hợp hơn như:

- Đổi HostingEnvironment sang IWebHostEnvironment  hay dùng trong Startup.cs

- Đổi cách kiểm tra điều kiện theo tên môi trường cũ env.IsDevelopment() thành env.EnvironmentName == Environments.Development

- Trong .NET Core 2.x chúng ta có dùng service.AddMvc() trong phương thức ConfigureServices của Startup.cs còn với ASP.NET Core 3.x nó được chia nhỏ hơn ra để tối ưu hoá. Ví dụ bạn chỉ cần dùng phần Web API thì chúng ta không cần add view làm gì. Vậy chỉ cần services.AddControllers() là đủ. Còn với ứng dụng MVC chúng ta cần thêm services.AddControllersWithViews() thay vì lúc nào cũng phải cả khối AddMvc().

- Với ASP.NET Core 3.x chúng ta có thể dùng luôn JSON Serialize rất nhanh hơn cả NewtonSoft.Json (theo quảng cáo) nên chúng ta sẽ dùng luôn được

services.AddControllers()
                .AddNewtonsoftJson(options =>
                  options.SerializerSettings.ContractResolver =
                     new DefaultContractResolver());

- Về phương thức Configure trong Startup.cs thì nó chia ra Authentication và Authorization rõ ràng, hơn nữa thứ tự nhúng các middleare bạn cũng cần đảm bảo. Đây là tài liệu: https://docs.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.1&tabs=visual-studio#migrate-startupconfigure

Và đây là cái mình làm:

Các middleare nên theo thứ tự từ StaticFiles, Routing, Cors, Authentication, Authorization, Endpoints.

- ASP.NET Core 3.x cũng bỏ luôn 

 app.UseSignalR(routes =>
            {
               
            });

Mà thay vào đó sẽ map luôn trong UseEndpoint.

- Phần cấu hình routing sẽ hoàn toàn nằm trong UseEndpoint thay vì UseMvc như ngày xưa (chúng ta có 2 cách cấu hình routing là trong từng action method tức dùng Atribute Route cách 2 là cấu hình trong Startup). Với phần rewrite URL trong MVC các bạn có thể làm như sau:

- Phần cấu hình Swagger cho Web API cũng dùng hoàn toàn OpenAPI:

ConfigureServices:

services.AddSwaggerGen(s =>
            {
                s.SwaggerDoc("v1", new OpenApiInfo
                {
                    Version = "v1",
                    Title = "TEDU Project",
                    Description = "TEDU API Swagger surface",
                    Contact = new OpenApiContact
                    {
                        Name = "ToanBN",
                        Email = "tedu.international@gmail.com",
                        Url = new Uri("https://www.tedu.com.vn")
                    },
                    License = new OpenApiLicense
                    {
                        Name = "MIT",
                        Url = new Uri("https://github.com/teduinternational/TEDUapp")
                    }
                });

                //First we define the security scheme
                s.AddSecurityDefinition("Bearer", //Name the security scheme
                    new OpenApiSecurityScheme
                    {
                        Description = "JWT Authorization header using the Bearer scheme.",
                        Type = SecuritySchemeType.Http, //We set the scheme type to http since we're using bearer authentication
                        Scheme = "bearer" //The name of the HTTP Authorization scheme to be used in the Authorization header. In this case "bearer".
                    });

                s.AddSecurityRequirement(new OpenApiSecurityRequirement{
                {
                    new OpenApiSecurityScheme{
                        Reference = new OpenApiReference{
                            Id = "Bearer", //The name of the previously defined security scheme.
                            Type = ReferenceType.SecurityScheme
                        }
                    },new List<string>()
                }
                });
            });

Còn với method Configure:

 app.UseSwagger(c =>
            {
                c.PreSerializeFilters.Add((document, request) =>
                {
                    var paths = document.Paths.ToDictionary(item => item.Key.ToLowerInvariant(), item => item.Value);
                    document.Paths.Clear();
                    foreach (var pathItem in paths)
                    {
                        document.Paths.Add(pathItem.Key, pathItem.Value);
                    }
                });
            });

            // Enable the Swagger UI middleware and the Swagger generator
            app.UseSwaggerUI(s => { s.SwaggerEndpoint("/swagger/v1/swagger.json", "Project API v1.1"); });

Một điểm nữa là với file Program.cs thì ASP.NET Core 3.1 đã sử dụng Generic Host tức class Host thay vì WebHost như trước: https://docs.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.1&tabs=visual-studio#hostbuilder-replaces-webhostbuilder

Bước 6: Chạy thử và fix lỗi.

Ngoài những cái lỗi lúc complie thì có những lỗi lúc runtime mình mới biết được. Ví dụ thư viện Swashbuckle.AspNetCore là phải nâng cấp lên bản 5.0.0-rc5 trở lên mới chạy được. Hay thư viện nén static file lúc runtime là LigerShark.WebOptimizer.Core nên phải chuyển sang bundle trước bằng Gulp theo hướng dẫn: https://docs.microsoft.com/en-us/aspnet/core/client-side/bundling-and-minification?view=aspnetcore-3.1&tabs=visual-studio#consume-bundleconfigjson-from-gulp

Các bạn hãy test thật kỹ những chỗ mà thay đổi nhất là liên quan đến câu truy vấn với Entity Framework Core.

Bước 7: Triển khai lên server Windows

Triển khai lên server thì các bạn nhớ cài cả bản Runtime cho .NET Core 3.1 và Runtime cho ASP.NET Core 3.1 nhé. Vì mình cũng bị lỗi giờ họ sẽ tách 2 runtime ra riêng. Tại địa chỉ: https://dotnet.microsoft.com/download/dotnet-core/3.1. Các bạn thấy cột bên phải có ASP.NET Core Runtime 3.1.0 và .NET Core Runtime 3.1.0 các bạn phải cài cả 2 trên server nhé.

Tổng kết

Với phiên bản .NET Core 3.x và ASP.NET Core 3.x thì nó là một bản nâng cấp với khá nhiều thay đổi. Đây là phiên bản LTS (Long term  support) hỗ trợ lâu dài nên các bạn có thể nâng cấp lên dần. Dự kiến đến cuối 2020 thì sẽ có .NET 5 tức là đồng bộ .NET Core 3.x với .NET Framework 4.x để đi thẳng lên 5 lúc đó sẽ là một nền tảng thống nhất không còn 2 cái như bây giờ. Nên lên 3.x là một bước quan trọng để lên .NET 5.

Qua sử dụng thử thì mình thấy .NET Core 3.x khá gọn gàng và tộc độ tốt.

Tác giả: Bạch Ngọc Toàn

Chú ý: Tất cả các bài viết trên TEDU.COM.VN đều thuộc bản quyền TEDU, yêu cầu dẫn nguồn khi trích lại trên website khác.

Lên trên