1. 程式人生 > >將 ASP.NET Core 2.1 升級到最新的長期支援版本ASP.NET Core 3.1

將 ASP.NET Core 2.1 升級到最新的長期支援版本ASP.NET Core 3.1

目錄

  1. 前言
  2. Microsoft.AspNetCore.Mvc.ViewFeatures.Internal 消失了
  3. 升級到 ASP.NET Core 3.1
    1. 專案檔案(.csproj)
    2. Program.cs
    3. Startup.cs
  4. ViewBag 與 Razor Pages 第一次接觸
  5. ViewBag 與 Razor Pages 第二次接觸
  6. 小結(檔案更改對比圖)
  7. ASP.NET Core 3.1的確很棒,肉眼可見的快、快、快!

 

 

前言

2019年的最後一個月,微軟終於釋出了.Net Core 3.1,這是 .Net Core 有史以來的第二個長期支援版本(至少 3 年的支援期限)。

作為一個大版本更新,.NET Core 3.0 引入了大量改進和新特性,例如新增加的 Windows Forms 和 WPF、新的 JSON API、對 ARM64 架構的支援,以及全面提升的效能。

所以升級是勢在必行的,那麼很多開發人員就面臨一個問題:

如果從上一個長期支援版本 ASP.NET Core 2.1 升級到最新的 ASP.NET Core 3.1 ?

 

.Net Core 版本列表:

From:https://dotnet.microsoft.com/download/dotnet-core

 

如果是之前的 .Net Framework,這個升級是非常平滑的,甚至不需要做任何改動(比如:.Net Framework 2.0 -> .Net Framework 4.5),但是 .Net Core 的升級卻有點麻煩,微軟給出了一系列的升級指南,不過都是從上一個版本到下一個緊鄰版本:

ASP.NET Core 2.1 -> 2.2:https://docs.microsoft.com/zh-cn/aspnet/core/migration/21-to-22

ASP.NET Core 2.2 -> 3.0:https://docs.microsoft.com/zh-cn/aspnet/core/migration/22-to-30

ASP.NET Core 3.0 -> 3.1:https://docs.microsoft.com/zh-cn/aspnet/core/migration/30-to-31

 

當然這個過程不僅僅是改個配置檔案的問題,.Net Core 的版本升級中居然有很多 Breaking changes,也就是說如果你用的類在 .Net Core 3.1 中突然刪除了,對不起,你只有調整自己程式碼的份了。

 

Microsoft.AspNetCore.Mvc.ViewFeatures.Internal 消失了

而且這個 Breaking changes 還不少,比如 FineUICore 之前的版本(支援ASP.NET Core 2.1)用到的 Microsoft.AspNetCore.Mvc.ViewFeatures.Internal 在 .Net Core 3.1 就被無情的刪掉了:

 

那位說了,你為啥要用 .Internal 裡面的類?

這是很正常的,FineUICore作為一個支援 ASP.NET Core 的控制元件庫,需要一些底層操作,為了支援快速資料繫結,類似如下的形式:

  • 支援 TagHelpers 的頁面程式碼:<f:DatePicker For="TheModel.StartDate"></f:DatePicker>
  • 支援 HtmlHelpers 的檢視程式碼:F.DatePickerFor(m => m.TheModel.StartDate)

可以參考示例:https://pages.fineui.com/#/DataModel/UICompare

為了支援這個特性,我們就需要計算表示式的文字值,以及表示式所繫結的資料,因此就用到了如下兩個靜態類方法:

  • ExpressionHelper.GetExpressionText
  • ExpressionMetadataProvider.FromLambdaExpression

在 .Net Core 2.2 之前的版本,這兩個方法是逃不過的。可惜不巧的是,微軟卻認為這些方法沒有公開的價值,因此就武斷的隱藏掉了(你讓之前使用這些類的程式情何以堪!)。

網路上有很多類似的提問,但是阻擋不了微軟隱藏幾乎所有 .Internal 名稱空間類的做法。

https://github.com/aspnet/AspNetCore/issues/8678

https://github.com/aspnet/Mvc/issues/8724

 

在 ASP.NET Core 3.1 雖然有替代的方法可以實現上述被刪掉的功能,但是就做不到向後相容了。FineUICore近期已經升級到 FineUICore v6.2.0,以便適用這個改動,升級之後支援 .Net Core 3.1+,不再對老版本提供支援了。

 

升級到 ASP.NET Core 3.1

下面我們會以 FineUICore.Examples 為例,講解如果將 ASP.NET Core 2.1 升級到 ASP.NET Core 3.1,這裡面的坑還真不少。

專案檔案(.csproj)

 

這裡面有幾點需要注意:

1. TargetFramework 改為 netcoreapp3.1

