将 React 与 ASP.NET Core 集成提供了一种开发可扩展全栈应用程序的强大方法。 React 能与 ASP.NET Core 无缝协作,构建动态、响应式的 Web 应用程序。 本章将指导您完成 React 与 ASP.NET Core 的配置步骤,使创建高性能、高效的全栈应用程序变得更加容易。 您将学习如何在 React 和 ASP.NET Core 之间进行 API 调用,确保这两种 强大技术之间的高效通信。
您还将学习如何优化前端和后端,以提供响应式的用户体验,充分利用这两种技术的优势。 这些技能对于希望使用市场上最新版本技术栈构建现代 Web 应用程序的开发人员至关重要。
本章我们将涵盖以下 关键主题:
- 使用 ASP.NET Core 搭建 React 环境
- 构建在线投票与 表决系统
通过本章学习,您将掌握如何开发与 ASP.NET Core 后端交互的 React 应用程序,并遵循 软件开发的最佳实践。
20年工作经验,承接微信小程序,App,网站,网站后端开发。有意向私聊我哈。微信:akluse
使用 ASP.NET Core 配置 React
在 本节中,我们将设置一个用于 将 React 与 ASP.NET Core 集成的开发环境,配置 ASP.NET Core 以提供 React 应用程序服务,并创建 React 项目的 基础结构以开始开发。
假设您已根据前一章的实践练习安装了 Node.js 和 npm,要使用 React,您将需要 create-react-app 工具,该工具为 React 应用程序提供了简化的设置。 此工具有助于管理 React 开发环境并创建基本的项目结构。 您可以在常规的 命令提示符中 使用以下命令全局安装它:
npm install -g create-react-app
正确安装所有内容后,我们可以继续在 下一节 中创建 ASP.NET Core Web API 项目。
创建 ASP.NET Core Web API 项目
创建 ASP.NET Core Web API 项目与前一章遵循的步骤非常相似,但我们将再次进行说明。 要创建项目,请遵循 以下步骤:
- 打开 Visual Studio,通过选择 ASP.NET Core Web API 模板创建新项目,如 图 14 .1 所示 :

图 14.1:ASP.NET Core Web API 模板
- 选择 ASP.NET Core Web API 模板后,为项目定义一个位置和一个名称,如图 14.2 所示:

图 14.2:Web API 项目名称和位置
- 在定义项目位置和名称后,选择.NET 9 版本并勾选"不使用顶级语句的控制器"选项,如 图 14 .3 所示 :

图 14.3:ASP.NET Core Web API 配置界面
- 创建 ASP.NET Core Web API 项目后, 需要安装 SPAServices.Extensions 包,该包将用于配置 ASP.NET Core 项目以托管 React 应用。 要安装此包,请在 Visual Studio 解决方案资源管理器中右键单击新建的项目,选择 管理 NuGet 程序包... ,如 图 14 .4 所示 :

图 14.4:"管理 NuGet 程序包..."选项
在 NuGet 包管理器窗口中,搜索 Microsoft.AspNetCore.SpaServices.Extensions 包并确认安装,如图 14.5 所示 :

