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,27 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable>
<SpaRoot>ClientApp\</SpaRoot>
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
<UserSecretsId>EqDemo.AspNetCoreStencil.AdvancedSearch</UserSecretsId>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<SpaProxyLaunchCommand>npm start</SpaProxyLaunchCommand>
<SpaProxyServerUrl>http://localhost:4444</SpaProxyServerUrl>
Comment on lines +11 to +12
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.

🔴 Migration to SPA Proxy breaks API calls — Stencil dev server has no reverse proxy for API routes

The old UseProxyToSpaDevelopmentServer("http://localhost:4444/") kept the browser at the ASP.NET server URL (port 5001) and transparently proxied SPA asset requests to the Stencil dev server. This meant the SPA's relative API calls (/api/easyquery at ClientApp/src/components/easy-query/easy-query.tsx:52, api/data-filtering at ClientApp/src/components/filter-bar/filter-bar.tsx:54) resolved against port 5001 and reached the ASP.NET backend. The new Microsoft.AspNetCore.SpaProxy package redirects the browser to the SPA dev server URL (http://localhost:4444). Once the browser is at port 4444, those relative API calls go to the Stencil dev server instead of the ASP.NET backend. The Stencil dev server (ClientApp/stencil.config.ts:15-18) has no proxy configuration for /api routes, so API calls will fail with 404. The standard .NET 8 SPA templates solve this by configuring the SPA dev server (e.g., Vite) with a proxy for /api routes back to ASP.NET — this Stencil setup lacks that.

Prompt for agents
The Microsoft.AspNetCore.SpaProxy package redirects the browser to the SPA dev server (http://localhost:4444). Since the SPA uses relative API URLs like /api/easyquery and api/data-filtering, these calls will go to port 4444 (Stencil dev server) instead of port 5001 (ASP.NET backend).

To fix this, you need to either:
1. Configure Stencil's dev server to proxy API requests back to the ASP.NET server. This would require a custom Stencil dev server plugin or middleware to forward /api/* requests to https://localhost:5001.
2. Revert to the old UseProxyToSpaDevelopmentServer approach (using Microsoft.AspNetCore.SpaServices.Extensions) which keeps the browser at the ASP.NET URL and transparently proxies SPA requests to the dev server.
3. Configure the SPA to use absolute URLs for API calls (e.g., https://localhost:5001/api/easyquery) when in development mode.

Option 2 is the simplest if you want to maintain the existing architecture. The relevant files are Startup.cs (Configure method), the csproj (package references), and potentially stencil.config.ts (dev server proxy config).
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.

Valid point. Microsoft.AspNetCore.SpaProxy redirects the browser to the SPA dev server URL, so relative API calls (/api/easyquery, api/data-filtering) would go to port 4444 instead of the ASP.NET backend.

However, the project already configures an AllowAllPolicy CORS policy that allows any origin, so API calls from port 4444 to the ASP.NET backend will succeed if the SPA uses absolute URLs or if a proxy is configured on the Stencil dev server.

The Stencil dev server (stencil.config.ts) doesn't currently support a built-in proxy configuration like Vite or webpack-dev-server do. Options to address this in a follow-up:

  1. Add a custom dev server middleware/plugin to Stencil that proxies /api/* to the ASP.NET backend
  2. Configure the SPA to use absolute API URLs in development mode (e.g., via an environment variable)
  3. Use a separate proxy like http-proxy-middleware in a wrapper dev server script

This is a dev-time ergonomics concern — production builds served from ClientApp/www via the ASP.NET server will work correctly since both SPA and API are on the same origin.

</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Data.SqlClient" Version="2.1.7" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="6.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.1" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="6.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.1" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="6.34.0" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.11" />
<PackageReference Include="Microsoft.AspNetCore.SpaProxy" Version="8.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.11" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="6.35.0" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.6" />
<PackageReference Include="System.Drawing.Common" Version="4.7.2" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.34.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.35.0" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageReference Include="VueCliMiddleware" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<!-- DB initialization packages. They are not necessary for EasyQuery working and can be removed in production -->
Expand Down Expand Up @@ -69,4 +70,4 @@
</ResolvedFileToPublish>
</ItemGroup>
</Target>
</Project>
</Project>
31 changes: 17 additions & 14 deletions AspNetCore/Stencil/AdvancedSearch/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System.IO;
using System.Data.SqlClient;

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;

Expand Down Expand Up @@ -51,11 +53,6 @@ public void ConfigureServices(IServiceCollection services)
// .RegisterDbGate<SqLiteGate>();
// .RegisterDbGate<SqlServerGate>();

services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/www";
});

//to support non-Unicode code pages in PDF Exporter
System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
}
Expand All @@ -75,8 +72,14 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();

var spaPath = Path.Combine(env.ContentRootPath, "ClientApp/www");
if (Directory.Exists(spaPath))
{
var fileProvider = new PhysicalFileProvider(spaPath);
app.UseDefaultFiles(new DefaultFilesOptions { FileProvider = fileProvider });
app.UseStaticFiles(new StaticFileOptions { FileProvider = fileProvider });
}

app.UseCors("AllowAllPolicy");

Expand Down Expand Up @@ -108,14 +111,14 @@ 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.UseProxyToSpaDevelopmentServer("http://localhost:4444/");
var fallbackSpaPath = Path.Combine(env.ContentRootPath, "ClientApp/www");
if (Directory.Exists(fallbackSpaPath))
{
endpoints.MapFallbackToFile("index.html", new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(fallbackSpaPath)
});
}
});

Expand Down