Scott J. Swindell
Setting up a Complete Framework for .NET 6 React Apps in Visual Studio 2022
Using Node.js, React.js, SASS, Jest, xUnit, Moq, NLog, Identity Framework, Entity Framework, and dependency injection

By now, I hope you've had a chance to check out Visual Studio 2022. I recently used it to create my first .NET 6 single-page application (SPA), using Node.js for hosting, React.js for a responsive frontend, Identity Framework for Authentication and Authorization, Entity Framework for data access, SASS for awesome styling, dependency injection, NLog for logging, and Jest, xUnit, plus Moq for automated testing the entire thing. This provides a robust framework, for building powerful, high-quality applications. In this tutorial, we’ll walk through the steps to set everything up.
Install Visual Studio 2022

First, if you haven’t done so, download and install Visual Studio (VS) 2022. The Community version is free and has all the features you need, for non-enterprise development.
Create the Project and Solution
Once you have Visual Studio 2022 installed, it’s time to create the project and solution. On the Visual Studio start screen, click on Create a new project.

This will bring up a wizard that lets you select the type of project to create. For our purposes, we’re going to select ASP.NET Core with React.js. Once you’ve selected it, click Next to enter the project name and location.

This brings us to the Configure your new project screen. Here, you’re going to enter your Project name, select the Location of your repository, and set the Solution name, which by default is the same as the Project name. Once you’ve set the names and location, click Next. For this tutorial, I'll be using the name Net6ReactApp.

That leads us to the Additional information screen. Here, we’re going to select .NET 6 (Long-term support) for the Framework. The Authentication type really depends on how you’re planning to authenticate your users. If anyone can use the app, then select None. For basic login functions, Individual Accounts sets up the fundamentals for user accounts. If you require Windows Authentication, then you'll have to go back a step and select the ASP.NET Core MVC application template and manually add React later.
We’re going to select Individual Accounts, because it uses Microsoft Identity Framework, which gives us an excellent platform, for authentication and authorization. Leave Configure for HTTPS configured, so the app’s connections to the users will be secure.

Once you have everything entered and selected, click Create, to have Visual Studio scaffold-out your new project. Once Visual Studio is finished creating your project and solution, it will take you to an overview of your app. Feel free to have a look around your solution, or explore some of the documentation, on the overview page. I’ll cover publishing your app to the cloud in a future article.

Run the App
As the project was scaffolded, Visual Studio created all the necessary files to run your React.js app using Node.js. When you’re done looking around, click on the Debug button on the toolbar to run the app. If you don't have the Debug button on your toolbar, right-click on the toolbar and select Debug from the list of options.
The first time you run the app, it has to restore all of the Node.js packages with Node Package Manager (NPM), which can take several minutes. Once NPM is finished, you’ll be prompted to trust the self-signed SSL certificate. This avoids getting a security warning when you view the app in a browser. Click Yes to continue.

This will lead to a more serious sounding prompt. Just click Yes again to trust the certificate.

Upon trusting the certificate, the website should start to load in the browser.

While the site loads, you may be prompted, by Windows Defender Firewall, to allow access to Node.js. This is required for the site to operate correctly, so click Allow Access.

Finally, after everything has loaded, Node.js will serve up your new single-page application (SPA). Take a look around the site to learn about your app.

Explore the Solution
After you’re finished looking around, let’s take a look at Solution Explorer. We have a Properties folder, which has important settings for the app. There’s also a ClientApp folder. This is where the React.js code for the project resides. Just like regular MVC projects, you’ll find Controllers and Models directories. In the Data folder, you’ll find the Entity Framework context and migrations. There’s also a Pages folder for the few pages that React.js won’t be handling, like an error page. Finally, we have appsettings.json and Program.cs for launching and initializing up the app.

Dependency Injection, Authentication, and Authorization
If you take a look at Program.cs, you’ll notice that we’re already set up for dependency injection. It initializes authentication and authorization, using Microsoft Identity Framework. This is because we selected Individual Accounts during scaffolding. It also sets up Entity Framework, URL routing, and HTTPS redirection.
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
using Microsoft.EntityFrameworkCore;
using Net6ReactApp.Data;
using Net6ReactApp.Models;
using NLog;
using NLog.Web;
// Initialize NLog
var logger = NLog.LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
logger.Debug("Initializing Program");
try
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
builder.Services.AddAuthentication()
.AddIdentityServerJwt();
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
// Setup NLog for Dependency injection
builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
builder.Host.UseNLog();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
// 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.UseRouting();
app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
app.MapRazorPages();
app.MapFallbackToFile("index.html"); ;
app.Run();
}
catch (Exception exception)
{
logger.Error(exception, "Execution stoped, due to Exception");
throw;
}
finally
{
LogManager.Shutdown();
}
Program.cs
Add NLog for better logging
Every app needs logging, in order to gain insight into what the app is doing, at any given time. ASP.NET has some basic support for logging, out of the box. However, for more power and flexibility, we’re going to use a popular third-party logger named NLog. It’s highly configurable. You can log to a file or database, and you have complete control over everything that gets logged.
Add NLog.Web.AspNetCore NuGet Package
To get started, first install the NLog NuGet package. If you’re not familiar with how to get there, just right-click on the project, and select Manage NuGet packages... from the context menu.