图 14.5:SpaServices.Extensions 安装
之后,您需要对 Program.cs 文件进行一些修改,以配置 ASP.NET Core 来提供 Angular 应用程序服务。 按如下方式修改 Program.cs 文件(以下展示的是高亮显示的 代码 行):
namespace Chapter14
{
public class Program
{
public static void Main(string[] args)
{
var builder =
WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddSpaStaticFiles(
configuration =>
{
configuration.RootPath =
"ClientApp/dist";
});
var app = builder.Build();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
}
}
在此配置中,AddSpaStaticFiles 用于提供 Angular 应用的静态文件。RootPath 被配置指向 ClientApp/dist 文件夹,该文件夹是 React 应用生产构建文件的存放位置。
ASP.NET Core 应用程序现已配置完成,可用于托管我们稍后将在本章通过分步方法创建的 React 应用程序。 在下一节中,我们将通过扩展 本节开发的 ASP.NET Core Web API 项目来构建一个示例应用程序。
构建在线投票系统
在本节中,我们将构建一个在线投票系统,该工具允许用户创建投票并参与表决,通过微软开发的 SignalR 库实现投票结果的实时更新,该库可简化服务器与客户端应用之间的实时通信。 该应用将包含基本的 CRUD 操作,使用户能够创建带有多选项的新投票、提交表决并实时查看结果。 应用后端将使用先前创建的 ASP.NET Core Web API 项目来定义前端调用的端点。 此外,我们将从零开始构建 React 应用,配置前后端之间的数据通信,并整合通过 SignalR 实现的实时功能。
从技术角度来看,该应用将运用诸如 RESTfulAPI 开发、使用 SignalR 实现实时通信以及内存数据库交互等关键概念(仅用于演示目的)。 此外,我们将利用 React 内置功能如路由和状态管理,并在前端实现 SignalR 以实现实时投票更新。 通过使用 JavaScript 和 Hooks,我们将在 React 应用与 ASP.NET Core Web API 项目之间保持数据流的一致性。 在本节结束时,您将清晰了解如何通过循序渐进的方式将 React 与 ASP.NET Core 集成,包括使用 SignalR 实现实时功能。
接下来,我们将详细介绍在线投票系统后端需要创建的领域模型。
创建领域模型
在本节中,我们将定义在线投票系统的核心领域模型。 领域模型定义了封装应用程序业务逻辑和数据结构的关键实体。 对于我们的应用,主要实体包括 Poll(投票) 、 Option(选项) 、 Vote(投票记录) ,以及几个请求模型,如 CreatePollRequest(创建投票请求) 、 VoteRequest(投票请求) 、 和 UpdatePollRequest(更新投票请求) .
我们将把这些实体存储在 ASP.NET Core 后端的内存中,这样无需实际数据库即可模拟数据存储。 这种配置非常适合快速开发和测试,使我们能够快速构建应用程序原型。 在实际生产环境中,通常会使用 Entity Framework Core(EF Core)或其他对象关系映射器 (ORM)来处理后端与数据库之间的交互。EF Core 是微软为.NET 开发的现代 ORM,允许开发者使用 C#对象而非 SQL 查询来集成应用程序与数据库。
尽管我们将实体存储在内存中,但仍需在后端正确定义它们,以便内存中的存储系统能够有效使用。
要为项目创建必要的模型,请按照 以下步骤操作:
- 在 ASP.NET Core Web API 项目中,使用 Visual Studio 的解决方案资源管理器,在项目根目录下创建一个名为 Models 的文件夹。
- 在 Models 文件夹中,创建一个名为 Option.cs 的文件,并添加 以下代码:
namespace Chapter14.Models { public class Option { public int Id { get; set; } public string Text { get; set; } public int PollId { get; set; } public int Votes { get; set; } = 0; } }在上述代码片段中,命名空间为 Chapter14.Models。 请根据您的项目名称修改命名空间。 本章中所有代码示例均适用此规则。
Option 模型代表投票中的单个选项。 每个选项包含唯一 ID、选项描述文本、已获票数等属性,并通过 PollId 关联所属投票。 这使得每个选项都能被唯一标识并与 正确的投票 相关联。
- 接下来,在 Models 文件夹中创建名为 Poll.cs 的文件,并添加 以下代码:
namespace Chapter14.Models { public class Poll { public int Id { get; set; } public string Question { get; set; } public List<Option> Options { get; set; } = new List<Option>(); public DateTime CreatedAt { get; set; } public DateTime ExpiresAt { get; set; } } }投票 模型是系统的核心实体,代表一个包含多个投票选项的投票活动。 它具有用于识别的 ID、用户投票的问题以及选项列表。 投票 还包括创建时间戳( CreatedAt )和过期时间戳( ExpiresAt )。 这些时间戳对于管理 投票活动 的生命周期至关重要。
- 在您的 Models 文件夹中,创建一个名为 Vote.cs 的文件并添加 以下代码:
namespace Chapter14.Models { public class Vote { public int Id { get; set; } public int PollId { get; set; } public int OptionId { get; set; } public string UserId { get; set; } public DateTime VotedAt { get; set; } } }该投票模型用于记录用户投出的每一张选票。 每张投票包含一个 Id 值、对应的 PollId 和 OptionId 值引用,以及可选的 UserId 值(用于追踪目的)。 同时通过 VotedAt 时间戳记录投票时间。 虽然本系统允许用户多次投票,但 UserId 仍可用于数据分析或追踪用途 。
- 在您的 Models 文件夹中,创建一个名为 CreatePollRequest.cs 的文件并添加 以下代码:
namespace Chapter14.Models { public class CreatePollRequest { public string Question { get; set; } public List<string> Options { get; set; } public DateTime ExpiresAt { get; set; } } }CreatePollRequest 模型将用于创建新的投票。 该模型包含投票问题、选项列表以及定义投票结束时间的 ExpiresAt 时间戳。 此结构允许前端将新投票提交至后端 进行创建。
- 在您的 Models 文件夹中,创建一个名为 UpdatePollRequest.cs 的文件,并添加 以下代码:
namespace Chapter14.Models { public class UpdatePollRequest { public DateTime ExpiresAt { get; set; } } }类似地, UpdatePollRequest 模型用于修改现有投票的特定属性,特别是 ExpiresAt 属性,该属性允许延长或调整投票的 过期日期。
- 在您的 Models 文件夹中,创建 一个名为 VoteRequest.cs 的文件,并添加 以下代码:
namespace Chapter14.Models { public class VoteRequest { public string UserId { get; set; } public int OptionId { get; set; } } }最后,VoteRequest 模型用于提交投票。 它包含 UserId 值(可选)以及用户所投票项的 OptionId 值。 该请求模型确保后端能接收到必要信息,以将投票正确登记到对应的调查和选项上。
这些领域模型将被存储在 ASP.NET Core 后端的内存中,模拟数据存储而无需数据库支持。 这种配置非常适合快速原型设计和测试,而在生产环境中,通常会使用 EF Core 等工具与持久化数据库进行集成。 既然模型已经定义完成,我们可以继续设置内存存储库来有效管理这些实体。
创建池服务和投票中心
要构建在线投票系统, 我们需要在后端管理投票活动与票数统计,并向前端提供实时更新。 这正是需要 SignalR 与 WebSockets 发挥作用的地方。WebSockets 通过单一连接提供双向通信通道,使服务器能随时向连接的客户端发送数据,非常适合需要实时交互的应用场景。SignalR 是一个简化应用程序实时网络功能实现的库。 它对 WebSockets 进行抽象封装,并在 WebSockets 不可用时采用回退方案(如长轮询),为实时通信提供统一解决方案。
为实现实时投票更新,我们将首先创建一个 SignalR 中心,该中心负责在投票提交后立即将更新的票数广播给所有连接的客户端。 SignalR 中心作为服务器与连接客户端之间实时双向通信的核心类,允许服务器直接调用客户端函数以实现无缝更新和交互。
按照以下步骤创建 SignalR 中心:
- 首先,在您的项目中创建一个 Hubs 文件夹,与 Models 文件夹同级。 在此文件夹内,创建一个名为 VoteHub.cs 的文件,并包含以下代码:
using Chapter14.Models; using Microsoft.AspNetCore.SignalR; namespace Chapter14.Hubs { public class VoteHub : Hub { public async Task BroadcastVoteUpdate( int pollId, List<Option> options ) { await Clients.All.SendAsync( "ReceiveVoteUpdate", pollId, options ); } } }VoteHub 类是一个 SignalR 集线器,用于促进服务器与客户端之间的通信。 其中 BroadcastVoteUpdate 方法会向所有 已连接的客户端发送实时投票更新,确保它们始终获取最新的投票结果而无需刷新 页面。
- 接下来我们将创建 PollService 用于管理系统中的投票和表决。 该服务将处理创建新投票、记录表决结果及广播结果等操作 通过 VoteHub .
在您的项目中,创建一个 Services 文件夹,与 Models 和 Hubs 同级。 在 Services 文件夹内,创建一个名为 PollService.cs 的文件,并包含以下代码:
using Chapter14.Hubs; using Chapter14.Models; using Microsoft.AspNetCore.SignalR; namespace Chapter14.Services { public class PollService { private readonly List<Poll> _polls = new(); private readonly List<Vote> _votes = new(); private readonly IHubContext<VoteHub> _hubContext; public PollService( IHubContext<VoteHub> hubContext ) { _hubContext = hubContext; } public List<Poll> GetPolls() { return _polls; } public Poll GetPoll(int pollId) { return _polls.FirstOrDefault( p => p.Id == pollId ); } public Poll CreatePoll( string question, List<string> options, DateTime expiresAt ) { var newPoll = new Poll { Id = _polls.Count > 0 ? _polls.Max(p => p.Id) + 1 : 1, Question = question, CreatedAt = DateTime.UtcNow, ExpiresAt = expiresAt }; foreach (var optionText in options) { newPoll.Options.Add(new Option { Id = newPoll.Options.Count > 0 ? newPoll.Options.Max( o => o.Id) + 1 : 1, Text = optionText, PollId = newPoll.Id }); } _polls.Add(newPoll); return newPoll; } public bool Vote( int pollId, int optionId, string userId ) { var poll = GetPoll(pollId); if (poll == null || poll.ExpiresAt < DateTime.UtcNow ) { return false; } var option = poll.Options.FirstOrDefault( o => o.Id == optionId ); if (option == null) { return false; } option.Votes++; _votes.Add(new Vote { Id = _votes.Count > 0 ? _votes.Max( v => v.Id) + 1 : 1, PollId = pollId, OptionId = optionId, UserId = userId, VotedAt = DateTime.UtcNow }); _hubContext.Clients.All.SendAsync( "ReceiveVoteUpdate", pollId, poll.Options ); return true; } public List<Option> GetPollResults(int pollId) { var poll = GetPoll(pollId); return poll?.Options ?? new List<Option>(); } } }让我们 分解前面的 代码块:
- PollService 类是在线投票系统的核心,负责管理内存中的投票活动及投票功能。 它执行关键任务,包括创建投票、登记选票以及通过 SignalR 广播实时投票结果。 该服务设计用于处理与投票活动及其关联选项相关的所有操作。 PollService 的核心采用简单的内存存储方案,使用两个列表:一个用于存储 Poll 对象,另一个存储 Vote 对象。 这些列表模拟数据库表,但完全在内存中运行,使得开发过程更快更简便,无需设置数据库。 _polls 列表存储投票活动,每个活动包含一个问题及一组选项。 该 _votes 列表存储了单个投票记录,可用于追踪 用户选择。
- 该服务始于其构造函数,该函数接收一个 IHubContext<VoteHub> 实例。 此对象是 SignalR 的一部分,负责向所有连接的客户端广播实时消息。SignalR 通过抽象化 WebSockets(或备用技术)来实现实时通信,无需客户端显式请求更新即可将数据从服务器发送到客户端。 在此场景中,VoteHub 用于在投票注册后立即发送票数更新。 该服务的主要功能之一是通过 GetPolls 方法获取所有投票。 该方法仅返回内存中存储的所有投票列表。 GetPoll 方法通过 ID 检索特定投票,使用 LINQ 在 内存集合中搜索。
- CreatePoll 方法用于创建新投票。 该方法接收投票问题、选项列表和投票截止日期等参数。 投票 ID 通过从现有最高 ID 递增自动生成,模拟关系型数据库中的自增主键。 每个投票选项也被赋予唯一 ID,并通过外键关系( PollId )与投票关联。 创建完成后,新投票会被添加到 _polls 列表中。
- 投票功能由 Vote 方法处理,这是服务的核心组成部分。 该方法首先通过 ID 获取相关投票项。 通过检查是否过期来确保投票仍然有效。 如果投票存在且有效,该方法会进一步检查所选投票选项是否存在于该投票中。 当两个条件都满足时,系统将通过增加所选选项的 Votes 计数来记录投票。 投票本身也会存储在 _votes 列表中,以便后续分析或审计追踪,尽管系统并不限制用户多次投票 。
- 一旦投票被登记,应用程序的实时特性便开始发挥作用。 通过 SignalR 的集线器上下文(_hubContext),服务将更新后的票数广播给所有已连接的客户端。 这是通过 SendAsync 方法实现的,该方法调用客户端的 ReceiveVoteUpdate 事件,并将更新后的投票选项作为参数传递。 这使得前端能够即时显示新结果,用户无需刷新页面或手动请求更新数据 。
- 该服务还包含一个名为 GetPollResults 的方法,通过返回投票选项列表(其中每个选项包含已获得的票数)来获取特定投票的当前计票结果。 该方法确保即使是在投票开始后才加入的用户,也能查看 该投票的当前结果。
总而言之, PollService 不仅管理投票的增删改查操作,还集成了 SignalR 以实时广播投票更新。 它通过将数据存储在内存中、记录投票并在新投票产生时同时更新所有客户端,模拟了一个完整的投票系统。 与 SignalR 的集成确保系统能为用户提供即时反馈,创造动态且交互式的投票体验。 该设计有效展示了内存数据存储的使用、与 SignalR 的实时通信,以及在 ASP.NET Core 应用程序中管理投票的实际应用。
- 最后,为了正确集成在线投票系统并确保其支持实时更新和跨域请求,需要对 Program.cs 文件进行一些重要修改,如 以下代码所示:
using Chapter14.Hubs; using Chapter14.Services; namespace Chapter14 { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddSignalR(); builder.Services .AddSingleton<PollService>(); builder.Services .AddSpaStaticFiles(configuration => { configuration.RootPath = "ClientApp/dist"; }); builder.Services.AddCors(options => { options.AddPolicy( "AllowSpecificOrigins", policy => { policy.WithOrigins( "http://localhost:3000") .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials(); }); }); var app = builder.Build(); app.UseStaticFiles(); app.UseSpaStaticFiles(); app.UseAuthorization(); app.UseCors("AllowSpecificOrigins"); app.MapControllers(); app.MapHub<VoteHub>("/voteHub"); app.Run(); } } }在此Program.cs文件的配置中,需要进行特定的调整,以支持与SignalR的实时通信,允许前端和后端之间的跨域资源共享(CORS),并注册处理投票操作的内存PollService。
让我们分解前面的 代码块:
- 第一个变化是通过 app.MapHub<VoteHub>("/voteHub"); 方法添加了 SignalR。 这将 VoteHub 映射到 /voteHub 端点,使客户端能够与后端建立实时连接。 每当有投票提交时,SignalR 将通过此中心向所有连接的客户端广播更新后的投票结果。 前端必须正确引用此端点,确保 React 应用程序能够连接到它并在不刷新 页面的情况下接收更新。
- 接下来,配置 CORS 策略以允许运行在 http://localhost:3000 上的 React 前端与 ASP.NET Core 后端进行交互。 通过 builder.Services.AddCors() 方法设置名为 AllowSpecificOrigins 的策略,允许来自 http://localhost:3000 的请求。 这确保前端可以向后端发起请求,执行诸如提交投票、获取投票结果以及连接 SignalR 中心等任务。 其中 AllowCredentials() 方法对于 SignalR 的正常运行至关重要,因为它需要具备在 WebSocket 连接过程中处理认证 cookie 或头信息的能力。
- 最后,PollService 通过 builder.Services.AddSingleton<PollService>(); 被注册为单例服务。 该服务处理投票系统的核心逻辑,如创建投票、登记选票和获取投票结果。 通过将其注册为单例,PollService 的同一实例将在整个应用程序中共享,确保内存数据在多个请求和 SignalR 广播之间保持一致。 这对于维护投票和计票在内存中的状态至关重要,特别是在每次客户端交互时。
在本节中,我们添加了必要的后端服务,这些服务将支持使用 SignalR 中心建立客户端与服务器应用之间的实时通信。 我们还创建了 PoolService,其中包含内存数据库所需的所有 CRUD 操作。 下一节中,我们将为 Poll 和 Vote 实体创建控制器,这将使我们能够处理创建投票、投出选票和获取投票结果的 API 请求。
创建控制器
为了正确实现在线投票系统的后端 API 管理功能,我们需要在 ASP.NET Core 项目中创建必要的控制器,以暴露供前端(React 应用)交互的 API 接口。 第一个控制器是 PoolsController,该控制器负责暴露对投票数据进行操作的 API 端点,包括创建投票、提交选票和获取投票结果等功能。 通过使用 ASP.NET Core 的 Web API 功能,该控制器将作为前端与后端逻辑的中介,确保 GET、POST 和 PUT 等 HTTP 请求得到正确处理。 创建此控制器后,您将建立允许用户与系统中投票功能交互的 API 路由,同时利用底层服务层(PollService)来执行所需的操作。
要为 您的 ASP.NET Core 应用程序创建控制器,请在 ASP.NET Core 项目中创建一个名为 PoolsController.cs 的文件,将其置于 Controllers 文件夹内,并指定 以下代码:
using Chapter14.Models;
using Chapter14.Services;
using Microsoft.AspNetCore.Mvc;
namespace Chapter14.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class PollsController : ControllerBase
{
private readonly PollService _pollService;
public PollsController(PollService pollService)
{
_pollService = pollService;
}
[HttpGet]
public ActionResult<List<Poll>> GetAllPolls()
{
var polls = _pollService.GetPolls();
return Ok(polls);
}
[HttpGet("{pollId}")]
public ActionResult<Poll> GetPoll(int pollId)
{
var poll = _pollService.GetPoll(pollId);
if (poll == null)
{
return NotFound(new
{
Message = "Poll not found"
});
}
return Ok(poll);
}
[HttpPost]
public ActionResult<Poll> CreatePoll(
[FromBody] CreatePollRequest request)
{
if (string.IsNullOrEmpty(request.Question) ||
request.Options == null ||
request.Options.Count < 2)
{
return BadRequest(new
{
Message = "Invalid poll data. A poll
must have a question and at least two
options."
});
}
var poll = _pollService.CreatePoll(
request.Question,
request.Options,
request.ExpiresAt
);
return CreatedAtAction(
nameof(GetPoll),
new { pollId = poll.Id },
poll
);
}
[HttpPost("{pollId}/vote")]
public ActionResult SubmitVote(
int pollId,
[FromBody] VoteRequest request
)
{
if (string.IsNullOrEmpty(request.UserId) ||
request.OptionId <= 0
)
{
return BadRequest(new
{
Message = "Invalid vote data. UserId
and OptionId are required."
});
}
var result = _pollService.Vote(
pollId,
request.OptionId,
request.UserId
);
if (!result)
{
return BadRequest(new
{
Message = "Unable to cast vote. Either
the poll has expired, the option is
invalid, or the user has already
voted."
});
}
return Ok(new
{
Message = "Vote successfully cast."
});
}
[HttpGet("{pollId}/results")]
public ActionResult<List<Option>>
GetPollResults(int pollId)
{
var results =
_pollService.GetPollResults(pollId);
if (results == null || results.Count == 0)
{
return NotFound(new
{
Message = "Poll not found or no votes
have been cast yet."
});
}
return Ok(results);
}
[HttpPut("{pollId}")]
public ActionResult UpdatePoll(
int pollId,
[FromBody] UpdatePollRequest request
)
{
var poll = _pollService.GetPoll(pollId);
if (poll == null)
{
return NotFound(new
{
Message = "Poll not found."
});
}
poll.ExpiresAt = request.ExpiresAt;
return Ok(poll);
}
}
}
在 PollsController 中,构造函数通过依赖注入方式注入了 PollService 的实例。 这种设计使得控制器能够与存储在内存中的投票管理逻辑进行交互。 通过使用依赖注入,控制器与服务实现了解耦,便于测试和未来必要时替换服务。 该控制器负责处理创建、更新、投票和获取投票数据的 API 请求,并通过服务广播实时更新。
GetAllPolls 方法 通过调用 GetPolls 方法从 PollService 中检索系统中的所有投票数据。 返回结果附带 HTTP 200 OK 状态码,表示数据检索成功。 Ok() 方法会自动将投票列表序列化为 JSON 格式,可供前端(如 React 应用)使用。
GetPoll 方法根据指定的 pollId 获取特定投票。 如果投票存在,则返回 HTTP 200 OK 状态码及投票数据。 如果未找到对应 ID 的投票,控制器将返回 HTTP 404 Not Found 状态码及一条 错误信息 。
CreatePoll 方法负责创建新的投票。 它接收一个 CreatePollRequest 对象作为输入,该对象包含投票问题、选项和截止日期。 该方法首先会验证投票数据是否有效(即必须提供问题内容和至少两个选项)。 如果数据无效,将返回 HTTP 400 Bad Request 并附带错误描述信息。 若数据有效,则通过服务的 CreatePoll 方法创建投票,并返回 HTTP 201 Created 状态码及该 新投票 的访问 URL。
SubmitVote 方法用于处理特定投票的提交操作。 该方法接收一个 pollId 参数和一个 VoteRequest 对象,该对象包含投票所需的 UserId 和 OptionId 值。 方法会验证投票数据的有效性,确保同时提供了 UserId 和 OptionId 。 若数据无效,则返回 HTTP 400 Bad Request 状态码及相应的错误信息。 若投票成功提交(即投票有效、未过期且选项存在),方法将返回 HTTP 200 OK 状态码及成功消息。 如果投票出现任何问题(例如投票已过期或选项无效),则返回 HTTP 400 错误请求 .
GetPollResults 方法用于获取指定投票的当前结果。 该方法通过 pollId 查找对应的投票及其选项。 如果投票存在且已有结果,则返回 HTTP 200 OK 状态码及包含选项和对应票数的数据。 若投票不存在或尚未收到任何投票,则返回 HTTP 404 Not Found 状态码及相应的 错误信息。
最后, UpdatePoll 方法允许更新现有投票的截止日期。 它接收一个 pollId 值和一个包含新截止日期的 UpdatePollRequest 对象。 如果投票存在,其截止日期将被更新,并返回带有 HTTP 200 OK 状态的投票信息。 如果未找到投票,该方法将返回 HTTP 404 Not Found 错误。 这一 操作允许在需要时延长或修改投票的生命周期 。
既然后端已全部完成,在下一节中,我们将创建用于调用 后端服务 的 React 应用。
创建 React 应用
在本节中,我们将重点介绍如何为我们的在线投票系统设置 React 前端。 该应用程序将允许用户与我们之前构建的后端进行交互,以创建投票、进行投票并查看实时投票结果。 到本节结束时,您将拥有一个功能完整的 React 应用程序,它能与后端服务实时通信,获取、显示、创建、更新和管理投票数据 (使用 SignalR 技术)。
首先,在 Visual Studio 中打开一个新终端,导航到您的 ASP.NET Core 项目根目录,然后通过执行 clientapp 来创建一个名为的 React 应用程序,命令如下:
npx create-react-app clientapp
这将在 ClientApp 文件夹中生成一个新的 React 项目。 应用生成后,您可以继续配置它以与后端 API 交互,并实现创建投票、提交投票和获取实时投票结果的功能, 使用 SignalR 技术。
为确保我们的 React 前端能有效与后端通信、处理实时更新并提供用户友好的界面,我们需要安装几个 必要的包。
我们需要的第一个包是 @microsoft/signalr,它将允许 React 应用程序连接到后端的 SignalR 中心以实现实时通信。 这对于实时投票更新等功能至关重要,因为 SignalR 实现了服务器与客户端之间的双向通信,能在投票提交后立即将更新推送到前端。 要安装它,请通过 Visual Studio 的终端导航到 ClientApp 文件夹的根目录,然后运行以下命令:
npm install @microsoft/signalr
接下来,为了实现响应式且 视觉吸引人的用户界面,我们将 使用 Bootstrap 和 react-bootstrap 。 Bootstrap 是一个 流行的 CSS 框架,可帮助您轻松创建响应式布局,而 react-bootstrap 则提供了一套专为 React 设计的 Bootstrap 组件。 这种集成使您能够轻松地在 React 应用中实现按钮、表单、模态框等用户界面元素。 您可以通过运行 react-bootstrap 和 Bootstrap 的 以下命令来安装它们:
npm install bootstrap react-bootstrap
此外,为确保前端应用具备完善的导航和路由功能,我们将使用 react-router-dom 。 该包允许我们在应用中定义不同路由,实现页面间的导航功能,例如创建投票、查看投票结果和提交 投票。 React Router 能确保前端在与不同组件交互时获得流畅动态的体验。 安装时请运行 以下命令:
npm install react-router-dom
安装这些包后,React 应用将完全具备通过 SignalR 处理实时更新、使用 Bootstrap 提供精致界面以及管理路由以实现流畅 用户体验的能力。
为确保 Bootstrap 样式在您的 React 应用中全局可用,您需要在应用的入口文件(通常是 src/index.js )中导入 Bootstrap CSS 文件,如 以下代码所示:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import 'bootstrap/dist/css/bootstrap.min.css';
const root =
ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
reportWebVitals();
通过添加 'bootstrap/dist/css/bootstrap.min.css' 这行导入语句,您将全局引入 Bootstrap 的样式表,使其在项目的所有组件中均可使用。 这样您就可以直接使用 Bootstrap 的响应式网格系统和预置样式组件(如按钮、表单和模态框),而无需在每个文件中手动引入 CSS。
如果不包含此导入,您的组件将无法访问 Bootstrap 的样式,从而错过 Bootstrap 提供的统一响应式设计。 这一行代码确保 Bootstrap 样式在整个应用程序中全局应用,简化开发流程,使您能快速创建专业外观的 UI 元素 且 高效。
下一节我们将重点构建在线投票系统的核心组件。 这些组件将处理多项任务,包括创建投票、提交选票和实时显示投票结果。 通过将界面分解为可管理且可复用的组件,我们将创建一个动态且用户友好的 React 应用,它能无缝对接我们已 搭建的后端服务。
创建 React 组件
在本节中,我们将重点构建在线投票系统所需的核心 React 组件。 通过将应用拆分为创建投票、提交选票和查看实时结果等独立组件,我们将把应用程序组织成可复用且易于维护的模块。 这些组件将允许用户通过创建新投票、参与投票以及动态查看更新的投票结果与系统进行交互。 在此过程中,我们还将集成 Bootstrap 以确保响应式且精美的用户界面,首先从 CreatePoll 组件开始,具体内容见下一节。
创建 CreatePoll 组件
CreatePoll 组件是我们在线投票系统的核心部分,允许用户通过输入问题和多个投票选项来创建新投票。 在开始前,你需要在 React 应用的 src 文件夹内创建 components 文件夹。 该文件夹将存放我们创建的所有组件。 该组件还需要与运行在本地主机的后端 API 进行通信。 请确保你的后端 API 在指定 URL 上正常运行,或根据你的 本地环境 调整相应 URL。
首先,导航至 components 文件夹并创建一个名为 CreatePoll.js 的文件。 该组件负责管理用户输入、验证数据,并通过向 /api/polls 端点发送 POST 请求与后端 API 交互以创建新投票。 以下是该组件的完整代码:
import React, { useState } from 'react';
import { Form, Button } from 'react-bootstrap';
import { useNavigate } from 'react-router-dom';
const CreatePoll = () => {
const [question, setQuestion] = useState('');
const [options, setOptions] = useState(['', '']);
const [error, setError] = useState(null);
const navigate = useNavigate();
const handleSubmit = async () => {
if (!question || options.some(
option => option.trim() === '')) {
setError('Please fill in the question and all
options.');
return;
}
const newPoll = {
question,
options,
expiresAt: new Date(
Date.now() + 7 * 24 * 60 * 60 * 1000
)
};
try {
const response =
await fetch('http://localhost:5261/api/polls', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(newPoll),
});
if (response.ok) {
console.log('Poll created successfully');
navigate('/polls');
} else {
const errorData = await response.json();
setError(`Failed to create poll:
${errorData.message}`);
}
} catch (err) {
setError('Error connecting to the server. Please try
again later.');
}
};
const addOption = () => {
setOptions([...options, '']);
};
return (
<div className="container">
<h2>Create a New Poll</h2>
{error && <div className="alert alert-danger">
{error}
</div>}
<Form>
<Form.Group controlId="formQuestion">
<Form.Label>Poll Question</Form.Label>
<Form.Control
type="text"
placeholder="Enter poll question"
value={question}
onChange={(e) => setQuestion(e.target.value)}
/>
</Form.Group>
{options.map((option, index) => (
<Form.Group key=
{index} controlId={`formOption${index}`}>
<Form.Label>Option {index + 1}</Form.Label>
<Form.Control
type="text"
placeholder={`Option ${index + 1}`}
value={option}
onChange={(e) => {
const newOptions = [...options];
newOptions[index] = e.target.value;
setOptions(newOptions);
}}
/>
</Form.Group>
))}
<Button
variant="secondary"
className="m-2"
onClick={addOption}
>
Add Another Option
</Button>
<Button
variant="primary"
className="m-2"
onClick={handleSubmit}
>
Create Poll
</Button>
</Form>
</div>
);
};
export default CreatePoll;
让我们分解 这段 代码块:
- 该组件首先使用 React 的 useState 钩子来管理三个关键状态:question、options 和 error。 其中 question 状态存储投票问题,options 状态是一个包含所有投票选项的数组。 而 error 状态则用于处理表单验证问题或与后端 API 通信时遇到的错误。
- 当用户点击创建投票按钮提交表单时,handleSubmit 函数将被触发。 该函数首先验证问题与选项字段是否已填写。 如有字段为空,则显示错误信息并中止表单提交。 验证通过后,系统会创建一个 newPoll 对象,其中包含问题描述、选项列表以及设置为当前日期七天后到期的截止日期。 投票数据通过 POST 请求发送至后端 /api/polls 接口,该地址应为 ASP.NET Core Web API 的本地主机精确 URL,例如 http://localhost:5261/api/polls。 如果请求成功,用户将通过 React Router 的 useNavigate 重定向至投票列表。 如果出现错误(例如网络问题或无效的 API 响应),则会 显示错误信息。
- CreatePoll 组件的另一关键特性是能够动态添加新的投票选项。 通过点击 Add Another Option 按钮调用 addOption 函数,该函数会向 options 数组追加一个新的空选项。 这使得用户可以根据需要添加任意数量的投票选项,系统默认最少提供两个选项。
- 此外,表单中还加入了错误处理机制。 如果出现问题,例如字段缺失或与后端通信故障,系统会更新错误状态并显示相应的提示信息,这些信息将呈现在用户表单的顶部区域。
现在我们已经创建好了 CreatePoll 组件,接下来让我们创建 PollList 组件来显示已注册投票的列表。
创建 PollList 组件
PollList 组件 是我们在线投票系统的另一个核心部分,它允许用户查看所有可用投票的列表。 该组件通过与后端 API 通信获取现有投票,并以用户友好的格式展示。 与 CreatePoll 组件类似,您需要在 React 应用的 src 文件夹内创建 components 文件夹,所有组件(包括 PollList )都将存放在此。 另外请确保您的后端 API 在 localhost 上正常运行,或调整组件中的 URL 以匹配您的 本地配置。
首先,导航至 components 文件夹并创建一个名为 PollList.js 的文件。 该组件将向 /api/polls 端点发起 GET 请求,以获取所有投票列表,并以表格或列表形式呈现供用户查看。 以下是该组件的完整代码:
import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { Button, Card } from 'react-bootstrap';
const PollList = () => {
const [polls, setPolls] = useState([]);
useEffect(() => {
fetch('http://localhost:5261/api/polls')
.then(response => response.json())
.then(data => setPolls(data))
.catch(error => console.error('Error fetching
polls:', error));
}, []);
return (
<div className="container">
<h2>Available Polls</h2>
<div className="row">
{polls.map(poll => (
<div className="col-md-4 mb-3" key={poll.id}>
<Card>
<Card.Body>
<Card.Title>{poll.question}</Card.Title>
{/* Button to vote on the poll */}
<Link to={`/poll/${poll.id}`}>
<Button
variant="primary"
className="m-2"
>
Vote
</Button>
</Link>
{/* Button to view poll results */}
<Link to={`/poll/${poll.id}/results`}>
<Button
variant="info"
className="m-2"
>
View Results
</Button>
</Link>
</Card.Body>
</Card>
</div>
))}
</div>
</div>
);
};
export default PollList;
该 PollList 组件用于从后端获取并显示可用投票列表。 它首先使用 React 的 useState 钩子来初始化投票状态,该状态将在数据获取后存储投票数据。 初始状态下,投票状态被设置为空数组,因为组件尚未获取任何数据。
当组件首次挂载时,useEffect 钩子用于处理从后端获取投票数据的副作用。 该获取请求发送至 /api/polls 端点,对应运行在 http://localhost:5261 的后端服务。 当收到响应并转换为 JSON 格式后,数据通过 setPolls 函数存储到 poll 状态中。 若获取操作过程中发生错误,将被捕获并记录到控制台。
成功获取投票数据后,组件会遍历 polls 数组,为每项投票动态创建卡片。 每张卡片内,投票问题显示为卡片标题。 每个投票卡片包含两个按钮:一个用于投票,另一个用于查看投票结果。 这些按钮通过 React Router 的 Link 组件渲染,使用户能导航到特定路由。Vote 按钮链接至该投票的投票页面,而 View Results 按钮则跳转到 results page。
卡片样式采用了 react-bootstrap 的 Card 和 Button 组件,确保布局简洁且响应式。 每个投票都渲染在 col-md-4 列中,形成响应式网格布局,卡片之间保留 3 个单位的下边距,使布局既美观又便于 浏览。
总之, PollList 组件从后端获取投票数据,以卡片网格形式展示,并为用户提供投票或查看结果的链接。 使用 React 钩子管理状态和副作用,结合 react-bootstrap 进行样式设计,创建了一个高效且 用户友好的组件。
既然我们已经创建了 PollList 组件,现在让我们创建 PollDetails 组件,该组件允许用户为 该投票 登记投票。
创建 PollDetails 组件
PollDetails 组件是我们在线投票系统的另一个关键部分,它使用户能够查看特定投票的详细信息并进行投票。 与 PollList 组件类似,它会与后端 API 交互以获取投票详情,包括问题和投票选项,并以直观的格式显示。 用户可以选择其中一个选项并提交投票。 与之前的组件一样,您需要确保在 React 应用的 src 文件夹中创建 components 文件夹,用于存储 PollDetails 组件及其他组件。 同时,请确认后端 API 已正确配置为在本地主机上运行,或在组件中调整 API URL 以匹配 您的环境。
首先,导航至 components 文件夹并创建一个名为 PollDetails.js 的文件。 该组件将向 /api/polls/{pollId} 端点发起 GET 请求,以获取特定投票详情并允许用户投票。 以下是 该组件的完整代码:
import React, { useEffect, useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Button, ListGroup } from 'react-bootstrap';
const PollDetails = () => {
const { pollId } = useParams();
const [poll, setPoll] = useState(null);
const [selectedOption, setSelectedOption] =
useState(null);
const navigate = useNavigate();
useEffect(() => {
fetch(`http://localhost:5261/api/polls/${pollId}`)
.then(response => response.json())
.then(data => setPoll(data))
.catch(error => console.error(
'Error fetching poll:',
error
));
}, [pollId]);
const submitVote = () => {
if (!selectedOption) {
alert('Please select an option to vote.');
return;
}
fetch(`http://localhost:5261/api/polls/${pollId}/vote`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userId: 'user123',
optionId: selectedOption
})
})
.then(response => {
if (response.ok) {
navigate(`/poll/${pollId}/results`);
} else {
alert('Error submitting vote.');
}
})
.catch(error => console.error(
'Error submitting vote:',
error
));
};
return poll ? (
<div className="container">
<h2>{poll.question}</h2>
<ListGroup>
{poll.options.map(option => (
<ListGroup.Item
key={option.id}
action
onClick={() => setSelectedOption(option.id)}
active={selectedOption === option.id}
>
{option.text}
</ListGroup.Item>
))}
</ListGroup>
<Button
className="mt-3"
variant="success"
onClick={submitVote}
>
Submit Vote
</Button>
</div>
) : (
<div>Loading...</div>
);
};
export default PollDetails;
让我们分解这段 代码块:
- 该 PollDetails 组件负责显示特定投票的详细信息并允许用户进行投票。 它会从后端获取投票数据,向用户展示投票选项,用户可选择其中一项并提交投票。 该组件使用了 React 的 useState 和 useEffect,以及 React Router 的 useParams 和 useNavigate 钩子来管理状态、处理副作用并实现应用程序内导航 。
- 在组件初始化时,useParams 用于从 URL 中提取 pollId 值,这使得组件能够请求特定投票的详细信息。 通过 useState 钩子管理两个关键状态:poll 用于存储获取到的投票数据,selectedOption 则保存用户投票时选择的选项。 借助 useEffect 钩子在组件首次加载时从后端获取投票数据。API 请求发送至 http://localhost:5261/api/polls/${pollId},收到响应后会将投票数据存入 poll 状态。 如果在获取过程中发生任何错误,它们将被捕获并记录到 控制台。
- 组件的主体部分在数据可用时渲染投票问题和选项。 如果投票状态仍为 null(即数据仍在获取中),则显示加载信息。 当数据准备就绪后,投票问题将作为标题显示,选项则通过 react-bootstrap 的 ListGroup 呈现为可点击项。 每个选项均可点击,点击时 selectedOption 状态会更新为对应选项的 ID。 被选中的选项通过激活状态进行视觉提示,该状态会高亮显示列表中的选中项。
- 提交投票 按钮允许用户提交他们的投票。 当点击时, submitVote 函数会检查是否已选择选项。 如果未选择任何选项,系统会提示用户进行选择。 一旦选择选项,该函数会向位于 http://localhost:5261/api/polls/${pollId}/vote 的后端发送一个 POST 请求, 携带 userId 和 optionId 作为有效载荷。 如果投票成功提交,用户将通过 useNavigate 重定向至投票结果页面。 如果在投票提交过程中出现错误, 系统会显示提示信息通知 用户。
总之, PollDetails 组件负责获取并显示投票详情,允许用户选择选项,并将投票结果提交至后端。 它使用 React hooks 进行状态管理、处理副作用和导航,同时 react-bootstrap 为展示投票选项提供了简洁且响应式的用户界面。 该组件确保用户能够无缝地与投票互动 并参与投票。
既然我们已经创建了 PollDetails 组件,接下来让我们创建 PollResults 组件,该组件将利用 SignalR 技术 实时显示 投票结果。
创建 PollResults 组件
在本节中, 我们将重点创建 PollResults 组件,该组件允许用户查看投票的实时结果。 该组件将从后端获取初始结果,然后使用 SignalR 接收投票时的实时更新,确保用户无需刷新页面即可动态查看票数变化。 通过集成 React 钩子和 SignalR,我们将实现前后端之间的无缝实时通信,确保显示的投票结果始终保持最新状态。 此外,该组件将使用 react-bootstrap 组件进行样式设计,以提供简洁、 响应式 UI 界面。
首先,请导航至 components 文件夹,该文件夹位于 React 应用的 src 目录内,然后新建一个名为 PollResults.js 的文件。 该组件将从后端 API 获取投票结果,以列表形式展示,并使用进度条直观呈现每个选项获得的票数。 此外,SignalR 将处理实时更新,确保投票结果保持最新状态。 以下是该组件的完整代码:
import React, { useEffect, useState } from 'react';
import * as signalR from '@microsoft/signalr';
import { useParams } from 'react-router-dom';
import {
Card, ListGroup, ProgressBar, Container
} from 'react-bootstrap';
const PollResults = () => {
const { pollId } = useParams();
const [options, setOptions] = useState([]);
useEffect(() => {
if (!pollId) return;
const fetchInitialResults = async () => {
try {
const response = await fetch(
`http://localhost:5261/api/polls/${pollId}/
results`
);
if (response.ok) {
const data = await response.json();
setOptions(data);
} else {
console.error(
'Failed to fetch initial poll results.'
);
}
} catch (error) {
console.error(
'Error fetching poll results:',
Error
);
}
};
fetchInitialResults();
const connection = new signalR.HubConnectionBuilder()
.withUrl("http://localhost:5261/voteHub")
.configureLogging(signalR.LogLevel.Information)
.build();
connection.start()
.then(() => {
console.log('Connected to the SignalR hub');
connection.on("ReceiveVoteUpdate", (updatedPollId,
updatedOptions) => {
if (updatedPollId === parseInt(pollId)) {
setOptions(updatedOptions);
}
});
})
.catch(err => console.error(
'Error connecting to SignalR hub:', err
));
return () => {
connection.stop();
};
}, [pollId]);
return (
<Container className="mt-4">
<h2 className="mb-4">Poll Results</h2>
<Card>
<Card.Body>
<ListGroup>
{options.map(option => (
<ListGroup.Item key={option.id}>
<div
className=
"d-flex justify-content-between"
>
<span>{option.text}</span>
<span>{option.votes} votes</span>
</div>
<ProgressBar
now={(option.votes / Math.max(
...options.map(o => o.votes))) * 100}
label={`${option.votes}`}
/>
</ListGroup.Item>
))}
</ListGroup>
</Card.Body>
</Card>
</Container>
);
};
export default PollResults;
让我们分解这段 代码块:
- 该 PollResults 组件首先利用 React 的 useState 和 useEffect 钩子来管理状态并处理副作用,例如获取投票结果和连接到 SignalR 中心。 组件通过 React Router 的 useParams 钩子从 URL 中提取 pollId 值,这是获取特定投票结果并订阅实时更新所必需的。
- 加载时,useEffect 钩子会发起初始 API 调用,从 /api/polls/{pollId}/results 端点获取投票的当前结果。 获取的数据包含投票选项及其对应的票数,随后通过 options 状态使用 setOptions 进行存储。 这确保了在组件挂载后立即向用户显示初始票数。
- 获取初始结果后,组件会使用 @microsoft/signalr 建立与 voteHub 的 SignalR 连接。 通过 HubConnectionBuilder 实例类型来构建并启动该连接。 成功连接到中心后,组件会监听 ReceiveVoteUpdate 事件的更新。 当有新投票提交时,后端会触发此事件并发送更新后的投票计数,随后通过更新 options 状态在 UI 中实时反映。 这确保用户能够动态查看投票计数随其他用户投票而变化。
- 该组件的 return 代码块使用 react-bootstrap 的 Card、ListGroup 和 ProgressBar 组件来渲染投票结果。 每个投票选项都显示在 ListGroup.Item 中,并附带其当前得票数。ProgressBar 用于直观展示各选项获得的票数比例,进度条长度反映了该选项的相对得票数。ProgressBar 会随着 options 状态被 SignalR 连接传入的数据修改而实时更新。
- 最后, useEffect 钩子通过在组件卸载时调用 connection.stop() 来清理 SignalR 连接,确保连接被正确关闭 以避免内存泄漏或不必要的 网络流量。
总之, PollResults 组件利用 React 钩子和 SignalR 高效地处理了投票结果的显示和实时更新。 它提供了一个动态且响应迅速的 UI,用户可以实时观看投票更新,确保流畅的体验。 该组件在向用户提供实时反馈方面起着关键作用,使他们能够随着更多参与者投出 选票而监测投票的演变过程。
既然我们已经创建了所有主要组件,在下一节中,我们将创建一个主页组件,让用户能够以 集中化的方式导航到应用程序的各个组件。
创建主页组件
该首页组件作为在线投票系统的门户页面,向用户介绍应用程序并快速访问核心功能。 它提供了简洁友好的界面,包含两个主要选项:查看所有可用投票或创建新投票。 该组件作为系统的核心导航枢纽,使用户能轻松开始使用投票功能。 通过 React Router 的 Link 和 react-bootstrap 组件,该页面设计美观,为用户在应用程序中导航提供了流畅的体验。
要实现这一点,请导航至 components 文件夹并创建一个名为 Home.js 的文件。 该组件将作为投票系统的入口点,允许用户查看所有可用投票列表或创建新投票。 以下是该组件的完整代码:
import React from 'react';
import { Link } from 'react-router-dom';
import { Button } from 'react-bootstrap';
const Home = () => {
return (
<div className="container text-center">
<h1>Welcome to the Online Polling System</h1>
<p>Select an option below to get started:</p>
<Link to="/polls">
<Button
variant="primary"
className="m-2"
>
View All Polls
</Button>
</Link>
<Link to="/create-poll">
<Button
variant="success"
className="m-2"
>
Create a New Poll
</Button>
</Link>
</div>
);
};
export default Home;
该首页组件是一个功能组件,使用 react-bootstrap 进行样式设计,并采用 React Router 的 Link 实现导航功能。 组件顶部以 h1 标题形式展示欢迎语欢迎使用在线投票系统 ,为界面定下基调。 标题下方有一段简短说明(<p>),指导用户选择选项以开始使用该系统 。
该组件设有两个主要按钮,用户可通过它们导航至应用程序的不同部分。 第一个按钮标记为查看所有投票 ,它被包裹在 React Router 的 Link 组件中,点击后将用户导向 /polls 路由。 该路由会将用户带至列出所有可用投票的页面。 按钮样式采用 react-bootstrap 的 Button 组件,并设置 variant="primary" 样式,使其呈现醒目且专业的外观 。
第二个按钮标记为创建新投票 ,同样被包裹在 Link 组件中,但此按钮会导航至 /create-poll 路由,将用户引导至投票创建页面。 该按钮采用 variant="success" 样式设计,使其作为创建新内容的操作项脱颖而出。 两个按钮都包含边距类(m-2)以确保它们之间有适当的间距,从而提供整洁且布局有序的界面。
整个内容被包裹在一个 div 块中,该块具有 container 和 text-center 类,用于将内容居中显示在页面中并保持响应式布局。 使用 react-bootstrap 和 React Router 使该组件不仅视觉上吸引人,而且功能强大,为用户提供了清晰直观的方式来浏览系统的核心功能。
考虑到我们现在已经准备好 React 应用的所有组件,让我们在 下一节中完成应用路由配置的最后一步。
配置路由
本节我们将重点介绍如何使用 React Router 为在线投票系统配置路由。 路由是任何 React 应用的关键部分,它定义了用户在不同页面间的导航方式。 在我们的应用中,我们使用 React Router 定义路由,使用户能够查看所有可用投票、查看投票详情、创建新投票以及查看 特定投票的实时结果。
在 App.js 文件中,使用 React Router 的 Router 、 Routes 和 Route 组件来设置路由。 Router 组件包裹整个应用,实现导航功能。 在 Router 内部, Routes 组件包含多个独立的 Route 元素,这些元素将特定路径映射到对应组件,确保用户访问 特定 URL 时能渲染正确的组件。
以下是 在 App.js 中配置路由的代码 :
import './App.css';
import {
BrowserRouter as Router, Route, Routes
} from 'react-router-dom';
import PollList from './components/PollList';
import PollDetails from './components/PollDetails';
import PollResults from './components/PollResults';
import Home from './components/Home';
import CreatePoll from './components/CreatePoll';
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} /> {}
<Route path="/polls" element={<PollList />} /> {}
<Route path=
"/poll/:pollId" element={<PollDetails />}
/> {}
<Route path=
"/poll/:pollId/results" element={<PollResults />}
/> {}
<Route path=
"/create-poll" element={<CreatePoll />}
/> {}
</Routes>
</Router>
);
}
export default App;
在 App.js 文件中,我们使用 React Router 为在线投票系统配置了主路由系统。 路由是实现用户在不同应用部分间跳转而无需重新加载整个页面的基础。 该路由系统根据用户访问的 URL 决定显示哪些组件。 在此配置中,我们使用 React Router 的 Router、Routes 和 Route 组件来定义当用户访问特定路由时应用的行为。
Router 组件包裹整个应用程序,并实现应用内的导航功能。 在 Router 内部,我们使用 Routes 组件作为所有 Route 元素的容器。 每个 Route 都指定了一个路径及用户访问该路径时应渲染的组件。 这种结构使我们能够将不同 URL 映射到应用中对应的页面或功能,确保用户可以轻松在查看投票、参与投票和创建 新投票 之间进行导航。
应用程序中定义的第一个路由是根路由(/),它指向 Home 组件。这是用户首次访问系统时看到的默认首页。从首页出发,用户可以查看所有可用投票或创建新投票。/polls 路径关联到 PollList 组件,该组件会显示所有现有投票的列表供用户参与。点击任一投票后,用户将通过动态路由 /poll/:pollId 进入详细投票页面,该路由会渲染 PollDetails 组件。 路由中的 :pollId 部分使应用能够根据 投票 ID 显示特定投票数据。
类似地, /poll/:pollId/results 路由通过 PollResults 组件显示特定投票的实时结果。 这使用户能够借助 SignalR 的实时更新功能查看投票进度。 最后, /create-poll 路径关联到 CreatePoll 组件,用户可在其中通过输入问题及多个投票选项来创建新投票。 该组件提供了直观的投票创建表单,使用户能与系统互动并贡献 新内容。
通过此路由配置, App.js 文件构建了无缝的导航体验,使用户无论是创建投票、参与投票还是查看结果,都能轻松与投票系统交互。 动态路由的使用实现了对特定投票数据的灵活高效处理,确保应用始终保持用户友好且易于 导航的特性。
完成所有配置后,现在需要同时运行 React 和 ASP.NET Core Web API 应用程序。
运行演示应用程序
本节将介绍使用 Visual Studio 运行在线投票系统 ASP.NET Core 后端和 React 前端的步骤。 由于该应用是全栈解决方案,您需要同时运行两个部分才能完整测试 应用程序功能。
要正确运行应用程序并查看结果,请按照 以下步骤操作:
- 首先,点击 Visual Studio 中的运行按钮(或按 F5 键)。 这将启动后端服务,默认情况下通常运行在 http://localhost:5124。 您可以通过在浏览器中访问该 URL 来验证 API 是否正常运行。
- 之后,你需要同时运行前端和后端。 为此,在 Visual Studio 中打开一个新的终端或命令提示符窗口,导航到你的 React 项目根目录(即 ClientApp 文件夹),然后执行 以下命令:
npm start这将启动 React 开发服务器,通常运行在 http://localhost:3000。 打开浏览器并访问 http://localhost:3000 即可查看应用程序。React 前端将与运行在 http://localhost:5124 的后端 API 进行通信。 当后端和前端同时运行时,您将在浏览器中看到在线投票系统的初始页面,如图 14.6 所示。

