Add a React App to a NET Core MVC App

In this tutorial, we will go step by step on how to add a React App to a NET Core MVC App. Handling 404 pages and client routing.

I am one to think that not everything I build has to be a Single Page Application (SPA). Recently I was building a project for a client, and I decided that only one section of the app needed to be a SPA and the rest of the app a “traditional” MVC app, so I started the app with the “MVC” part and later added the “SPA”. This is how I did it.

[topads][/topads]

Adding a React app to your existing NET Core MVC app is easier than you might think

One thing to note is that I am using VS Code and the .NET Core CLI on macOS.

The Existing NET Core MVC App

If you wish to follow along, clone the following repo dotnet-core-mvc-react (intial-app branch. Yes, the branch name is misspelled >_<) and open the project.

git clone -b intial-app --single-branch https://github.com/esausilva/dotnet-core-mvc-react.git

To run the project in VS Code, open the integrated terminal and type the following

dotnet watch run

Our awesome app has three views under Home controller and configured with a custom 404 page.

Initial Net Core MVC app
Initial C# app

You get a new feature request and it is a perfect candidate for a React app. Fortunately, adding a React app is easier than you might think.

Go ahead and stop the project (if you have it running) by pressing Ctrl + C in your terminal.

Creating the React app

Rather than creating the app from scratch or using Create React App (CRA), we will create a new .NET Core project with the React template. This default template will have React Router installed making it easier for us.

Open your terminal, navigate to a different directory and type the followingdotnet new react -n temp-react-core

Now in Finder navigate to this newly created project directory (temp-react-core) and copy the ClientApp directory to the root of our actual project. Also, copy WeatherForecastController.cs to the Controllers directory and WeatherForecast.cs to the Models directory of our project.

You will end up with the following project structure.

|-- ClientApp/
|-- Controllers/
|  |-- HomeController.cs
|  |-- WeatherForecastController.cs
|-- LICENSE
|-- Models/
|  |-- ErrorViewModel.cs
|  |-- WeatherForecast.cs
|-- Program.cs
|-- Properties
|-- README.md
|-- Startup.cs
|-- Views/
|-- appsettings.Development.json
|-- appsettings.json
|-- bin/
|-- dotnet-core-mvc-react.csproj
|-- obj/
|-- wwwroot/

Now back to VS Code, open WeatherForecastController.cs and WeatherForecast.cs and change the namespace to match our project’s namespace. You will end up with the following

// WeatherForecastController
namespace dotnet_core_mvc_react.Controllers
{ ... }

// WeatherForecast
namespace dotnet_core_mvc_react
{ ... }

Still in VS Code, open dotnet-core-mvc-react.csproj and replace it with the following contents

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <RootNamespace>dotnet_core_mvc_react</RootNamespace>
    <SpaRoot>ClientApp\</SpaRoot>
    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
    <RootNamespace>dotnet_core_mvc_react</RootNamespace>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.1.0" />
  </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\**" />
      <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
        <RelativePath>%(DistFiles.Identity)</RelativePath>
        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
        <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
      </ResolvedFileToPublish>
    </ItemGroup>
  </Target>

</Project>

All of that is just configuration telling Webpack to build the React app. The line <SpaRoot>ClientApp\</SpaRoot> tells .NET Core where the React app lives.

Another thing to note is that we are adding a new package Microsoft.AspNetCore.SpaServices.Extensions.

Open Terminal again and type the following to restore this new package

dotnet restore

Now open Startup.cs and add the following under the ConfigureServices and Configure methods

public void ConfigureServices(IServiceCollection services) {
	...
	// In production, the React files will be served from this directory
    services.AddSpaStaticFiles(configuration => {
        configuration.RootPath = "ClientApp/build";
    });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
	...
    // After app.UseStaticFiles()
	app.UseSpaStaticFiles();
 	...
	// After app.UseEndpoints()
	app.UseSpa(spa => {
      spa.Options.SourcePath = "ClientApp";
      if (env.IsDevelopment()) {
        spa.UseReactDevelopmentServer(npmScript: "start");
      }
    });
}

The order of configurations under the Configure method matters, if you add app.UseSpa(...) before app.UseEndpoints(...) then React will take over routing and none of your MVC routes will work.

At this point, we have configured the React app to work nicely with .NET Core. In the background, the framework will run react-scripts which in turn run CRA to build and serve React in the development environment.

[signupform][/signupform]

Running the app for the first time

Now that everything is set up we can safely install and update the JavaScript packages. Open Terminal, navigate to ClientApp directory within our project and type the following to install

npm i

Optional: To update the packages I use npm-check. Go ahead and use that tool or the one of your preference to update the JavaScript packages.

Still in Terminal, go back to the root of the project and run the app

dotnet watch run

Now try to access the React app by typing the following: https://localhost:5001/ClientApp. You will notice you hit a blank page, but if you click on the menu items they actually take you to their respective pages.

Default Client React App
Default Client App

