Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web

.NET 8 Minimum API and React Frontend

4.96/5 (13 votes)
6 May 2024CPOL6 min read 22.3K   378  
A responsive React frontend interacts in real-time with a .NET 8 Minimal API backend to dynamically display and manage state transitions for a simulated traffic light system.
This article encapsulates how React integrates with a .NET 8 Minimal API to create a dynamic and responsive traffic light management system, demonstrating modern web development practices with decoupled architecture for efficient and scalable application design.

Introduction

The concept of Minimal APIs in .NET focuses on simplifying the creation of web APIs by reducing boilerplate code, enabling you to define endpoints more concisely. These APIs leverage the core features of ASP.NET Core and are designed to create HTTP APIs quickly with minimal coding effort. They are ideal for microservices and small applications where you want to avoid the complexity of a full MVC application. In ASP.NET Core Minimal APIs, you can define endpoints directly in the Program.cs file without needing controllers or other scaffolding. 

Assume to have 4 sets of lights, as follows.

Lights 1: Traffic is travelling south

Lights 2: Traffic is travelling west

Lights 3: Traffic is travelling north

Lights 4: Traffic is travelling east

The lights in which traffic is travelling on the same axis can be green at the same time. During normal hours all lights stay green for 20 seconds, but during peak times north and south lights are green for 40 seconds while west and east are green for 10 seconds. Peak hours are 08:00 to 10:00 and 17:00 to 19:00. Yellow lights are shown for 5 seconds before red lights are shown. Red lights stay on until the cross-traffic is red for at least 4 seconds, once a red light goes off then the green is shown for the required time.

In this article we implement a React front-end and .Net 8 Minimum API backend. The backend will contain the logic and state of the running traffic lights. The front-end will be a visual representation of the traffic lights, with the data served from the backend.

Create solution with Visual Studio “Standalone Typescript React Project” template

Prerequisites

  • Visual Studio 2022 (17.1 or above).
  • Node.js 18.20 or above

Create React Project

Start Visual Studio 2022, select “Standalone TypeScript React Project”.

Image 1

Image 2

When you get to the Additional information windows, check the Add integration for Empty ASP.NET Web API Project option. This option adds files to your Angular template so that it can be hooked up later with the ASP.NET Core project.

Image 3

Select this option, proxy will be setup once the project is created. Essentially this template runs “npx create-react-app” to create a react app.

Create Web API Project

In same solution add ASP.NET Core Web API project.

Image 4

Image 5

Name this backend project as “TrafficLightsAPI”. Select .NET 8.0 for framework.

Image 6

Image 7

Please note the template generate Minimum API sample, Weather Forecast in program.cs.

Set the startup project

Right-click the solution and select Set Startup Project. Change the startup project from Single startup project to Multiple startup projects. Select Start for each project’s action.

Image 8

Make sure the backend project and move it above the frontend, so that it starts up first.

Change client proxy setting

Check APP URL in TrafficLightsAPI  https launch profiles UI.

Image 9

Then open vite.config.ts in your React project root folder. Update https://localhost:5001 to https://localhost:7184 .

Image 10

Now start the solution by press “F5” or click “Start” button at the top menu.

Image 11

API Implementation

Let's begin with the backend implementation for the traffic light system using .NET 8. I'll guide you through setting up the necessary models, service logic, and controllers to manage the traffic lights according to the provided specifications.

Define Models and Enums

First, we'll define the necessary models and enumerations to represent the traffic lights and their states.

LightState Enum

This enum will represent the possible states of a traffic light.

C#
public enum LightState
{
    Green,
    Yellow,
    Red
} 

TrafficLight Model

This model will hold the current state of a traffic light and possibly other properties related to timing.

C#
public class TrafficLight
{
    public string Direction { get; set; } = string.Empty;
    public LightState CurrentState { get; set; }
    public string CurrentStateColor => CurrentState.ToString();
    public int GreenDuration { get; set; }
    public int YellowDuration { get; set; } = 5;
    public DateTime LastTransitionTime { get; set; }  // The last state transition
    public bool IsRightTurnActive { get; set; } = false;  // Check the right-turn signal
    public int GroupId { get; set; }  // 1 for North-South, 2 for East-West
}

