如果我需要升级 .NET 8 到 .NET 10,我将这项工作视为 API 契约迁移,而不是项目文件编辑。一项服务可能可以编译、通过单元测试,但仍然可能以改变的 JSON 结构、状态码、身份验证响应或 OpenAPI 文档的形式给消费者带来意外。
这种风险现在很重要,因为微软已经确认.NET 8 和 .NET 9 将于 2026 年 11 月 10 日结束支持。.NET 10 和 C# 14 是当前的稳定版本,而 .NET 10 是受支持的 LTS 目标。
为什么截止日期改变了我的升级顺序
我的第一步是盘点,而不是重新定位目标。我列出每一个可部署项目、测试项目、global.json、容器基础镜像、CI SDK 固定版本和 Microsoft 包引用。dotnet --list-sdks 显示一台机器可以构建什么;dotnet --info 显示当前环境实际解析的内容。
如果这份盘点需要更多细节,我之前关于dotnet sdk check的指南是一个有用的起点。对于仍在使用 .NET 8 的 API,更广泛的Web API 设置和安全检查清单可以帮助在迁移前识别需要保护的行为。
然后,我将迁移分为三个变更:SDK 和目标框架、NuGet 依赖项以及运行时基础架构。保持这些变更可见可以更容易定位故障。一个巨大的依赖刷新提交可能很快就能创建,但很难诊断。
在契约测试背后将 .NET 8 升级到 .NET 10
在更改 net8.0 之前,我围绕消费者不能容忍改变的端点添加一小部分测试。我关心可观察的行为:状态码、内容类型、必需的 JSON 名称和身份验证边界。我避免断言整个序列化字符串,因为无害的属性顺序可能使该测试变得嘈杂。
以下是针对 Minimal API 的一个重点 xUnit 测试:
using System.Net;
using System.Text.Json;
using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;
public sealed class ProductContractTests(
WebApplicationFactory<Program> factory)
: IClassFixture<WebApplicationFactory<Program>>
{
[Fact]
public async Task GetProduct_keeps_the_public_contract()
{
using var client = factory.CreateClient(new()
{
BaseAddress = new Uri("https://localhost")
});
using var response = await client.GetAsync("/products/42");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(
"application/json",
response.Content.Headers.ContentType?.MediaType);
await using var stream =
await response.Content.ReadAsStreamAsync();
using var json = await JsonDocument.ParseAsync(stream);
Assert.Equal(
42,
json.RootElement.GetProperty("id").GetInt32());
Assert.True(json.RootElement.TryGetProperty("name", out _));
}
}
Enter fullscreen mode Exit fullscreen mode
测试项目引用与应用程序相同的 10.0 服务线中的 Microsoft.AspNetCore.Mvc.Testing。这个测试不是一个完整的兼容性套件。它是针对最重要的少数契约的模板:成功的读取、验证失败、未授权请求和未找到的响应。这些检查可以捕获单元测试在 HTTP 管道之下无法看到的行为变化。
对于顶级 Minimal API,我还向测试项目公开入口点:
app.Run();
public partial class Program
{
}
Enter fullscreen mode Exit fullscreen mode
微软维护了一个完整的.NET 10 WebApplicationFactory 示例。我使用它作为测试主机设置的参考,而不是创建自定义服务器管道。
一起移动运行时、包和管道
在基线变为绿色后,我将应用程序和测试项目更改为 net10.0。我将 Microsoft.AspNetCore、Microsoft.Extensions 和 EF Core 包更新为兼容的 10.0 服务版本,然后查看官方的.NET 10 破坏性变更目录,而不是仅凭编译器错误猜测。
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
Enter fullscreen mode Exit fullscreen mode
我在本地和 CI 中运行相同的短序列:
dotnet package list --outdated
dotnet restore
dotnet build --warnaserror
dotnet test
dotnet publish -c Release
Enter fullscreen mode Exit fullscreen mode
管道必须安装 .NET 10 SDK,容器化服务必须使用匹配的 10.0 构建和运行时镜像。仅更新开发人员机器对到达生产环境的工件几乎没有证明作用。
OpenAPI 值得明确决定。ASP.NET Core 10 的内置生成器默认发出 OpenAPI 3.1。如果现有的客户端生成器只理解 3.0,我会临时固定格式并单独安排该契约变更:
builder.Services.AddOpenApi(options =>
{
options.OpenApiVersion =
Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_0;
});
Enter fullscreen mode Exit fullscreen mode
这种固定是一种兼容性工具,而不是永远避免 OpenAPI 3.1 的理由。我只在消费者经过测试和升级之前保留它。
我何时不会使用这种直接路径
当数据库提供程序、身份验证组件、托管平台或商业依赖项不支持 .NET 10 时,直接重新定位是错误的做法。在这种情况下,我首先隔离阻塞因素。通过 .NET 9 进行迁移可能有助于分步暴露更改,但 .NET 9 的支持截止日期也是 2026 年 11 月,因此它不是最终目标。
我还避免将运行时升级与 EF 模型重新设计、身份验证重写或 OpenAPI 生成器替换结合在一起。这些可能都值得做,但单独的提交和部署保留了一个有用的回滚边界。
对于共享库,临时的 net8.0;net10.0 多目标可以让应用程序独立迁移。对于可部署的 API,多目标不能替代选择和验证生产将执行的运行时。
在将服务迁移到 .NET 10 之前,您会固定哪个 API 契约?
感谢您的阅读 ? 下次见。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.