When you click Home, notice the URL changes to https://localhost:5001. If you were to hit refresh then the React home will go away and load the Home View in HomeController. This is not good.

Client React App - Home
Client App – Home

Now, if you were to type a route that does not exist (say https://localhost:5001/dfdf) you will get a blank page. React took over our custom 404 page and just displays a blank page. In fact, when we accessed https://localhost:5001/ClientApp we got React Router’s “404 page”.

Fixing the Home issue

Open index.js under ClientApp -> src directory and assign baseURL to "ClientApp".

// From
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href');

// To
const baseUrl = "ClientApp"

Back to your browser, access https://localhost:5001/. You will get the correct non-React home. Now access https://localhost:5001/ClientApp, and you will get the React home.

Client React App - Correct Home
Client App – Correct Home

Fixing our custom 404 page

Since React is taking over the custom 404 page, we will have to handle 404s within React. There are two options for taking care of this issue.

Option 1

Let’s create a Not Found component and hook it up to React Router.

Create a new file under ClientApp -> src -> Components directory and call it NotFound.js. Copy the following:

import React from "react";

const NotFound = () => (
  <h1 style={{ textAlign: "center" }}>
    Sorry! The resource you are looking for has not been found
  </h1>
);

export { NotFound };

Now open App.js under ClientApp -> src directory and replace it with the following:

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 { NotFound } from "./components/NotFound";

import "./custom.css";

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

  render() {
    return (
      <Layout>
        <Switch>
          <Route exact path="/" component={Home} />
          <Route path="/counter" component={Counter} />
          <Route path="/fetch-data" component={FetchData} />
          <Route component={NotFound} />
        </Switch>
      </Layout>
    );
  }
}

In order to display the custom 404 page correctly, we would need to import Switch from react-router and enclose our routes with it.

Then we import the NotFound component and add it after the last route. If we add it before the first route or in between, and you try accessing any other route after it, then the NotFound component will display instead of the requested component. This is because the NotFound Route does not have a path and is basically a “catch-all” route.

Now, if we were to not include the Switch, then the NotFound component would display alongside every other route.

Try accessing https://localhost:5001/dfdf, and you will get the 404 page from React. Nice!!

404 Not Found Page
404 Not Found Page

Option 2

Open NotFound.js and replace the contents with the following:

const NotFound = () => {
  window.location = "/Home/NotFoundPage";
  return null;
};

export { NotFound };

Now open HomeController.cs and comment out the annotation above NotFoundPage action result

// [Route("error/404")]
public IActionResult NotFoundPage() {
    return View();
}

Finally open Startup.cs and comment out the following line under the Configure method.

// app.UseStatusCodePagesWithReExecute("/error/{0}");

Try accessing https://localhost:5001/dfdf and you will get the 404 page served by .NET.

Final Repo

https://github.com/esausilva/dotnet-core-mvc-react

Bonus

If you already have authentication set up, any calls from within React to your Web API will already be authenticated since cookies attach automatically to ajax calls, including the auth cookies.

More than likely you will not want to access your awesome new feature with ClientApp as the route. Just change the baseURL under index.js to whatever you want want it to be, say AwesomeNewFeature

const baseUrl = "AwesomeNewFeature";

With this change, we can access the app like so https://localhost:5001/AwesomeNewFeature

Conclusion

That is it! You made it through the end. We just learned that adding a React App to an existing NET Core MVC app is indeed pretty straight forward.

Let me know in the comments any questions, corrections, or even praise. I look forward to reading them.

Thanks for reading this far. I hope you enjoyed the article and will share it on your social media accounts. You can connect with me on LinkedIn or on Twitter.


Originally published on newline.


Consider giving back by getting me a coffee (or a couple) by clicking the following button:

[bottomads][/bottomads]

Spread the love

5 thoughts on “Add a React App to a NET Core MVC App

  1. Tathagat Mohanty says:

    Thanks for such a detailed guide on how to integrate react with MVC Webapp. I am trying to do something similar and that is how I came across your article. But the difference is I am trying to integrate a TypeScript Webapp (React+Redux template created using VS 2019) instead of a React Webapp. And I am getting quite a few errors in the TS files, specially in the “form.d.ts” declaration files. Can you help me find what else do I need to change in order to solve those errors. Any suggestion would be really helpful.

    Thanks
    Tathagat

    • esausilva says:

      You could paste those errors here, however I’m not sure if I’ll be of much help as I have not done TypeScript

  2. Jim Black says:

    I love the Adding a React App to a .NET Core MVC App post. I followed the steps and it went smoothly. When i apply to my .net MVC, i don’t know how to. My app is different with the .net CORE app. It doesn’t have startup.cs page. it doesn’t have using Microsoft.AspNetCore.Builder. Will you have a tutor for Adding a React App to a .NET MVC App soon?

  3. Gopi says:

    I have tried the same steps mentioned in the article i am able to run the react pages but MVC pages are not loading.

    I am using .NET 8 along with React-TS .

    Did you tried it with latest .NET 8 and React-TS ??

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.