从 .NET Framework 迁移到 .NET 的迁移前检查清单

· 更新日期: · · .NET, .NET Framework, C#, 现代化, Windows 开发, 迁移

更新记录(2 条,最后更新 2026年09月03日)

本文的修改记录。已保存的更新前版本,可通过带有 DOI 的永久链接阅读。

本文此前是日文原文的节译,缺少大量章节、表格、Mermaid 图、图题、脚注与 FAQ。现已改写为日文原文的完整译文,技术主张与日文版一致,并补上了此前缺失的图与表,同时统一了全篇的术语译法。 查看更新前的版本 (DOI: 10.5281/zenodo.22276795)
补充了日文原文中已有的咨询引导(consultation_services)。正文内容没有改动。 查看更新前的版本 (DOI: 10.5281/zenodo.21615467)
首次发布
引用本文(DOI: 10.5281/zenodo.21615466)

本文保存于 Zenodo。以下同时提供始终指向最新版本的 DOI,以及固定于您正在阅读版本的 DOI。

小村 豪(2026)。《从 .NET Framework 迁移到 .NET 的迁移前检查清单》。小村软件有限公司。https://doi.org/10.5281/zenodo.21615466 https://comcomponent.com/zh-CN/blog/2026/03/15/003-dotnet-framework-to-dotnet-premigration-checklist/

DOI(最新版本)
10.5281/zenodo.21615466
DOI(此版本)
10.5281/zenodo.22281982

下载附日英工作表的 Excel 检查清单

这个文件由 Checklist-ja 和 Checklist-en 两张工作表组成,把 13 章的动手前检查清单落成 方针 / 现行 .NET Framework 一侧的整理 / 应用类别与技术选型 / 不受支持的技术与要注意的 API / Windows 专用依赖 / shared library 与数据访问 / 运维与 build 这 7 个类别共 34 个条目。Status 与 Notes 列留空,因此可以直接当作每个项目的盘点表使用。阅读正文之前先浏览一遍,会更容易把握到第 13 章为止的脉络。

把 .csproj 的 TargetFramework 改成 net10.0,更新几个 NuGet 包,build 通过就结束。

……如果迁移是这样,那相当太平。现实中往往不是这么回事。

.NET Framework 的现场里,System.Web、WCF、Web Forms、陈旧的 packages.config、web.config.install.xdt、原生 DLL、COM / ActiveX、只在设计时才运行的第三方控件、隐含的 x86 前提、依赖设计器的 ResX、老旧的序列化器等等,潜藏着许多平时根本不会意识到的前提。

所以,从 .NET Framework 迁移到 .NET,真正重要的是进入实现之前的盘点。 只要动手前能把论点拆开,迁移就不是“一场大赌注”,而是“按顺序逐个解决的作业”。

盘点改变迁移的性质该图说明在进入实现之前先做盘点、把论点拆开,迁移就不是大赌注而是按顺序逐个解决的作业。跳过盘点潜藏着的隐含前提动手前盘点,拆开论点变成按顺序逐个解决的作业迁移变成一场大赌注

图1:动手前能否把论点拆开,决定了迁移是赌注还是作业。

本文整理在把现有的 .NET Framework 4.x 业务应用程序 迁移到 当前的 .NET 之前,应该确认哪些内容。主要对象是下面这些。

  • 类库
  • 控制台应用程序
  • Windows 服务
  • WinForms / WPF
  • ASP.NET Framework(MVC / Web API / Web Forms)
  • 使用 WCF 的应用程序
  • 使用 EF6 的应用程序

撰写时点为 2026-03-15。支持期限和官方工具的推荐做法都会变化,如果隔了较长时间才阅读,请一并确认官方信息。

1. 先讲结论

先把不容易偏离的结论放在前面。

  • 迁移前先梳理 .NET Framework 一侧 是第一步。Microsoft 的官方指南也推荐:移植前先把版本提升到 .NET Framework 4.7.2 以上,并先完成 PackageReference 化、SDK 样式化和依赖关系更新。
  • 难度由 应用模型而非代码量 决定。类库和控制台相对轻,ASP.NET Framework、Web Forms、WCF 服务端、WF(Workflow Foundation) 则容易变重。
  • WinForms / WPF 即使迁移到 .NET,仍然是 Windows 专用。在这里理解错,就会踩到“迁移完了却装不进 Linux 容器”这个经典的坑。
  • ASP.NET Framework → ASP.NET Core 实质上是架构迁移。小规模应用有时能一口气迁完,但较大的生产系统还是以分阶段迁移为前提更安全。
  • WCF 和 EF6 有时可以与运行时的迁移拆开处理。 WCF 客户端有面向 .NET 的受支持包,EF6 则可以在迁到 modern .NET 之后再单独迁移到 EF Core。
  • 另一方面,AppDomain 创建、.NET Remoting、CAS(Code Access Security,代码访问安全)、COM+、Workflow Foundation、BinaryFormatter 依赖 是红灯。不先找出来,后段的工时就会爆炸。
  • packages.config / install.ps1 / XDT(XML Document Transform,用于转换 web.config 等文件的机制)/ content 资产 / 原生 DLL / COM / ActiveX / x86 前提,即使 build 通过,在运行时或设计时也容易出问题,所以要在动手前盘查。
  • 2026-03 时点 .NET 10 是 LTS。新的迁移,落地点基本上以 现行 LTS 为基准来考虑最自然。
  • 没有准备测试、度量和回滚方案的迁移很危险。与其说迁移是实现作业,不如说是把前提条件一个一个可视化的作业。

图中实线表示始终成立的关系,虚线表示带条件的关系(成立条件写在详情页各关系的说明中)。关系的完整列表(共 30 条,附依据与确信度)以及主要概念的定义,汇总在知识地图详情页(日文)。数据:JSON-LD / Turtle

2. 先决定“现在是否真的该迁移”

最先要决定的不是“怎么迁移”,而是 这个应用现在真的该迁移吗。

这里含糊不清,就容易做出技术上正确却在业务上过重的迁移,或者反过来,明明该迁移却一再往后拖的判断。

最先要决定的问题该图说明在讨论怎么迁移之前应先决定这个应用现在是否真的该迁移,这里含糊不清就容易导致业务上过重的迁移或过度拖延。先决定含糊不清地推进现在真的该迁移吗怎么迁移过重的迁移 或 拖得太久

图2:不先决定“现在该不该迁移”,判断就会偏向其中一边。

2.1 留在 .NET Framework 的判断,也完全可能成立

只要运行在受支持的 Windows 上,.NET Framework 4.8.1 就会持续获得支持。也就是说,并不是“不立刻把全部迁到 modern .NET 就马上有危险” 这么简单的事。

但是,留下来有明确的限制。

  • 走不出 Windows 专用
  • 要一直背着 ASP.NET Web Forms 或老旧的服务端技术栈
  • 难以享受新版 .NET 的性能改善、语言特性和生态系统的好处
  • 容易与云、容器、CI/CD 的当代前提脱节

反过来说,如果对下面这些依赖很深,那么 暂时向 .NET Framework 4.8.1 靠拢维持稳定运维,同时另开一条线制定替换计划 是合理的。

把留下来当作合理选择该图说明当存在 Web Forms、WCF 服务端兼容、COM+ 等很深的遗留依赖时,暂时向 .NET Framework 4.8.1 靠拢维持稳定运维、同时另开一条线制定替换计划是合理的做法。存在很深的遗留依赖暂时靠拢 4.8.1 稳定运维另开一条线制定替换计划Windows 专用等限制仍然存在