Traffic Light Service

This service will handle the logic for timing and state transitions of the traffic lights.

C#
using TrafficLightsAPI.Models;
 
namespace TrafficLightsAPI.Services
{
    public class TrafficLightService
    {
        private List<TrafficLight> _lights;
 
        public TrafficLightService()
        {
            var currentDateTime = DateTime.Now;
            _lights = new List<TrafficLight>
            {
                new TrafficLight { Direction = "North", GreenDuration = 20, GroupId = 1, CurrentState = LightState.Green, LastTransitionTime = currentDateTime },
                new TrafficLight { Direction = "South", GreenDuration = 20, GroupId = 1, CurrentState = LightState.Green, LastTransitionTime = currentDateTime },
                new TrafficLight { Direction = "East", GreenDuration = 20, GroupId = 2, CurrentState = LightState.Red, LastTransitionTime = currentDateTime },
                new TrafficLight { Direction = "West", GreenDuration = 20, GroupId = 2, CurrentState = LightState.Red, LastTransitionTime = currentDateTime }
            };
        }
 
 
        public List<TrafficLight> RetrieveLights() => _lights;
 
        public void UpdateLights()
        {
            lock (_lights)
            {
                DateTime currentTime = DateTime.Now;
                bool isPeakHours = IsPeakHours(currentTime);
 
                AdjustSouthboundForNorthRightTurn(currentTime);
 
                foreach (var group in _lights.GroupBy(l => l.GroupId))
                {
                    bool shouldSwitchToYellow = group.Any(l => l.CurrentState == LightState.Green && ShouldSwitchFromGreen((currentTime - l.LastTransitionTime).TotalSeconds, isPeakHours, l.Direction));
                    bool shouldSwitchToRed = group.Any(l => l.CurrentState == LightState.Yellow && (currentTime - l.LastTransitionTime).TotalSeconds >= 5);
 
                    if (shouldSwitchToYellow)
                    {
                        foreach (var light in group)
                        {
                            if (light.CurrentState == LightState.Red)
                            {
                                break;
                            }
                            light.CurrentState = LightState.Yellow;
                            light.LastTransitionTime = currentTime;
                            if (light.Direction == "North")
                            {
                                light.IsRightTurnActive = false;
                            }
                        }
                    }
                    else if (shouldSwitchToRed)
                    {
                        foreach (var light in group)
                        {
                            light.CurrentState = LightState.Red;
                            light.LastTransitionTime = currentTime;
                        }
                        SetOppositeGroupToGreen(group.Key);
                    }
                }
            }
        }
 
        #region Private Methods
 
        private void SetOppositeGroupToGreen(int groupId)
        {
            int oppositeGroupId = groupId == 1 ? 2 : 1;
            foreach (var light in _lights.Where(l => l.GroupId == oppositeGroupId))
            {
                light.CurrentState = LightState.Green;
                light.LastTransitionTime = DateTime.Now;
            }
        }
 
        private bool IsPeakHours(DateTime time)
        {
            return (time.Hour >= 8 && time.Hour < 10) || (time.Hour >= 17 && time.Hour < 19);
        }
 
        private bool ShouldSwitchFromGreen(double elapsedSeconds, bool isPeakHours, string direction)
        {
            int requiredSeconds = direction == "North" || direction == "South" ?
                                  isPeakHours ? 40 : 20 :
                                  isPeakHours ? 10 : 20;
            return elapsedSeconds >= requiredSeconds;
        }
 
 
        private void AdjustSouthboundForNorthRightTurn(DateTime currentTime)
        {
            bool isPeakHours = IsPeakHours(currentTime);
            var northLight = _lights.Single(l => l.Direction == "North");
 
            if (northLight.CurrentState == LightState.Green && !northLight.IsRightTurnActive && ShouldActivateRightTurn((currentTime - northLight.LastTransitionTime).TotalSeconds, isPeakHours))
            {
                northLight.IsRightTurnActive = true;
                foreach (var light in _lights.Where(l => l.Direction != "North"))
                {
                    if (light.CurrentState != LightState.Red)
                    {
                        light.CurrentState = LightState.Red;
                        light.LastTransitionTime = currentTime;
                    }
                }
 
            }
        }
 
