Aspnetcore: The SPA default page middleware could not return the default page '/index.html' in production application

Created on 25 Jul 2018  路  71Comments  路  Source: dotnet/aspnetcore

I have an ASP.Net Core / Angular 5 application that is logging errors to the Windows Event Log in our production environment. The following error is getting logged frequently but intermittently in production, and I have no idea why. The application is definitely in production mode, has been published, and is functioning.

System.InvalidOperationException: The SPA default page middleware could not return the default page '/index.html' because it was not found, and no other middleware handled the request. Your application is running in Production mode, so make sure it has been published, or that you have built your SPA manually. Alternatively you may wish to switch to the Development environment.

AFAIK no users are experiencing a problem with the site when these errors occur. Any idea why these errors are being thrown, and whether there is in fact a problem that needs to be addressed?

affected-most area-mvc bug feature-spa severity-minor

Most helpful comment

So the reason we're getting this is someone is trying to POST AND OPTIONS to /index.html not GET. Which causes this error message and causes a 500 error.

Of course of Azure/IIS sees too many errors in too short a time it tries and recycles the app. the problem is that it's doing it over and over again which causes everything in that App Service Plan to crash. :<

How do we properly handle this without the site throwing a 500 error?

Note that this only happens in production, not development and as far as I can tell there is no way to handle this error with your own route. This is a major DOS attack vector in spa services that needs to be fixed ASAP.

All 71 comments

Do angular output directory and specified path in services.AddSpaStaticFiles(configuration => configuration.RootPath = $"your/output/path"); match? Files in the specified folder must be computed to publish in your .csproj file like this:

  <Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
    <!-- Include the newly-built files in the publish output -->
    <ItemGroup>
      <DistFiles Include="your\output\path\**" />
      <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
        <RelativePath>%(DistFiles.Identity)</RelativePath>
        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
      </ResolvedFileToPublish>
    </ItemGroup>
  </Target>

I am setting the RootPath to the Angular dist folder:

```c#
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});


And here is what's in my .csproj file:

```xml
<PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
    <IsPackable>false</IsPackable>
    <SpaRoot>ClientApp\</SpaRoot>
    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
    <!-- Set this to true if you enable server-side prerendering -->
    <BuildServerSideRenderer>false</BuildServerSideRenderer>
</PropertyGroup>

<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 -- --prod" />
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm run build:ssr -- --prod" Condition=" '$(BuildServerSideRenderer)' == 'true' " />

    <!-- Include the newly-built files in the publish output -->
    <ItemGroup>
      <DistFiles Include="$(SpaRoot)dist\**; $(SpaRoot)dist-server\**" />
      <DistFiles Include="$(SpaRoot)node_modules\**" Condition="'$(BuildServerSideRenderer)' == 'true'" />
      <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
        <RelativePath>%(DistFiles.Identity)</RelativePath>
        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
      </ResolvedFileToPublish>
    </ItemGroup>
  </Target>

I'm wondering if my problem is that I still have the SSR build instructions in the csproj even though SSR is not enabled, and dist-server does not exist..?

SSR isn't the problem. You should set path in ConfigureServices() method via services.AddSpaStaticFiles(configuration => configuration.RootPath = $"ClientApp/dist");. Then in Configure you shouldn't specify source path:

app.UseSpaStaticFiles();
app.UseSpa(configuration => { /*spa.Options.SourcePath = "ClientApp";*/ });

spa.Options.SourcePath is only used in development build judging from its' description.

Sorry - not sure if you saw my edit, but I was in fact setting the RootPath correctly, I was just looking in the wrong place. :) I will try removing the SourcePath setting, however, I'm fairly certain this configuration came with the dotnet new angular template. What is the SourcePath setting used for and why shouldn't it be set?

This is used to determine the folder where package.json scripts should be executed.
Like new AngularCliBuilder(npmScript: "build:ssr") or spa.UseAngularCliServer(npmScript: "start");.

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

    spa.UseSpaPrerendering(options =>
    {
        options.BootModulePath = $"{spa.Options.SourcePath}/dist-server/main.bundle.js";
        options.BootModuleBuilder = env.IsDevelopment()
            ? new AngularCliBuilder(npmScript: "build:ssr")
            : null;
        options.ExcludeUrls = new[] { "/sockjs-node" };
    });

    if (env.IsDevelopment())
    {
        spa.UseAngularCliServer(npmScript: "start");
    }
});