图3:依赖很深时,用 4.8.1 稳定运维与替换计划两条腿走路更现实。

  • Web Forms 的界面资产量很大
  • 必须严格保持 WCF 服务端兼容
  • 对 Workflow Foundation 或 COM+ 的依赖很深
  • 第三方的设计时组件不支持 modern .NET
  • 业务上不允许做大的规格变更

2.2 各个选项会改变什么

选项 会变好什么 会留下什么 / 失去什么 适合的情况
留在 .NET Framework 4.8.1 不破坏既有资产,容易稳定运维 Windows 专用、老旧的应用模型、现代化的上限 有很深的遗留依赖,当下以业务优先、想维持稳定运维
迁移到 modern .NET 但留在 Windows 能把运行时和工具链现代化。性能、开发体验、SDK 样式带来的好处很大 Windows API 依赖仍然存在。不会变成跨平台 WinForms / WPF、Windows Service、使用 Windows API 的业务应用
迁移到 modern .NET,并把将来的 Linux / 容器 / 云也纳入视野 部署位置的自由度提高。运维模型也更容易更新 需要先剥掉 Windows 专用 API 和应用模型 想把服务端向云靠拢,也想一并革新基础设施

重要的不是 想不想迁移,而是先决定 迁移之后想落在哪里。

要决定的是落地点该图说明重要的不是想不想迁移,而是先决定迁移之后想落在哪里。作为问题不够充分决定这个想不想迁移先要决定的事迁移之后想落在哪里选项与该看的论点随之确定

图4:把问题从“想不想迁移”换成“想落在哪里”,选项就会确定下来。

3. 应该先决定的 4 个方针

3.1 落地点的 .NET 版本

撰写时点,按 Microsoft 的支持策略,.NET 10 是 LTS。 另一方面,.NET 8 LTS 和 .NET 9 STS 都预定在 2026-11-10 结束支持。 STS 却和 LTS 落在同一天,是因为 STS 的支持期限从 18 个月延长到了 24 个月。按“STS 是 18 个月”这个旧前提去算,.NET 9 看起来会在 2026-05-12 结束,所以要以日期为依据时,请用官方的生命周期信息确认。

因此,今后要新从 .NET Framework 迁移的话,除非有特别的理由,把现行 LTS 作为落地点 是自然的选择。

这里实务上的思路很简单。

  • 想尽快做完的小型迁移,直接落到现行 LTS
  • 以长期运维为前提的核心系统,同样以现行 LTS 为基本考虑
  • “因为既有库的情况想用上一个 LTS”作为理由是可能成立的,但要 看维护到什么时候,用日期来判断
落地版本的思路该图说明无论是小型迁移还是核心系统都以现行 LTS 为基本,若因既有库的情况想用上一个 LTS,则要看维护期限的日期来判断。基本既有库的情况选择落地点的 .NET落到现行 LTS也考虑上一个 LTS用日期确认维护期限再判断

图5:落地点以现行 LTS 为基本,若选旧 LTS 则以维护期限的日期为依据。

3.2 继续 Windows 专用,还是将来瞄准跨平台

这个判断会大幅改变应该关注的论点。

  • 继续 Windows 专用,就可以走用 WPF / WinForms 或 Windows Compatibility Pack、先把运行时现代化的现实路线。
  • 将来也想瞄准 Linux / 容器化,就需要在早期阶段盘点 System.Drawing.Common、注册表、WMI、EventLog、Windows Service、COM、Office Interop 等以 Windows 为前提的 API。

不先决定这一点就开始迁移,中途就会出现“固定在 Windows 上不就好了吗”“不对,我们本来是想装进容器的”这类互相拧巴的争论。

Windows专用还是跨平台的分叉该图说明继续 Windows 专用时可以用兼容包先把运行时现代化,将来瞄准 Linux 或容器化时则需要在早期阶段盘点以 Windows 为前提的 API。继续 Windows 专用将来也要上容器不决定就开始瞄准哪一边用兼容包把运行时现代化尽早盘点 Windows 前提 API中途话题拧巴

图6:不先定下这个分叉,该看的论点就定不下来,中途会拧巴。

3.3 一次做完,还是分阶段迁移

迁移的形态大致有 3 种。

  • 接近 in-place 的一次性迁移
  • 用 side-by-side 让新旧并存的迁移
  • 以 route / library 为单位一点点靠拢的分阶段迁移

尤其是 ASP.NET Framework 应用,Microsoft 的指南也明确介绍了 incremental migration。 如果条件是不想停生产环境、功能数量多、周边依赖多,那么从一开始就按分阶段迁移来设计会更顺畅。

迁移的三种形态该图说明迁移有接近 in-place 的一次性迁移、side-by-side 新旧并存的迁移、以 route 或 library 为单位一点点靠拢的分阶段迁移这三种形态,而不想停生产环境时以分阶段迁移为前提更顺畅。选择迁移的形态一次性迁移(偏 in-place)side-by-side 新旧并行以 route / library 为单位分阶段迁移不想停生产环境就以这个为前提

图7:形态有三种,功能多又停不得的生产系统要以分阶段迁移为前提。

3.4 决定“这次不放进迁移范围的是什么”

迁移之所以容易失败,是因为要做的事塞得太多。

例如,同时做下面这些事情,就容易变重。

  • .NET Framework → .NET
  • ASP.NET Framework → ASP.NET Core
  • EF6 → EF Core
  • Windows 服务器 → Linux 容器
  • 身份验证基础设施的变更
  • 日志 / 监控基础设施的变更
  • 数据库的迁移

当然,这些迟早可能都需要做。 但 是否必须同时做,是另一回事。

现实中,像下面这样拆开会更顺利。

  1. 先把运行时和项目结构现代化
  2. 在此基础上迁移应用模型
  3. 最后更新 ORM、身份验证、云、监控
不贪多的拆分顺序该图说明先把运行时和项目结构现代化、再迁移应用模型、最后更新 ORM 与身份验证、云、监控这一拆分顺序。全都塞在一起把运行时与结构现代化迁移应用模型更新 ORM、身份验证、云、监控迁移变重,容易失败

图8:不要全部同时做,按运行时、应用模型、周边的顺序拆开就不会变重。

4. 动手前要打好的地基

Microsoft 的移植前指南相当务实。 说到底就是一句话:在迁移之前,先把现在的 .NET Framework 项目挪到现代化的入口附近。

4.1 提升到 .NET Framework 4.7.2 以上

官方指南推荐移植前把目标定为 .NET Framework 4.7.2 以上。 理由是,即使 .NET Standard 并没有原样保留既有 API,也更容易靠向更新的替代 API。

实务上,如果可能,以 4.8.1 为基准来考虑会更清楚。

  • 从支持的角度看很顺
  • 便于当作 .NET Framework 一侧的最终稳定点
  • 容易立起“先在现行 Framework 一侧梳理”这条方针

先做这件事会改变什么

  • .NET Standard 2.0 共享库的处理更容易稳定
  • 能先减少来自旧运行时的杂音
  • 更容易区分兼容性问题的原因是“Framework 太旧”还是“迁到 modern .NET”

4.2 向 PackageReference 靠拢

移植前指南推荐把引用改成 PackageReference 形式。 先做这件事,依赖关系管理的可见性会明显变好。

