Understanding the ASP.NET Core backend
In this section, we are going to start by creating an ASP.NET Core and React app using the standard template in Visual Studio. This template is perfect for us to review and understand basic backend components in an ASP.NET Core SPA.
Once we have scaffolded the app using the Visual Studio template, we will inspect the ASP.NET Core code, starting from its entry point. During our inspection, we will learn how the request/response pipeline is configured and how requests to endpoints are handled.
Creating an ASP.NET Core and React templated app
Let's open Visual Studio and carry out the following steps to create our templated app:
- In the start-up dialog, choose Create a new project:
Figure 1.2 – Visual Studio start-up dialog
- Next, choose ASP.NET Core Web Application in the wizard that opens and click the Next button:
Figure 1.3 – Creating a new web app in Visual Studio
- Give the project a name of your choice and choose an appropriate location to save the project to. Then, click the Create button to create the project:
Figure 1.4 – Specifying a project name and location
Another dialog will appear that allows us to specify the version of ASP.NET Core we want to use, as well as the specific type of project we want to create.
- Select ASP.NET Core 5.0 as the version and React.js in the dialog. Then, click the Create button, which will create the project:
Figure 1.5 – The project template and ASP.NET Core version
Important Note
If ASP.NET Core 5.0 isn't listed, make sure that the latest version of Visual Studio is installed. This can be done by choosing the Check for Updates option from the Help menu.
- Now that the project has been created, press F5 to run the app. After a minute or so, the app will appear in a browser:

Figure 1.6 – The home page of the app
We'll find out later in this chapter why the app took so long to run the first time. For now, we've created the ASP.NET Core React SPA. Now, let's inspect the backend code.
Understanding the backend entry point
An ASP.NET Core app is a console app that creates a web server. The entry point for the app is a method called Main
in a class called Program
, which can be found in the Program.cs
file in the root of the project:
public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); }
This method creates a web host using Host.CreateDefaultBuilder
, which configures items such as the following:
- The location of the root of the web content
- Where the settings are for items, such as the database connection string
- The logging level and where the logs are output
We can override the default builder using fluent APIs, which start with Use
. For example, to adjust the root of the web content, we can add the highlighted line in the following snippet:
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseContentRoot("some-path"); webBuilder.UseStartup<Startup>(); });
The last thing that is specified in the builder is the Startup
class, which we'll look at in the following section.
Understanding the Startup class
The Startup
class is found in Startup.cs
and configures the services that the app uses, as well as the request/response pipeline. In this subsection, we will understand the two main methods within this class.
The ConfigureServices method
Services are configured using a method called ConfigureServices
. This method is used to register items such as the following:
- Our controllers, which will handle requests
- Our authorization policies
- Our CORS policies
- Our own classes, which need to be available in dependency injection
Services are added by calling methods on the services
parameter and, generally, start with Add
. Notice the call to the AddSpaStaticFiles
method in the following code snippet:
public void ConfigureServices(IServiceCollection services) {0 services.AddControllersWithViews(); services.AddSpaStaticFiles(configuration => { configuration.RootPath = "ClientApp/build"; }); }
This is a key part of how the React app is integrated into ASP.NET Core in production since this specifies the location of the React app.
Important Note
It is important to understand that the ASP.NET Core app runs on the server, with the React app running on the client in the browser. The ASP.NET Core app simply serves the files in the ClientApp/Build
folder without any interpretation or manipulation.
The ClientApp/Build
files are only used in production mode, though. Next, we'll find out how the React app is integrated into ASP.NET Core in development mode.
The Configure method
When a request comes into ASP.NET Core, it goes through what is called the request/response pipeline, where some middleware code is executed. This pipeline is configured using a method called Configure
. We will use this method to define exactly which middleware is executed and in what order. Middleware code is invoked by methods that generally start with Use
in the app
parameter. So, we would typically specify middleware such as authentication early in the Configure
method, and in the MVC middleware toward the end. The pipeline that the template created is as follows:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { ... app.UseStaticFiles(); app.UseSpaStaticFiles(); app.UseRouting(); app.UseEndpoints( ... ); app.UseSpa(spa => { spa.Options.SourcePath = "ClientApp"; if (env.IsDevelopment()) { spa.UseReactDevelopmentServer(npmScript: "start"); } }); }
Notice that a method called UseSpaStaticFiles
is called in the pipeline, just before the routing and endpoints are set up. This allows the host to serve the React app, as well as the web API.
Also, notice that a UseSpa
method is called after the endpoint middleware. This is the middleware that will handle requests to the React app, which will simply serve the single page in the React app. It is placed after UseEndpoints
so that requests to the web API take precedence over requests to the React app.
The UseSpa
method has a parameter that is actually a function that executes when the app is run for the first time. This function contains a branch of logic that calls spa.UseReactDevelopmentServer(npmScript: "start")
if you're in development mode. This tells ASP.NET Core to use a development server by running npm start
. We'll delve into the npm start
command later in this chapter. So, in development mode, the React app will be run on a development server rather than having ASP.NET Core serve the files from ClientApp/Build
. We'll learn more about this development server later in this chapter.
Next, we will learn how custom middleware can be added to the ASP.NET Core request/response pipeline.
Custom middleware
We can create our own middleware using a class such as the following one. This middleware logs information about every single request that is handled by the ASP.NET Core app:
public class CustomLogger { private readonly RequestDelegate _next; public CustomLogger(RequestDelegate next) { _next = next ?? throw new ArgumentNullException(nameof(next)); } public async Task Invoke(HttpContext httpContext) { if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); // TODO - log the request await _next(httpContext); // TODO - log the response } }
This class contains a method called Invoke
, which is the code that is executed in the request/response pipeline. The next method to call in the pipeline is passed into the class and held in the _next
variable, which we need to invoke at the appropriate point in our Invoke
method. The preceding example is a skeleton class for a custom logger. We would log the request details at the start of the Invoke
method and log the response details after the _next
delegate has been executed, which will be when the rest of the pipeline has been executed.
The following diagram is a visualization of the request/response pipeline and shows how each piece of middleware in the pipeline is invoked:

Figure 1.7 – Visualization of the request/response pipeline
We make our middleware available as an extension method on the IApplicationBuilder
interface in a new source file:
public static class MiddlewareExtensions { public static IApplicationBuilder UseCustomLogger(this IApplicationBuilder app) { return app.UseMiddleware<CustomLogger>(); } }
The UseMiddleware
method in IApplicationBuilder
is used to register the middleware class. The middleware will now be available in an instance of IApplicationBuilder
in a method called UseCustomLogger
.
So, the middleware can be added to the pipeline in the Configure
method in the Startup
class, as follows:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseCustomLogger(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseSpaStaticFiles(); app.UseMvc(...); app.UseSpa(...); }
In the previous example, the custom logger is invoked at the start of the pipeline so that the request is logged before it is handled by any other middleware. The response that is logged in our middleware will have been handled by all the other middleware as well.
So, the Startup
class allows us to configure how all requests are generally handled. How can we specify exactly what happens when requests are made to a specific resource in a web API? Let's find out.
Understanding controllers
Web API resources are implemented using controllers. Let's have a look at the controller that the template project created by opening WeatherForecastController.cs
in the Controllers
folder. This contains a class called WeatherForecastController
that inherits from ControllerBase
with a Route
annotation:
[ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase { ... }
The annotation specifies the web API resource URL that the controller handles. The [controller]
object is a placeholder for the controller name, minus the word Controller
. This controller will handle requests to weatherforecast
.
The Get
method in the class is called an action method. Action methods handle specific requests to the resource for a specific HTTP method and subpath. We decorate the method with an attribute to specify the HTTP method and subpath the method handles. In our example, we are handling an HTTP GET
request to the root path (weatherforecast
) on the resource:
[HttpGet] public IEnumerable<WeatherForecast> Get() { ... }
Let's have a closer look at the web API at runtime by carrying out the following steps:
- Run the app in Visual Studio by pressing F5.
- When the app has opened in our browser, press F12 to open the browser developer tools and select the Network panel.
- Select the Fetch data option from the top navigation bar. An HTTP
GET
request toweatherforecast
will be shown:Figure 1.8 – A request to the weatherforecast endpoint in the browser developer tools
- An HTTP response with a
200
status code is returned with JSON content:

Figure 1.9 – The response body for the weatherforecast endpoint in the browser developer tools
If we look back at the Get
action method, we are returning an object of the IEnumerable<WeatherForecast>
type. The MVC middleware automatically converts this object into JSON and puts it in the response body with a 200
status code for us.
So, that was a quick look at the backend that the template scaffolded for us. The request/response pipeline is configured in the Startup
class and the endpoint handlers are implement using controller classes.
In the next section, we'll walk through the React frontend.