Adding React to (a legacy) ASP .NET MVC project

Gergő Nagygyörgy
5 min readJan 2, 2022

The article is about how to integrate React into a pre-existing (or a new) server side rendering ASP .NET Web application.

Lets say you have a server side rendering app, with C# MVC, and by chance some jQuery (which is still the most used JavaScript library on the web by far). But you want to be part of the newest, latest hype train, so you start to consider using React. The first thing you find is Create-react-app, you try it and then realize there are a lot of things that has to change in the current application are to be enhanced, and throw away server side rendering, and convert the previous app to strictly a JSON based API, and render things on the client with JavaScript. So basically you should start over. Which in some cases just not feasible, you cant rewrite the project, understandable.

With this solution you can use React as it meant to be, but long forgotten. To be just part of the web site, not be the whole application. Just like with jQuery. You can enhance your web site with interactive controls, but instead of jQuery now we can use the newest fanciest JavaScript tools, like React.

I personally found this solution to be , in general, really convenient. Not just in legacy projects, but green field projects using ASP .NET with server side rendering. MVC comes with a lot of well polished features we can all enjoy.

The main selling point of the article is the idea in itself. The rest of the post is pretty much about describing the steps that has been taken to have a working example of the aforementioned idea. The project itself is available at github.

Project setup

Server Side

Create a new project starting with the ‘ASP.NET Core Web App’ project template in Visual Studio (community edition should be good enough). All you have to do here is set up a basics MVC project. Go through the wizard, and fill everything as you would love to have it set up.

Choosing ASP.NET Core Web app as the starter project template in Visual Studio

Client side

Create folder called ClientApp in the previously created server side project. Then continue with setting up the project with creating a package.json:

npm init

Set up the bundler with:

npm i esbuild

Since this project is going to use React we install React, ReactDOM and the type definitions respectively

npm i typescript react react-dom @types/react @types/react-dom

Initialize a tsconfig.json for typescript support with the CLI. With specifying the location of the source files and that the project uses React:

tsc --init --rootDir src --jsx react

Reading through the esbuild documentation, a couple of settings should be changed in tsconfig to better support the bundler (isolatedModules to true position). Also worth nothing that only certain tsconfig.json file entries are respected.

Build script

Lets set up a build script file in JavaScript.

The output will be many files for every component instead of one bundled file. So to reduce the amount of data the client has to download, and to prevent the browser from loading, parsing and compiling unused code.

Listing all the components we want to bundle is cumbersome. Fortunately, tiny-glob package is here to the rescue.

npm i tiny-glob

Next lets install tailwind (please follow the guide through) with its dependencies (autoprefixer and autoprefixer).

ESBuild does not support postCSS by default, so lets install another package to fix that:

npm i -D @deanc/esbuild-plugin-postcss

Concurrently helps to run TypeScript compiler in watch mode, and ESBuild in watch mode. So when any changes are made to the code, it will compile automatically. And the reason behind running these commands in parallel is because ESBuild do not care about TypeScript syntax. Type checking needs to be done in parallel with the bundler.

And now lets put all this together, and get the build process finished. Lets have a look at the scripts part of the package file:

The Start command executes more then one additional npm commands with the help of concurrently. Its configured to run ‘watch-tsx’, ‘watch-build’ and ‘watch-jest’ commands. The first one runs TypeScript compiler in watch mode, while disabling its output with ‘ — noEmit’ parameter. The compiler only used for type checking, since ESBuild does compilation without doing so. The second ‘watch-build’ command starts up a build script, with ‘ — watch’ parameter to tell the bundler to rebuilding whenever a change occurs to any of the source files. The third command runs Jest in (you guessed it right) watch mode.

The ‘prod’ command also worth mentioning. By setting up ‘NODE_ENV’ to be ‘production’ we tell tailwind to go through the source files to do its tree-shaking, which tells the compiler to include only the definitions that are in use.

The build file is pretty much copy and paste from ESBuild documentation. With the help of ‘tiny-glob’, the scripts lists all .tsx files to be used as entry point parameter. Later on or in a real project situation, this could be too vague. Since we might have some composition in our components, exporting all the source files separately might not be adequate. Coming up with a separate folder and placing only the components meant for exporting could be one option. Or listing all the files manually.

Storybook to the rescue

There is no single entry point because this is not really a SPA anymore, but rather just a bunch of components disconnected from each other, in one project. Storybook is just the right tool for this kind of problem, with the ability to set up components in isolation from each other. This way developing gets easier. Developing the components can be done in isolation from the actual rendering, where the component meant to be used. It is a good practice regardless, to use this tool, but its rather a necessity then a good to have with this setup.

Setting up storybook

Storybook comes with a really convenient tooling. There is a command to rule them all. Just punch in the following command to the terminal:

npx sb init

…and add an import to ‘.storybook/preview.js’ file on top. Where the tailwind directives are used.

import “../index.css”

Counter example

The first component included in the sample project is a simple counter example. Its purpose is only to show the basic concept. And its not all that interesting. So lets jump to a component that handles input from the user.

Example component with data

Usually the way of sending data to the server consists of making Ajax request to the server from JavaScript. But this time the intention being that the application will not use Ajax calls, but instead submit the page without JavaScript being involved. There needs to be a way to extract the data made in the React component, so to include it in the request while submitting the page.

And the way its done is through hidden input fields. The component will render these input fields as the payload when posting the form. Then all has to be done is to submit the whole page, regardless of what has happened in JavaScript.

The C# example project has a controller called PeopleController with two methods, one returning the view and the other responsible for handling the post request coming from the page with an array of objects which is of type PersonViewModel.

Initializing the React component with data can be done through simple serializing the model object and passing it down as a property.

--

--