Skip to content

Upgrade AspNetCore React AdvancedSearch sample to .NET 8#39

Open
devin-ai-integration[bot] wants to merge 3 commits into
masterfrom
devin/1777991041-net8-aspnetcore-react-advancedsearch
Open

Upgrade AspNetCore React AdvancedSearch sample to .NET 8#39
devin-ai-integration[bot] wants to merge 3 commits into
masterfrom
devin/1777991041-net8-aspnetcore-react-advancedsearch

Conversation

@devin-ai-integration
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot commented May 5, 2026

Summary

Upgrades the EqDemo.AspNetCoreReact.AdvancedSearch sample (AspNetCore/React/AdvancedSearch/) from .NET 6 to .NET 8 and migrates from the deprecated Microsoft.AspNetCore.SpaServices.Extensions package to the SpaProxy template pattern.

.csproj changes

AspNetCore/React/AdvancedSearch/EqDemo.AspNetCoreReact.AdvancedSearch.csproj

Property / Package Before (net6) After (net8)
TargetFramework net6.0 net8.0
Microsoft.EntityFrameworkCore.Sqlite 6.0.1 8.0.26
Microsoft.EntityFrameworkCore.SqlServer 6.0.1 8.0.26
Microsoft.AspNetCore.SpaServices.Extensions 6.0.1 removed
Microsoft.AspNetCore.SpaProxy 8.0.26 (added)
Microsoft.Data.SqlClient 2.1.7 5.2.3
Microsoft.IdentityModel.JsonWebTokens 6.34.0 7.7.1
System.IdentityModel.Tokens.Jwt 6.34.0 7.7.1
System.Data.SqlClient 4.8.6 removed (replaced by Microsoft.Data.SqlClient)
System.Net.Http 4.3.4 removed (BCL provides this on net8.0)
System.Text.RegularExpressions 4.3.1 removed (BCL provides this on net8.0)
System.Drawing.Common 4.7.2 removed (no source references; BCL adequate for transitive uses)

Other Korzh / EasyData package versions are intentionally untouched per the upgrade scope.

Also added the SpaProxy MSBuild properties (mirroring a fresh dotnet new react template):

<SpaRoot>ClientApp\</SpaRoot>
<SpaProxyServerUrl>http://localhost:3000</SpaProxyServerUrl>
<SpaProxyLaunchCommand>npm start</SpaProxyLaunchCommand>

SpaProxyServerUrl is set to http://localhost:3000 rather than the typical https://localhost:444xx because ClientApp/package.json's start script is react-scripts start, which serves on http://localhost:3000 by default — and ClientApp/ has no .env, setupProxy.js, or cross-env overrides to change that. Setting SpaProxyServerUrl to a non-matching URL would leave Microsoft.AspNetCore.SpaProxy polling forever for a dev server that never appears (caught by Devin Review and fixed in 0bf81ba0). Per the task scope, no changes were made under ClientApp/.

The PublishRunWebpack target's DistFiles <RelativePath> was changed from %(DistFiles.Identity) to wwwroot\%(RecursiveDir)%(FileName)%(Extension) (and <ExcludeFromSingleFile>true</ExcludeFromSingleFile> added) so prebuilt SPA assets land in wwwroot/ for the production fallback to serve via the standard static-files middleware.

Code / config changes

AspNetCore/React/AdvancedSearch/Startup.cs

  • Removed using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer; (package no longer referenced).
  • Removed services.AddSpaStaticFiles(...) call (no longer needed — production assets are served from wwwroot via UseStaticFiles()).
  • Removed app.UseSpaStaticFiles() and the app.UseSpa(spa => { ... spa.UseReactDevelopmentServer(...); }) block. In Development the SPA is now launched via Microsoft.AspNetCore.SpaProxy, driven by the SpaProxyServerUrl / SpaProxyLaunchCommand MSBuild properties.
  • Added endpoints.MapFallbackToFile("index.html") inside UseEndpoints so client-side routes fall back to the prebuilt index.html in production (matches the .NET 6/8 React template pattern).