Thanks - I am using a proxy:

c# spa.UseProxyToSpaDevelopmentServer("https://localhost:4200");
So sounds like I probably don't need the SourcePath setting. That said - are you saying the SourcePath setting is the cause of the errors in my production environment?

No, SourcePath does nothing if you use proxy during development. Application probably can't find index.html because it wasn't specified like this: services.AddSpaStaticFiles(configuration => configuration.RootPath = $"ClientApp/dist");

I think maybe you aren't seeing the edits I made to my original response. I am setting the RootPath as suggested. (Maybe try refreshing the this Github page to ensure you are seeing the latest content).

Now I see it. Can you show what's inside of UseSpa()?

Not much... Will of course remove the SourcePath setting since it's not needed, but this is what is currently in production:

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

if (env.IsDevelopment())
{
spa.UseProxyToSpaDevelopmentServer("https://localhost:4200");
}
});
```

Is index.html present after build in ClientApp/dist? I'd try to remove node_modules and install again, remove SourcePath and redeploy.

Index.html is present in clientapp/dist, otherwise the application wouldn't function at all - that is what is confusing about the error message. The error is being logged in a production application that seems to working fine. I will try removing the node_modules folder locally, and then rebuild and republish and see if that makes a difference. What makes you think that will resolve the error?

Removing the node_modules folder and republishing did not solve the issue - still seeing quite a few of these errors logged on the production server. Any idea why that would be?

I'm seeing the same issue as @danobri on a production site that appears to be working fine otherwise. The exceptions are intermittent, but persistent. I'm using the Angular spa template pretty much straight away with SSR enabled.

It's an IIS Default Document issue. Just add the following to your web.config:

<system.webServer>
   ...
      <defaultDocument enabled="false" />
</system.webServer>

I have this same issue in production - using SPA for react.
Everything about the site works fine - except for getting this exception.
Got the exception near initial deployment....then received about 100 of the exceptions within a couple of minutes around 8 hour later (was it an app pool reset?). I cannot make it happen by resetting the pool, etc. I did not experience the error on 2.1.2, but started getting it with 2.1.3 (however it is intermittent - so may not be determining factor)

So, it was our vulnerability scanner that caused the exceptions to be generated. This means I can now reproduce it by scanning the IP this core app is installed on. I tried the defaultDocument enabled=false in web.config, with no success. (running scanner gave errors again)

Any ideas @SteveSandersonMS ?

I'm also getting this error randomly in a couple of production sites that work just fine.

ASP.NET Core 2.1, using React 16 in the front end.

Below are the relevant parts of my Startup class. I get these notifications at off hours so it might be some scanner. Nothing critical but I'd love to solve it. Any help appreciated. Thanks!

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


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

            if (env.IsDevelopment())
            {
                spa.UseReactDevelopmentServer(npmScript: "start");
            }
        });

Here is our case for past 2 days. It is really random. We have 3 servers and one of them barely has this issue in last 48 hours but it did have a lot before that.

image

This happened to my Azure App Service. All I had to do was to restart the service. Worked like charm.

check angular.json file use "outputPath": "dist",

And Startup file

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

My case is a reactjs app. Also, restarting the service does not help. The service is not down. It is a error caused by requests from vulnerability scanners (or from systems trying to exploit the vulnerability)

I can't even get a dotnet new react to start in production (building with dotnet publish -c Release) without it throwing this exception:
System.InvalidOperationException: The SPA default page middleware could not return the default page '/index.html' because it was not found, and no other middleware handled the request...

Seems like there is definitely a bug in the building process of the spa somewhere?

@SteveSandersonMS
@Eilon

@ryanbrandenburg / @mkArtakMSFT - any ideas?

So the reason we're getting this is someone is trying to POST AND OPTIONS to /index.html not GET. Which causes this error message and causes a 500 error.

Of course of Azure/IIS sees too many errors in too short a time it tries and recycles the app. the problem is that it's doing it over and over again which causes everything in that App Service Plan to crash. :<

How do we properly handle this without the site throwing a 500 error?

Note that this only happens in production, not development and as far as I can tell there is no way to handle this error with your own route. This is a major DOS attack vector in spa services that needs to be fixed ASAP.

Would be nice if an error wasn't the default behavior. I was able to prevent the exceptions using a rewrite rule in my web.config.

<rewrite>
  <rules>
    <rule name="Prevent requests to index">
      <match url="^index\.html$" />
      <action type="Redirect" url="/" redirectType="Permanent" appendQueryString="true" />
    </rule>
  </rules>
</rewrite>

I also had this issue what I did is use the rewrite middleware by doing the below.

app.UseRewriter(new RewriteOptions()
                .AddRedirect("index.html", "/"));

I would really like to see the root of this problem solved. We have a .net core 2.1 app with the latest React version, no web config since we are using json files for environment variables. We are randomly getting this error several times at once. Most of the time it's fine and then randomly it will happen a dozen times. The redirect seems to just be a work around and not solve the root issue.

Anyone has got a solution for this?
I'm facing the same issue, can't get the app deployed successfully..

Seems like these javascript services are unsupported?
@SteveSandersonMS can you advise what course of action to take?

I had 1000's of these errors within the first hour after we deployed to Azure App Service. By removing this line from MyProject.csproj the errors stopped and everything kept working.

<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="2.1.1" />

@jraadt If you removed the SpaServices extensions, how are you doing the SPA setup in Startup.cs? Using the default template, your app should no longer build after removing that package reference.

@joshberry I agree it makes no sense. As a sanity test I created a new project and did the following steps:

  1. dotnet new angular
  2. dotnet restore
  3. dotnet run
  4. I opened a web browser to verify the default ASP.NET Core/Angular app is running fine.
  5. Delete <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="2.1.1" /> from the csproj file.
  6. dotnet restore
  7. dotnet run
  8. Everything still built fine and I refreshed my web browser and verified the app still runs just fine. Incremented counter and everything.

I wonder if it's included in the Microsoft.AspNetCore.App package. I'd love some clarification from the Microsoft team on this.

Edit: I'm running 2.1.500.

Microsoft.AspNetCore.SpaServices.Extensions was indeed in Microsoft.AspNetCore.App for 2.1, so that's why the explicit reference wasn't needed:

But I don't think it was in there in the 2.0.x world because it came out after 2.0.0.

How does any of that explain the errors stopping? Any idea?

This error just occurred again for me so maybe it was strange coincidence that when I deployed that code it the errors stopped for awhile. This time I went through the log files to figure out what users were causing the errors. I discovered it had something to do with the combination of SignalR, App Services, and these particular users had an older version of the web app cached. As soon as I had them clear cache, app data, etc. they no longer produce this error. I hope this helps others.

I feel this error is not about finding ever user that sends OPTIONS to your endpoint and having them stop. That this issue has not been addressed is crazy. We get it every week due to vulnerability scanners.

Hi guys, I just started using Azure App Service and my deployment is throwing the same error. Unlike others who can still access the app mine crashes completely. I noticed from the console that visual studio deploy place the Spa files 3 folders deep, I think this might be the issue, I'm not sure how to make the files only be put within the dist folder.
image

Is there anyone else who has the same folder structure withing their deployment?

Hi folks,

I too have this issue with ASP.NET Core, running on .NET Core targeting platform with Angular 7. The application works fine but my log files are plagued with this exception.

image

Hosted server is
Distributor ID: Ubuntu
Description: Ubuntu 18.04.1 LTS
Release: 18.04
Codename: bionic

This is a picture of the directory structure, ClientApp/dist/index.html exists
image

Middleware is configured, out of the box with ASP.NET Core + Angular template, like so
app.UseSpa(spa => { spa.Options.SourcePath = "ClientApp"; });

angular.json has the output path correctly set to dist

"options": {
  "outputPath": "dist",
  "index": "src/index.html",
  "main": "src/main.ts",
  "tsConfig": "src/tsconfig.app.json",
  "progress": true,
  "polyfills": "src/polyfills.ts",
  "assets": [
    "src/assets"
  ],
  "styles": [
    "node_modules/bootstrap/dist/css/bootstrap.min.css",
    "node_modules/ngx-toastr/toastr.css",
    "src/styles.css"
  ],
  "scripts": []
},

So the reason we're getting this is someone is trying to POST AND OPTIONS to /index.html not GET. Which causes this error message and causes a 500 error.

Of course of Azure/IIS sees too many errors in too short a time it tries and recycles the app. the problem is that it's doing it over and over again which causes everything in that App Service Plan to crash. :<

How do we properly handle this without the site throwing a 500 error?

Note that this only happens in production, not development and as far as I can tell there is no way to handle this error with your own route. This is a major DOS attack vector in spa services that needs to be fixed ASAP.

@JohnGalt1717 Have you found any workaround for this?

@tatvapci27 I wrote middleware and caught to error explicitly and return 400.

It has to be registered in the right order. I can't remember which at the moment so you'll need to experiment before and after the spa registration.

@JohnGait1717

Middleware is first in first out so you could have it right before the Spa middleware.

Could you possibly share the code of this middleware component you have added? Sounds very straight forward, but would be good to put it straight into my app when I'm finished work - otherwise I could create a production ready piece of middleware and share for a temporary workaround.

I stand corrected.

The middleware didn't work.

I added an exception handler that looks like this:

public class ExceptionHandlerMiddleware
    {
        private readonly ILogger Logger;
        public ExceptionHandlerMiddleware(ILogger logger) {
            Logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        public async Task Invoke(HttpContext context) {
            context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;

            var ex = context.Features.Get<IExceptionHandlerFeature>()?.Error;
            if (ex == null)
                return;

            Logger.LogError(new EventId(500, "InternalServerError"), ex, ex.Message);

            context.Response.ContentType = "text/html";
            context.Response.StatusCode = (int) HttpStatusCode.BadRequest;
            using (var writter = new StreamWriter(context.Response.Body)) {
                await writter.WriteLineAsync("<html>");
                await writter.WriteLineAsync("<body>");
                await writter.WriteLineAsync("<p>Invalid request.</p>");
                #if(DEBUG)
                await writter.WriteLineAsync($"<p>{ex.InnerException?.Message ?? ex.Message}</p>");
                #endif
                await writter.WriteLineAsync("</body>");
                await writter.WriteLineAsync("</html>");
                await writter.FlushAsync();
            }
        }
    }

and added it to the Configure function in the Startup.cs but only if not in development mode like this:

                app.UseExceptionHandler (new ExceptionHandlerOptions {
                    ExceptionHandler = new ExceptionHandlerMiddleware (loggerFactory.CreateLogger ("SystemErrors")).Invoke
                });

This effectively catches them and returns an HTML page with a 400 error in those cases. Hope that helps someone!

Thanks for that, I'll have a look at middleware later today which just redirects to index.html with GET

Guys,
I found this discussion because my asp.net core app + ng7 (with /api hosted) returned always index.html on /api calls.

Solution was to call UseMvc and UseSpa (inside Configure method) in correct order.
Correct order:
1) UseMvc
2) UseSpa
Please check your order call. I hope, this help somebody...

ON THE END OF Startup.cs::Configure()

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

        app.UseMvcWithDefaultRoute();
        app.UseSpa(spa =>
        {
            spa.Options.SourcePath = "wwwroot"; // see https://go.microsoft.com/fwlink/?linkid=864501
        });

Having checked my code for the above order, I can confirm my app is using the order of

  1. UseMvc
  2. UseSpa

This remains to be an issue and plagues my log files rendering them almost useless when it comes to parsing for actual errors.

I have the same issue, but what I can see is that the index.html that the server returns is from wwwroot/ not from ClientApp/build (my distribution directory) therefore problem lies with wrong index.html being served. Can anybody tell me how can this be fixed?

@Aj1402 I actually just fixed my issue. My issue was that the default .net core project sets up the paths with ClientApp but my pipeline was publishing using the string /site/wwwroot/clientapp. In my case I was using a linux app service so the difference of ClientApp vs. clientapp was failing given the case sensitivity. The error message did not really help me find that error and so your error might be different but i thought I would share this for any future person.

So I just ran into this issue. Turns out that the problem is that asp.net core is weird in how it builds its stuff.

There are a bunch of DLLs that look like your production build in ProjectName/bin/Release/netcoreappX.X/, however, there are also a bunch of the same DLLs plus more stuff like your build client and drivers in the folder below is called publish. So if you run the one above, it can't find your ClientApp folder. If you run the one in the publish folder, it finds it since the publish command moves it there.

TLDR: Run dotnet ProjectName/bin/Release/netcoreappX.X/publish/ProjectName.dll since it has the built ClientApp files right next to it.

Hope this helps somebody.

@cole21771 This helped me figure it out. I was the calling executable on the command line using its absolute path in the publish directory (let's say /home/me/project/bin/publish/myapp). This would give me the The SPA default page middleware could not return the default page '/index.html' exception. I got it to work by first changing the working directory to the publish folder and running it from there, so cd /home/me/project/bin/publish and ./myapp.

Hi,

All of the solutions and problems I have seen so far are due application setup changes, Unfortunately my problem is occurring in production, where 1000 users each day visit and use the webshop, only a few requests yesterday ended up with this error. I am really curious why this problem occurs.

I am running Angular in the front-end and ASP.NET Core v2 in the back-end.

  1. One issue occurred after doing a GET request: /api/account/session This is a very commonly used request to check if the user is logged in or not. Usually it just returns 200 or 401 but now it returned 400. Which, for that api request, was redirected to /Error.

image

I think the user did not see this because it was just due an API request. However it did also occur during a user coming back after payment (see issue 2 below)

  1. Second one is a bit more serious, because from the logs I can see that the user tried to come back from a payment operation (externally). Usually the url for that would be /payment/complete?payload=... but in this situation it seem to have redirected to /index.html?payload=... something I am 100% sure about that I never redirect users to index.html at all.

image

Other associated logs:

image

The trace mentioning method: proceedWithPayment() - request is redirect means that the user is redirected to an external site, completes the payment, and after a minute comes back.

Fortunately I can see that later logs point out that the user has probably not seen any issue and has visited the payment completion page (with the same payload query parameter). I am just very very curious why this issue occurs and how to avoid or prevent them.

It is clear to me that when someone would browse /Error, this error occurs, I can not reproduce the issue (exception and logs) when I redirect to /Error, it only shows me the content in HTML that an error occurred and I should switch to development mode to see the error.

Here is the stacktrace if that is of any use for the second issue.

System.InvalidOperationException:
   at Microsoft.AspNetCore.SpaServices.SpaDefaultPageMiddleware+<>c__DisplayClass0_0.<Attach>b__1 (Microsoft.AspNetCore.SpaServices.Extensions, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)
   at Microsoft.AspNetCore.Builder.UseExtensions+<>c__DisplayClass0_1.<Use>b__1 (Microsoft.AspNetCore.Http.Abstractions, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)
   at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware+<Invoke>d__7.MoveNext (Microsoft.AspNetCore.StaticFiles, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at Microsoft.AspNetCore.Builder.RouterMiddleware+<Invoke>d__4.MoveNext (Microsoft.AspNetCore.Routing, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware+<Invoke>d__8.MoveNext (Microsoft.AspNetCore.Cors, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware+<Invoke>d__8.MoveNext (Microsoft.AspNetCore.Cors, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware+<Invoke>d__8.MoveNext (Microsoft.AspNetCore.Cors, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware+<Invoke>d__8.MoveNext (Microsoft.AspNetCore.Cors, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware+<Invoke>d__8.MoveNext (Microsoft.AspNetCore.Cors, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware+<Invoke>d__8.MoveNext (Microsoft.AspNetCore.Cors, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware+<Invoke>d__6.MoveNext (Microsoft.AspNetCore.Authentication, Version=2.1.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware+<Invoke>d__7.MoveNext (Microsoft.AspNetCore.StaticFiles, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware+<Invoke>d__7.MoveNext (Microsoft.AspNetCore.StaticFiles, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult (System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware+<Invoke>d__6.MoveNext (Microsoft.AspNetCore.Diagnostics, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)

I ran into the sam issue when user copy content from IE browser and paste to Excel. IIS logs many OPTIONS request 500 error for /assets/... under ClientApp/dist/ folder .
After trying many suggestions about this issue.
It works for me, to config IIS's URL Rewrite rule to response 200 OK when OPTIONS or POST request for /myapp/ or /myapp/assets/...

The URL Rewrite rule is :

<system.webServer>
    <rewrite>
      <rules>
        <rule name="avoid OPTIONS request error when paste from IE to excel" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_METHOD}" pattern="OPTIONS|POST" />
            <add input="{PATH_INFO}" pattern="^(/myapp)?/assets/|^/myapp/$" />
          </conditions>
          <action type="CustomResponse" statusCode="200" statusReason="OK" statusDescription="OK" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>

Any update on this? None of the above mentioned solutions fix the problem:

spa.Options.SourcePath

Since spa.Options.SourcePath is used in Development, as per my understanding its completely ignored in Production.

So the reason we're getting this is someone is trying to POST AND OPTIONS to /index.html not GET. Which causes this error message and causes a 500 error.

Of course of Azure/IIS sees too many errors in too short a time it tries and recycles the app. the problem is that it's doing it over and over again which causes everything in that App Service Plan to crash. :<

How do we properly handle this without the site throwing a 500 error?

Note that this only happens in production, not development and as far as I can tell there is no way to handle this error with your own route. This is a major DOS attack vector in spa services that needs to be fixed ASAP.

I can confirm that its not any bot or someone sending requests to Index.html. I have CloudFlare on my website, I setup a Page Rule to redirect all requests to Index.html to https://www.mywebsite.com/. But still seeing the 404 errors for Index.html in my log.

Would be nice if an error wasn't the default behavior. I was able to prevent the exceptions using a rewrite rule in my web.config.

<rewrite>
  <rules>
    <rule name="Prevent requests to index">
      <match url="^index\.html$" />
      <action type="Redirect" url="/" redirectType="Permanent" appendQueryString="true" />
    </rule>
  </rules>
</rewrite>

Even setting Redirect Rule in web.config doesn't work.

Solution was to call UseMvc and UseSpa (inside Configure method) in correct order.

Using wrong order can produce similar error but the issue discussed here is different, and it occurs even with correct order. In case of wrong order,it will always return Index.html. But the issue being discussed here is about the random 404 errors for index.html in log, despite the website seems to be working fine.

I was beating my head against the wall on this and dropping a few cuss words when it finally hit me. I had my configuration defined as configuration.RootPath = "ClientApp/dist";. However, in my build process (within docker running on linux), I noticed that the final build output in the container was under the folder clientapp. Note the case sensitivity changes, which matters for Linux of course. Changing my config in Startup.cs to configuration.RootPath = "clientapp/dist"; fixed it for me. Note that I couldn't run my app at all, which seems slightly different from what others in this thread are experiencing.

I'm also having this same exact issue. Best fix is to manually copy the ClientApp/dist to the wwwroot directory.

Note: This is a hack until we finally get some answers (been over a year!!!!!!!!!)

Does anyone have a msbuild copy task (that runs AfterTargets="PublishRunWebpack") that would do this during the build? For whatever reason, I'm unable to get the msbuild copy task to work. Tried both $(TargetDir) and $(OutputPath). Is there something special with the wwwroot directory that we can't recursively copy all directories and files in the ClientApp/dist to the wwwroot?

Update: RE: msbuild copy task... Apparently the msbuild Copy task needs attributes/properties OverwriteReadOnlyFiles="true" Retries="3" RetryDelayMilliseconds="300" to work. I also increased ms delay to 500 as occasionally the 300 wouldn't work. By 'occasionally' not work with 300 ms, it is on the rare side so be careful for "why isn't the build working this time, it worked last time" moments.

Does anyone have a concrete list of repro steps for this? Earlier in the thread it seemed that people were running into this when they just did dotnet new react; dotnet publish -c Release; .\bin\Release\netcoreapp3.0\publish\*.exe, but when I tried that locally it worked fine. There seems to be a common theme with some people solving it by setting the correct ContentRootPath, but it's hard for me to tell if that's the root problem here since I'm not able to reproduce.

So if I can get either 1. A GitHub repo that I can clone then run 1 or 2 dotnet commands to replicate the behavior or 2. A short list of steps to take an app from dotnet new to demonstrating this behavior that would be greatly helpful in diagnosing this. If possible I would prefer number 1.

@ryanbrandenburg The problem is it doesn't show up every time, which is why we don't know what sets it off.

@ryanbrandenburg in my case the issue occurs with Angular app with SSR enabled and deployed on live server. In my case the website is accessible and there aren't any 404/500 errors even when search engine crawls, at least Google Search console isn't reporting any 404 (we have several hundred thousand pages in Google index).

But when I enable ApplicationInsights, I see many 404 error for index.html, I believe these are for SSR.

This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 4 days. It will be closed if no further activity occurs within 3 days of this comment. If it is closed, feel free to comment when you are able to provide the additional information and we will re-investigate.

See our Issue Management Policies for more information.

I can confirm that errors is related to POST and OPTIONS call to index.html.
I really don't know if with new 3.1 version issue still present.

Sentry is really happy about it :)
image

I can confirm that the issue still presents in 3.1 and as @meriturva mentioned it's very easy to reproduce:
Send OPTIONS or POST request to index.html using postman

Yes,same issue for my spa project with vuejs in 3.1.
When post a request with invalid URL to server, then you will get that issue.
_"System.InvalidOperationException: The SPA default page middleware could not return the default page '/index.html' because it was not found, and no other middleware handled the request."_
but when get with invalid URL, it works normally.

I second futureinfo that it happens on vuejs too, for POST requests at least.
This is really intermittent and can't be reproduced locally (or at least I couldn't).
Again, this is happening only in production, with users having no problem whatsoever.

I can confirm that this still happens in 3.1 with the default settings.

To fix this, I simply build my Angular project (I'm on Angular) in aot mode, then copy the contents of ClientAppdist\ClientApp to the target's ClientAppdist folder.

Lame, but that's where we are.

It looks like anything that is not a GET will cause that error, regardless of whether the default spa page exists or not. In my case, a misspelled POST to a controller didn't match the controller route and fell through to this middleware.

The error message can be misleading and hide the root cause. Maybe it should include the original path and method.

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

It's a shame this issue hasn't been addressed after more than 2 years. My guess is that most .NET developers building SPA applications are either deploying their client assets separately and bypassing the use of SpaDefaultPageMiddleware altogether or living with these exceptions in their production environment.

In my case, our PaSS solution (Cloud Foundry) hits our containers with a specific OPTIONS request so this shows up in our logs even though the app is working fine.

My current workaround (see below) is to write a simple middleware before the call to UseSpa to look for that specific request and return a 404. As others have pointed out, however, you can easily DoS a server simply by making erroneous HTTP requests.

@SteveSandersonMS and @javiercn you were both helpful in previous SPA related issues I've logged. Any chance you can take a quick look at this one and provide better guidance?

```c#
...
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});

        // HACK: Cloud Foundry's Spring Boot actuators makes an OPTIONS call to /cloudfoundryapplication
        //       https://docs.run.pivotal.io/console/using-actuators.html#troubleshoot
        //       which raises an error in .NET Core's SpaDefaultPageMiddleware. This middleware
        //       ensures a 404 is returned to minimize the noise in the logs.
        //       See https://github.com/dotnet/aspnetcore/issues/5223 for more details.
        app.Use(next => async context =>
        {
            if (context.Request.Method == HttpMethods.Options &&
                context.Request.Path == "/cloudfoundryapplication")
            {
                context.Response.StatusCode = StatusCodes.Status404NotFound;
                return;
            }

            await next(context);
        });

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

            if (env.IsDevelopment())
            {
                spa.UseAngularCliServer(npmScript: "start");
            }
        });