Next, search for NLog.Web.AspNetCore, under the Browse tab. Once you locate it, select the latest stable version, then click Install. It should be pretty quick, and it will pop up a readme.txt file, when it’s complete.

Configure NLog
After installing the NLog NuGet package, it’s time to configure it. The settings for NLog reside in nlog.config, which we’ll have to create. To do this, right-click on the project in Solution Explorer. Click Add on the context menu, then New Item...

This will bring up a dialog box. Select Text File and enter nlog.config as the filename.

Once the file is created, copy and paste the following configuration XML into the file. For complete information on the configuration options, visit the NLog config section on their website.
Once you’ve copied the content into the nlog.config file, replace instances of Net6ReactApp with the name of your own app, and make any other changes you’d like.
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
internalLogLevel="Info"
internalLogFile=".\logs\internal-nlog-Net6ReactApp.txt">
<!-- ASP.NET Core Renderer -->
<extensions>
<add assembly="NLog.Web.AspNetCore"/>
</extensions>
<!-- Logging Targets -->
<targets>
<!-- File Target for general logging -->
<target xsi:type="File" name="allfile" fileName=".\logs\Net6ReactApp-${shortdate}.log"
layout="${longdate} | ${event-properties:item=EventId_Id:whenEmpty=0} | ${level:uppercase=true} | ${logger} | ${message} ${exception:format=tostring}" />
<!-- File Target for own logging -->
<target xsi:type="File" name="ownFile-web" fileName=".\logs\Net6ReactApp-own-${shortdate}.log"
layout="${longdate} | ${event-properties:item=EventId_Id:whenEmpty=0} | ${level:uppercase=true} | ${logger} | ${message} ${exception:format=tostring} | url: ${aspnet-request-url} | action: ${aspnet-mvc-action} | ${callsite}" />
<!-- Console Target to improve startup detection -->
<target xsi:type="Console" name="lifetimeConsole" layout="${MicrosoftConsoleLayout}" />
</targets>
<!-- Logging Rules -->
<rules>
<!-- All logs -->
<logger name="*" minlevel="Trace" writeTo="allfile" />
<!-- Output hosting lifetime messages to console target for faster startup detection -->
<logger name="Microsoft.Hosting.Lifetime" minlevel="Info" writeTo="lifetimeConsole, ownFile-web" final="true" />
<!-- Skip non-critical Microsoft logs and so log only own logs (BlackHole) -->
<logger name="Microsoft.*" maxlevel="Info" final="true" />
<logger name="System.Net.Http.*" maxlevel="Info" final="true" />
<logger name="*" minlevel="Trace" writeTo="ownFile-web" />
</rules>
</nlog>
nlog.config
Once the nlog.config file is finished, we need to update appsettings.json. Locate the Logging section and set the LogLevel.Default to Trace. This will give us more verbose logging. If you’d rather less detailed logging, skip this step.
...
"Logging": {
"LogLevel": {
"Default": "Trace",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
...
appsettings.json
Initialize NLog in Program.cs
Next, we need to update Program.cs to initialize NLog and set up dependency injection. Add the following statements at the top of Program.cs.
using NLog;
using NLog.Web;
// Initialize NLog
var logger = NLog.LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
logger.Debug("Initializing Program");
After adding the initialization code, wrap the remaining code in the file with a try block, with the following catch and finally blocks:
catch (Exception exception)
{
logger.Error(exception, "Execution stoped, due to Exception");
throw;
}
finally
{
LogManager.Shutdown();
}
Finally, we need to set up NLog for dependency injection. Insert the following code right above the var app = builder.Build(); statement.
// Setup NLog for dependency injection
builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
builder.Host.UseNLog();
Using NLog
In order to use NLog in your app, simply inject a new logger in the constructor, as seen below, and call the appropriate Log method.
private readonly NLog.ILogger _logger;
public WeatherForecastController(NLog.ILogger logger)
{
_logger = logger;
_logger.Log(NLog.LogLevel.Trace, "WeatherForecastController Initializing");
}
Once you have it all set up, run the app to see if you get a log file, where you’re expecting it.
Add SASS for better styling
Syntactically Awesome Style Sheets (SASS) has become the standard technology for improving cascading style sheets (CSS). It extends CSS, adding a bunch of new, useful functionality. We’re going to be adding SASS to our application, using the steps below.
There is already a CSS file called custom.css under ClientApp\src. We’re going to rename that to custom.scss to make it a SASS file.

Next, we need to replace the reference to custom.css in App.js with a reference to bundle.css. This tells it to use the bundle.css file, which will be created, from the SCSS files.

Now it’s time to add some Node packages, using the following command:
npm i --save-dev autoprefixer clean-webpack-plugin css-loader cssnano
file-loader mini-css-extract-plugin node-sass postcss-loader sass-
loader style-loader webpack webpack-cli ajv acorn bootstrap jquery
After installing all the packages required by SASS, we need to edit the package.json file and add the following two lines to the “scripts” section:
"webpack-dev": "webpack --progress --profile --mode development",
"webpack-prod": "webpack --progress --profile --mode production"

Next, we have to configure Webpack, to bundle our SCSS file. Create a webpack.config.js file in your ClientApp folder. Next, populate it with the following contents. You can add more SCSS and JavaScript files by adding the filenames to the entry value array.
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const path = require('path');
const directoryName = './public';
const fileName = 'bundle';
module.exports = (env, argv) => {
return {
mode: argv.mode === "production" ? "production" : "development",
entry: ['./src/custom.scss'],
output: {
filename: fileName + '.js',
path: path.resolve(__dirname, directoryName)
},
module: {
rules: [
{
test: /\.s[c|a]ss$/,
use:
[
'style-loader',
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader', // Run postcss actions
options: {
postcssOptions: {
plugins: function () { // postcss plugins, can be exported to postcss.config.js
let plugins = [require('autoprefixer')];
if (argv.mode === "production") {
plugins.push(require('cssnano'));
}
return plugins;
}
}
}
},
'sass-loader'
]
}
]
},
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: fileName + '.css'
})
]
};
};
webpack.config.js
In order to test everything out, enter the following command in your Node.js window. This will bundle the src/custom.scss file into public/bundle.css.
npm run webpack-dev
If all goes well, you should see bundle.css in ClientApp/public

