From 0dd04abfc3b5aa13d2373ded3f2bb2b16e298cb2 Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Tue, 18 Aug 2026 21:26:57 -0500 Subject: [PATCH 1/2] Upgrade Aspire and .NET dependencies --- .github/workflows/build-arm64.yml | 2 +- .github/workflows/build.yaml | 4 ++-- .github/workflows/copilot-setup-steps.yml | 6 +++--- .github/workflows/elasticsearch-docker-8.yml | 2 +- Dockerfile | 10 +++++----- .../Exceptionless.AppHost.csproj | 13 +++++++------ .../Extensions/ElasticsearchExtensions.cs | 2 ++ src/Exceptionless.AppHost/Program.cs | 17 ++++++++++++----- .../Exceptionless.Tests.csproj | 2 +- 9 files changed, 34 insertions(+), 24 deletions(-) diff --git a/.github/workflows/build-arm64.yml b/.github/workflows/build-arm64.yml index 6b8c9a6f99..bb6a3a0237 100644 --- a/.github/workflows/build-arm64.yml +++ b/.github/workflows/build-arm64.yml @@ -25,7 +25,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v5 with: - dotnet-version: 10.0.301 + dotnet-version: 10.0.400 - name: Build Reason env: GITHUB_EVENT: ${{ toJson(github) }} diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 21ecd0eab3..e510d24be3 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -240,9 +240,9 @@ jobs: run: | export PATH="$HOME/.dotnet/tools:$PATH" if dotnet tool list --global | grep -Eiq '^aspire\.cli[[:space:]]'; then - dotnet tool update --global Aspire.Cli --version 13.4.6 + dotnet tool update --global Aspire.Cli --version 13.5.0 else - dotnet tool install --global Aspire.Cli --version 13.4.6 + dotnet tool install --global Aspire.Cli --version 13.5.0 fi echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH" aspire --version diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 0809e4fb80..44a0f1886f 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -25,15 +25,15 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v5 with: - dotnet-version: 10.0.301 + dotnet-version: 10.0.400 - name: Install Aspire CLI run: | export PATH="$HOME/.dotnet/tools:$PATH" if dotnet tool list --global | grep -Eiq '^aspire\.cli[[:space:]]'; then - dotnet tool update --global Aspire.Cli --version 13.4.4 + dotnet tool update --global Aspire.Cli --version 13.5.0 else - dotnet tool install --global Aspire.Cli --version 13.4.4 + dotnet tool install --global Aspire.Cli --version 13.5.0 fi echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH" aspire --version diff --git a/.github/workflows/elasticsearch-docker-8.yml b/.github/workflows/elasticsearch-docker-8.yml index 6f65a4ccd6..fe18e066d8 100644 --- a/.github/workflows/elasticsearch-docker-8.yml +++ b/.github/workflows/elasticsearch-docker-8.yml @@ -16,7 +16,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v5 with: - dotnet-version: 10.0.301 + dotnet-version: 10.0.400 - name: Build Reason env: GITHUB_EVENT: ${{ toJson(github) }} diff --git a/Dockerfile b/Dockerfile index 80cc955548..4904cdf6f9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:10.0.301 AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0.400 AS build ARG MinVerVersionOverride WORKDIR /app @@ -40,7 +40,7 @@ RUN dotnet publish -c Release -o out --no-build # job -FROM mcr.microsoft.com/dotnet/aspnet:10.0.9 AS job +FROM mcr.microsoft.com/dotnet/aspnet:10.0.11 AS job WORKDIR /app COPY --from=job-publish /app/src/Exceptionless.Job/out ./ @@ -59,7 +59,7 @@ RUN dotnet publish -c Release -o out --no-build /p:SkipSpaPublish=true # api -FROM mcr.microsoft.com/dotnet/aspnet:10.0.9 AS api +FROM mcr.microsoft.com/dotnet/aspnet:10.0.11 AS api WORKDIR /app COPY --from=api-publish /app/src/Exceptionless.Web/out ./ @@ -77,7 +77,7 @@ RUN dotnet publish -c Release -o out --no-build # app -FROM mcr.microsoft.com/dotnet/aspnet:10.0.9 AS app +FROM mcr.microsoft.com/dotnet/aspnet:10.0.11 AS app WORKDIR /app COPY --from=app-publish /app/src/Exceptionless.Web/out ./ @@ -151,7 +151,7 @@ USER elasticsearch RUN wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh && \ chmod +x dotnet-install.sh && \ - ./dotnet-install.sh --version 10.0.9 --runtime aspnetcore && \ + ./dotnet-install.sh --version 10.0.11 --runtime aspnetcore && \ rm dotnet-install.sh EXPOSE 8080 9200 diff --git a/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj b/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj index 16cc44ac96..9027f0d4f7 100644 --- a/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj +++ b/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj @@ -1,17 +1,18 @@ - + Exe net10.0 enable enable a9c2ddcc-e51d-4cd1-9782-96e1d74eec87 - $(NoWarn);ASPIRECERTIFICATES001 + + $(NoWarn);ASPIRECERTIFICATES001;ASPIRE010 - - - - + + + + diff --git a/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs b/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs index ec5c5e4031..2ab1c1d31f 100644 --- a/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs +++ b/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs @@ -46,6 +46,7 @@ public static IResourceBuilder AddElasticsearch(this IDis tags: null, timeout: null)); +#pragma warning disable CS0618 // Preserve the manifest connection-string representation until Aspire provides a compatible replacement for custom container resources. return builder.AddResource(elasticsearch) .WithImage(ElasticsearchContainerImageTags.Image, ElasticsearchContainerImageTags.Tag) .WithImageRegistry(ElasticsearchContainerImageTags.ElasticsearchRegistry) @@ -58,6 +59,7 @@ public static IResourceBuilder AddElasticsearch(this IDis .WithEnvironment("ES_JAVA_OPTS", "-Xms1g -Xmx1g") .WithHealthCheck(healthCheckKey) .PublishAsConnectionString(); +#pragma warning restore CS0618 } public static IResourceBuilder WithKibana(this IResourceBuilder builder, Action>? configureContainer = null, string? containerName = null) diff --git a/src/Exceptionless.AppHost/Program.cs b/src/Exceptionless.AppHost/Program.cs index b12b48aefe..048e93d8d6 100644 --- a/src/Exceptionless.AppHost/Program.cs +++ b/src/Exceptionless.AppHost/Program.cs @@ -62,7 +62,7 @@ .WithImageTag("v1.27.10") .WithEndpointProxySupport(false) .WithHttpEndpoint(port: 8026, targetPort: 8025, name: "http") - .WithUrlForEndpoint("http", u => { u.DisplayText = "Mail"; u.DisplayOrder = 100; }) + .WithUrlForEndpoint("http", u => { u.DisplayText = "Mail"; SetDisplayOrder(u, 100); }) .WithHttpHealthCheck("/readyz") .WithEndpoint(targetPort: 1025, port: 1026) .WithUrlForEndpoint("tcp", u => u.DisplayLocation = UrlDisplayLocation.DetailsOnly); @@ -115,7 +115,7 @@ .WaitFor(cache) .WaitFor(mail) .WithExternalHttpEndpoints() - .WithUrlForEndpoint("https", u => { u.DisplayText = "Open API"; u.DisplayOrder = 100; }) + .WithUrlForEndpoint("https", u => { u.DisplayText = "Open API"; SetDisplayOrder(u, 100); }) .WithUrlForEndpoint("http", u => u.DisplayLocation = UrlDisplayLocation.DetailsOnly) .WithHttpHealthCheck("/health"); @@ -178,7 +178,7 @@ .WithUrlForEndpoint("https", u => { u.DisplayText = "Open App (Old)"; - u.DisplayOrder = 100; + SetDisplayOrder(u, 100); }) .WithParentRelationship(api); @@ -206,7 +206,7 @@ .WithUrlForEndpoint("http", u => { u.DisplayText = "Open App"; - u.DisplayOrder = 100; + SetDisplayOrder(u, 100); u.Url = $"{u.Url.TrimEnd('/')}/next/"; }) .WithParentRelationship(api); @@ -232,7 +232,7 @@ .WithUrlForEndpoint("http", u => { u.DisplayText = "Open Docs"; - u.DisplayOrder = 100; + SetDisplayOrder(u, 100); }) .WithParentRelationship(api); } @@ -242,3 +242,10 @@ await builder.Build().RunAsync(); bool HasArgument(string name) => args.Any(arg => StringComparer.OrdinalIgnoreCase.Equals(arg, name) || StringComparer.OrdinalIgnoreCase.Equals(arg, name.TrimStart('-'))); + +static void SetDisplayOrder(ResourceUrlAnnotation annotation, int displayOrder) +{ +#pragma warning disable CS0618 // Aspire 13.5 temporarily obsoletes this field and plans to restore it as a property. + annotation.DisplayOrder = displayOrder; +#pragma warning restore CS0618 +} diff --git a/tests/Exceptionless.Tests/Exceptionless.Tests.csproj b/tests/Exceptionless.Tests/Exceptionless.Tests.csproj index 6fcf11f7a2..882c75121e 100644 --- a/tests/Exceptionless.Tests/Exceptionless.Tests.csproj +++ b/tests/Exceptionless.Tests/Exceptionless.Tests.csproj @@ -11,7 +11,7 @@ - + From a8dafffd60ec38e1f9c21f12b7dd73b3a5c743ab Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Tue, 18 Aug 2026 22:13:00 -0500 Subject: [PATCH 2/2] Adopt Aspire 13.5 app model guidance --- .../Exceptionless.AppHost.csproj | 4 +- .../Extensions/ElasticsearchExtensions.cs | 5 +- src/Exceptionless.AppHost/Program.cs | 170 ++++++++++-------- 3 files changed, 94 insertions(+), 85 deletions(-) diff --git a/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj b/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj index 9027f0d4f7..4e3dead578 100644 --- a/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj +++ b/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj @@ -5,8 +5,8 @@ enable enable a9c2ddcc-e51d-4cd1-9782-96e1d74eec87 - - $(NoWarn);ASPIRECERTIFICATES001;ASPIRE010 + true + $(NoWarn);ASPIRECERTIFICATES001 diff --git a/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs b/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs index 2ab1c1d31f..c1714d3559 100644 --- a/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs +++ b/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs @@ -46,7 +46,6 @@ public static IResourceBuilder AddElasticsearch(this IDis tags: null, timeout: null)); -#pragma warning disable CS0618 // Preserve the manifest connection-string representation until Aspire provides a compatible replacement for custom container resources. return builder.AddResource(elasticsearch) .WithImage(ElasticsearchContainerImageTags.Image, ElasticsearchContainerImageTags.Tag) .WithImageRegistry(ElasticsearchContainerImageTags.ElasticsearchRegistry) @@ -57,9 +56,7 @@ public static IResourceBuilder AddElasticsearch(this IDis .WithEnvironment("xpack.security.enabled", "false") .WithEnvironment("action.destructive_requires_name", "false") .WithEnvironment("ES_JAVA_OPTS", "-Xms1g -Xmx1g") - .WithHealthCheck(healthCheckKey) - .PublishAsConnectionString(); -#pragma warning restore CS0618 + .WithHealthCheck(healthCheckKey); } public static IResourceBuilder WithKibana(this IResourceBuilder builder, Action>? configureContainer = null, string? containerName = null) diff --git a/src/Exceptionless.AppHost/Program.cs b/src/Exceptionless.AppHost/Program.cs index 048e93d8d6..fb3fc029f8 100644 --- a/src/Exceptionless.AppHost/Program.cs +++ b/src/Exceptionless.AppHost/Program.cs @@ -24,9 +24,32 @@ string exceptionlessServerUrl = worktreePorts?.ApiHttpsUrl ?? $"https://api-ex.dev.localhost:{DefaultApiHttpsPort}"; const string SharedEmailConnectionString = "smtp://localhost:1026"; -var elastic = builder.AddElasticsearch("Elasticsearch", port: 9200) - .WithDataVolume("exceptionless.data.v1") - .WithEndpointProxySupport(false); +IResourceBuilder elastic; +if (builder.ExecutionContext.IsRunMode) +{ + var ownedElastic = builder.AddElasticsearch("Elasticsearch", port: 9200) + .WithDataVolume("exceptionless.data.v1") + .WithEndpointProxySupport(false); + + var localElastic = ownedElastic + .WithLifetime(ContainerLifetime.Persistent) + .WithContainerName("Exceptionless-Elasticsearch"); + + if (!servicesOnly && includeDevTools) + { + localElastic.WithKibana(b => b + .WithLifetime(ContainerLifetime.Persistent) + .WithEndpointProxySupport(false) + .WithContainerName("Exceptionless-Kibana") + .WithParentRelationship(ownedElastic)); + } + + elastic = localElastic; +} +else +{ + elastic = builder.AddConnectionString("Elasticsearch"); +} var storage = builder.AddAzureStorage("Storage") .RunAsEmulator(c => @@ -67,20 +90,6 @@ .WithEndpoint(targetPort: 1025, port: 1026) .WithUrlForEndpoint("tcp", u => u.DisplayLocation = UrlDisplayLocation.DetailsOnly); -var ownedElastic = elastic; -elastic = ownedElastic - .WithLifetime(ContainerLifetime.Persistent) - .WithContainerName("Exceptionless-Elasticsearch"); - -if (!servicesOnly && includeDevTools) -{ - elastic = elastic.WithKibana(b => b - .WithLifetime(ContainerLifetime.Persistent) - .WithEndpointProxySupport(false) - .WithContainerName("Exceptionless-Kibana") - .WithParentRelationship(ownedElastic)); -} - var ownedCache = cache; cache = ownedCache .WithLifetime(ContainerLifetime.Persistent) @@ -161,82 +170,85 @@ .WithEndpoint("http", e => e.Port = worktreePorts.JobsHttp); } -#pragma warning disable ASPIREBROWSERLOGS001 - var oldApp = builder.AddJavaScriptApp("OldApp", "../../src/Exceptionless.Web/ClientApp.angular", "serve") - .WithBrowserLogs() - .WithReference(api) - .WithEnvironment("ASPNETCORE_URLS", oldAppAspNetCoreUrls) - .WithEnvironment("USE_HTTPS", "true") - .WithEnvironment("LIVERELOAD_PORT", oldAppLiveReloadPort.ToString()) - .WithHttpEndpoint(port: oldAppPort, targetPort: oldAppPort, name: "https", env: "PORT", isProxied: false) - .WithEndpoint("https", e => - { - e.TargetHost = "angular-ex.dev.localhost"; - e.UriScheme = "https"; - }) - .WithHttpsDeveloperCertificate() - .WithUrlForEndpoint("https", u => - { - u.DisplayText = "Open App (Old)"; - SetDisplayOrder(u, 100); - }) - .WithParentRelationship(api); - - if (worktreePorts is not null) + if (builder.ExecutionContext.IsRunMode) { - oldApp.WithEnvironment("API_HTTP", worktreePorts.ApiHttpUrl) - .WithEnvironment("API_HTTPS", worktreePorts.ApiHttpsUrl); - } +#pragma warning disable ASPIREBROWSERLOGS001 + var oldApp = builder.AddJavaScriptApp("OldApp", "../../src/Exceptionless.Web/ClientApp.angular", "serve") + .WithBrowserLogs() + .WithReference(api) + .WithEnvironment("ASPNETCORE_URLS", oldAppAspNetCoreUrls) + .WithEnvironment("USE_HTTPS", "true") + .WithEnvironment("LIVERELOAD_PORT", oldAppLiveReloadPort.ToString()) + .WithHttpEndpoint(port: oldAppPort, targetPort: oldAppPort, name: "https", env: "PORT", isProxied: false) + .WithEndpoint("https", e => + { + e.TargetHost = "angular-ex.dev.localhost"; + e.UriScheme = "https"; + }) + .WithHttpsDeveloperCertificate() + .WithUrlForEndpoint("https", u => + { + u.DisplayText = "Open App (Old)"; + SetDisplayOrder(u, 100); + }) + .WithParentRelationship(api); - var app = builder.AddViteApp("App", "../Exceptionless.Web/ClientApp") - .WithBrowserLogs() - .WithReference(api) - .WithReference(oldApp) - .WithEnvironment("PUBLIC_EXCEPTIONLESS_SERVER_URL", exceptionlessServerUrl) - .WithEnvironment("PORT", appPort.ToString()) - .WithEndpoint("http", e => - { - // 7131 (HTTPS via Aspire dev cert) instead of Vite's default 5173 to avoid clashing with other local Vite projects. - e.Port = appPort; - e.TargetPort = appPort; - e.TargetHost = "web-ex.dev.localhost"; - e.IsProxied = false; - }) - .WithHttpsDeveloperCertificate() - .WithUrlForEndpoint("http", u => + if (worktreePorts is not null) { - u.DisplayText = "Open App"; - SetDisplayOrder(u, 100); - u.Url = $"{u.Url.TrimEnd('/')}/next/"; - }) - .WithParentRelationship(api); - - if (worktreePorts is not null) - { - app.WithEnvironment("API_HTTP", worktreePorts.ApiHttpUrl) - .WithEnvironment("API_HTTPS", worktreePorts.ApiHttpsUrl) - .WithEnvironment("OLDAPP_HTTP", worktreePorts.OldAppHttpsUrl) - .WithEnvironment("OLDAPP_HTTPS", worktreePorts.OldAppHttpsUrl); - } + oldApp.WithEnvironment("API_HTTP", worktreePorts.ApiHttpUrl) + .WithEnvironment("API_HTTPS", worktreePorts.ApiHttpsUrl); + } - if (includeDevTools) - { - builder.AddDenoTask("Docs", "../../docs", "serve") + var app = builder.AddViteApp("App", "../Exceptionless.Web/ClientApp") .WithBrowserLogs() - .WithHttpEndpoint(port: docsPort, targetPort: docsPort, name: "http", env: "PORT", isProxied: false) + .WithReference(api) + .WithReference(oldApp) + .WithEnvironment("PUBLIC_EXCEPTIONLESS_SERVER_URL", exceptionlessServerUrl) + .WithEnvironment("PORT", appPort.ToString()) .WithEndpoint("http", e => { - e.TargetHost = "localhost"; - e.UriScheme = "http"; + // 7131 (HTTPS via Aspire dev cert) instead of Vite's default 5173 to avoid clashing with other local Vite projects. + e.Port = appPort; + e.TargetPort = appPort; + e.TargetHost = "web-ex.dev.localhost"; + e.IsProxied = false; }) + .WithHttpsDeveloperCertificate() .WithUrlForEndpoint("http", u => { - u.DisplayText = "Open Docs"; + u.DisplayText = "Open App"; SetDisplayOrder(u, 100); + u.Url = $"{u.Url.TrimEnd('/')}/next/"; }) .WithParentRelationship(api); - } + + if (worktreePorts is not null) + { + app.WithEnvironment("API_HTTP", worktreePorts.ApiHttpUrl) + .WithEnvironment("API_HTTPS", worktreePorts.ApiHttpsUrl) + .WithEnvironment("OLDAPP_HTTP", worktreePorts.OldAppHttpsUrl) + .WithEnvironment("OLDAPP_HTTPS", worktreePorts.OldAppHttpsUrl); + } + + if (includeDevTools) + { + builder.AddDenoTask("Docs", "../../docs", "serve") + .WithBrowserLogs() + .WithHttpEndpoint(port: docsPort, targetPort: docsPort, name: "http", env: "PORT", isProxied: false) + .WithEndpoint("http", e => + { + e.TargetHost = "localhost"; + e.UriScheme = "http"; + }) + .WithUrlForEndpoint("http", u => + { + u.DisplayText = "Open Docs"; + SetDisplayOrder(u, 100); + }) + .WithParentRelationship(api); + } #pragma warning restore ASPIREBROWSERLOGS001 + } } await builder.Build().RunAsync();