Background
I wanted to integrate an existing React app to ASP.NET Core web app. The goal was to be able to launch the ASP.NET Core web project and render the React app as I would have rendered it by running npm start from ClientApp folder.
Setup
To check it out, I created a fresh ASP.NET Core app using VS2022 New Project template "ASP.NET Core with React.js and Redux". The project boilerplate only supports up to .NET 5.0 so I immediately upgraded the project to .NET 7.0, and it worked as it supposed to: displaying a menu with Counter and Fetch Data, and React and Redux code all worked fine and hitting breakpoints when I launched the ASP.NET Core web app in debug mode, served by IIS Express. Running command npm start from inside the folder ClientApp (created automatically by the VS) also rendered the same frontend.
It seemed to me that running ASP.NET Core project to achieve the same outcome by running npm start is mainly done through this piece of code in Startup.cs:
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
Replace ClientApp content
Now I replaced the entire ClientApp folder content with my own React app (credit this post for the guidance). To make sure the React code works, I ran the usual commands from inside ClientApp:
npm install, npm run build, npm start
All ran without issues. When npm start ran, it rendered to https://localhost:3000/ fine, as shown below:
So, standing by itself, everything works.
Error
Now that npm start from the ClientApp folder works, supposedly, without changing anything (since we already have this in place in Startup):
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
I expected to see the same React app rendering in browser, as seen earlier when I ran npm start from inside the ClientApp folder.
But instead, I got this mysterious error:
An unhandled exception occurred while processing the request.
IOException: The response ended prematurely.
System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, bool async, CancellationToken cancellationToken)
HttpRequestException: An error occurred while sending the request.
System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, bool async, CancellationToken cancellationToken)HttpRequestException: Failed to proxy the request to http://localhost:62632/, because the request to the proxy target failed. Check that the proxy target server is running and accepting requests to http://localhost:62632/.
The underlying exception message was 'An error occurred while sending the request.'.Check the InnerException for more details.
Microsoft.AspNetCore.SpaServices.Extensions.Proxy.SpaProxy.PerformProxyRequest(HttpContext context, HttpClient httpClient, Task baseUriTask, CancellationToken applicationStoppingToken, bool proxy404s)
Why a dynamic port number?
As you can see from the error, it looks like the app tried to send request to http://localhost:[dynamic port] every time I ran the ASP.NET Core web project. Why is this and how to configure the app to not to do that? I don't see anywhere that I could set that up.
Here is my launchSettings.json:
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:28790",
"sslPort": 44360
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"MyAspCoreWtihReactApp": {
"commandName": "IIS",
"launchBrowser": true,
"applicationUrl": "https://localhost:3000;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
And this is the project meta file:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable>
<SpaRoot>ClientApp\</SpaRoot>
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
<StartupObject>MyApp.Core.React.Program</StartupObject>
<PackageProjectUrl>https://localhost:44360</PackageProjectUrl>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="5.0.17" />
</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="Build" 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\**; $(SpaRoot)build-ssr\**" />
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
<RelativePath>%(DistFiles.Identity)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</ResolvedFileToPublish>
</ItemGroup>
</Target>
</Project>
Summary
- React app itself works fine when running
npm startfrom inside theClientAppfolder - When running the ASP.NET Core web project, the SPA extension supposedly would run
npm startbut somehow it kept sending http request to a dynamic http port and errored out.
How to solve this dynamic port assigning seemed to be the key?
I also tried to do this line in Startup
spa.UseProxyToSpaDevelopmentServer("https://localhost:44360")
and it didn't work either.
Thanks in advance for your help!