2. 刪除 Microsoft.AspNetCore.App 的引用,這個會預設包含在框架 Sdk=Microsoft.NET.Sdk.Web 中

3. 新增 Microsoft.AspNetCore.Mvc.NewtonsoftJson 的引用

 

有一點需要特別注意, .Net Core 3.0 已經從共享框架中刪除了 Newtonsoft.Json,並引入新的 System.Text.Json 類庫。

但是我們的 FineUICore 示例程式碼很多地方都依賴了 Newtonsoft.Json 的特性,因此需要引入 Microsoft.AspNetCore.Mvc.NewtonsoftJson,這個引用會自動包含 Newtonsoft.Json 庫,可以求證於編譯時的 Debug 資料夾,位於專案的 \bin\Debug\netcoreapp3.1:

 

完整的專案工程檔案:

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

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

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\FineUICore\FineUICore.csproj" />
  </ItemGroup>

</Project>

 

Program.cs

需要注意的幾點:

1. 將 IWebHostBuilder 改為  IHostBuilder

2. 將 Web 伺服器的啟動程式碼放到 ConfigureWebHostDefaults 中

 

當然,這些改動我們不需要特別關心,簡單的照做就行了。

完整的 Progam.cs 程式碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace FineUICore.Examples
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

 

Startup.cs

這裡的改動有點多,我們分兩個截圖分別說明 ConfigureServices 和 Configure 的改動:

 

這裡有幾點需要調整:

1. 用 AddControllersWithViews 替代 AddMvc

2. 對引數 ModelBinderProviders 更改放到 AddMvcOptions 中

3. 新增 AddNewtonsoftJson 用來使用老的 Newtonsoft.Json 來序列化 JSON 資料

 

ASP.NET Core 3.1 添加了三個新的服務註冊方法來代替之前的 AddMvc:

  • AddRazorPages:新增對 Razor Pages 的支援
  • AddControllersWithViews:新增對控制器和檢視的支援
  • AddControllers:新增對控制器的支援

這些方法可以組合,比如如下程式碼等效於之前版本的 AddMvc:

services.AddControllersWithViews();
services.AddRazorPages();

這個方法的完整程式碼:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDistributedMemoryCache();
    services.AddSession();

    // 配置請求引數限制
    services.Configure<FormOptions>(x =>
    {
        x.ValueCountLimit = 1024;   // 請求引數的個數限制(預設值:1024)
        x.ValueLengthLimit = 4194304;   // 單個請求引數值的長度限制(預設值:4194304 = 1024 * 1024 * 4)
    });

    // FineUI 服務
    services.AddFineUI(Configuration);

    services.AddControllersWithViews().AddMvcOptions(options =>
    {
        // 自定義模型繫結(Newtonsoft.Json)
        options.ModelBinderProviders.Insert(0, new JsonModelBinderProvider());
    }).AddNewtonsoftJson();
}

 

下面來看下 Configure 方法:

這裡面有幾點重要改動:

1. 將 IHostingEnvironment 改為 IWebHostEnvironment

2. 新增 app.UseRouting();app.UseAuthorization();,注意新增的位置,要放到 UseStaticFiles 的後面

3. UseMvc 改為 UseEndpoints

完整的函式程式碼:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }
    app.UseStaticFiles();
    app.UseSession();

    app.UseRouting();

    app.UseAuthorization();

    // FineUI 中介軟體(確保 UseFineUI 位於 UseEndpoints 的前面)
    app.UseFineUI();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "area",
            pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });

}

 

對於支援 Razor Pages 的專案,這裡的 UseEndpoints 程式碼要改為:

app.UseEndpoints(endpoints =>
{
    endpoints.MapRazorPages();
});

 

ViewBag 與 Razor Pages 第一次接觸

在傳統的 Controller/Model/View 的 MVC 架構中,我們可以在控制器中使用 ViewBag 將資料傳遞到 檢視中,雖然 ViewBag 只是字典類 ViewData 的一個動態封裝,但是寫法更簡單:

然而在 Razor Pages 的頁面模型類中,卻少了對 ViewBag 的支援,因此在 ASP.NET Core 2.2 之前的版本里,我們通過在基類中新增一個 ViewBag 屬性來解決:

可以看出,這裡的 ViewBag 其實是一個 dynamic 型別,內部儲存單元還是 ViewData,這段程式碼來自:https://forums.asp.net/t/2128012.aspx?Razor+Pages+ViewBag+has+gone+

 

這麼一個看似很技巧的東西其實來自微軟某位程式設計師的自(zi)信(da),如果你留意觀察,就會發現一個奇怪的邏輯:

  • Controller 基類 和 View 檢視中支援 ViewBag
  • Razor Pages的後臺模型類中不支援 ViewBag,然後 Razor Pages的頁面中支援 ViewBag

