Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,69 +1,71 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable>
<SpaRoot>ClientApp\</SpaRoot>
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
<UserSecretsId>EqDemo.AspNetCoreReact.AdvancedSearch</UserSecretsId>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="6.0.1" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="2.1.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.1" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="6.34.0" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.6" />
</ItemGroup>
<ItemGroup>
<!-- DB initialization packages. They are not necessary for EasyQuery working and can be removed in production -->
<PackageReference Include="Korzh.DbUtils.Import" Version="1.4.1" />
<PackageReference Include="Korzh.DbUtils.SqlServer" Version="1.4.1" />
<PackageReference Include="Korzh.DbUtils.Sqlite" Version="1.4.1" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="EasyData.Exporters.ClosedXML" Version="1.5.9" />
<PackageReference Include="EasyData.Exporters.PdfSharp" Version="1.5.9" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Korzh.EasyQuery.AspNetCore" Version="7.4.0" />
<PackageReference Include="Korzh.EasyQuery.EntityFrameworkCore.Relational" Version="7.4.0" />
<PackageReference Include="Korzh.EasyQuery.SqlServerGate" Version="7.4.0" />
<PackageReference Include="Korzh.EasyQuery.SqLiteGate" Version="7.4.0" />
<PackageReference Include="Korzh.EasyQuery.DataExport" Version="7.4.0" />
<PackageReference Include="System.Drawing.Common" Version="4.7.2" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.34.0" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
</ItemGroup>
<ItemGroup>
<!-- Don't publish the SPA source files, but do show them in the project files list -->
<Content Remove="$(SpaRoot)**" />
<None Remove="$(SpaRoot)**" />
<None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
</ItemGroup>
<Target Name="DebugEnsureNodeEnv" BeforeTargets="PreBuildEvent" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
<Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
</Target>
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build" />
<!-- Include the newly-built files in the publish output -->
<ItemGroup>
<DistFiles Include="$(SpaRoot)build\**" />
<ResolvedFileToPublish Include="@(DistFiles-&gt;'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
<RelativePath>%(DistFiles.Identity)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</ResolvedFileToPublish>
</ItemGroup>
</Target>
</Project>
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable>
<SpaRoot>ClientApp\</SpaRoot>
<!-- ClientApp uses Create React App; `npm start` runs `react-scripts start`,
which serves on http://localhost:3000 by default. SpaProxyServerUrl must
match that URL so Microsoft.AspNetCore.SpaProxy can detect when the dev
server is ready. -->
<SpaProxyServerUrl>http://localhost:3000</SpaProxyServerUrl>
<SpaProxyLaunchCommand>npm start</SpaProxyLaunchCommand>
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
<UserSecretsId>EqDemo.AspNetCoreReact.AdvancedSearch</UserSecretsId>
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SpaProxy" Version="8.0.26" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.26" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.26" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.7.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.7.1" />
</ItemGroup>
<ItemGroup>
<!-- DB initialization packages. They are not necessary for EasyQuery working and can be removed in production -->
<PackageReference Include="Korzh.DbUtils.Import" Version="1.4.1" />
<PackageReference Include="Korzh.DbUtils.SqlServer" Version="1.4.1" />
<PackageReference Include="Korzh.DbUtils.Sqlite" Version="1.4.1" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="EasyData.Exporters.ClosedXML" Version="1.5.9" />
<PackageReference Include="EasyData.Exporters.PdfSharp" Version="1.5.9" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Korzh.EasyQuery.AspNetCore" Version="7.4.0" />
<PackageReference Include="Korzh.EasyQuery.EntityFrameworkCore.Relational" Version="7.4.0" />
<PackageReference Include="Korzh.EasyQuery.SqlServerGate" Version="7.4.0" />
<PackageReference Include="Korzh.EasyQuery.SqLiteGate" Version="7.4.0" />
<PackageReference Include="Korzh.EasyQuery.DataExport" Version="7.4.0" />
</ItemGroup>
<ItemGroup>
<!-- Don't publish the SPA source files, but do show them in the project files list -->
<Content Remove="$(SpaRoot)**" />
<None Remove="$(SpaRoot)**" />
<None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
</ItemGroup>
<Target Name="DebugEnsureNodeEnv" BeforeTargets="PreBuildEvent" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
<Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
</Target>
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build" />
<!-- Include the newly-built files in the publish output, served as static files from wwwroot -->
<ItemGroup>
<DistFiles Include="$(SpaRoot)build\**" />
<ResolvedFileToPublish Include="@(DistFiles-&gt;'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
<RelativePath>wwwroot\%(RecursiveDir)%(FileName)%(Extension)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</ResolvedFileToPublish>
</ItemGroup>
</Target>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy"
}
},
"EqReactDemo": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy"
}
}
}
Expand Down
24 changes: 6 additions & 18 deletions AspNetCore/React/AdvancedSearch/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -43,12 +42,6 @@ public void ConfigureServices(IServiceCollection services)

services.AddControllersWithViews();

// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});

services.AddEasyQuery()
.UseSqlManager()
.AddDefaultExporters()
Expand Down Expand Up @@ -78,10 +71,9 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseCors("AllowAllPolicy");

app.UseHttpsRedirection();
// In production, prebuilt SPA assets are copied into wwwroot by the
// PublishRunWebpack target and served via the default static-files middleware.
app.UseStaticFiles();
if (!env.IsDevelopment()) {
app.UseSpaStaticFiles();
}

app.UseRouting();

Expand All @@ -103,15 +95,11 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});

app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";

if (env.IsDevelopment()) {
spa.UseReactDevelopmentServer(npmScript: "start");
}
// 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");
Comment on lines +99 to +102
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.

});

//Init demo database (if necessary)
Expand Down