图14.6:在线投票系统首页
在线投票系统的首页采用简洁明了的布局,顶部为用户提供两个主要操作:查看所有可用投票和创建新投票。 页面中央显示简短介绍和两个按钮,引导用户进入系统的 核心功能。
- 当用户点击 查看所有投票 按钮时,将跳转至现有投票列表页面,可浏览并参与进行中的投票。 若点击 创建新投票 按钮,则进入投票创建表单,允许用户自定义问题及投票选项来发起新投票。 这种结构确保用户能轻松导航并快速访问投票创建与参与功能,通过直观友好的界面实现与 投票系统的互动。
如果您点击 创建新投票 按钮,系统将跳转至投票表单页面,如 图 14 .7 所示 :

图14.7:投票表单
- 在填写完包含您所需问题及选项的投票创建表单后,可通过点击创建投票按钮进行保存。 投票成功创建后,系统将跳转至投票列表页面,新创建的投票将与现有投票一并显示。 该行为由 CreatePoll 组件配置实现,确保用户能流畅创建新投票并立即看到可供投票的列表。 投票创建完成后,以下是投票组件列表的显示效果:

图14.8:投票列表组件
在投票列表页面上,用户可以看到所有可用投票的列表。 每个投票都显示其关联的问题,并为用户提供两个主要操作: 投票 和 查看结果 。 该页面作为中心枢纽,用户可以通过参与投票过程或查看 正在进行中 的投票实时结果来与现有投票互动。
- 当用户 点击特定投票的 投票 按钮时,将被重定向至投票详情页面。 在此页面上,投票问题与可用选项一同显示,如 图 14 .9 所示。