說白了,微軟的邏輯是:你可以在 Razor Pages 中使用 ViewBag(儘管也可以設定) ,但是別在模型類中設定 ViewBag。

 

這位大蝦(DamianEdwards)在一個 Github 的 issue 中提到這樣一個觀點:

We purposefully didn't add ViewBag because I wanted to discourage its use. As @pranavkm points out you can add it back easily enough if you wish but I don't have plans to add it back to the built-in base class.

原文:https://github.com/aspnet/Mvc/issues/6754 

我帶點感情色彩的翻譯一下哈:

我就看 ViewBag 不順眼了,所以故意不新增 ViewBag。並且我也不準備在以後的版本中把 ViewBag 加回來,你非要用的話自己弄吧!

在另一個回覆中,DamianEdwards提到了 ViewBag 可能有效能問題:

ViewBag uses dynamic which in our testing introduces a measurable performance impact on the processing of pages or views that use it.

不過,這個決定權不能留給使用者自己嗎?如果我只是想通過 ViewBag 傳遞兩三個資料,絕對不會遇到什麼效能問題,並且這應該是 90% 的應用場景。

 

最終,吐槽歸吐槽,程式碼是你寫的,你想咋搞就咋搞。

 

ViewBag 與 Razor Pages 第二次接觸

然而,在升級到 ASP.NET Core 3.1 之後,這個地方又報錯了。

原因我們前面提過,對 ViewBag 的封裝用到了 DynamicViewData,這個類是定義在 Microsoft.AspNetCore.Mvc.ViewFeatures.Internal 裡面的。而在 ASP.NET Core 3.1 中,微軟刪除 Microsoft.AspNetCore.Mvc.ViewFeatures.Internal 的公開訪問許可權。

 

在實際專案中,我們還是要遵循微軟的建議,使用 ViewData 而不是 ViewBag。

然而我們的 FineUICore 示例專案有 750 多個頁面,很多地方都用到了 ViewBag,為了和之前的版本相容,我們還是要把 ViewBag 找回來。

既然 DymaicViewData 不見了,就自己建立一個 DymaicViewData:

public class DynamicViewData : DynamicObject
{
    private ViewDataDictionary ViewData;

    public DynamicViewData(ViewDataDictionary viewData)
    {
        ViewData = viewData;
    }

    /// <summary>
    /// 獲取所有key
    /// </summary>
    public override IEnumerable<string> GetDynamicMemberNames()
    {
        return ViewData.Keys;
    }

    /// <summary>
    /// 呼叫 ViewBag.key; 時執行
    /// </summary>
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        result = ViewData[binder.Name];
        return true;
    }

    /// <summary>
    /// 呼叫 ViewBag.key = "value"; 時執行
    /// </summary>
    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        ViewData[binder.Name] = value;
        return true;
    }
}

 

在後臺模型類基類中,呼叫方法改為:

private DynamicViewData _viewBag;

public dynamic ViewBag
{
    get
    {
        if (_viewBag == null)
        {
            _viewBag = new DynamicViewData(ViewData);
        }
        return _viewBag;
    }
} 

 

小結(檔案改動對比圖)

綜上所述,升級到 ASP.NET Core 3.1 主要改動三個專案檔案,其他地方基本沒有變化。

為了更直觀的檢視這三個檔案的改動,我們做了下面三幅對比圖片(以FineUICore.EmptyProject專案為例)。

1. 專案工程檔案(FineUICore.EmptyProject.csproj)

 

2. Program.cs

 

3. Startup.cs

 

 

ASP.NET Core 3.1的確很棒,肉眼可見的快、快、快!

剛把 FineUICore 的官網示例部署到伺服器時,管理員是這麼給我說的:

 

之前一直沒有注意,看來 ASP.NET Core 的效能的確是要好一些。

我對比了 FineUIPro(WebForms) 和 FineUICore(ASP.NET Core) 的線上示例網站,發現 ASP.NET Core 3.1 的確比 WebForms 快多了。

肉眼可見的快、快、快,下面兩個動畫圖片可見一斑:

 

https://pro.fineui.com/

 

 

 

https://core.fineui.com/

 

這兩個站點部署在同一臺伺服器的同一個IIS裡面,為了便於區分,FineUICore(ASP.NET Core)為深色主題,FineUIPro(WebForms)為淺色主題。

 

注:由於是共享伺服器,所以伺服器資源在不同時刻會有變化,並且網路環境也是不斷變化的,可以在同一時刻(測試間隔小於1s)多次測試兩個網站。

 

當然每個人測出來的結果會不盡相同,歡迎分享你的測試截圖....

 

 

 

完整的FineUICore(基礎版)和示例網站原始碼請到加入我的圈子下載:https://fineui.com/fans/