AngularCliMiddleware is not working with the new Angular CLI's build system
Is there an existing issue for this?
- [X] I have searched the existing issues
Describe the bug
Serving an Angular 17 SPA build with the new Angular CLI's build system. Angular.io guide for esbuild
UseAngularCliServer(npmScript: start) is running into a timeout.
For example, consider the following code in Startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSpa(
spa =>
{
spa.Options.SourcePath = "ClientApp";app.UseSpa(
spa =>
{
spa.Options.SourcePath = "ClientApp";
spa.UseAngularCliServer("start");
});
spa.UseAngularCliServer("start");
});
}
After a timeout the following error is thrown: "The Angular CLI process did not start listening for requests within the timeout period of 120 seconds. Check the log output for error information."
So it does not support the new Angular build system.
Expected Behavior
The ASP .NET core Backend and the Angular SPI is served.
Steps To Reproduce
Create a new "Angular and ASP.NET Core" project from Visual Studio 2022 project templates. Change the angular.json of the newly created project as following: Using the browser-esbuild builder
The following is what you would typically find in angular.json for an application:
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
Changing the builder field is the only change you will need to make.
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser-esbuild",
Exceptions (if any)
TimeoutException: The Angular CLI process did not start listening for requests within the timeout period of 120 seconds. Check the log output for error information. Microsoft.AspNetCore.SpaServices.Extensions.Util.TaskTimeoutExtensions.WithTimeout<T>(Task<T> task, TimeSpan timeoutDelay, string message) Microsoft.AspNetCore.SpaServices.Extensions.Proxy.SpaProxy.PerformProxyRequest(HttpContext context, HttpClient httpClient, Task<Uri> baseUriTask, CancellationToken applicationStoppingToken, bool proxy404s) Microsoft.AspNetCore.Builder.SpaProxyingExtensions+<>c__DisplayClass2_0+<<UseProxyToSpaDevelopmentServer>b__0>d.MoveNext() Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
.NET Version
8.0.101
Anything else?
In Microsoft.AspNetCore.SpaServices.AngularCli.AngularCliMiddleware.cs is a condition for waiting until the output of the ng serve command is matching a specific regex pattern:
openBrowserLine = await scriptRunner.StdOut.WaitForMatch(
new Regex("open your browser on (http\\S+)", RegexOptions.None, RegexMatchTimeout));
But the output has changed due to the new Angular build system.
Old build system:
New build system:
So the regex pattern does not match any more. This pattern has to be fitted!
my suggestion is to change the regex pattern to "(open your browser on (http\S+)|Local: http\S+)"
Is there any plan to fix this or to allow the use of a custom regex as suggested in this #52325 issue?
Hi, I have the same problem. I changed to the new build system application and now I receive the timeout after 120 seconds.
I tried a lot of things but I couldn't. And I didn't find updated posts talking about this problem.
Any help or anyone got a solution?
Thanks!
@PeteAtWSA did you find another solution? Please if you have any help, I appreaciate
We are printing the expected text before the serve command:
"start": "echo open your browser on https://localhost:4200/ && ng serve"
We are printing the expected text before the serve command:
"start": "echo open your browser on https://localhost:4200/ && ng serve"
Thanks again, I added to to my package.json
"start": "echo 'open your browser on https://localhost:4200/' && ng serve",
But I continue failing in the timeout. Any idea why is continue failing?
Thanks again @PeteAtWSA
Finally I solved my problem. I don't know if the best solution and my knowledge in .NET is 0. I had this code before I changed the build system:
app.UseSpa(spa =>
{
spa.Options.SourcePath = "../../MyProject";
if (environment.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
spa.Options.DefaultPageStaticFileOptions = new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
var headers = ctx.Context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue
{
NoCache = true,
NoStore = true,
MustRevalidate = true,
MaxAge = TimeSpan.Zero
};
}
};
});
And I realized that if I run my frontend and backend separately (by executing npm start from my frontend), everything was working perfectly. So I was researching how to execute my process directly from .NET without spa.UseAngularCliServer(npmScript: "start") and I did using ProcessStartInfo.
The final code looks like this:
app.UseSpa(spa =>
{
spa.Options.SourcePath = "../../MyProject";
if (environment.IsDevelopment())
{
string angularProjectPath = spa.Options.SourcePath;
ProcessStartInfo psi = new ProcessStartInfo
{
FileName = "npm",
Arguments = "start",
WorkingDirectory = angularProjectPath,
UseShellExecute = true,
};
Process process = Process.Start(psi);
spa.UseProxyToSpaDevelopmentServer("http://localhost:4200");
}
});
This code works perfectly, and I didn't need to use any other possible solutions involving modified NuGet packages.
I hope the solution helps others with the same problem!
I've found AspNetCore.SpaYarp by Bernd Hirschmann, and after only a few days of experimenting with it, it seems like a better approach than both the legacy and new SPA Proxy methods available with the old and new templates. Moving the YARP proxy to the back-end fixes CORS issues when using back-end authentication middleware, and his approach to monitoring for the start of the SPA side seems much cleaner.
my suggestion is to change the regex pattern to "(open your browser on (http\S+)|Local: http\S+)"
@javiercn @jiayac, can you try to make sure some fix or official workaround is available for this? The development-time experience for the Microsoft-provided ASP.NET Angular template is not only obsolete, but broken with Angular 17+ projects (Angular 19 due next month) that use the new standard Vite/Esbuild. This is a significant issue.
If the regex could be updated to allow for the new output format, that would be really appreciated. In the other linked issue it was mentioned that the package may be deprecated in the future, but it isn't yet, so if the fix for the regex could be put in in a minor release that would be a big help, I think we along with many others depend on this working and need it working with Angular 17, 18, and 19 and the new build system.
Pity this issue has not been addressed :(
I used @SoyDiego 's manual process approach.
For it to work with the vite/caching stuff I added the following:
app.Map("/@fs", applications =>
{
applications.UseSpa(spa =>
{
if (Environment.IsDevelopment())
{
spa.UseProxyToSpaDevelopmentServer("http://localhost:4200");
}
});
});
Terribly hacky, but will only be for dev. Prebuilt static files seems to have no issues though, except for not being dev friendly.
Update for Angular 19: You want to turn off hmr in the dev-server config due to the way CSS is loaded.
Finally I solved my problem. I don't know if the best solution and my knowledge in .NET is 0. I had this code before I changed the build system:
app.UseSpa(spa => { spa.Options.SourcePath = "../../MyProject";
if (environment.IsDevelopment()) { spa.UseAngularCliServer(npmScript: "start"); } spa.Options.DefaultPageStaticFileOptions = new StaticFileOptions { OnPrepareResponse = ctx => { var headers = ctx.Context.Response.GetTypedHeaders(); headers.CacheControl = new CacheControlHeaderValue { NoCache = true, NoStore = true, MustRevalidate = true, MaxAge = TimeSpan.Zero }; } }; });And I realized that if I run my frontend and backend separately (by executing
npm startfrom my frontend), everything was working perfectly. So I was researching how to execute my process directly from .NET withoutspa.UseAngularCliServer(npmScript: "start")and I did usingProcessStartInfo.The final code looks like this:
app.UseSpa(spa => { spa.Options.SourcePath = "../../MyProject";
if (environment.IsDevelopment()) { string angularProjectPath = spa.Options.SourcePath; ProcessStartInfo psi = new ProcessStartInfo { FileName = "npm", Arguments = "start", WorkingDirectory = angularProjectPath, UseShellExecute = true, }; Process process = Process.Start(psi); spa.UseProxyToSpaDevelopmentServer("http://localhost:4200"); } });This code works perfectly, and I didn't need to use any other possible solutions involving modified NuGet packages.
I hope the solution helps others with the same problem!
Thanks for this solution! I use it and it works like a charm.
If you want to hide the angular process terminal window you can also add WindowStyle = ProcessWindowStyle.Hidden like this:
ProcessStartInfo psi = new ProcessStartInfo
{
FileName = "npm",
Arguments = "start",
WorkingDirectory = angularProjectPath,
UseShellExecute = true,
WindowStyle = ProcessWindowStyle.Hidden // <--
};
There has been an update to Microsoft.AspNetCore.SpaServices.Extensions package and the fix to this issue not in there. Any update on this would be appreciated.
It seems necessary to specify a port for DevServerPort, set to 4200 for SpaOptions. Otherwise, this change will not work in the package.json 'start' script, which includes 'echo open your browser on https://localhost:4200/ && ng serve'
If you do not specify a port, it will assign a random port
This is still a problem.
I ran into this exact issue after updating angular CLI from 16 to 19.
Running on .net 8.0.4
Can we please get a formal resolution?
@philDaprogrammer I switched to SpaProxy, it removes the need to have any dotnet middleware involved. Http requests that are intended for the dotnet backend can be proxied and it has a better startup experience. https://learn.microsoft.com/en-us/aspnet/core/client-side/spa/intro?view=aspnetcore-7.0&preserve-view=true#developing-single-page-apps