图14.9:投票页面
- 用户可选择其中一项并提交投票。 投票完成后,应用程序将显示确认信息,表明投票已成功提交,并将用户重定向至投票结果页面,如图 14.10 所示。 :

图14.10:投票结果
或者,当用户在投票列表页面点击 查看结果 按钮时,同样会跳转至 投票结果 页面,在此可实时查看投票选项中各选项的票数更新。 结果通过进度条形式展示每个选项获得的票数,并借助 SignalR 实现数据实时更新。 这让用户能够监测随着新投票产生而变化的投票趋势,提供了一种引人入胜且 动态交互的体验。
按照本节步骤操作后,您已成功实现 ASP.NET Core 后端与 React 前端的并发运行。 该配置使您能够体验在线投票系统的完整功能,用户可以实时创建投票、参与投票并查看结果。 同时测试两个组件展现了全栈集成的强大能力,确保流畅的用户体验和可靠的后端数据处理。 环境正确配置完成后,您已准备好深入探索各项功能,并根据实际需求对应用程序进行进一步优化。
摘要
在本章中,我们构建了一个全栈在线投票系统,结合了后端的 ASP.NET Core 和前端的 React 框架。 我们首先使用 ASP.NET Core 开发了健壮的 API,通过控制器和服务实现了投票创建、投票操作和结果统计功能。 在前端部分,我们创建了 React 组件使用户能够与 API 交互,运用路由、状态管理和 HTTP 请求等技术处理投票相关数据。 我们还使用 Bootstrap 进行样式设计,并通过 React Router 实现了流畅的页面导航。
这些技能对于开发现代全栈应用至关重要,使开发者能够提供流畅响应的用户体验,同时保持后端服务的可靠性。 理解如何将 React 等前端框架与 ASP.NET Core 后端连接,构成了可扩展、可维护应用程序的基础,这些应用能有效管理数据并实现 实时通信。
在下一章中,我们将运用目前所学的技能,进一步探索如何高效规划和构建全栈应用程序。 这将加深您对不同 JavaScript 框架如何与 ASP.NET Core 集成的理解,并指导您开发结构清晰、 可扩展的项目。
396

被折叠的 条评论
为什么被折叠?



