From afcdc776b1bb3f5aa558b9fadfd455e2fae7d50c Mon Sep 17 00:00:00 2001 From: jasontaylordev Date: Thu, 26 Feb 2026 09:36:34 +1000 Subject: [PATCH 1/4] Upgrade to .NET 10 - Update ServicePulse, ServicePulse.Tests, and Particular.PlatformSample.ServicePulse to net10.0 - Update Dockerfile to use .NET 10 SDK and runtime images - Update global.json to SDK 10.0.100 - Fix breaking changes: - IPNetwork: use System.Net.IPNetwork instead of Microsoft.AspNetCore.HttpOverrides.IPNetwork - ForwardedHeadersOptions: use KnownIPNetworks instead of KnownNetworks - IPNetwork.BaseAddress instead of IPNetwork.Prefix - X509CertificateLoader instead of X509Certificate2 constructor - Fix IDE0018 code style warning in ServicePulse.Host --- global.json | 2 +- .../Particular.PlatformSample.ServicePulse.csproj | 2 +- src/ServicePulse.Host/Hosting/HostArguments.cs | 3 +-- src/ServicePulse.Tests/ServicePulse.Tests.csproj | 4 ++-- src/ServicePulse/Dockerfile | 4 ++-- src/ServicePulse/ServicePulse.csproj | 2 +- src/ServicePulse/Settings.cs | 1 - src/ServicePulse/WebApplicationBuilderExtensions.cs | 4 ++-- src/ServicePulse/WebApplicationExtensions.cs | 6 +++--- 9 files changed, 13 insertions(+), 15 deletions(-) diff --git a/global.json b/global.json index 2134ed947e..f72210cac2 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.400", + "version": "10.0.100", "rollForward": "latestFeature" } } \ No newline at end of file diff --git a/src/Particular.PlatformSample.ServicePulse/Particular.PlatformSample.ServicePulse.csproj b/src/Particular.PlatformSample.ServicePulse/Particular.PlatformSample.ServicePulse.csproj index ce2d8ddf1c..2ada072576 100644 --- a/src/Particular.PlatformSample.ServicePulse/Particular.PlatformSample.ServicePulse.csproj +++ b/src/Particular.PlatformSample.ServicePulse/Particular.PlatformSample.ServicePulse.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 false true Particular ServicePulse binaries for use by Particular.PlatformSample. Not intended for use outside of Particular.PlatformSample. diff --git a/src/ServicePulse.Host/Hosting/HostArguments.cs b/src/ServicePulse.Host/Hosting/HostArguments.cs index 0a7dc0d000..f749a16a07 100644 --- a/src/ServicePulse.Host/Hosting/HostArguments.cs +++ b/src/ServicePulse.Host/Hosting/HostArguments.cs @@ -355,8 +355,7 @@ void ValidateArgs() goto case ExecutionMode.Run; case ExecutionMode.Run: - Uri spUri; - if (!Uri.TryCreate(Url, UriKind.Absolute, out spUri) || (!validProtocols.Contains(spUri.Scheme, StringComparer.OrdinalIgnoreCase))) + if (!Uri.TryCreate(Url, UriKind.Absolute, out Uri spUri) || (!validProtocols.Contains(spUri.Scheme, StringComparer.OrdinalIgnoreCase))) { throw new Exception("The value specified for 'url' is not a valid URL"); } diff --git a/src/ServicePulse.Tests/ServicePulse.Tests.csproj b/src/ServicePulse.Tests/ServicePulse.Tests.csproj index a4dfa7b200..28cf6ff769 100644 --- a/src/ServicePulse.Tests/ServicePulse.Tests.csproj +++ b/src/ServicePulse.Tests/ServicePulse.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable false @@ -13,7 +13,7 @@ - + diff --git a/src/ServicePulse/Dockerfile b/src/ServicePulse/Dockerfile index baf02139c1..61574cc16c 100644 --- a/src/ServicePulse/Dockerfile +++ b/src/ServicePulse/Dockerfile @@ -7,7 +7,7 @@ RUN npm install RUN npm run build # Host build image -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0 AS build ARG TARGETARCH WORKDIR / ENV CI=true @@ -15,7 +15,7 @@ COPY --from=frontend . . RUN dotnet publish src/ServicePulse/ServicePulse.csproj -a $TARGETARCH -o /app # Host runtime image -FROM mcr.microsoft.com/dotnet/aspnet:8.0-noble-chiseled-composite +FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled-composite WORKDIR /app ENV ASPNETCORE_HTTP_PORTS=9090 diff --git a/src/ServicePulse/ServicePulse.csproj b/src/ServicePulse/ServicePulse.csproj index 68d5b7ed87..6bf1d31d2d 100644 --- a/src/ServicePulse/ServicePulse.csproj +++ b/src/ServicePulse/ServicePulse.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable true diff --git a/src/ServicePulse/Settings.cs b/src/ServicePulse/Settings.cs index 494136b38e..962409bb27 100644 --- a/src/ServicePulse/Settings.cs +++ b/src/ServicePulse/Settings.cs @@ -3,7 +3,6 @@ using System.Net; using System.Text.Json; using Microsoft.Extensions.Logging; -using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork; class Settings { diff --git a/src/ServicePulse/WebApplicationBuilderExtensions.cs b/src/ServicePulse/WebApplicationBuilderExtensions.cs index 1645af9da8..1064c0421c 100644 --- a/src/ServicePulse/WebApplicationBuilderExtensions.cs +++ b/src/ServicePulse/WebApplicationBuilderExtensions.cs @@ -62,7 +62,7 @@ static X509Certificate2 LoadCertificate(Settings settings) } return string.IsNullOrEmpty(settings.HttpsCertificatePassword) - ? new X509Certificate2(certPath) - : new X509Certificate2(certPath, settings.HttpsCertificatePassword); + ? X509CertificateLoader.LoadCertificateFromFile(certPath) + : X509CertificateLoader.LoadPkcs12FromFile(certPath, settings.HttpsCertificatePassword); } } diff --git a/src/ServicePulse/WebApplicationExtensions.cs b/src/ServicePulse/WebApplicationExtensions.cs index 3f2f76da01..2b4d367e3e 100644 --- a/src/ServicePulse/WebApplicationExtensions.cs +++ b/src/ServicePulse/WebApplicationExtensions.cs @@ -27,7 +27,7 @@ public static void UseForwardedHeaders(this WebApplication app, Settings setting // Configuration var knownProxies = settings.ForwardedHeadersKnownProxies.Select(p => p.ToString()).ToArray(); - var knownNetworks = settings.ForwardedHeadersKnownNetworks.Select(n => $"{n.Prefix}/{n.PrefixLength}").ToArray(); + var knownNetworks = settings.ForwardedHeadersKnownNetworks.Select(n => $"{n.BaseAddress}/{n.PrefixLength}").ToArray(); return new { @@ -58,7 +58,7 @@ public static void UseForwardedHeaders(this WebApplication app, Settings setting // Clear default loopback-only restrictions options.KnownProxies.Clear(); - options.KnownNetworks.Clear(); + options.KnownIPNetworks.Clear(); // Enabled by default if (settings.ForwardedHeadersTrustAllProxies) @@ -76,7 +76,7 @@ public static void UseForwardedHeaders(this WebApplication app, Settings setting foreach (var network in settings.ForwardedHeadersKnownNetworks) { - options.KnownNetworks.Add(network); + options.KnownIPNetworks.Add(network); } } From 1eb6e0895434fe6f898ce31ea131c659474f4fe8 Mon Sep 17 00:00:00 2001 From: jasontaylordev Date: Thu, 26 Feb 2026 09:43:59 +1000 Subject: [PATCH 2/4] Update CI workflow to .NET 10 SDK --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 934367675c..98e43616aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: - name: Setup .NET SDK uses: actions/setup-dotnet@v5.1.0 with: - dotnet-version: 8.0.x + dotnet-version: 10.0.x - name: Set up Node.js uses: actions/setup-node@v6.2.0 with: From 30f304f8aac3e283cbb804f7b1e8bbc9a1619d4d Mon Sep 17 00:00:00 2001 From: jasontaylordev Date: Thu, 26 Feb 2026 09:48:32 +1000 Subject: [PATCH 3/4] Update release workflow to .NET 10 SDK and base image labels --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5dc363ea1d..a558080c0a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,7 @@ jobs: - name: Setup .NET SDK uses: actions/setup-dotnet@v5.1.0 with: - dotnet-version: 7.0.x + dotnet-version: 10.0.x - name: Set up Node.js uses: actions/setup-node@v6.2.0 with: @@ -170,7 +170,7 @@ jobs: org.opencontainers.image.created=${{ steps.date.outputs.date }} org.opencontainers.image.title=ServicePulse org.opencontainers.image.description=ServicePulse provides real-time production monitoring for distributed applications. It monitors the health of a system's endpoints, detects processing errors, sends failed messages for reprocessing, and ensures the specific environment's needs are met, all in one consolidated dashboard. - org.opencontainers.image.base.name=mcr.microsoft.com/dotnet/aspnet:8.0-noble-chiseled-composite + org.opencontainers.image.base.name=mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled-composite annotations: | index:org.opencontainers.image.source=https://github.com/Particular/ServicePulse/tree/${{ github.sha }} index:org.opencontainers.image.authors="Particular Software" @@ -182,7 +182,7 @@ jobs: index:org.opencontainers.image.created=${{ steps.date.outputs.date }} index:org.opencontainers.image.title=ServicePulse index:org.opencontainers.image.description=ServicePulse provides real-time production monitoring for distributed applications. It monitors the health of a system's endpoints, detects processing errors, sends failed messages for reprocessing, and ensures the specific environment's needs are met, all in one consolidated dashboard. - index:org.opencontainers.image.base.name=mcr.microsoft.com/dotnet/aspnet:8.0-noble-chiseled-composite + index:org.opencontainers.image.base.name=mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled-composite file: src/ServicePulse/Dockerfile tags: ghcr.io/particular/servicepulse:${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.number) || env.MinVerVersion }} From 5b1853e4ed32bf68b6e04cddb4f3ddb27de343d6 Mon Sep 17 00:00:00 2001 From: jasontaylordev Date: Thu, 26 Feb 2026 09:54:25 +1000 Subject: [PATCH 4/4] Fix flaky sorting-endpoints tests with waitFor --- .../monitoring/sorting-endpoints.spec.ts | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Frontend/test/specs/monitoring/sorting-endpoints.spec.ts b/src/Frontend/test/specs/monitoring/sorting-endpoints.spec.ts index 4a636d9231..68841efc34 100644 --- a/src/Frontend/test/specs/monitoring/sorting-endpoints.spec.ts +++ b/src/Frontend/test/specs/monitoring/sorting-endpoints.spec.ts @@ -1,5 +1,6 @@ import { expect } from "vitest"; import { test, describe } from "../../drivers/vitest/driver"; +import { waitFor } from "@testing-library/vue"; import { groupEndpointsBy } from "./actions/groupEndpointsBy"; import { endpointGroupNames } from "./questions/endpointGroupNames"; import { endpointGroup } from "./questions/endpointGroup"; @@ -68,10 +69,12 @@ describe("FEATURE: Endpoint sorting", () => { await sortEndpointsBy({ column: columnName.ENDPOINTNAME }); //Assert - expect(endpointGroupNames()).toEqual(["Universe.Solarsystem.Venus", "Universe.Solarsystem.Mercury", "Universe.Solarsystem.Earth"]); - expect(endpointGroup("Universe.Solarsystem.Venus").Endpoints).toEqual(["Endpoint4", "Endpoint3"]); - expect(endpointGroup("Universe.Solarsystem.Mercury").Endpoints).toEqual(["Endpoint2", "Endpoint1"]); - expect(endpointGroup("Universe.Solarsystem.Earth").Endpoints).toEqual(["Endpoint6", "Endpoint5"]); + await waitFor(() => { + expect(endpointGroupNames()).toEqual(["Universe.Solarsystem.Venus", "Universe.Solarsystem.Mercury", "Universe.Solarsystem.Earth"]); + expect(endpointGroup("Universe.Solarsystem.Venus").Endpoints).toEqual(["Endpoint4", "Endpoint3"]); + expect(endpointGroup("Universe.Solarsystem.Mercury").Endpoints).toEqual(["Endpoint2", "Endpoint1"]); + expect(endpointGroup("Universe.Solarsystem.Earth").Endpoints).toEqual(["Endpoint6", "Endpoint5"]); + }); }); test("EXAMPLE: Endpoints inside of the groups and group names should be sorted in ascending order when clicking twice on the endpoint name column title", async ({ driver }) => { @@ -95,10 +98,12 @@ describe("FEATURE: Endpoint sorting", () => { await sortEndpointsBy({ column: columnName.ENDPOINTNAME }); //Click the column title again for ascending //Assert - expect(endpointGroupNames()).toEqual(["Universe.Solarsystem.Earth", "Universe.Solarsystem.Mercury", "Universe.Solarsystem.Venus"]); - expect(endpointGroup("Universe.Solarsystem.Earth").Endpoints).toEqual(["Endpoint5", "Endpoint6"]); - expect(endpointGroup("Universe.Solarsystem.Mercury").Endpoints).toEqual(["Endpoint1", "Endpoint2"]); - expect(endpointGroup("Universe.Solarsystem.Venus").Endpoints).toEqual(["Endpoint3", "Endpoint4"]); + await waitFor(() => { + expect(endpointGroupNames()).toEqual(["Universe.Solarsystem.Earth", "Universe.Solarsystem.Mercury", "Universe.Solarsystem.Venus"]); + expect(endpointGroup("Universe.Solarsystem.Earth").Endpoints).toEqual(["Endpoint5", "Endpoint6"]); + expect(endpointGroup("Universe.Solarsystem.Mercury").Endpoints).toEqual(["Endpoint1", "Endpoint2"]); + expect(endpointGroup("Universe.Solarsystem.Venus").Endpoints).toEqual(["Endpoint3", "Endpoint4"]); + }); }); });