Upgrade AspNetCore React AdvancedSearch sample to .NET 8#39
Upgrade AspNetCore React AdvancedSearch sample to .NET 8#39devin-ai-integration[bot] wants to merge 3 commits into
Conversation
- 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 EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
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>
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>
| // 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"); |
There was a problem hiding this comment.
🔴 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Acknowledged — the analysis is correct, this is a real functional gap. I'm intentionally not fixing it in this PR for two reasons:
-
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 theSpaServices→SpaProxymigration the task explicitly asked for, not a build prerequisite. -
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) toClientApp/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 toClientApp/.
@tobydrinkall — happy to send a follow-up PR for just ClientApp/package.json's "proxy" field if you'd like; just let me know.
Summary
Upgrades the EqDemo.AspNetCoreReact.AdvancedSearch sample (
AspNetCore/React/AdvancedSearch/) from .NET 6 to .NET 8 and migrates from the deprecatedMicrosoft.AspNetCore.SpaServices.Extensionspackage to the SpaProxy template pattern..csprojchangesAspNetCore/React/AdvancedSearch/EqDemo.AspNetCoreReact.AdvancedSearch.csprojTargetFrameworknet6.0net8.0Microsoft.EntityFrameworkCore.Sqlite6.0.18.0.26Microsoft.EntityFrameworkCore.SqlServer6.0.18.0.26Microsoft.AspNetCore.SpaServices.Extensions6.0.1Microsoft.AspNetCore.SpaProxy8.0.26(added)Microsoft.Data.SqlClient2.1.75.2.3Microsoft.IdentityModel.JsonWebTokens6.34.07.7.1System.IdentityModel.Tokens.Jwt6.34.07.7.1System.Data.SqlClient4.8.6Microsoft.Data.SqlClient)System.Net.Http4.3.4System.Text.RegularExpressions4.3.1System.Drawing.Common4.7.2Other Korzh / EasyData package versions are intentionally untouched per the upgrade scope.
Also added the SpaProxy MSBuild properties (mirroring a fresh
dotnet new reacttemplate):SpaProxyServerUrlis set tohttp://localhost:3000rather than the typicalhttps://localhost:444xxbecauseClientApp/package.json'sstartscript isreact-scripts start, which serves onhttp://localhost:3000by default — andClientApp/has no.env,setupProxy.js, orcross-envoverrides to change that. SettingSpaProxyServerUrlto a non-matching URL would leaveMicrosoft.AspNetCore.SpaProxypolling forever for a dev server that never appears (caught by Devin Review and fixed in0bf81ba0). Per the task scope, no changes were made underClientApp/.The
PublishRunWebpacktarget'sDistFiles<RelativePath>was changed from%(DistFiles.Identity)towwwroot\%(RecursiveDir)%(FileName)%(Extension)(and<ExcludeFromSingleFile>true</ExcludeFromSingleFile>added) so prebuilt SPA assets land inwwwroot/for the production fallback to serve via the standard static-files middleware.Code / config changes
AspNetCore/React/AdvancedSearch/Startup.csusing Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer;(package no longer referenced).services.AddSpaStaticFiles(...)call (no longer needed — production assets are served fromwwwrootviaUseStaticFiles()).app.UseSpaStaticFiles()and theapp.UseSpa(spa => { ... spa.UseReactDevelopmentServer(...); })block. In Development the SPA is now launched viaMicrosoft.AspNetCore.SpaProxy, driven by theSpaProxyServerUrl/SpaProxyLaunchCommandMSBuild properties.endpoints.MapFallbackToFile("index.html")insideUseEndpointsso client-side routes fall back to the prebuiltindex.htmlin production (matches the .NET 6/8 React template pattern).AspNetCore/React/AdvancedSearch/Properties/launchSettings.json"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy"to theenvironmentVariablesof both theIIS ExpressandEqReactDemoprofiles.Microsoft.AspNetCore.SpaProxyships its dev-time middleware as a hosting-startup assembly, so without this env var theSpaProxyStartupFilteris never registered andnpm startis never auto-launched (caught by Devin Review and fixed in621d3be4). Mirrors the pattern already used by the siblingAspNetCore/Angular/AdvancedSearchandAspNetCore/Vue3/AdvancedSearch.Serverprojects.Program.cswas unchanged — it already usesHost.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):Result on every commit (
97b9bfc8,0bf81ba0,621d3be4):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 innuget.configis unreachable from the build sandbox. All required packages restore fromnuget.org.--ignore-failed-sourceswas passed only torestoreto convert the MyGet error to a warning; the build itself ran with--no-restoreand produced zero errors.CI status
Devin Review— passed across iterations. Three concrete findings were filed and addressed in subsequent commits:0bf81ba0—SpaProxyServerUrlaligned tohttp://localhost:3000to matchreact-scripts start.621d3be4—ASPNETCORE_HOSTINGSTARTUPASSEMBLIESenv var added to bothlaunchSettings.jsonprofiles."proxy"field for dev-mode/api/*forwarding) was triaged and intentionally not fixed here — it requires aClientApp/package.jsonedit 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 onmaster, this PR does not change those.Review & Testing Checklist for Human
Risk: yellow (single-project framework + identity package upgrade + SPA hosting model change from
SpaServices→SpaProxy).dotnet run --project AspNetCore/React/AdvancedSearch) and confirmMicrosoft.AspNetCore.SpaProxylaunchesnpm start, the CRA dev server comes up onhttp://localhost:3000, and a browser opened against the ASP.NET Core URL is redirected to the SPA. Known dev-mode caveat: becauseClientApp/has no CRA"proxy"field orsetupProxy.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 fromSpaServices-style "ASP.NET hosts SPA" toSpaProxy-style "SPA dev server fronts requests". The fix is a one-line"proxy": "https://localhost:5001"addition toClientApp/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 toClientApp/to close the gap.dotnet publish -c Releaseand run the published output, then hit a deep client-side route (e.g./some/spa/path) and confirmMapFallbackToFile("index.html")returns the prebuiltwwwroot/index.html.EqDemoSqLiteconnection string) — building queries, running them, and exporting to PDF/Excel — to confirmMicrosoft.Data.SqlClient5.2.3 + EF Core 8 +JsonWebTokens7.x didn't introduce a runtime regression.Notes
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).global.json/Directory.Build.propswere added — Session 7 owns those.Link to Devin session: https://app.devin.ai/sessions/34f03b8bfb62472b857ab915440fbfe0
Requested by: @tobydrinkall
Devin Review