目录
使用Visual Studio“独立Typescript React项目”模板创建解决方案
步骤 3:修改 TrafficLightService 以确保线程安全
介绍
.NET中最小API的概念侧重于通过减少样板代码来简化Web API的创建,使你能够更简洁地定义终结点。这些API利用ASP.NET Core的核心功能,旨在以最少的编码工作快速创建HTTP API。它们非常适合微服务和小型应用程序,在这些应用程序中,您希望避免完整MVC应用程序的复杂性。在ASP.NET Core Minimal API中,您可以直接在Program.cs文件中定义终结点,而无需控制器或其他基架。
假设有4组灯,如下所示。
信号灯 1:车辆向南行驶
信号灯 2:车辆向西行驶
信号灯 3:车辆向北行驶
信号灯 4:车辆向东行驶
在同一轴上行驶的交通灯可以同时为绿色。在正常时间,所有灯保持绿色20秒,但在高峰时段,北灯和南灯为绿色40秒,而西灯和东灯为绿色10秒。高峰时段为08:00至10:00和17:00至19:00。在显示红灯之前,黄灯会显示5秒钟。红灯一直亮着,直到交叉交通变为红色至少4秒,一旦红灯熄灭,绿色就会在所需的时间内显示。
在本文中,我们实现了React前端和.Net 8最小API后端。后端将包含正在运行的交通信号灯的逻辑和状态。前端将是交通信号灯的可视化表示,数据从后端提供。
使用Visual Studio“独立Typescript React项目”模板创建解决方案
先决条件
- Visual Studio 2022(17.1或更高版本)。
- Node.js 18.20或以上
创建React项目
启动Visual Studio 2022,选择“独立TypeScript React项目”。
进入“其他信息”窗口后,选中“为空Web API项目添加集成ASP.NET”选项。此选项将文件添加到您的Angular模板中,以便稍后可以将其与ASP.NET Core项目挂钩。
选择此选项,将在创建项目后设置代理。本质上,此模板运行“npx create-react-app”来创建一个react应用程序。
创建Web API项目
在同一解决方案中ASP.NET添加Core Web API项目。
将此后端项目命名为“TrafficLightsAPI”。选择“.NET 8.0”作为框架。
请注意,该模板生成最小API样本,program.cs天气预报。
设置启动项目
右键单击解决方案,然后选择“设置启动项目”。将启动项目从“单个启动项目”更改为“多个启动项目”。为每个项目的操作选择“开始”。
确保后端项目并将其移动到前端上方,以便它首先启动。
更改客户端代理设置
在TrafficLightsAPI https启动配置文件UI中检查APP URL。
然后在React项目根文件夹中打开vite.config.ts。将https://localhost:5001更新到https://localhost:7184 。
现在按“F5”或单击顶部菜单中的“开始”按钮启动解决方案。
API实现
让我们从使用.NET 8的交通信号灯系统的后端实现开始。我将指导您设置必要的模型、服务逻辑和控制器,以根据提供的规范管理交通信号灯。
定义模型和枚举
首先,我们将定义必要的模型和枚举来表示交通信号灯及其状态。
LightState枚举
此枚举将表示交通信号灯的可能状态。
public enum LightState
{
Green,
Yellow,
Red
}
红绿灯模型
该模型将保存交通信号灯的当前状态以及可能与时间相关的其他属性。
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
}
红绿灯服务
此服务将处理交通信号灯的定时和状态转换逻辑。
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
}
}
最小API端点
删除从模板生成的天气预报代码。然后设置新的API端点并注册服务。
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();
用于更新灯光的后台服务
在.NET应用程序中实现后台作业以自动更新交通信号灯是模拟实时交通信号灯系统的有效方法。我们将使用定期调用TrafficLightService中的UpdateLights方法的后台服务。这种方法允许独立于API请求更新交通信号灯状态,模拟交通信号灯自动变化的真实行为。
步骤 1:定义后台服务
可以通过派生自BackgroundService在.NET中创建后台服务。此服务将运行一个计时器,该计时器定期触发UpdateLights方法。
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);
}
}
步骤 2:注册后台服务
在Program.cs或Startup.cs(取决于项目设置)中,需要注册此服务,以便在应用程序执行以下操作时启动:
builder.Services.AddHostedService<TrafficLightBackgroundService>();
步骤 3:修改 TrafficLightService 以确保线程安全
由于现在将从后台服务调用UpdateLights,因此请确保以线程安全的方式访问TrafficLightService中的任何共享资源。如果多个线程将修改交通信号灯状态,则可能需要锁定资源或使用并发集合。
public void UpdateLights()
{
lock (_lights)
{
// Existing logic to update lights here
}
}
React前端实现
让我们开始使用TypeScript设置React前端,以与您创建的红绿灯后端进行交互。我们将构建一个简单的界面来显示交通信号灯,并根据从后端接收的数据实时更新它们。
使用Visual Studio Code打开traffic-light文件夹。
将Material-UI(最近更名为MUI)与React项目集成,将为交通信号灯系统提供精美的外观,并提供有凝聚力的用户体验。
安装Material-UI
npm install @mui/material @emotion/react @emotion/styled
创建TrafficLight组件
创建一个新组件以显示单个交通信号灯。该组件将接收当前光源状态的道具并显示适当的颜色。
// 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;
创建TrafficLightsContainer组件
此组件将管理从后端获取交通信号灯状态并相应地更新UI。
// 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;
更新应用组件
更新主App组件以包含TrafficLightsContainer。
// 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;
更新服务代理
打开vite.config.ts,将代理从“/weatherforecast”更改为“/trafficlights”。
运行应用程序
回到Visual Studio,现在解决方案如下所示:
单击“开始”按钮或按F5。
结论
RESTful API使用:
React前端通过RESTful API与.NET后端进行通信。这种交互涉及请求和接收交通信号灯数据,然后React使用这些数据相应地更新UI。
解耦架构:
前端和后端是松散耦合的。后端可以独立于前端运行,专注于逻辑和数据管理,而前端则专注于演示和用户交互。这种关注点分离增强了应用程序的可伸缩性和可维护性。
本文介绍了React如何与.NET 8最小API集成,以创建动态且响应迅速的交通灯管理系统,并演示采用解耦架构实现高效且可扩展的应用程序设计的现代Web开发实践。
https://www.codeproject.com/Articles/5381609/NET-8-Minimum-API-and-React-Frontend