【FineUICore】将 ASP.NET Core 2.1 升级到 ASP.NET Core 3.1
目录
- 前言
- Microsoft.AspNetCore.Mvc.ViewFeatures.Internal 消失了
- 升级到 ASP.NET Core 3.1
- 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项目为例)。
注:下面截图中的 ASP.NET Core 2.1 和 ASP.NET Core 3.1 刚好标反了,阅读时请注意区分。
1. 项目工程文件(FineUICore.EmptyProject.csproj)
2. Program.cs
3. Startup.cs
注:上面截图中的 ASP.NET Core 2.1 和 ASP.NET Core 3.1 刚好标反了,阅读时请注意区分。
ASP.NET Core 3.1的确很棒,肉眼可见的快、快、快!
刚把 FineUICore 的官网示例部署到服务器时,管理员是这么给我说的:
之前一直没有注意,看来 ASP.NET Core 的性能的确是要好一些。
我对比了 FineUIPro(WebForms) 和 FineUICore(ASP.NET Core) 的在线示例网站,发现 ASP.NET Core 3.1 的确比 WebForms 快多了。
肉眼可见的快、快、快,下面两个动画图片可见一斑:
【ASP.NET WebForms】https://pro.fineui.com/
【ASP.NET Core 3.1】https://core.fineui.com/
这两个站点部署在同一台服务器的同一个IIS里面,为了便于区分,FineUICore(ASP.NET Core)为深色主题,FineUIPro(WebForms)为浅色主题。
注:由于是共享服务器,所以服务器资源在不同时刻会有变化,并且网络环境也是不断变化的,可以在同一时刻(测试间隔小于1s)多次测试两个网站。
当然每个人测出来的结果会不尽相同,欢迎分享你的测试截图....
延伸阅读:
【推荐】全新ASP.NET Core,比WebForms还简单!
ASP.NET Core 快速入门(FineUICore + Razor Pages + Entity Framework Core)
完整的FineUICore(基础版)和示例网站源代码请到加入我的圈子下载:https://fineui.com/fans/