- 数据可视化
- 图表库
【免费下载链接】ScottPlot
Interactive plotting library for .NET
导读
本文基于 ScottPlot 仓库dev/ai-prompts目录下记录的 AI 协作会话,完整还原了 ScottPlot 团队将 Blazor、MAUI、Uno 等沙箱示例项目从 .NET 8/9 升级到 .NET 10 时遭遇的四个典型运行时/构建问题及其修复全过程。读者将掌握 .NET 10 中 Blazor WebAssembly 的模块加载新机制、独立 WASM 应用的静态资源清单要求、MAUI Windows 打包模式与 Windows SDK 投影包的正确配置,以及 WinRT.Runtime 版本冲突的排查思路——这些都是 .NET 大版本升级中高频出现的兼容性坑点,可直接迁移到自己的项目迁移实践中。
文档背景:仓库中 AI Prompts 说明 建议在使用 AI 工具执行直接修改代码的复杂任务时,以
YYYY-MM-DD [GitHubUsername] description of feature.md的命名格式记录 AI 对话,案例原文 即是这一实践的典型样本。
一、为何要为 AI 协作保留对话记录
1.1 文档规范
dev/ai-prompts/README.md 给出了非常简短但明确的三条约定:
- 当使用 AI 工具执行直接修改代码的复杂任务时,建议记录 AI 对话;
- 文件命名格式为
YYYY-MM-DD [GitHubUsername] description of feature.md; - Markdown 格式不限,自由组织即可。
这套约定的价值在于:AI 排障过程天然包含"报错信息 → 根因分析 → 修改 → 复现验证"的完整链路,这些信息散落在终端输出与聊天记录中极易丢失,而按日期与作者归档后,就形成了一份可回溯的项目级迁移知识库——当下次升级或回滚时,无需重新摸索。
1.2 本文涉及的案例范围
案例文档 记录了四个独立案例,全部围绕 ScottPlot 5 的沙箱示例工程(位于src/ScottPlot5/ScottPlot5 Sandbox/与src/ScottPlot5/ScottPlot5 Demos/):
| 案例 | 项目 | 升级路径 | 核心问题 |
|---|---|---|---|
| 1 | Sandbox.Blazor(含 Blazor WebAssembly 控制库) | .NET 9 → .NET 10 | WASM 动态模块加载失败 + SkiaSharp 原生库 DLL 异常 |
| 2 | Sandbox.Blazor.WebAssembly(独立 WASM 应用) | .NET 9 → .NET 10 | 静态资源清单文件缺失 |
| 3 | Sandbox.Maui | .NET 8 → .NET 10 | COM 类未注册异常 + MainPage 过时警告 |
| 4 | Sandbox.UnoPlatform | .NET 8 → .NET 10 | WinRT.Runtime 版本冲突 |
二、案例一:Blazor WebAssembly 升级 .NET 10 的模块加载与 SkiaSharp 问题
2.1 问题现象
Sandbox.Blazor 从 .NET 9 升级到 .NET 10 后,应用启动、页面加载前即抛出运行时异常:WASM 运行时无法加载动态导入的模块,错误指向类似Failed to fetch dynamically imported module: https://localhost:7014/0的路径。
2.2 根因一:程序包版本错配
初步检查发现,工程目标框架已改为net10.0,但引用的Microsoft.AspNetCore.Components.WebAssembly与Microsoft.AspNetCore.Components.WebAssembly.DevServer仍是 .NET 8 时代的8.0.10。运行时与程序集版本不一致会直接导致 WASM 初始化阶段失败。
修复方式:将两个包统一升级为10.0.0(.NET 10 处于 Preview 阶段时使用10.0.0-*)。当前仓库中的 Sandbox.Blazor.csproj 正是修复后的状态:
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.0" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.0" PrivateAssets="all" />2.3 根因二:脚本加载模式未随运行时升级
版本对齐后异常依旧,说明还有第二个问题。.NET 10 中 Blazor WebAssembly 运行时改用ES6 模块(module)加载机制,而index.html仍沿用 .NET 8 时代的旧脚本引入方式,导致运行时以新机制取模块时路径落空。
修复方式:将脚本标签改为 module 模式。当前 Sandbox.Blazor/wwwroot/index.html 中的写法即为修复后的标准形态:
<script src="_framework/blazor.webassembly.js" type="module"></script>补充:对照当前工程,Program.cs 使用标准的
WebAssemblyHostBuilder.CreateDefault(args)初始化,根组件挂载在#app元素上,与index.html中的<div id="app">一一对应,这是 WASM 启动链路的正确形态。
2.4 根因三:SkiaSharp WebAssembly 原生绑定缺失
应用能走得更远了,但随即抛出System.DllNotFoundException in SkiaSharp.dll与System.TypeInitializationException(SkiaSharp.SKTypeface类型初始化失败)。ScottPlot 5 的 Blazor 控件底层依赖 SkiaSharp 在浏览器中的WASM 原生绑定,这类库需要显式保证原生资产不被裁剪、正确随包发布。
修复要点集中在 Sandbox.Blazor.csproj:
<WasmNativeStrip>false</WasmNativeStrip> ... <PackageReference Include="SkiaSharp.Views.Blazor" Version="3.119.0" />WasmNativeStrip=false:阻止链接器剥离 SkiaSharp 的 WASM 原生库(这是该问题最关键的一处配置,剥离后运行时找不到原生符号就会抛 DLL 异常);- 显式引用
SkiaSharp.Views.Blazor:确保 WebAssembly 原生绑定被正确纳入发布产物。
同时,控制库 ScottPlot.Blazor.csproj 将Microsoft.AspNetCore.Components.Web改为按目标框架条件化引用:net10.0用10.0、net8.0用8.0,从而让同一控制库同时兼容两个 TFM。
2.5 底层原理印证:SkiaSharp 在 Blazor 中的渲染链路
从源码看,BlazorPlotBase.cs 是 Blazor 控件的核心基类,它实现IPlotControl,内部通过UserInputProcessor将浏览器的指针事件(OnPointerMoved、OnPointerPressed、OnPointerWheelChanged等)统一转换为 ScottPlot 的交互指令。这意味着:所有交互能力都建立在 SkiaSharp 能在 WASM 环境中正常初始化并完成绘制的基础之上。一旦原生绑定缺失导致SKTypeface初始化失败,整个控件在页面加载阶段就会崩溃——这也解释了为什么 SkiaSharp 原生资产配置是本次迁移的咽喉环节。
三、案例二:独立 WASM 应用的静态资源清单缺失
3.1 问题现象
Sandbox.Blazor.WebAssembly项目升级到 .NET 10 后启动即抛:
System.InvalidOperationException: The static resources manifest file '...\bin\Debug\net10.0\Sandbox.Blazor.WebAssembly.staticwebassets.endpoints.json' was not found.3.2 根因:.NET 10 对独立 WASM 应用的破坏性变更
这是 .NET 10 针对独立(standalone)Blazor WebAssembly 应用的已知破坏性变更:WebAssemblyHostBuilder.CreateDefault(args)启动时会读取staticwebassets.endpoints.json清单文件,但Microsoft.NET.Sdk.BlazorWebAssemblySDK 默认不生成该文件。
对比一下两种宿主形态:
- 宿主型(hosted)WASM 应用:由 ASP.NET Core 服务端工程在构建时生成该清单,因此无此问题;
- 独立型 WASM 应用:没有服务端工程,清单默认缺失,升级后必然报错。
3.3 修复:打开清单生成开关
在项目文件中加入 MSBuild 属性即可:
<GenerateStaticWebAssetsEndpointsManifest>true</GenerateStaticWebAssetsEndpointsManifest>该属性指示 SDK 在构建时产出清单文件。干净重建后,bin\Debug\net10.0\目录下就会出现Sandbox.Blazor.WebAssembly.staticwebassets.endpoints.json,运行时异常随之消失。
当前仓库中该修复已落地在两处独立 WASM 工程:
- Sandbox.Blazor.WebAssembly.csproj(
Microsoft.NET.Sdk.BlazorWebAssembly,targetingnet10.0) - Sandbox.Blazor.csproj(同为独立 WASM 应用,同样命中此问题并应用了相同修复)
排查提示:若你的独立 WASM 工程升级 .NET 10 后遇到 "manifest file ... was not found" 异常,优先检查 csproj 中是否设置了
GenerateStaticWebAssetsEndpointsManifest=true;而宿主型WASM 应用通常不需要手动设置。
四、案例三:MAUI 沙箱的 COM 异常、包类型与过时 API
4.1 问题现象
Sandbox.Maui 从 .NET 8 升级到 .NET 10 后,Windows 桌面启动时抛出:
System.Runtime.InteropServices.COMException: Class not registered (0x80040154 (REGDB_E_CLASSNOTREG))且用户明确要求:使用最新 .NET 10 目标与包,但不要使用 .NET 11 Preview。
4.2 根因一:过时的 WindowsSdkPackageVersion 重复属性
对照解决方案中其他 Windows 目标工程(如Sandbox.WinUI.Desktop、ScottPlot.Maui)可以发现:它们都不显式设置WindowsSdkPackageVersion,而是交由 .NET 10 SDK 自动解析正确的 Windows SDK 投影包版本。
而 Sandbox.Maui 的工程文件中残留了两处 .NET 8 时代的WindowsSdkPackageVersion(值形如10.0.19041.34、10.0.19041.38),这些旧投影包与 .NET 10 运行时不兼容,正是REGDB_E_CLASSNOTREG的直接诱因。
修复:删除全部WindowsSdkPackageVersion属性,让 SDK 自动解析。从当前 Sandbox.Maui.csproj 可以看到,工程只保留了TargetFrameworks、SupportedOSPlatformVersion、TargetPlatformMinVersion,已无任何WindowsSdkPackageVersion残留。
4.3 根因二:日志包版本未对齐
Microsoft.Extensions.Logging.Debug仍停留在8.0.0,与net10.0目标框架不匹配。当前仓库中已更新为 Version="10.0.0"。
4.4 根因三:打包模式引发 COM 类未注册
在 Visual Studio 中运行仍报Class not registered (0x80040154),且错误发生在 Windows App SDK 初始化阶段(DeploymentManager.Initialize)。原因在于应用默认按打包(MSIX)应用运行,但缺少包注册信息,导致 COM 类未被注册。
参考解决方案中 Sandbox.WinUI.Desktop.csproj 已有的做法,为 MAUI 工程加入:
<WindowsPackageType>None</WindowsPackageType>该属性指示构建产出非打包(unpackaged)Windows 应用,完全绕过 MSIX 包注册流程,从根源上消除REGDB_E_CLASSNOTREG。这一配置已固化在 Sandbox.Maui.csproj 中。
4.5 附带处理:MainPage 过时警告
升级后构建伴随警告 CS0618:
'Application.MainPage.set' is obsolete:该属性已弃用,应通过重写Application.CreateWindow完成初始化;多窗口场景下改用Windows[0].Page。
修复方式是把MainPage = new AppShell()改为重写CreateWindow。当前 App.xaml.cs 即修复后的形态:
protected override Window CreateWindow(IActivationState? activationState) { return new Window(new AppShell()); }说明:
MainPage的废弃与WindowsPackageType是相互独立的两个问题——前者是 .NET 10 MAUI 的 API 迁移要求,后者解决打包模式导致的 COM 异常,两者需要分别处理。
五、案例四:Uno 沙箱的 WinRT.Runtime 版本冲突
5.1 问题现象
Sandbox.UnoPlatform 从 .NET 8 升级到 .NET 10 后构建报错:
Assembly 'ScottPlot.WinUI' ... uses 'WinRT.Runtime, Version=2.2.0.0' which has a higher version than referenced assembly 'WinRT.Runtime' with identity 'WinRT.Runtime, Version=2.0.0.0'5.2 根因:传递依赖带来的版本降级
ScottPlot.WinUI通过其Microsoft.WindowsAppSDK 1.5.240802000依赖编译时使用的是WinRT.Runtime2.2.0.0;而 Uno SDK 5.3.90 默认将WinRT.Runtime解析到较旧的2.0.0.0。NuGet 解析结果低于程序集实际编译版本,构建即报版本冲突。
5.3 修复:显式固定 CsWinRT 版本
WinRT.Runtime程序集由Microsoft.Windows.CsWinRT包提供,因此在 Uno 工程中显式引用Microsoft.Windows.CsWinRT 2.2.0,即可让 NuGet 把WinRT.Runtime解析到 2.2.0.0,与ScottPlot.WinUI的编译基准对齐。当前 Sandbox.UnoPlatform.csproj 已包含该引用:
<PackageReference Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />5.4 关联佐证:ScottPlot.WinUI 的 Windows 依赖面
对照 ScottPlot.WinUI.csproj:Windows 目标框架下引用Microsoft.WindowsAppSDK 1.5.240802000、Microsoft.Windows.SDK.BuildTools 10.0.22621.756及SkiaSharp.Views.WinUI 3.119.0,并显式钉住Microsoft.Windows.SDK.NET.Ref的RuntimeFrameworkVersion与TargetingPackVersion(10.0.22000.27)。可以推断,WinAppSDK 依赖面越复杂,下游工程(如 Uno)越容易出现WinRT.Runtime之类的传递版本错位,显式引用 CsWinRT 是对这类冲突最直接的解决办法。
六、可复用的迁移排查清单
综合四个案例,将 .NET 8/9 → .NET 10 迁移的检查要点汇总如下,供后续项目升级时逐项核对:
6.1 Blazor WebAssembly
- 升级
Microsoft.AspNetCore.Components.WebAssembly与.DevServer至10.0.0(且与 TFM 一致); index.html中脚本改为<script src="_framework/blazor.webassembly.js" type="module"></script>;- 依赖 SkiaSharp 等含 WASM 原生资产的库时,设置
WasmNativeStrip=false并显式引用对应 Views 包; - 独立 WASM 应用必须设置
GenerateStaticWebAssetsEndpointsManifest=true(宿主型应用通常无需)。
6.2 MAUI / WinUI(Windows 目标)
- 删除遗留的
WindowsSdkPackageVersion旧值,交由 SDK 自动解析; - 日志、依赖等通用包版本与
net10.0对齐; - 桌面运行遇 COM 类未注册时,考虑
<WindowsPackageType>None</WindowsPackageType>改为非打包模式; - 用
CreateWindow重写替代已过时的MainPage赋值。
6.3 Uno / WinUI 多目标工程
- 出现
WinRT.Runtime版本冲突时,显式引用Microsoft.Windows.CsWinRT到与上游程序集一致的版本; - 核对 WinAppSDK、Windows SDK BuildTools 等 Windows 依赖面版本是否在传递解析中被降级。
6.4 过程规范
- 将每个报错与修复按
YYYY-MM-DD [用户名] 功能描述.md命名归档至dev/ai-prompts/,形成团队可复用的迁移知识库。
七、总结
这四个案例揭示了 .NET 大版本升级中两类典型的"隐形断裂":一类是运行时机制的变更(如 WASM 从脚本加载转向 ES6 模块、CreateDefault开始强依赖静态资源清单、MAUI 废弃MainPage),另一类是版本解析的漂移(程序包停留在旧大版本、旧投影包残留、传递依赖降级)。两者的共同特征是:编译未必失败,但启动或运行阶段才会暴露。ScottPlot 仓库以 AI 对话形式将这些排障过程固化在 2025-03-28 案例文档 中,并在 Sandbox.Blazor.csproj、Sandbox.Maui.csproj、Sandbox.UnoPlatform.csproj 等工程文件中留下了最终修复形态,既是迁移实践的直接参考,也是排查同类问题时最可信的对照样本。
- 数据可视化
- 图表库
【免费下载链接】ScottPlot
Interactive plotting library for .NET
相关推荐
Sandstorm 自托管运维 FAQ 实战指南:应急登录、域名迁移、沙箱隔离与疑难排障
Sandstorm 自托管运维 FAQ 实战指南:应急登录、域名迁移、沙箱隔离与疑难排障 导读 Sandstorm https://link.gitcode.c
后端容器运行时安全云原生VS Code 进程沙箱化改造全解析:从 Electron 渲染器到沙箱架构的迁移实录
VS Code 进程沙箱化改造全解析:从 Electron 渲染器到沙箱架构的迁移实录 导读 本文基于仓库内发布的官方工程博客 blogs/2022/11/28
文档教程深入解析.NET Runtime安全机制:10个核心防护策略与沙箱环境实战指南
深入解析.NET Runtime安全机制:10个核心防护策略与沙箱环境实战指南 .NET Runtime作为微软跨平台运行时的核心组件,为云原生、移动应用、桌面
语言运行时标准库JIT编译编译器
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考