AspNetCore/React/AdvancedSearch/Properties/launchSettings.json

  • Added "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy" to the environmentVariables of both the IIS Express and EqReactDemo profiles. Microsoft.AspNetCore.SpaProxy ships its dev-time middleware as a hosting-startup assembly, so without this env var the SpaProxyStartupFilter is never registered and npm start is never auto-launched (caught by Devin Review and fixed in 621d3be4). Mirrors the pattern already used by the sibling AspNetCore/Angular/AdvancedSearch and AspNetCore/Vue3/AdvancedSearch.Server projects.

Program.cs was unchanged — it already uses Host.CreateDefaultBuilder + ConfigureWebHostDefaults, which is fine on .NET 8.

No changes were made to anything under ClientApp/ (React TS/JS source, package.json, etc.) — the .NET upgrade does not require it.

Build verification

Local commands run on the upgrade branch (.NET SDK 8.0.420):

dotnet restore AspNetCore/React/AdvancedSearch/EqDemo.AspNetCoreReact.AdvancedSearch.csproj --ignore-failed-sources
dotnet build  AspNetCore/React/AdvancedSearch/EqDemo.AspNetCoreReact.AdvancedSearch.csproj -c Release --no-restore

Result on every commit (97b9bfc8, 0bf81ba0, 621d3be4):

Build succeeded.
    2 Warning(s)
    0 Error(s)