It also creates a bundle.js file, which needs to be included in the project. If you want to automate this process, I suggest you add a pre-build event to the project, which executes the npm run webpack-dev command.
Add Automated Testing for more reliable code
When it comes to ensuring code quality and program correctness, nothing beats automated testing. For our app, we need to test both the frontend JavaScript code and the backend C# code. We’re going to be using xUnit for backend testing, because it’s fast, powerful, and it plays nice with .NET 6. It requires a little bit of setup, which we’ll cover.
For frontend testing, we’re going to use Jest, because it’s created by the same folks that created React, and it comes set-up, right out of the box. Below, I’ll explain the steps necessary to do both backend and frontend automated testing.
Frontend Testing
React was actually designed with unit testing in mind, making it easy to test. In fact, when we scaffolded our project, it automatically set up Jest for running tests. It also created a sample test, App.test.js.

import React from 'react';
import { createRoot } from 'react-dom/client';
import { MemoryRouter } from 'react-router-dom';
import App from './App';
it('renders without crashing', async () => {
const div = document.createElement('div');
const root = createRoot(div);
root.render(
<MemoryRouter>
<App />
</MemoryRouter>);
await new Promise(resolve => setTimeout(resolve, 1000));
});
App.test.js
The scaffolding process also created a test script in package.json.

Using Jest
To run the tests, all we need to do is open a Node.js command prompt and navigate to the ClientApp folder. From there, run the following command, and if all goes well, you should see something similar to the output below..
npm run test

That’s all the setup that’s required for front-end unit testing, using React and Jest. From here, you’re free to add more tests, as you develop your code.
Backend Testing
As mentioned, for testing our C# code, we’re going to be using xUnit. When .NET Core first came out, it was the recommended testing framework. Now there is support for others, but xUnit is still a favorite by many, because it is fast and easy to use.
Install xUnit
To install xUnit, open Package Manager Console in Visual Studio, under the Tools menu.

This will bring up the Package Manager Console window.

In the console, change to the directory with your project name and enter the following command.
dotnet add package Microsoft.NET.Test.Sdk
That will install the required MS testing SDK. Next, we need to install xUnit itself, using the following command.
dotnet add package xunit
After running the command, you should see something similar to this:

Next, we need to add xUnit to the Visual Studio Task Runner, with the following command:
dotnet add package xunit.runner.visualstudio
Finally, we need to install Coverlet Collector, using this command:
dotnet add package coverlet.collector
With xUnit now installed, we can create our first unit test. We’re going to write tests for the WeatherForecastController class, which was created, when we scaffolded our project.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using NLog;
namespace Net6ReactApp.Controllers
{
[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly NLog.ILogger _logger;
public WeatherForecastController(NLog.ILogger logger)
{
_logger = logger;
_logger.Log(NLog.LogLevel.Trace, "WeatherForecastController Initializing");
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}
WeatherForecastController.cs
It’s always recommended, to place your unit tests, in a separate project, so we’ll need to create one. Right-click on the solution in Solution Explorer, and select Add, then New Project...

In the dialog, locate xUnit Test Project and click Next

Give the new project the same name as your project, with the word Tests appended to the end.

For the framework, choose .NET 6.0 (Long-term support), then click Create, as seen below.

Once we have our new unit test project added to our solution, it’s time to write our first unit test. Select WeatherForecastController in Solution Explorer, then on the Search bar at the top of Visual Studio, type Create Unit Tests and hit Enter.

This will bring up the Create Unit Tests dialog. Select xUnit.net 2.0 for the Test Framework, and leave the other default options. If you don’t see xUnit.net 2.0 under Test Framework, ensure that you have the Visual Studio 2022 extension xUnit.net.TestGenerator2022 installed.

Once you click OK, VS 2022 will scaffold the xUnit tests for us, and it should create a WeatherForcastControllerTests class.

You may notice that Net6ReactAppTests has a missing dependency.

Right-click on Microsoft.NET.Test.Sdk and click Update...

Update any dependencies that require updating, and the errors should go away.
Install Moq
Now, in order to enhance our xUnit tests, we’re going to add Moq. We’ll use Moq as our mocking framework, because it’s versatile, simple to use, and it’s incredibly well-known. If you aren’t familiar with mocking, it allows you to create mock implementations for interfaces and configure them with expected behaviors. Then, after the test has run, the mock can verify whether the expected behavior occurred. For more information, check out my other blog posts on mocking and unit testing. To install Moq, into our xUnit tests project, right-click on your tests project and select Manage NuGet Packages...

Once NuGet Package Manager opens, search for Moq and install the latest stable version.

Using xUnit
Now that Moq is installed, open WeatherForecastControllerTest.cs in your Tests project. Copy the following code into your the file.
namespace Net6ReactApp.Controllers.Tests
{
public class WeatherForecastControllerTests
{
private static readonly List<string> Summaries = new List<string>(new string[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" });
[Fact()]
public void WeatherForecastControllerTest()
{
// Arrange: Set up a mock ILogger
Mock<NLog.ILogger> loggerMock = new();
loggerMock.Setup(mock => mock.Log(NLog.LogLevel.Trace, "WeatherForecastController Initializing"));
// Act: Initialize a WeatherForecastController using the mock
WeatherForecastController controller = new WeatherForecastController(loggerMock.Object);
// Assert: Verify the controller was initialized, and the logger was used.
Assert.NotNull(controller);
loggerMock.VerifyAll();
}
[Fact()]
public void GetTest()
{
// Arrange: Set up a mock ILogger and create a controller
Mock<NLog.ILogger> loggerMock = new();
loggerMock.Setup(mock => mock.Log(NLog.LogLevel.Trace, "WeatherForecastController Initializing"));
WeatherForecastController controller = new WeatherForecastController(loggerMock.Object);
DateTime today = DateTime.Now.Date;
// Act: Call the Get method
var forecasts = controller.Get().ToList();
// Assert: Verify the results
Assert.NotNull(forecasts);
int dateOffset = 1;
foreach (var forecast in forecasts)
{
Assert.NotNull(forecast.Summary);
Assert.True(Summaries.Contains(forecast.Summary));
Assert.True(forecast.Date.Date.Equals(today.AddDays(dateOffset++).Date));
Assert.True(forecast.TemperatureC >= -20 && forecast.TemperatureC <= 55);
}
loggerMock.VerifyAll();
}
}
}
WeatherForcastControllerTests.cs
Be sure to replace the namespace with the namespace of your app. Once you’ve completed finished with this file, it’s time to open up Test Explorer and run the test.

Once in Test Explorer, click the icon to Run All Tests In View.

This will build the projects and run all the xUnit tests.

Once the tests complete, you should see them show as passed.

This concludes setup, for automated testing the backend C# code. From here, you're free to add more tests, for your custom code.