        private bool ShouldActivateRightTurn(double elapsedSeconds, bool isPeakHours)
        {
            // Activate right-turn signal for the last 10 seconds of the green phase
            int greenDuration = isPeakHours ? 40 : 20;
 
            return elapsedSeconds >= (greenDuration - 10);
        }
 
        #endregion
 
    }
}

Minimum API Endpoint

Remove Weather Forecast code which generated from the template. Then setup the new API endpoints and register services.

C#
using TrafficLightAPI.Services;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddSingleton<TrafficLightService>();
builder.Services.AddHostedService<TrafficLightBackgroundService>();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
 
app.UseHttpsRedirection();
 
app.MapGet("/trafficlights", (TrafficLightService trafficLightService) =>
{
    return Results.Ok(trafficLightService.RetrieveLights());
}).WithName("GetTrafficLights")
.WithOpenApi();
app.Run();

Background Service to Update Lights

Implementing a background job to automatically update the traffic lights in your .NET application is an efficient way to simulate a real-time traffic light system. We'll use a background service that periodically calls the UpdateLights method in the TrafficLightService. This approach allows the traffic light states to be updated independently of API requests, simulating real-world behavior where traffic lights change automatically.

Step 1: Define the Background Service

You can create a background service in .NET by deriving from BackgroundService. This service will run a timer that triggers the UpdateLights method at regular intervals.

C#
using Microsoft.Extensions.Hosting;
using System;
using System.Threading;
using System.Threading.Tasks;
 
public class TrafficLightBackgroundService : BackgroundService
{
    private readonly TrafficLightService _trafficLightService;
    private Timer _timer;
 
    public TrafficLightBackgroundService(TrafficLightService trafficLightService)
    {
        _trafficLightService = trafficLightService;
    }
 
    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _timer = new Timer(UpdateTrafficLights, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
 
        return Task.CompletedTask;
    }
 
    private void UpdateTrafficLights(object state)
    {
        _trafficLightService.UpdateLights();
    }
 
    public override async Task StopAsync(CancellationToken stoppingToken)
    {
        _timer?.Change(Timeout.Infinite, 0);
        await base.StopAsync(stoppingToken);
    }
}

Step 2: Register the Background Service

In the Program.cs or Startup.cs (depending on your project setup), you need to register this service so that it starts when your application does:

C#
builder.Services.AddHostedService<TrafficLightBackgroundService>();

Step 3: Modify TrafficLightService for Thread Safety

Since UpdateLights will now be called from a background service, ensure that any shared resources within TrafficLightService are accessed in a thread-safe manner. You might need to lock resources or use concurrent collections if multiple threads will modify the traffic light states.

C#
public void UpdateLights()
{
    lock (_lights)
    {
        // Existing logic to update lights here
    }
}

React Frontend Implementation

Let's start setting up the React frontend with TypeScript to interact with the traffic light backend you've created. We'll build a simple interface to display the traffic lights and update them in real-time based on the data received from the backend.

Open traffic-light folder with Visual Studio Code.

Integrating Material-UI (recently rebranded as MUI) with React project will give a polished look to traffic light system and provide a cohesive user experience.

Install Material-UI

Shell
npm install @mui/material @emotion/react @emotion/styled

Creating the TrafficLight Component

Create a new component to display a single traffic light. This component will receive props for the current light state and display the appropriate color.

TypeScript
// src/components/TrafficLight.tsx
import { Paper } from '@mui/material';
import { styled } from '@mui/system';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faArrowsTurnRight } from '@fortawesome/free-solid-svg-icons';
 
interface TrafficLightProps {
  direction: string;
  currentState: string;
  isRightTurnActive: boolean;
}
 
