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”.
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.
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.
Name this backend project as “TrafficLightsAPI”. Select .NET 8.0 for framework.
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.
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.
Then open vite.config.ts in your React project root folder. Update https://localhost:5001 to https://localhost:7184 .
Now start the solution by press “F5” or click “Start” button at the top menu.
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.
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.
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; }
public bool IsRightTurnActive { get; set; } = false;
public int GroupId { get; set; }
}
Traffic Light Service
This service will handle the logic for timing and state transitions of the traffic lights.
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)
{
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.
using TrafficLightAPI.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<TrafficLightService>();
builder.Services.AddHostedService<TrafficLightBackgroundService>();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
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.
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:
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.
public void UpdateLights()
{
lock (_lights)
{
}
}
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
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.
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';
}
};
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.
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);
return () => clearInterval(interval);
}, []);
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.
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”.
Run the Application
Back to Visual Studio, now the solution looks like this:
Click “Start” button or press F5.
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.