改成 PackageReference 会改变什么

  • 包引用集中到 csproj
  • 传递依赖更容易看清
  • restore 的前提与 modern .NET 一侧对齐
  • 与 CLI / CI 的配合更好

不过,这里有雷。

典型的雷

NuGet 的官方文档明确写出了从 packages.config 迁移到 PackageReference 的下列限制。

  • Visual Studio 的内置迁移功能在 ASP.NET 项目中不可用
  • 依赖 install.ps1 / uninstall.ps1 的包,可能不会按预期工作
  • content 文件夹里的资产有时会被忽略
  • web.config.install.xdt 之类的 XDT 转换不会被应用
  • lib 目录正下方程序集结构较旧的包,可能无法被正确解析

也就是说,最好不要以为这 只是换个包格式。 尤其是 classic ASP.NET,安装 NuGet 包时改写 web.config 的做法相当普遍,所以迁移时隐含的前提很容易暴露出来。

改成PackageReference后暴露的隐含前提该图说明从 packages.config 迁移到 PackageReference 时,install.ps1、XDT 转换和 content 文件夹的资产可能不会被应用,于是安装时改写 web.config 的隐含前提容易暴露出来。转换为 PackageReferenceinstall.ps1 可能不再运行XDT 转换不会被应用content 资产可能被忽略隐含前提暴露出来

图9:本以为只是换个格式,结果依赖安装时机关的那些前提会一口气暴露出来。

用什么来做转换

并非只能手工。不过每种工具的覆盖范围都不一样。

工具 能做什么 前提与注意事项
Visual Studio 的转换功能 在解决方案资源管理器中右键点击“引用”或 packages.config,选择 Migrate packages.config to PackageReference... 即可完成转换 需要 Visual Studio 2017 15.7 以上。在 ASP.NET 和 C++ 项目中不可用。菜单里没出现时,先执行一次 NuGet 还原或打开一次包管理器,再重新右键
.NET Upgrade Assistant 把项目的分析与升级一起完成 官方文档中已标记为 不推荐,并引导改用 GitHub Copilot 现代化
GitHub Copilot 现代化 支持从评估、规划、代码修改到验证的全过程。是目前官方引导的重心 如 4.5 所述,前提是 Visual Studio、Copilot 和 C# 代码
手工做 SDK 样式化 新建一个 csproj,只把需要的条目搬过去 文件数量少的项目,这样反而最快

Visual Studio 的转换功能会在执行前为项目创建备份,并在最后输出一份转换报告(顶层依赖、传递依赖、检测到的兼容性问题)。文档里也写了回退的步骤:从备份文件夹把 csproj 和 packages.config 写回去,然后在包管理器控制台执行 update-package -reinstall。

也就是说,可以用能回退的方式来试。在动手前的估算阶段,先只转换 1 个项目并读一读报告,是最快的做法。

用能回退的方式试转换该图说明 Visual Studio 的转换功能会先创建备份再转换并输出转换报告,因此可以先只转换一个项目读报告,必要时从备份写回去。有问题没问题会创建备份只转换 1 个项目阅读转换报告写回并重新安装推广到其他项目

图10:转换带着备份和报告可以试,所以先用一个项目摸清底细。

4.3 向 SDK 样式靠拢

移植前指南也推荐转换成 SDK 样式的项目格式。

这一步相当有效。

改成 SDK 样式会改变什么

  • csproj 大幅精简
  • 与 PackageReference 配合好
  • 更容易做 multi-targeting
  • 更容易转向以 dotnet build / dotnet test / dotnet publish 为中心的 CI/CD
  • 更接近 modern .NET 一侧的结构,后段的差异会减少

反过来说,带着旧 csproj 和旧的 NuGet 管理方式直接跳到 modern .NET,差异就太大了。

用SDK样式化减少差异该图说明带着旧 csproj 和旧 NuGet 管理方式直接跳到 modern .NET 差异太大,因此先向 SDK 样式靠拢、接近 modern .NET 一侧的结构,以减少后段的差异。直接跳过去先做 SDK 样式化旧 csproj + 旧 NuGet 管理差异太大接近现代一侧的结构后段的差异减少

图11:中间垫一步 SDK 样式化,跳到 modern .NET 时的差异就小了一档。

4.4 先更新依赖关系

这一条同样出自官方指南:依赖关系要向 可用的最新版本 靠拢,可能的话再向 支持 .NET Standard 的版本 靠拢。

先做这件事的意义

  • 能早点知道“这个包在 modern .NET 上能不能用”
  • 能避免旧依赖变成杂音
  • 更容易把 shared library 做成 netstandard2.0
  • 更容易让后段的迁移工作专注在“代码的移植”上
先更新依赖关系的意义该图说明先把依赖关系更新到较新版本,就能早点知道它们能否用于 modern .NET,避免旧依赖带来的杂音,并让后段的迁移工作专注在代码移植上。先把依赖更新到最新能早点知道是否支持 modern .NET能减少旧依赖的杂音后段能专注于代码的移植

图12:依赖更新做得越早,迁移本体就越能专注在移植本身。

4.5 也要确认官方工具的前提

2026-03 时点,Microsoft 引导的重心已经移到 GitHub Copilot 现代化 一侧。 比起只把过去的迁移辅助工具当作前提,把它看成包含评估、规划、代码修改、验证在内的一整套支持流程,更贴合实务。

不过,现行文档的前提是 Visual Studio 2026 或仍在支持期内的 Visual Studio 2022 系列、GitHub Copilot,以及 C# 代码。

需要确认这一点的理由

  • 能对官方工具期待什么会随之改变
  • 能统一团队的 IDE / build agent / 扩展的前提
  • 能做出 在 VB.NET 解决方案中不要对自动化期待过高 的判断

VB.NET 混在其中的现场并不罕见。 所以,一开始就确认“最新的官方工具能帮到什么程度”是有价值的。

官方工具的现状与前提该图说明 .NET Upgrade Assistant 已被标记为不推荐、引导重心移到 GitHub Copilot 现代化,但其前提是 Visual Studio、Copilot 和 C# 代码,因此在 VB.NET 解决方案中需要做出不要对自动化期待过高的判断。不推荐、引导去向如果混有 VB.NET.NET Upgrade AssistantGitHub Copilot 现代化前提:Visual Studio + Copilot + C#不要对自动化期待过高

图13:工具的重心已移到 Copilot 现代化,不符合其前提的现场需要另作安排。

5. 按项目类别估算难度

迁移常被笼统地说成“从 .NET Framework 到 .NET”,但现实中 每种项目类别都是另一场游戏。

5.1 粗略的难度感

类别 难度感 主要论点
类库 低~中 API 兼容性、依赖关系、目标拆分
控制台 / 批处理 / 部分 Windows Service 低~中 发布方式、原生依赖、配置
WinForms / WPF 中 仍是 Windows 专用、设计器、第三方 UI、BinaryFormatter 周边
ASP.NET MVC / Web API 中~高 迁到 ASP.NET Core 的应用模型迁移、身份验证、session、配置、DI
ASP.NET Web Forms 高 界面模型差异大,以替换 UI 层为前提
WCF 客户端 中 包替换、契约、配置
WCF 服务端 高 选 CoreWCF,还是重新设计成 gRPC / HTTP API
EF6 → EF Core 同时进行 高 ORM 完全不同、行为差异、迁移历史