const Light = styled(Paper)(({ theme, color }) => ({
  height: '100px',
  width: '100px',
  borderRadius: '50%',
  display: 'flex',
  justifyContent: 'center',
  alignItems: 'center',
  backgroundColor: color,
  color: theme.palette.common.white,
  fontSize: '1.5rem',
  fontWeight: 'bold',
  margin: '10px'
}));
 
const TrafficLight: React.FC<TrafficLightProps> = ({ direction, currentState, isRightTurnActive }) => {
  const getColor = (state: string) => {
    switch (state) {
      case 'Green':
        return 'limegreen';
      case 'Yellow':
        return 'yellow';
      case 'Red':
        return 'red';
      default:
        return 'grey';  // default color when state is unknown
    }
  };
 
  return (
    <div>
      <Light color={getColor(currentState)} elevation={4}>
        {direction}
      </Light>
      {isRightTurnActive && direction === 'North' && (
        <div style={{ color: 'green', marginTop: '10px', fontSize: '24px' }}>
          <FontAwesomeIcon icon={faArrowsTurnRight} /> Turn Right
        </div>
      )}
    </div>
  );
};
export default TrafficLight;

Creating the TrafficLightsContainer Component

This component will manage fetching the traffic light states from the backend and updating the UI accordingly.

TypeScript
// src/components/TrafficLightsContainer.tsx
import React, { useEffect, useState } from 'react';
import TrafficLight from './TrafficLight';
import { Grid } from '@mui/material';
 
interface TrafficLightData {
  direction: string;
  currentStateColor: string;
  isRightTurnActive: boolean;
}
 
const TrafficLightsContainer: React.FC = () => {
  const [trafficLights, setTrafficLights] = useState<TrafficLightData[]>([]);
 
  useEffect(() => {
    const fetchTrafficLights = async () => {
      try {
        const response = await fetch('trafficlights');
        const data = await response.json();
        setTrafficLights(data);
      } catch (error) {
        console.error('Failed to fetch traffic lights', error);
      }
    };
 
    fetchTrafficLights();
    const interval = setInterval(fetchTrafficLights, 1000); // Poll every 5 seconds
 
    return () => clearInterval(interval); // Cleanup on unmount
  }, []);
 
  return (
    <Grid container justifyContent="center">
        {trafficLights.map(light => (
            <TrafficLight key={light.direction} direction={light.direction} currentState={light.currentStateColor} isRightTurnActive={light.isRightTurnActive} />
        ))}
    </Grid>
);
};

export default TrafficLightsContainer;

Update App Component

Update the main App component to include the TrafficLightsContainer.

TypeScript
// src/App.tsx
import './App.css';
import TrafficLightsContainer from './components/TrafficLightsContainer';
import { CssBaseline, Container, Typography } from '@mui/material';

function App() {
  return (
    <div className="App">
      <CssBaseline />
      <Container maxWidth="sm">
        <header className="App-header">
          <Typography variant="h4" component="h1" gutterBottom>
            Traffic Lights System
          </Typography>
          <TrafficLightsContainer />
        </header>
      </Container>
    </div>
  );
}

export default App;

Update Service Proxy

Open vite.config.ts, change proxy from “/weatherforecast” to “/trafficlights”.

Image 12

Run the Application

Back to Visual Studio, now the solution looks like this:

Image 13

Click “Start” button or press F5.

Image 14

Conclusion

RESTful API Consumption:

The React frontend communicates with the .NET backend via RESTful APIs. This interaction involves requesting and receiving traffic light data, which React then uses to update the UI accordingly.

Decoupled Architecture:

The frontend and backend are loosely coupled. The backend can operate independently of the frontend, focusing on logic and data management, while the frontend focuses on presentation and user interactions. This separation of concerns enhances the scalability and maintainability of the application.

This article encapsulates how React integrates with a .NET 8 Minimal API to create a dynamic and responsive traffic light management system, demonstrating modern web development practices with decoupled architecture for efficient and scalable application design.

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)