...
```

Here is my workaround:

public static IApplicationBuilder UseRootRewrite(this IApplicationBuilder builder)
{
    builder.Use((context, next) =>
    {
        if (context.Request.Path == "/" && !HttpMethods.IsGet(context.Request.Method))
        {
            context.Request.Method = "GET";
        }

        return next();
    });

    return builder;
}

Asp.net Core team can easily fix this issue by modifying line 50 in rc\Middleware\SpaServices.Extensions\src\SpaDefaultPageMiddleware.cs:

 // If we have an Endpoint, then this is a deferred match - just noop.
 if (context.GetEndpoint() != null || !HttpMethods.IsGet(context.Request.Method))
 {
     return next();
 }

@caseysick Add it before app.UseSpa:

app.UseCustomStaticFiles();
app.UseCustomSpaStaticFiles();
app.UseRouting();
app.UseCors(ServerConstants.AnyOriginCorsPolicy);
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.UseCustomEndpoints();
app.UseRootRewrite();
app.UseCustomSpa(HostEnvironment);

And this is the updated version of UseRootRewrite:

public static IApplicationBuilder UseRootRewrite(this IApplicationBuilder builder)
{
    string[] excludeUrls = new[] { "/sockjs-node" };

    builder.Use((context, next) =>
    {
        if (!HttpMethods.IsGet(context.Request.Method) && 
            !context.Request.Path.StartsWithSegments(excludeUrls))
        {
            context.Request.Method = "GET";
        }

        return next();
    });

    return builder;
}

@caseysick Here is the complete solution:

public static class ApplicationBuilderExtensions
{
    public static IApplicationBuilder UseRootRewrite(this IApplicationBuilder builder)
    {
        string[] excludeUrls = new[] { "/sockjs-node" };

        builder.Use((context, next) =>
        {
            if (!HttpMethods.IsGet(context.Request.Method) && 
                !context.Request.Path.StartsWithSegments(excludeUrls))
            {
                context.Request.Method = "GET";
            }

            return next();
        });

        return builder;
    }
}

public class Startup
{
    public IWebHostEnvironment HostEnvironment { get; }
    public IConfiguration Configuration { get; }

    public Startup(IConfiguration configuration, IWebHostEnvironment env)
    {
        HostEnvironment = env;
        Configuration = configuration;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        ...
    }

    public void Configure(IApplicationBuilder app)
    {
        ...
        app.UseCustomStaticFiles();
        app.UseCustomSpaStaticFiles();
        app.UseRouting();
        app.UseCors(ServerConstants.AnyOriginCorsPolicy);
        app.UseCookiePolicy();
        app.UseAuthentication();
        app.UseAuthorization();
        app.UseSession();
        app.UseCustomEndpoints();
        app.UseRootRewrite();
        app.UseCustomSpa(HostEnvironment);
    }
}

On my development machine, my REACT as part of a bigger VS 2019 solution (REACT and a C# API)
NEtutors
runs fine in IISExpress. Publishing "to PROD" causes host to look in ClientApp/build for index.html. IISExpress is looking in ClientApp/public and finding Index.html there. I have to change the Startup.cs (ConfigureServices and app.UseSpa)file depending upon where the solution is being published.


`
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace [redacted]
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {

        services.AddControllersWithViews();

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

            // for DiscountASP
            configuration.RootPath = "ClientApp/build";
        });
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }

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

        app.UseRouting();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller}/{action=Index}/{id?}");
        });

        app.UseSpa(spa =>
        {
            // for IISExpress
            // spa.Options.SourcePath = System.IO.Path.Join(**env.ContentRootPath, "ClientApp"**); // "ClientApp/build"; //

            // for DiscountASP
            spa.Options.SourcePath = System.IO.Path.Join(env.ContentRootPath, "ClientApp/build"); //"ClientApp"); 

            //https://github.com/dotnet/aspnetcore/issues/6342#issue-395713046
            spa.Options.StartupTimeout = System.TimeSpan.FromSeconds(300);
            if (env.IsDevelopment())
            {
                spa.UseReactDevelopmentServer(npmScript: "start");
            }
        });
    }
}

}
`

Was this page helpful?
0 / 5 - 0 ratings