5.2 类库的关键在于怎么切“共享边界”

类库相对容易迁移。 但这只限于 库真的像一个库那样被拆分出来的时候。

有下面这些依赖时,难度就会上升。

  • 接触了 System.Web
  • 直接读取 HttpContext.Current
  • 公开 API 中含有 WPF / WinForms 的类型
  • 过度依赖注册表、WMI、EventLog 等 Windows API
  • 依赖 AppDomain 或 Remoting

按 只切出业务逻辑就轻,连应用模型也一起背着就重 来看,会比较容易理解。

判断类库分量的方法该图说明只切出业务逻辑的类库很轻,而背着 System.Web、UI 类型、Windows API、AppDomain 等应用模型的类库则很重。只切出逻辑即可背着应用模型审视类库迁移很轻迁移很重System.Web、UI 类型、Windows API 依赖等

图14:库的难度不取决于行数,而取决于它背了多少应用模型。

5.3 WinForms / WPF 容易迁移,但仍是 Windows 专用

WinForms 和 WPF 可以迁移到 .NET。 但两者都 仍然是 Windows 专用框架。

在这里把期望值搞错会很危险。

  • 会变好的
    • 能用上 modern .NET 的运行时、语言、SDK 样式
    • 容易把 CI/CD 和包管理挪到当代做法上
    • 容易获得部分性能和可维护性的改善
  • 不会变的
    • 仍然是 Windows 专用
    • UI 控件和设计时组件的兼容性问题仍在
    • ActiveX / COM / 原生 DLL 的问题不会消失

另外,WinForms / WPF 有时需要确认 BinaryFormatter 的影响。 特别是当剪贴板、拖放、ResX、设计时序列化牵涉到自定义类型时,把 target 提升到 .NET 9 以后就容易浮出水面。

WinForms / WPF迁移的期望值该图说明 WinForms 和 WPF 能迁移到 .NET 并用上 modern .NET 的运行时、语言和 SDK 样式,但仍是 Windows 专用这一点不会改变,有时还需要确认 BinaryFormatter 的影响。迁移 WinForms / WPF能享受 modern .NET 的好处仍然是 Windows 专用需要确认 BinaryFormatter

图15:桌面 UI 可以迁移,但 Windows 专用这个性质会原样带过去。

5.4 ASP.NET Framework 不是“运行时迁移”而是“应用模型迁移”

从 ASP.NET Framework 迁到 ASP.NET Core,Microsoft 的指南也明确说是 non-trivial。 这不只是因为 API 名字变了,而是因为 作为前提的架构不同。

容易出现差异的地方是下面这些。

  • Hosting model
  • Middleware pipeline
  • Request processing model
  • Session / Cache
  • Authentication / Authorization
  • Configuration
  • Dependency Injection
  • Logging / Monitoring

ASP.NET Framework 应用要先确认的是这些。

  • 从哪个 route / endpoint 开始迁
  • 能不能把 System.Web 依赖从 shared library 里剥掉
  • 身份验证 / session / 异常处理 / 日志怎么对齐
  • 要不要在不停生产环境的前提下分阶段迁移

尤其是大型应用,从一开始就按 incremental migration 来考虑更现实。

ASP.NET的迁移是应用模型迁移该图说明从 ASP.NET Framework 迁到 ASP.NET Core 不是替换 API 名称,而是 hosting、middleware、身份验证、配置、DI 等前提架构都会变的应用模型迁移,大型应用以分阶段迁移为前提更现实。大型应用ASP.NET Framework → Core作为前提的架构改变hosting、middleware、身份验证、配置、DI按 incremental migration 规划

图16:ASP.NET 的迁移迁的不是运行时而是应用模型,规模越大越要以分阶段为前提。

5.5 Web Forms 不是“资产迁移”,而要从“职责拆分”入手

Web Forms 和 ASP.NET Core 不是同一个应用模型。 所以在估算上,不要以能原样把界面资产搬过去为前提 会更安全。

实务上,多半先从下面这些拆分入手。

  • 把界面逻辑和业务逻辑分开
  • 拆开埋在 Page / UserControl / ViewState 里的职责
  • 把业务逻辑和数据访问挪到 shared library
  • UI 用 Razor Pages / MVC / Blazor 等别的模型重新组织

也就是说,Web Forms 的项目,重要的是 在迁移运行时之前,有没有职责拆分的计划。

Web Forms从职责拆分入手该图说明 Web Forms 与 ASP.NET Core 不是同一个应用模型,因此要先把界面逻辑与业务逻辑分开、把逻辑挪到 shared library、UI 用别的模型重新组织,从职责拆分入手。以能原样搬走为前提Web Forms 的界面资产分开界面逻辑与业务逻辑把逻辑挪到 shared libraryUI 用别的模型重新组织估算会崩

图17:Web Forms 不是资产迁移,而要按先拆职责、再用别的模型重组来看。

5.6 WCF 客户端和 WCF 服务端要分开考虑

这里最好不要混为一谈。

WCF 客户端

WCF Client 有面向 modern .NET 的 受支持 NuGet 包。 所以,如果只是调用 WCF 的一侧,有时并没有看上去那么重。

WCF 服务端

另一方面,托管 WCF 服务的一侧就是另一回事了。 Microsoft 的指南大致介绍了两条现代化路径。

  • 用 CoreWCF 保持既有客户端兼容的方向
  • 转向 gRPC 等现代 RPC / HTTP 基础的方向

不过,CoreWCF 并不是把 WCF 的全部原样搬过来,而是一个子集。 它适合用来保持与既有客户端的兼容,但 代码修改和测试是前提。

WCF按客户端和服务端分别评估该图说明 WCF 客户端有面向 modern .NET 的受支持包,有时并没有看上去那么重,而服务端一侧则需要在保持兼容的 CoreWCF 与改用 gRPC 等重新设计之间做选择。客户端服务端保持兼容重新设计在哪一侧使用 WCF用受支持的包迁移选择路径CoreWCF(子集)gRPC / HTTP API

图18:调用方和托管方的难度与选项都不同,不要混在一起估算。

6. 盘查在 .NET 中不能用 / 直接用会卡住的技术

这是动手前绝对要做的一步。 Microsoft 有一份 在 .NET Framework 能用、但在 .NET 6+ 不能用的技术 清单。

6.1 容易亮红灯的技术

技术 在 .NET 中的状态 应该怎么考虑
AppDomain.CreateDomain 等 AppDomain 的创建 不支持 隔离改用独立进程 / 容器 / AssemblyLoadContext 来考虑
.NET Remoting 不支持 重新设计为 IPC、HTTP、gRPC、Socket、Pipe 等
CAS / Security Transparency 作为安全边界不受支持 用 OS / 容器 / 权限分离来考虑
System.EnterpriseServices(COM+) 不支持 把以 COM+ 为前提的设计分离、替换掉
Workflow Foundation 不支持 连同 CoreWF 等替代方案一起,另行估算
WCF server 内置能力里无法原样运行 在 CoreWCF 与 gRPC 之间做选择
BinaryFormatter .NET 9 以后实现总是抛出异常 迁移序列化器,审查 ResX / 剪贴板 / 拖放

6.2 AppDomain“即使部分 API 还在,创建仍是另一回事”

AppDomain 周边稍微有点复杂。 .NET 中虽然还留着一部分 API surface,但 创建新的 AppDomain 来做隔离 这种用法是不受支持的。