The two warnings (NU1801: Unable to load the service index for source https://www.myget.org/F/korzh-nuget/api/v3/index.json) are pre-existing infra noise — the secondary MyGet feed declared in nuget.config is unreachable from the build sandbox. All required packages restore from nuget.org. --ignore-failed-sources was passed only to restore to convert the MyGet error to a warning; the build itself ran with --no-restore and produced zero errors.

CI status

  • Devin Review — passed across iterations. Three concrete findings were filed and addressed in subsequent commits:
    • 0bf81ba0SpaProxyServerUrl aligned to http://localhost:3000 to match react-scripts start.
    • 621d3be4ASPNETCORE_HOSTINGSTARTUPASSEMBLIES env var added to both launchSettings.json profiles.
    • One remaining finding (CRA "proxy" field for dev-mode /api/* forwarding) was triaged and intentionally not fixed here — it requires a ClientApp/package.json edit which the task scope forbids; see the dev-mode caveat in the human checklist below.
  • security/snyk (Cognition-default) — fails. Pre-existing org-policy noise: the sibling .NET 8 upgrade PR Upgrade Vue3/AdvancedSearch.Server to .NET 8 #38 fails the same two Snyk checks. The Snyk dashboard isn't reachable from the build VM (auth required), but no high/critical CVE is known for the specific package versions chosen here (EFCore 8.0.26, Microsoft.Data.SqlClient 5.2.3, JsonWebTokens 7.7.1, SpaProxy 8.0.26).
  • license/snyk (Cognition-default) — fails. Pre-existing org-policy noise: same pattern as Upgrade Vue3/AdvancedSearch.Server to .NET 8 #38; the repo already pins commercial Korzh/EasyData packages on master, this PR does not change those.

Review & Testing Checklist for Human

Risk: yellow (single-project framework + identity package upgrade + SPA hosting model change from SpaServicesSpaProxy).

  • Run the project locally (dotnet run --project AspNetCore/React/AdvancedSearch) and confirm Microsoft.AspNetCore.SpaProxy launches npm start, the CRA dev server comes up on http://localhost:3000, and a browser opened against the ASP.NET Core URL is redirected to the SPA. Known dev-mode caveat: because ClientApp/ has no CRA "proxy" field or setupProxy.js, relative /api/easyquery/* calls from the SPA will hit the React dev server (port 3000) rather than the ASP.NET backend (port 5001) — this is a runtime regression inherent to migrating from SpaServices-style "ASP.NET hosts SPA" to SpaProxy-style "SPA dev server fronts requests". The fix is a one-line "proxy": "https://localhost:5001" addition to ClientApp/package.json, but that file is out of scope for this .NET-framework-upgrade PR per the task rules. Recommend a small follow-up PR scoped to ClientApp/ to close the gap.
  • Verify the production fallback: dotnet publish -c Release and run the published output, then hit a deep client-side route (e.g. /some/spa/path) and confirm MapFallbackToFile("index.html") returns the prebuilt wwwroot/index.html.
  • Smoke-test the EasyQuery Advanced Search UI end-to-end against the bundled SQLite Northwind (EqDemoSqLite connection string) — building queries, running them, and exporting to PDF/Excel — to confirm Microsoft.Data.SqlClient 5.2.3 + EF Core 8 + JsonWebTokens 7.x didn't introduce a runtime regression.

Notes

  • Scope was strictly limited to AspNetCore/React/AdvancedSearch/ (.csproj, Startup.cs, Properties/launchSettings.json). No other .csproj, .sln, root, or sibling-project files were touched (other Devin sessions are upgrading those in parallel).
  • No global.json / Directory.Build.props were added — Session 7 owns those.
  • The MyGet feed warning and the two Snyk failures are pre-existing infra/policy noise, not introduced by this PR (sibling PR Upgrade Vue3/AdvancedSearch.Server to .NET 8 #38 has the same Snyk pattern).

Link to Devin session: https://app.devin.ai/sessions/34f03b8bfb62472b857ab915440fbfe0
Requested by: @tobydrinkall


Devin Review

Status Commit
⚪ Not started

Run Devin Review

💡 Connect your GitHub account to enable automatic code reviews.

Open in Devin Review (Staging)
Open in Devin Review

- Bump TargetFramework from net6.0 to net8.0
- Bump Microsoft.EntityFrameworkCore.Sqlite/SqlServer 6.0.1 -> 8.0.26
- Replace Microsoft.AspNetCore.SpaServices.Extensions 6.0.1 with
  Microsoft.AspNetCore.SpaProxy 8.0.26 and add SpaRoot/SpaProxyServerUrl/
  SpaProxyLaunchCommand MSBuild properties
- Remove System.Data.SqlClient, System.Net.Http, System.Text.RegularExpressions,
  System.Drawing.Common (functionality is provided by the .NET 8 BCL)
- Bump Microsoft.Data.SqlClient 2.1.7 -> 5.2.3
- Bump Microsoft.IdentityModel.JsonWebTokens / System.IdentityModel.Tokens.Jwt
  6.34.0 -> 7.7.1
- Replace AddSpaStaticFiles/UseSpaStaticFiles/UseSpa configuration with the
  SpaProxy template pattern: serve prebuilt SPA from wwwroot via
  UseStaticFiles + MapFallbackToFile("index.html"); update PublishRunWebpack
  target to copy ClientApp/build into wwwroot at publish time

Co-Authored-By: Toby Drinkall <toby.drinkall@cognition.ai>
@devin-ai-integration
Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

devin-ai-integration[bot]

This comment was marked as resolved.

react-scripts start (npm start) launches the CRA dev server on
http://localhost:3000 by default, and ClientApp/ has no .env or
setupProxy.js to override that. Setting SpaProxyServerUrl to a
non-matching URL (https://localhost:44464) would leave SpaProxy
polling forever for a dev server that never appears.

Match the URL to CRA's actual default so the dev launch can
detect readiness without modifying ClientApp/.

Co-Authored-By: Toby Drinkall <toby.drinkall@cognition.ai>
devin-ai-integration[bot]

This comment was marked as resolved.

Microsoft.AspNetCore.SpaProxy ships its dev-time middleware as a hosting
startup assembly, so ASP.NET Core only loads it when its assembly name
appears in ASPNETCORE_HOSTINGSTARTUPASSEMBLIES. Without that env var,
the SpaProxyStartupFilter is never registered, the React dev server is
never auto-launched, and dev-mode requests fall through to
MapFallbackToFile("index.html") which 404s in Development (no wwwroot
yet).

Add the env var to both the IIS Express and EqReactDemo profiles to
match the pattern already used by the sibling Angular and Vue3
AdvancedSearch projects (see Properties/launchSettings.json in those
projects).

Co-Authored-By: Toby Drinkall <toby.drinkall@cognition.ai>
Copy link
Copy Markdown
Author

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 7 additional findings in Devin Review.

Open in Devin Review

Comment on lines +99 to +102
// SPA fallback: serve the prebuilt index.html for client-side routes in production.
// In Development the SPA is launched via Microsoft.AspNetCore.SpaProxy using the
// SpaProxyServerUrl/SpaProxyLaunchCommand MSBuild properties from the .csproj.
endpoints.MapFallbackToFile("index.html");
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Missing client-side proxy configuration breaks API calls in development mode

The migration from UseReactDevelopmentServer to Microsoft.AspNetCore.SpaProxy changes how the browser connects to the dev server. Previously, the browser connected to the ASP.NET server (localhost:5001), which internally proxied frontend requests to the React dev server — API calls using relative URLs like /api/easyquery (ClientApp/src/components/EasyQuery.js:44) stayed on the same origin and worked correctly.

With SpaProxy, the browser is redirected to the React dev server directly (localhost:3000). Relative API URLs now resolve to localhost:3000 instead of the ASP.NET backend at localhost:5001, causing all API requests to fail.

Other samples in this repo that use SpaProxy include client-side proxy config

The Vue3 example has vite.config.js with server.proxy forwarding to the backend (AspNetCore/Vue3/AdvancedSearch.Client/vite.config.js:48-53). The Angular example has proxy.conf.js forwarding /api/easyquery (AspNetCore/Angular/AdvancedSearch/ClientApp/proxy.conf.js:8-10). The React example has neither a "proxy" field in package.json nor a src/setupProxy.js file.

Prompt for agents
The migration to Microsoft.AspNetCore.SpaProxy requires adding a client-side proxy configuration so the React dev server (localhost:3000) can forward API requests to the ASP.NET backend (localhost:5001). Without this, all relative API calls (like /api/easyquery in EasyQuery.js:44 and api/SampleData/WeatherForecasts in FetchData.js:10) will 404 against the React dev server.

Fix options:
1. Add a proxy field to ClientApp/package.json: "proxy": "https://localhost:5001"
2. Or create ClientApp/src/setupProxy.js with http-proxy-middleware configuration forwarding /api/* to https://localhost:5001 (similar to what the Angular sample does in its proxy.conf.js).

Option 1 is simpler for this CRA-based app. Option 2 gives more granular control. Both are standard patterns for CRA + ASP.NET Core SpaProxy setups.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — the analysis is correct, this is a real functional gap. I'm intentionally not fixing it in this PR for two reasons:

  1. Out of scope per the explicit task rule. The task that opened this PR says: "Do NOT change anything in the React ClientApp/ JavaScript/TypeScript source unless strictly necessary to make the .NET build succeed." The .NET build succeeds without a CRA proxy; this is a runtime/dev-mode behavior change inherent to the SpaServicesSpaProxy migration the task explicitly asked for, not a build prerequisite.

  2. Already flagged in the PR description. The "Review & Testing Checklist for Human" item Migrate ASP.NET 4.x MVC AdvancedSearch sample to .NET Core - Steps 1 & 2 #1 already calls this out and recommends a follow-up PR adding "proxy": "https://localhost:5001" (or similar) to ClientApp/package.json — exactly the option you suggested. Not duplicating that change here so a human can decide whether to keep the existing dev workflow (open ASP.NET URL → static fallback) or open a small follow-up PR scoped to ClientApp/.

@tobydrinkall — happy to send a follow-up PR for just ClientApp/package.json's "proxy" field if you'd like; just let me know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant