Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET Core with React template returns index.html

I am learning full-stack web development with .NET Core and React, so I created an ASP.NET Core Web Application project with React template in Visual Studio 2019.

At some point I noticed that if I request a non-existing URL, I don’t get an error or 404 page as I would expect but rather the index.html as the response.

I want that my backend returns a 404 status code whenever a non-existing URL is called.

I tried to fix this by adding a React Switch tag around Route tags in App.js and added a component that is shown when the requested URL doesn’t match the defined routes:

import React, { Component } from 'react';
import { Route, Switch } from 'react-router';
import { Layout } from './components/Layout';
import { Home } from './components/Home';
import { FetchData } from './components/FetchData';
import { Counter } from './components/Counter';
import NoMatch from './components/NoMatch'; // <-- Added this

export default class App extends Component {
  static displayName = App.name;

  render () {
    return (
      <Layout>
        <Switch> // <-- Added this
          <Route exact path='/' component={Home} />
          <Route path='/counter' component={Counter} />
          <Route path='/fetch-data' component={FetchData} />
          <Route component={NoMatch} /> // <-- Added this
        </Switch> // <-- Added this
      </Layout>
    );
  }
}
import React from 'react';

export default function NoMatch() {
  return (
    <div>
      <h1>Error</h1>
      <h2>404</h2>
      <p>Page was not found</p>
    </div>
  );
}

But I think it is not a real solution to the problem since I later discovered that sending a request to a non-existing API via fetch function also returns index.html as a response. The project has an example component FetchData that has a constructor with fetch function. Replacing the example URL with a non-existing path reproduces the behavior:

constructor (props) {
  super(props);
  this.state = { forecasts: [], loading: true };

  fetch('api/nonexistingpath') // <-- Changed this URL
    .then(response => response.json())
    .then(data => {
      this.setState({ forecasts: data, loading: false });
    });
}

So, I thought that the problem is in the .NET Core backend. I went to the Startup file and tried to fix this behavior there, I noticed that when removing everything from the parentheses of this piece of code:

app.UseMvc(routes =>
{
  routes.MapRoute(
    name: "default",
    template: "{controller}/{action=Index}/{id?}");
});

doesn’t change the behavior of the program. However, if I remove this code entirely, the frontend will load, but the fetch function doesn’t return the data, it again returns the index.html. I tried to change the template, but it seems to me, that it has no effect on programs behavior.

I am really confused, am I getting something wrong? Wouldn’t it be the expected default behavior to return an error or 404 page when requesting a non-existing URL? I couldn’t find much on the internet either.

https://stackoverflow.com/a/53687522/10951989

I found this answer, but it doesn’t give any references or explanation on why it is the default behavior.

https://stackoverflow.com/a/44164728/10951989

I tried to use code from this answer, but it blocks everything that is not an API call. Can somebody help me?

Thank you in advance!

Update #1

Ok, after long attempts, I seem to have found the solution that works for me:

app.MapWhen(x => x.Request.Path.Value.StartsWith("/api"), builder =>
{
  app.UseMvc();
});

app.MapWhen(x => !x.Request.Path.Value.StartsWith("/api"), builder =>
{
  app.UseSpa(spa =>
  {
    spa.Options.SourcePath = "ClientApp";

    if (env.IsDevelopment())
    {
      spa.UseReactDevelopmentServer(npmScript: "start");
    }
  });
});
like image 407
AlexRekowski Avatar asked Sep 03 '19 14:09

AlexRekowski


1 Answers

The ASP.NET Core + React template creates a project that does two things at once:

  1. Acts as a web server to host the static files (your React app)
  2. Serves API responses (your C# controllers)

The behavior you're noticing (serving index.html instead of returning 404 for missing pages) is part of #1. Changing your React routing code didn't make a difference because it's server behavior. ASP.NET Core calls this a "SPA fallback route". This excellent blog post calls it "configurable static" behavior:

A web server can be configured to respond with an alternative file if the requested resource doesn’t actually exist on the disk. If a request for /bears comes in and the server has no bears file, the configuration can tell it to respond with an index.html file instead.

The goal is to make it easier to have "pretty" URLs. If your React single-page app describes a route like /counter, but there is no counter.html on the server, then someone navigating directly to /counter from a bookmark or refreshing their browser will see a 404 error message. By configuring the server (ASP.NET Core in this case) to serve index.html instead of 404, the React app will be loaded and can respond correctly to the path in the URL.

If you comment out the app.UseSpaStaticFiles() line in your Startup class, your server should start returning real 404s. But that leaves the above problem with frontend routing.

Here's a snippet I use in my projects to serve index.html except when the request path starts with /api:

    app.UseMvc();

    app.MapWhen(x => !x.Request.Path.Value.StartsWith("/api"), builder =>
    {
        app.Run(async (context) =>
        {
            context.Response.ContentType = "text/html";
            context.Response.Headers[HeaderNames.CacheControl] = "no-store, no-cache, must-revalidate";
            await context.Response.SendFileAsync(Path.Combine(env.WebRootPath, "index.html"));
        });
    });
like image 59
Nate Barbettini Avatar answered Oct 02 '22 15:10

Nate Barbettini