因此,如果之前把 AppDomain 用在下面这些用途上,就需要重新设计。

  • 插件隔离
  • 动态加载后的卸载
  • 部分信任代码的隔离
  • 临时运行环境的分离

迁移前该看的不只是 有没有出现 AppDomain 这个词,而是 当初为什么要用 AppDomain。

AppDomain的用法决定判断该图说明 AppDomain 即使部分 API 还在,创建新 AppDomain 做隔离的用法也不受支持,因此要区分只是用到类型还是用于隔离,若用于隔离则改用独立进程或 AssemblyLoadContext 重新设计。只是用到类型或读取信息曾创建它来做隔离出现了 AppDomain多数情况可以照旧需要重新设计独立进程 / 容器 / AssemblyLoadContext

图19:决定要不要重新设计的不是词出现与否,而是“当初为什么用它”。

6.3 Remoting“比看上去更深”

Remoting 自不必说,delegate 的 BeginInvoke() / EndInvoke() 调用 这类异步委托也可能落进影响范围。它本身不是 Remoting,但在 modern .NET 中不受支持,所以迁移前需要一并盘查。

因此,搜索时把下面这些一起看,会更稳妥。

  • System.Runtime.Remoting
  • MarshalByRefObject
  • RealProxy
  • BeginInvoke( / EndInvoke(

6.4 BinaryFormatter 会随目标版本突然浮到台面上

BinaryFormatter 在越旧的代码库里,越容易出现“毫无自觉地在用”的情况。

  • 持久化数据
  • 缓存
  • Session 保存
  • 插件状态
  • 剪贴板 / 拖放
  • ResX
  • WinForms / WPF 设计器周边

在 .NET 9 以后,运行时不再包含 BinaryFormatter 的实现,API 总是抛出 PlatformNotSupportedException。 也就是说,这不是“以后再说”,而是 在决定目标版本的那一刻就要先审查的论点。

BinaryFormatter浮到台面上的路径该图说明 BinaryFormatter 常在持久化数据、ResX、剪贴板等处被无自觉地使用,而在 .NET 9 以后 API 总是抛出异常,因此是决定目标版本时就要先审查的论点。无自觉的使用(ResX、剪贴板等)把 target 定为 .NET 9 以后API 总是抛出异常定下来的那一刻就先审查

图20:BinaryFormatter 在定下 target 的瞬间就开始生效,不要往后拖,先审查。

6.5 先拿来 grep 的搜索词

动手前,光是对整个解决方案用下面这些词搜一遍,视野就会大不一样。

System.Web
HttpContext.Current
System.Runtime.Remoting
MarshalByRefObject
AppDomain
BinaryFormatter
ServiceHost
ChannelFactory
System.EnterpriseServices
Workflow
packages.config
web.config.install.xdt
install.ps1
DllImport
AxInterop
Microsoft.Office.Interop

找到其中一个并不意味着立刻出局。 这是 一张地图,用来知道哪里能走标准路线迁移、哪里要另开一条轨道。

实际的搜索命令

工具用 rg(ripgrep)或 PowerShell 都可以。

# PowerShell。对解决方案目录下批量搜索
Get-ChildItem -Recurse -Include *.cs,*.vb,*.config,*.csproj,*.vbproj |
    Select-String -Pattern 'BinaryFormatter|System\.Runtime\.Remoting|MarshalByRefObject|AppDomain\.CreateDomain' |
    Select-Object Path, LineNumber, Line
# ripgrep。想先用命中数量摸个大概时
rg -n --stats "BinaryFormatter|System\.Runtime\.Remoting|AppDomain\.CreateDomain" --glob "*.cs" --glob "*.vb"

找到之后接着做什么

搜索只是入口,之后的判断才是正题。每个词接下来要看的地方都是确定的。

找到的词 接下来要确认的事 落点
System.Web / HttpContext.Current 能不能从 shared library 里挪出去。调用方能不能改成装进 DTO 传递 用 8.5 的策略剥掉
System.Runtime.Remoting / MarshalByRefObject / BeginInvoke 当初的远程调用是为了什么。是跨进程、跨机器,还是只是异步调用 重新设计为 IPC、HTTP、gRPC 或基于 Task 的方式
AppDomain 只是用到类型名,还是用 CreateDomain 做隔离 若用于隔离,则改用独立进程或 AssemblyLoadContext(6.2)
BinaryFormatter 写出去的数据以后还需不需要读回来。有没有经由 ResX 或剪贴板间接使用 迁移到别的序列化器,并为既有数据制定改读方案(6.4)
ServiceHost / ChannelFactory 是托管一侧还是客户端一侧 按 5.6 的说法分开评估
packages.config / install.ps1 / web.config.install.xdt 安装时改写了什么 4.2 说的雷。把配置明确地重新拿在手里
DllImport / AxInterop / Microsoft.Office.Interop 位数的前提,以及运行时需要的整套文件 进入 9.4 的 bitness 确认

做到这一步,得到的就不是“能不能迁移”,而是 “哪些走标准路线、哪些要另行估算”的清单。动手前想要的,正是这种形式的清单。

从搜索走到清单该图说明搜索只是入口,顺着每个词接下来要确认的地方走下去,就能变成哪些能走标准路线迁移、哪些需要另行估算的清单。用搜索词盘查解决方案顺着每个词找到下一处确认点形成标准路线与另行估算的清单找到了也不等于立刻出局

图21:搜索本身只是入口,一路判断下去才会变成动手前想要的那份清单。

7. 决定 Windows 专用前提允许到什么程度

迁移中常见的误解是“迁到 .NET 就会变成跨平台”。 没有这种魔法。应用如果和 Windows 绑得很深,迁移之后照样是 Windows 专用。

7.1 保持 Windows 专用地迁移,是相当现实的做法

Microsoft 提供了 Windows Compatibility Pack,它让注册表、WMI、EventLog、Windows Service、Directory Services 等许多 Windows 系 API 能在 modern .NET 中使用。

它的存在在实务上意义很大。

  • 先想迁到 modern .NET
  • 但 短期内不会走出 Windows
  • 所以 Windows API 依赖暂时可以接受

在这样的现场,它是很有力的选项。

也就是说,迁移的第一个目标未必非得是 跨平台化。

7.2 但 Windows 专用 API 也是“以后才会显现的债”

有了 Windows Compatibility Pack,也不意味着什么都可以放心。

  • 想部署到 Linux 容器
  • 想以 Kubernetes 为前提运行
  • 想让 macOS / Linux 的开发者也能跑同一套 build
  • 将来想在云上减少 Windows VM

如果有这类目标,那么 趁现在就把 Windows API 依赖可视化 会更好。

兼容包的用武之地与欠下的债该图说明短期内不走出 Windows 时可以用 Windows 兼容包暂时接受 Windows API 依赖并迁到 modern .NET,但若将来瞄准容器和云,这些依赖就是以后才会显现的债,应趁现在可视化。短期内仍在 Windows若将来瞄准容器与云先想迁到 modern .NET用兼容包暂时接受依赖依赖是以后才显现的债趁现在先可视化

图22:兼容包是现实的桥,但那份依赖会随目标变成债,所以要先可视化。

7.3 System.Drawing.Common 特别容易被误解

System.Drawing.Common 在 .NET 6 以后是 Windows 专用库。 如果有用它做图像处理或文字绘制的代码,就要先决定走哪一边。

  • 是继续在 Windows 上运维
  • 还是将来也想在 Linux / macOS 上运行

前者的话,有些情况暂时保持原样也可以。 后者的话,就需要从一开始就把替换成 SkiaSharp 或 ImageSharp 之类的方案纳入迁移计划。

System.Drawing.Common的分叉该图说明 System.Drawing.Common 在 .NET 6 以后是 Windows 专用库,继续在 Windows 上运维时有些情况可以暂时保持原样,而将来也要在 Linux 或 macOS 上运行时则需要从一开始就把替换为 SkiaSharp 或 ImageSharp 纳入计划。继续在 Windows 上运维也想在 Linux / macOS 上运行正在使用 System.Drawing.Common有些情况暂时保持原样即可替换为 SkiaSharp / ImageSharp从一开始就纳入迁移计划

图23:System.Drawing.Common 是 Windows 专用,处理方式从一开始就随落地点分叉。

7.4 表明被 Windows 绑定的典型气味

出现下面这些引用或 API 时,按“至少一开始保持 Windows 专用地迁移”来估算会更稳妥。

  • Microsoft.Win32.Registry
  • System.Management
  • System.Diagnostics.EventLog
  • System.ServiceProcess
  • System.DirectoryServices
  • System.Drawing
  • DllImport / P/Invoke
  • COM 引用
  • AxInterop.*
  • Microsoft.Office.Interop.*

8. 共享库的拆分方式会改变难度

在较大的解决方案里,说 迁移的成败由 shared library 的拆分方式决定,也一点都不夸张。

8.1 先做分类

把库大致分成 3 类,会更容易梳理。

  1. 纯粹的业务逻辑 / 领域逻辑
  2. 稍微依赖应用模型的中间层
  3. 与 UI / Web / Windows API 紧密贴合的层

其中最该先迁的是第 1 类。

  • 计算
  • 规则判定
  • DTO / 契约
  • 领域服务
  • 简单的数据转换

这部分能干净地拿出来,难度就会一下子降低。

库的三种分类与迁移顺序该图说明把库分成纯粹的业务逻辑、稍微依赖应用模型的中间层、与 UI 或 Windows API 紧密贴合的层这三类,其中最先迁移的是纯粹的业务逻辑。最先迁移其次最后纯粹的业务逻辑能干净拿出来则难度下降偏应用模型的中间层UI、Windows API 紧贴层

图24:把库分成三层,从纯粹的逻辑开始依次抢救出来。

8.2 netstandard2.0 至今仍是有效的桥

按照 Microsoft 的指导,需要与 .NET Framework 一侧共存的 shared library,首先考虑 .NET Standard 2.0 是基本做法。

这里重要的有 2 点。

  • .NET Framework 不支持 .NET Standard 2.1
  • 如果想让新旧两侧都引用同一个 shared library,2.0 更容易成为现实解
名为netstandard2.0的桥该图说明 .NET Framework 不支持 .NET Standard 2.1,因此若想让新旧两侧都引用同一个 shared library,.NET Standard 2.0 更容易成为现实解。可以引用可以引用Framework 不支持.NET Framework 一侧netstandard2.0 的 shared librarymodern .NET 一侧netstandard2.1

图25:作为新旧两侧共用的桥,现实解是 2.0 而不是 2.1。

8.3 做成 netstandard2.0 会改变什么

方针 会怎么变 适合的情况 注意事项
做成 netstandard2.0 新旧两侧都容易引用 纯粹的业务逻辑、通用契约、工具类 承载不了应用模型特有的 API
multi-target(例如 net48;net10.0) 保持通用代码的同时能保留环境差异 环境差异只有一点点的库 条件分支和 build 管理会增加
直接只做成 net10.0 将来最干净 不需要新旧共存的新层 无法从 .NET Framework 引用

8.4 兼容模式并非万能

.NET Standard 2.0 有引用 .NET Framework 库的兼容模式。 但这 并不是什么都能透明运行的魔法。

例如,像 WPF 这类以应用模型特有 API 为前提的库,通常就很难。 也就是说,虽然叫 shared library,也要把它限定在真正能共享的职责范围内,这一点很重要。

8.5 ASP.NET 系的库,胜负在于能否剥掉 System.Web

分阶段迁移 ASP.NET Framework 时,如果 shared library 直接挂在 HttpContext.Current 或 System.Web 上,会相当痛苦。

这时的基本策略是下面几种之一。

  • 把 System.Web 依赖推到接口之外
  • 改成用 DTO 接收来自 HttpContext 的信息
  • 在迁移过渡期使用 adapter
  • 实在不行就用 multi-target 分阶段剥掉

8.6 库按 leaf-first 提升

ASP.NET 的 incremental migration 指南明确写着,支撑库要按 postorder depth-first,也就是 从叶子开始依次 提升。

这不限于 Web,在一般的解决方案里同样很有效。

  • 依赖目标已经先提升,上层的可见性更好
  • 更容易把兼容性问题局部化
  • 更容易以库为单位做测试
按leaf-first提升该图说明把支撑库按依赖的叶子开始依次提升,依赖目标先完成后上层的可见性更好,也更容易把兼容性问题局部化并以库为单位做测试。先提升依赖叶子上的库再提升依赖已就绪的中间层最后是上层的应用层把问题局部化后再测试

图26:从叶子开始依次提升,等着手上层时脚下已经齐了。

9. 盘点 NuGet / 外部依赖 / 第三方组件

这里做得潦草,迁移后半段会最痛。

9.1 依赖关系分成 4 类会更容易梳理

  1. 公开的 NuGet 包
  2. 公司内部的 private package / internal library
  3. 本地 DLL 引用
  4. COM / ActiveX / 原生 DLL / SDK

其中,只看第 1 类是不够的。 真正危险的是第 3 类和第 4 类。

依赖的四种分类与危险度该图说明依赖关系可分为公开 NuGet、公司内部包、本地 DLL 引用、COM 与 ActiveX 与原生 DLL 四类,只看公开 NuGet 不够,真正危险的是本地 DLL 与 COM 及原生一类。盘点依赖关系公开 NuGet、公司内部包本地 DLL 引用COM / ActiveX / 原生 DLL真正危险的是这一侧

图27:只数公开 NuGet 是不够的,本地 DLL 与 COM、原生一类才要先盘查。

9.2 每个依赖要确认的内容

对每个依赖,至少要确认到这个程度。

  • 是否以 modern .NET 为目标
  • 是否支持 PackageReference
  • 用 SDK 样式有没有问题
  • 有没有 x86 / x64 / ARM64 的限制
  • 是否依赖设计时工具或 Visual Studio 扩展
  • 是否以 install script / config transform 为前提
  • 支持是否还在延续

9.3 第三方 UI / 报表 / 设计时组件要另行估算

在 WinForms / WPF / ASP.NET 的迁移中,这一块影响相当大。

  • 表格控件
  • 报表引擎
  • PDF 输出组件
  • 图表组件
  • 与设计器集成的 UI 库
  • ActiveX 封装

这些牵涉的 不只是运行时,还有设计时支持。 迁移估算时只看“能不能编译”,多半会失手。

9.4 原生 DLL 与位数一定要看

在 .NET Framework 时代看起来是以 AnyCPU 运行的,实际上可能依赖着这些东西。

  • 固定为 x86 的 COM
  • 只支持 32bit 的 ActiveX
  • 特定版本的 VC++ Runtime
  • 已签名的原生 DLL

这一带并不是迁到 modern .NET 后突然冒出来的问题,而只是 原本就存在的限制浮到了台面上。 正因如此,在迁移前把它可视化才有价值。

位数限制浮到台面上该图说明看似以 AnyCPU 运行的应用可能依赖固定为 x86 的 COM、只支持 32bit 的 ActiveX 或特定版本的 VC++ Runtime,这些只是原本就存在的限制在迁到 modern .NET 时浮到台面上。看起来以 AnyCPU 运行依赖固定 x86 的 COM、32bit ActiveX 等迁移让原本的限制浮到台面迁移前先可视化

图28:位数问题不是迁移造出来的,只是原本的限制变得看得见了。

10. 把 EF6、序列化器、数据周边当作另一个问题来处理

运行时的迁移,和数据访问、序列化的重新设计,尽量当作不同的问题来处理会更顺利。

10.1 EF6 → EF Core 不是直接升级

Microsoft 的 EF 指南里也说,EF Core 是 EF6 的 total rewrite,不存在 direct upgrade path。

所以,使用 EF6 的应用按下面的顺序推进更现实。

  1. 先迁到 modern .NET
  2. 必要时保留 EF6,让应用继续运行
  3. 之后再作为另一个独立项目迁移到 EF Core

不要把 runtime migration 和 ORM migration 混在一起做。光是做到这一点,难度就会明显下降。

EF6与运行时的分离迁移该图说明 EF Core 是 EF6 的全面重写、不存在直接升级路径,因此先迁到 modern .NET、必要时保留 EF6 继续运行、之后再作为独立项目迁移到 EF Core 的顺序更现实。同时做的话先迁到 modern .NET必要时保留 EF6 继续运行之后作为独立项目迁移到 EF CoreORM 的行为差异混进来,变重

图29:运行时和 ORM 不要同时动,保留 EF6 先迁运行时才现实。

10.2 保留 EF6 会改变什么

  • 好处
    • 能把数据访问层的差异往后放
    • 能专注在 business logic 和应用模型的迁移上
    • 不会混进“EF Core 的行为差异”
  • 注意事项
    • 从新开发的角度看,EF Core 才是主力
    • EF6 Designer / EDMX 的使用形态另有限制

10.3 基于 EDMX 的 EF6 要一直看到“设计时”

EF6 的文档里写着,EF Designer 在 .NET / .NET Standard 项目以及 SDK 样式的 .NET Framework 项目上不被直接支持。

在基于 EDMX 的应用里,需要把这 3 点分开考虑。

  • 运行时能不能跑
  • Designer 能不能用
  • 生成代码怎么处理

如果大量使用了 EDMX,那么最好一开始就把这一点纳入估算。

10.4 BinaryFormatter 和自制序列化容易成为“隐藏依赖”

序列化器只靠代码搜索容易漏看。

  • 持久化格式
  • 消息传递
  • 缓存
  • 旧的 WCF / SOAP 契约
  • ResX
  • 剪贴板 / 拖放

这一带还牵涉到 数据的兼容性。 也就是说,不只是“能不能 build”,还要确认到 能不能读取旧数据。

11. 把配置、发布、运维、CI/CD 也纳入迁移对象

迁移的对象不只是源代码。

11.1 配置文件

在 .NET Framework 一侧,app.config / web.config 里可能承载了相当多东西。

  • connection string
  • custom config section
  • WCF endpoint 配置
  • binding redirect
  • diagnostics
  • ASP.NET 的各种配置
  • 包安装时 transform 的结果

在 modern .NET 一侧,配置的持有方式和读取路径有时会变。 所以“配置文件以后再说”很危险。

最先要做的是 配置的盘点。

  • 配置文件里有些什么
  • 哪些是应用启动时必需的
  • 哪些是环境差异
  • 哪些是由 NuGet 或 installer 自动注入的

11.2 发布方式

发布的形态也要在动手前看一遍。

  • 是不是在 IIS 下
  • 是不是 Windows Service
  • 是不是 Scheduled Task
  • 是不是 ClickOnce / MSI / 自制 installer
  • 是不是以本地服务器为前提
  • self-contained / framework-dependent 哪个更合适

即使可执行本体迁过去了,如果发布和启动的机制还停留在旧前提,最后还是会卡住。

11.3 日志、监控、运维流程

运维周边也容易被漏看。

  • 是不是以 Windows Event Log 为前提
  • 有没有在看 Performance Counter
  • 是不是基于 WMI 的监控
  • 服务账户和权限是不是写死的
  • 日志的输出位置是不是以本地文件为前提

迁移之后代码能跑,但 运维转不动,这种情况很常见。

11.4 CI/CD 与 build agent

迁移前,还要确认下面这些。

  • build agent 上能不能装上需要的 .NET SDK
  • 以 nuget.exe / msbuild.exe 为前提的 pipeline 怎么处理
  • 要不要转向以 dotnet CLI 为中心
  • 测试执行、coverage、publish 的 job 怎么更新
  • 公司内部模板或 reusable pipeline 是不是以旧格式为前提

在别人本地能跑,CI 却挂掉,是迁移里的经典戏码。

迁移对象不只是代码该图说明即使可执行本体迁移完成,配置文件、发布方式、日志与监控等运维、CI/CD 与 build agent 若仍停留在旧前提,最后还是会卡住,因此这些也要纳入迁移对象。源代码的迁移只做这些还没完配置文件与发布方式日志、监控、运维流程CI/CD 与 build agent本地能跑,CI 却挂掉

图30:把配置、发布、运维、CI/CD 都算进来,才算把迁移对象数清楚。

12. 现实的迁移推进方式

把前面的论点合起来看,现实的推进方式大致会落成下面的形状。

12.1 先把现行 Framework 一侧整理好

  1. 靠到 .NET Framework 4.7.2 以上,可能的话到 4.8.1
  2. 提升依赖关系
  3. 重新审视 packages.config
  4. 在可行范围内向 PackageReference 和 SDK 样式靠拢
  5. 确认现行应用在这个状态下能正常运行

光是做这些,后段的差异就会少很多。

12.2 先把 shared library 抢救出来

接着,把业务逻辑和通用契约挪到 netstandard2.0 或 multi-target。 提升的顺序基本是 leaf-first。

12.3 应用本体按应用模型改变策略

  • 类库 / 控制台 / 部分服务 相对容易直截了当地推进
  • WinForms / WPF 保持 Windows 专用地做现代化
  • ASP.NET MVC / Web API 规模小就一次做完,分量重就分阶段迁移
  • Web Forms 以替换界面资产为前提,先把 shared logic 挪出去
  • WCF 服务端 先决定是维持 CoreWCF 还是重新设计成 gRPC

12.4 守住“不要一次全做完”

特别想避开的是这样的组合。

  • 运行时迁移 + ORM 全面更换
  • 运行时迁移 + 身份验证基础设施变更
  • 运行时迁移 + 云的全面迁移
  • 运行时迁移 + 监控基础设施变更
  • 运行时迁移 + UI 框架革新

即使全都需要,不要堆进同一个 sprint,多半会更顺利。

12.5 先备好测试和基线再动

至少要准备到这个程度再动手。

  • 单元测试
  • 主要业务流程的集成测试
  • 代表性界面 / API 的快照式确认
  • 性能基线
  • 主要日志的查看方法
  • 回滚步骤

在无法确定迁移后“哪里坏了”的状态下推进,是相当危险的。

动手前要备好的东西该图说明先备好测试与性能基线、日志的查看方法和回滚步骤再动手迁移,就能在迁移后确定哪里坏了,而没有准备就推进很危险。备好测试与基线然后再动手迁移能确定哪里坏了没准备就推进坏了也定位不到,很危险

图31:迁移取决于动手前的准备,有基线才谈得上定位坏在哪里。

13. 动手前检查清单

以能直接贴进项目管理工具的形式放在这里。

13.1 方针

  • 能用一句话说清为什么要迁移
  • 已决定这次的落地点是 Windows 专用的 modern .NET,还是 将来的跨平台化
  • 已决定 target 的 .NET 版本
  • 已决定这次范围里 不包含的内容(EF Core 化、身份验证革新、云的全面迁移等)

13.2 现行 .NET Framework 一侧的整理

  • 已靠到 .NET Framework 4.7.2 以上,可能的话到 4.8.1
  • 已把依赖关系提升到较新的版本
  • 已盘查 packages.config 的有无
  • 已确认能否做 PackageReference 化
  • 已确认能否做 SDK 样式化
  • 现行应用在这个状态下能 build、启动、测试

13.3 应用类别与技术选型

  • 已按类库 / 桌面 / Web / WCF 等类别分别评估难度
  • 理解 WinForms / WPF 仍然是 Windows 专用
  • 理解 ASP.NET Framework 是迁到 ASP.NET Core 的应用模型迁移
  • 已把 Web Forms 的 UI 层替换纳入估算
  • 已把 WCF 分成客户端和服务端分别评估

13.4 不受支持的技术与要注意的 API

  • 已盘查 AppDomain 依赖
  • 已盘查 Remoting / MarshalByRefObject / BeginInvoke / EndInvoke
  • 已盘查 CAS / Security Transparency / COM+ / WF
  • 已盘查 BinaryFormatter 依赖
  • 已盘查 System.Web 依赖

13.5 Windows 专用依赖

  • 已盘查注册表、WMI、EventLog、Windows Service、Directory Services 的使用
  • 已盘查 System.Drawing.Common 的使用
  • 已盘查 COM / ActiveX / Office Interop / P/Invoke / 原生 DLL
  • 已确认 x86 / x64 / ARM64 的限制

13.6 shared library 与数据访问

  • 已把 shared library 分类为 业务逻辑 / 与应用模型紧贴的层
  • 已盘查哪些能做成 netstandard2.0
  • 已盘查哪些库需要 multi-target
  • 已判断能否保留 EF6、只先迁运行时
  • 已确认 EDMX / Designer 依赖

13.7 运维与 build

  • 已盘点配置文件
  • 已确认发布方式(IIS / Service / MSI / ClickOnce 等)
  • 已确认日志 / 监控 / 权限 / 运行账户的前提
  • 已确认 CI/CD 与 build agent 是否需要更新
  • 已制定回滚步骤

14. 总结

从 .NET Framework 迁移到 .NET,重要的不是“用哪条命令迁”,而是 在动手前看穿什么能原样过去、什么是另一个问题。

要抓重点的话,就是这 6 条。

  • 迁移前先梳理 .NET Framework 一侧
  • 按应用模型分别评估难度
  • 先盘查不能用的技术
  • 决定 Windows 专用前提允许到什么程度
  • 决定 shared library 怎么切
  • 不要把 ORM、身份验证、云迁移一股脑堆在一起

迁移这件事,最初一周会让估算的精度大不相同。 反过来说,只要那一周能把论点梳理清楚,后半段就能相当接近普通的开发。

最初一周怎么用该图说明如果最初一周能梳理清楚什么能原样过去、什么是另一个问题,估算精度就会提高,后半段也能相当接近普通的开发。最初一周梳理论点估算精度提高后半段能接近普通开发区分能原样过去的与另一个问题

图32:能不能把最初一周用在盘点上,决定了整个迁移的精度。

“先改成 net10.0 试试看”,作为探索并不坏。 但作为正式迁移,在那之前还有该看的地方。本文整理的正是这些地方。

15. 参考资料

共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。

与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。

本文与以下服务页面相关联,欢迎从最接近的入口查看。

技术咨询 & 设计评审

如果希望在动手之前先厘清迁移范围、分阶段迁移的阶段怎么划分,以及 Windows 专用前提能允许到什么程度,这个主题很适合以技术咨询与设计评审的形式推进。

常见问题

汇总了咨询这一主题时常见的问题。

从 .NET Framework 迁移到 .NET 之前,首先应该做什么?
在进入实现之前,先把 .NET Framework 一侧梳理干净。Microsoft 的官方指南也推荐:移植前先把版本提升到 .NET Framework 4.7.2 以上(实务上如果可能就提到 4.8.1),把 packages.config 转成 PackageReference,把项目格式向 SDK 样式靠拢,并把依赖关系更新到较新的版本。先做完这些,后段现代化到 modern .NET 的差异会大幅减少,也更容易区分兼容性问题的原因究竟是“Framework 太旧”还是“迁到 .NET”。同时,AppDomain、Remoting、BinaryFormatter 等不受支持技术的盘点也要在动手前完成。
继续留在 .NET Framework 的判断成立吗?
完全可能成立。只要运行在受支持的 Windows 上,.NET Framework 4.8.1 就会持续获得支持,所以并不是“不立刻把全部迁到 modern .NET 就马上有危险”。如果符合这些条件:Web Forms 的界面资产量很大、必须严格保持 WCF 服务端兼容、对 Workflow Foundation 或 COM+ 依赖很深、第三方的设计时组件不受支持,那么暂时向 4.8.1 靠拢维持稳定运维,同时另开一条线制定替换计划,是合理的做法。但走不出 Windows 专用、难以享受新版 .NET 的性能与语言特性等限制仍然存在。
哪些技术无法迁移到 .NET,或者特别容易卡住?
AppDomain 的创建、.NET Remoting、CAS(代码访问安全)、COM+(System.EnterpriseServices)、Workflow Foundation 在 modern .NET 中不受支持,属于必须重新设计的红灯项。WCF 服务端在内置能力里无法原样运行,只能在 CoreWCF 与 gRPC / HTTP API 重新设计之间做选择。BinaryFormatter 在 .NET 9 以后运行时不再包含实现,API 总是抛出异常,因此需要把持久化数据、ResX、剪贴板与拖放都纳入审查。此外,packages.config 的 install.ps1 / XDT 转换、原生 DLL、COM / ActiveX、隐含的 x86 前提也都是即使 build 通过、运行时仍容易出问题、需要重点注意的地方。
把 WinForms 或 WPF 应用迁移到 .NET 之后就能跨平台了吗?
不能。WinForms / WPF 即使迁移到 .NET,仍然是 Windows 专用框架。迁移能得到的是 modern .NET 的运行时、语言特性、SDK 样式以及与 CI/CD 的配合度,并不会因此就能装进 Linux 容器。如果将来要瞄准 Linux 或容器化,就需要在早期阶段盘点 System.Drawing.Common、注册表、WMI、EventLog、Windows Service、COM、Office Interop 等以 Windows 为前提的 API。反过来,如果决定继续留在 Windows,可以走使用 Windows Compatibility Pack 先把运行时现代化的现实路线。

作者简介

本文作者的个人简介页面。

Go Komura

小村软件有限公司 代表

以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。

返回博客列表