将 ASP.NET Core 2.1 升级到最新的长期支持版本ASP.NET Core 3.1
目錄
前言
Microsoft.AspNetCore.Mvc.ViewFeatures.Internal 消失了
升級到 ASP.NET Core 3.1
項目文件(.csproj)
Program.cs
Startup.cs
ViewBag 與 Razor Pages 第一次接觸
ViewBag 與 Razor Pages 第二次接觸
小結(文件更改對比圖)
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)多次測試兩個網站。?
當然每個人測出來的結果會不盡相同,歡迎分享你的測試截圖....?
?
總結
以上是生活随笔為你收集整理的将 ASP.NET Core 2.1 升级到最新的长期支持版本ASP.NET Core 3.1的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 给微软的依赖注入框架写一些扩展方法
- 下一篇: WTM系列视频教程:View和Taghe
