从零开始使用Blazor和.NET 9构建学生成绩管理系统
其实后端程序员也可以快速构建出拥有漂亮界面的UI。
本文将从零开始,使用Blazor Server和**. Net 9**构建一个功能完善的学生成绩管理系统。我们将采用分层架构设计,结合最新的Bootstrap Blazor组件库,实现从数据建模到部署上线的完整开发流程。通过本文,您将掌握Blazor Server的核心开发技巧、EF Core 9.0的数据操作方法以及企业级应用的最佳实践。
一、需求分析
1.1 用户角色与功能需求
学生成绩管理系统需满足两类核心用户的需求:
角色 | 核心功能 | 权限控制 |
---|---|---|
教师 | 成绩录入/修改、课程管理、统计分析 | 全部功能访问权限 |
学生 | 成绩查询、个人信息查看 | 仅查看本人数据 |
1.2 功能模块划分
系统采用模块化设计,包含以下关键模块:
- 学生管理:基础信息CRUD,支持批量导入导出
- 课程管理:课程信息维护与学生选课管理
- 成绩管理:成绩录入、修改、查询与统计分析
- 用户认证:基于角色的身份验证与授权
1.3 非功能需求
- 性能:支持1000+学生数据的高效查询
- 安全:密码加密存储,操作日志记录
- 易用性:响应式设计,适配桌面/平板设备
- 可扩展性:模块化架构,支持功能横向扩展
二、架构设计
2.1 技术栈选型
技术领域 | 选型 | 优势 |
---|---|---|
前端框架 | Blazor Server (. Net 9) | 共享C#代码,实时双向绑定 |
UI组件库 | Bootstrap Blazor v9.7.0 | 企业级UI组件,丰富的数据表格功能 |
数据访问 | EF Core 9.0 | 强大的ORM,支持Code First开发 |
身份认证 | ASP. Net Core Identity | 完善的用户管理与授权机制 |
数据库 | SQL Server LocalDB | 开发便捷,生产环境可无缝迁移至SQL Server |
2.2 分层架构设计
采用Clean Architecture思想,实现关注点分离:
StudentGradeManagement/
├── Application/ # 应用服务层(业务逻辑)
├── Domain/ # 领域层(实体与接口)
├── Infrastructure/ # 基础设施层(数据访问、外部服务)
└── Web/ # 表示层(Blazor UI)
各层职责:
- 领域层:定义核心实体(Student, Course, Grade)和仓储接口
- 应用层:实现业务逻辑,协调领域对象
- 基础设施层:提供EF Core数据访问实现、文件存储等
- 表示层:Blazor组件与页面,处理用户交互
2.3 数据库设计
核心实体关系模型:
Parse error on line 28: ...eTime CreatedAt } StudentCo ----------------------^ Expecting 'ATTRIBUTE_WORD', got 'BLOCK_STOP'三、环境搭建
3.1 开发环境准备
-
安装. Net 9 SDK
从微软官网下载并安装. Net 9 SDK,验证安装:dotnet --version # 应显示9.0.x
-
配置Visual Studio 2022
安装ASP. Net和Web开发工作负载,确保勾选:- . Net 9 SDK
- Blazor Server开发工具
- SQL Server LocalDB
-
创建Blazor Server项目
使用以下命令创建项目:dotnet new blazorserver -n StudentGradeManagement --framework net9.0 cd StudentGradeManagement
3.2 项目结构调整
按照分层架构重构项目结构:
# 创建核心项目
dotnet new classlib -n Domain
dotnet new classlib -n Application
dotnet new classlib -n Infrastructure
# 添加项目引用
dotnet add Web reference Application
dotnet add Application reference Domain
dotnet add Infrastructure reference Domain
dotnet add Web reference Infrastructure
3.3 安装必要依赖
在Web项目中安装关键NuGet包:
# Bootstrap Blazor组件库
dotnet add package BootstrapBlazor --version 9.7.0
# EF Core 9.0 SQL Server提供器
dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 9.0.0
# 身份认证
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore --version 9.0.0
# Excel处理
dotnet add package MiniExcel --version 1.41.2
四、核心代码实现
4.1 领域模型设计
Student.cs(领域层):
using System.ComponentModel.DataAnnotations;
namespace Domain.Entities;
public class Student
{
public int StudentId { get; set; }
[Required(ErrorMessage = "姓名不能为空")]
[MaxLength(50)]
public string Name { get; set; } = string.Empty;
[Required]
[RegularExpression(@"^\d{10}$", ErrorMessage = "学号必须为10位数字")]
public string StudentNumber { get; set; } = string.Empty;
[MaxLength(20)]
public string Class { get; set; } = string.Empty;
[EmailAddress]
public string? Email { get; set; }
public ICollection<Grade> Grades { get; set; } = new List<Grade>();
public ICollection<StudentCourse> StudentCourses { get; set; } = new List<StudentCourse>();
}
Course.cs(领域层):
using System.ComponentModel.DataAnnotations;
namespace Domain.Entities;
public class Course
{
public int CourseId { get; set; }
[Required]
[MaxLength(100)]
public string CourseName { get; set; } = string.Empty;
[Required]
[MaxLength(20)]
public string CourseCode { get; set; } = string.Empty;
public int Credits { get; set; }
public ICollection<Grade> Grades { get; set; } = new List<Grade>();
public ICollection<StudentCourse> StudentCourses { get; set; } = new List<StudentCourse>();
}
Grade.cs(领域层):
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Domain.Entities;
public class Grade
{
public int GradeId { get; set; }
[ForeignKey("Student")]
public int StudentId { get; set; }
[ForeignKey("Course")]
public int CourseId { get; set; }
[Range(0, 100, ErrorMessage = "成绩必须在0-100之间")]
public decimal Score { get; set; }
[MaxLength(500)]
public string? Remark { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.Now;
// 导航属性
public Student Student { get; set; } = null!;
public Course Course { get; set; } = null!;
}
4.2 数据访问层实现
AppDbContext.cs(基础设施层):
using Domain.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace Infrastructure.Data;
public class AppDbContext : IdentityDbContext<IdentityUser>
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<Student> Students => Set<Student>();
public DbSet<Course> Courses => Set<Course>();
public DbSet<Grade> Grades => Set<Grade>();
public DbSet<StudentCourse> StudentCourses => Set<StudentCourse>();
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// 配置多对多关系
builder.Entity<StudentCourse>()
.HasKey(sc => new { sc.StudentId, sc.CourseId });
// 设置索引
builder.Entity<Student>()
.HasIndex(s => s.StudentNumber)
.IsUnique();
builder.Entity<Course>()
.HasIndex(c => c.CourseCode)
.IsUnique();
// 种子数据
builder.Entity<Course>().HasData(
new Course { CourseId = 1, CourseName = "高等数学", CourseCode = "MATH101", Credits = 4 },
new Course { CourseId = 2, CourseName = "大学物理", CourseCode = "PHYS102", Credits = 3 },
new Course { CourseId = 3, CourseName = "计算机导论", CourseCode = "CS103", Credits = 4 }
);
}
}
4.3 业务服务实现
StudentService.cs(应用层):
using Domain.Entities;
using Domain.Repositories;
using Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
namespace Application.Services;
public class StudentService : IStudentService
{
private readonly AppDbContext _context;
public StudentService(AppDbContext context)
{
_context = context;
}
public async Task<List<Student>> GetAllStudentsAsync()
{
return await _context.Students
.Include(s => s.Grades)
.ThenInclude(g => g.Course)
.ToListAsync();
}
public async Task<Student?> GetStudentByIdAsync(int id)
{
return await _context.Students
.Include(s => s.Grades)
.ThenInclude(g => g.Course)
.FirstOrDefaultAsync(s => s.StudentId == id);
}
public async Task AddStudentAsync(Student student)
{
_context.Students.Add(student);
await _context.SaveChangesAsync();
}
public async Task UpdateStudentAsync(Student student)
{
_context.Entry(student).State = EntityState.Modified;
await _context.SaveChangesAsync();
}
public async Task DeleteStudentAsync(int id)
{
var student = await _context.Students.FindAsync(id);
if (student != null)
{
_context.Students.Remove(student);
await _context.SaveChangesAsync();
}
}
// 成绩统计方法
public async Task<GradeStatistics> GetGradeStatisticsAsync(int courseId)
{
var grades = await _context.Grades
.Where(g => g.CourseId == courseId)
.Select(g => g.Score)
.ToListAsync();
return new GradeStatistics
{
Average = grades.Any() ? grades.Average() : 0,
Highest = grades.Any() ? grades.Max() : 0,
Lowest = grades.Any() ? grades.Min() : 0,
Count = grades.Count,
PassRate = grades.Any() ?
(decimal)grades.Count(g => g >= 60) / grades.Count * 100 : 0
};
}
}
4.4 Blazor页面实现
学生列表页面(Pages/Students.razor):
@page "/students"
@inject IStudentService StudentService
@inject NavigationManager NavManager
@inject ILogger<Students> Logger
@inject ToastService ToastService
<PageTitle>学生管理</PageTitle>
<Card Title="学生信息管理" Description="查看和管理所有学生信息">
<Table TItem="Student"
Items="@students"
ShowToolbar="true"
IsStriped="true"
IsBordered="true"
OnSaveAsync="OnSaveAsync"
OnDeleteAsync="OnDeleteAsync"
AutoGenerateColumns="true">
<TableColumns>
<TableColumn @bind-Field="@context.StudentId" IsVisible="false" />
<TableColumn @bind-Field="@context.Name" />
<TableColumn @bind-Field="@context.StudentNumber" />
<TableColumn @bind-Field="@context.Class" />
<TableColumn @bind-Field="@context.Email" />
<TableColumn Title="操作">
<Button Icon="fa-solid fa-edit"
Size="ButtonSize.ExtraSmall"
OnClick="() => EditStudent(context.StudentId)">
</Button>
<Button Icon="fa-solid fa-graduation-cap"
Size="ButtonSize.ExtraSmall"
OnClick="() => ViewGrades(context.StudentId)">
</Button>
</TableColumn>
</TableColumns>
</Table>
</Card>
@code {
private List<Student> students = new();
protected override async Task OnInitializedAsync()
{
await LoadStudents();
}
private async Task LoadStudents()
{
try
{
students = await StudentService.GetAllStudentsAsync();
}
catch (Exception ex)
{
Logger.LogError(ex, "加载学生数据失败");
await ToastService.ShowError("加载数据失败", "错误");
}
}
private async Task<bool> OnSaveAsync(Student student)
{
try
{
if (student.StudentId == 0)
{
await StudentService.AddStudentAsync(student);
await ToastService.ShowSuccess("添加成功", "提示");
}
else
{
await StudentService.UpdateStudentAsync(student);
await ToastService.ShowSuccess("更新成功", "提示");
}
await LoadStudents();
return true;
}
catch (Exception ex)
{
Logger.LogError(ex, "保存学生数据失败");
await ToastService.ShowError($"保存失败: {ex.Message}", "错误");
return false;
}
}
private async Task<bool> OnDeleteAsync(IEnumerable<Student> students)
{
try
{
foreach (var student in students)
{
await StudentService.DeleteStudentAsync(student.StudentId);
}
await LoadStudents();
await ToastService.ShowSuccess("删除成功", "提示");
return true;
}
catch (Exception ex)
{
Logger.LogError(ex, "删除学生数据失败");
await ToastService.ShowError($"删除失败: {ex.Message}", "错误");
return false;
}
}
private void EditStudent(int id)
{
NavManager.NavigateTo($"/students/edit/{id}");
}
private void ViewGrades(int id)
{
NavManager.NavigateTo($"/students/{id}/grades");
}
}
4.5 成绩统计图表实现
成绩分析页面(Pages/GradeAnalysis.razor):
@page "/grade-analysis"
@inject IGradeService GradeService
@inject ICourseService CourseService
@inject ILogger<GradeAnalysis> Logger
@using Blazor.ECharts.Options.Series
@using Blazor.ECharts.Options.XAxis
@using Blazor.ECharts.Options.YAxis
<PageTitle>成绩分析</PageTitle>
<Card Title="成绩统计分析" Description="分析各课程成绩分布情况">
<div class="row">
<div class="col-md-3">
<Select @bind-Value="selectedCourseId"
Placeholder="选择课程"
OnSelectedItemChanged="OnCourseChanged">
@foreach (var course in courses)
{
<SelectItem Value="@course.CourseId">@course.CourseName</SelectItem>
}
</Select>
</div>
</div>
@if (statistics != null)
{
<div class="row mt-4">
<div class="col-md-6">
<Card Title="成绩分布">
<ECharts @ref="_chart"
Options="_chartOptions"
Style="height: 400px;"></ECharts>
</Card>
</div>
<div class="col-md-6">
<Card Title="统计指标">
<ul class="list-group">
<li class="list-group-item">
<strong>平均分:</strong> @statistics.Average.ToString("F1")
</li>
<li class="list-group-item">
<strong>最高分:</strong> @statistics.Highest
</li>
<li class="list-group-item">
<strong>最低分:</strong> @statistics.Lowest
</li>
<li class="list-group-item">
<strong>及格率:</strong> @statistics.PassRate.ToString("F1")%
</li>
<li class="list-group-item">
<strong>参考人数:</strong> @statistics.Count
</li>
</ul>
</Card>
</div>
</div>
}
</Card>
@code {
private List<Course> courses = new();
private int selectedCourseId = 0;
private GradeStatistics? statistics;
private ECharts? _chart;
private EChartsOptions _chartOptions = new();
protected override async Task OnInitializedAsync()
{
courses = await CourseService.GetAllCoursesAsync();
if (courses.Any())
{
selectedCourseId = courses.First().CourseId;
await OnCourseChanged(selectedCourseId);
}
}
private async Task OnCourseChanged(int courseId)
{
if (courseId == 0) return;
try
{
statistics = await GradeService.GetGradeStatisticsAsync(courseId);
var distribution = await GradeService.GetGradeDistributionAsync(courseId);
// 配置图表
_chartOptions = new EChartsOptions
{
Title = new Title { Text = "成绩分布直方图" },
Tooltip = new Tooltip { Trigger = "axis", AxisPointer = new AxisPointer { Type = "shadow" } },
XAxis = new XAxis
{
Type = AxisType.Category,
Data = new List<string> { "0-59", "60-69", "70-79", "80-89", "90-100" }
},
YAxis = new YAxis { Type = AxisType.Value },
Series = new List<ISeries>
{
new BarSeries
{
Name = "学生人数",
Data = new List<double>
{
distribution.Fail,
distribution.Pass,
distribution.Good,
distribution.Excellent,
distribution.Expert
},
ItemStyle = new ItemStyle
{
Color = new JsFunction("function(params) { return params.dataIndex === 0 ? '#ff4d4f' : '#52c41a'; }")
}
}
}
};
StateHasChanged();
}
catch (Exception ex)
{
Logger.LogError(ex, "加载成绩统计失败");
}
}
}
4.6 身份认证与授权
Program.cs配置:
var builder = WebApplication.CreateBuilder(args);
// 添加数据库上下文
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// 添加身份认证
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<AppDbContext>();
// 添加授权策略
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("TeacherOnly", policy => policy.RequireRole("Teacher"));
options.AddPolicy("StudentOnly", policy => policy.RequireRole("Student"));
});
// 添加服务
builder.Services.AddScoped<IStudentService, StudentService>();
builder.Services.AddScoped<ICourseService, CourseService>();
builder.Services.AddScoped<IGradeService, GradeService>();
// 添加Blazor组件
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
// 添加Bootstrap Blazor组件
builder.Services.AddBootstrapBlazor();
// 添加ECharts
builder.Services.AddECharts();
var app = builder.Build();
// 配置中间件
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
// 初始化角色和管理员用户
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
await SeedData.Initialize(services);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred seeding the DB.");
}
}
app.Run();
五、性能优化策略
5.1 服务器GC优化
. Net 9引入的动态适应应用大小(DATAS) 垃圾回收机制可显著减少内存占用:
// Program.cs中配置
builder.Services.Configure<GCSettings>(settings =>
{
settings.LatencyMode = GCLatencyMode.SustainedLowLatency;
});
5.2 组件渲染优化
使用ShouldRender
方法减少不必要的渲染:
protected override bool ShouldRender()
{
// 仅当数据实际更改时才重新渲染
return _dataHasChanged;
}
5.3 数据加载优化
实现虚拟滚动加载大量数据:
<Virtualize Items="@students" Context="student" ItemsProvider="LoadStudents">
<div class="student-item">
@student.Name - @student.StudentNumber
</div>
<LoadingTemplate>
<div class="text-center py-4">加载中...</div>
</LoadingTemplate>
</Virtualize>
@code {
private async ValueTask<ItemsProviderResult<Student>> LoadStudents(ItemsProviderRequest request)
{
var result = await StudentService.GetStudentsAsync(
request.StartIndex, request.Count);
return new ItemsProviderResult<Student>(
result.Items, result.TotalCount);
}
}
六、部署指南
6.1 Windows环境部署(IIS)
-
发布应用:
dotnet publish -c Release -o ./publish
-
配置IIS:
- 安装. Net 9 Hosting Bundle
- 创建应用池(无托管代码)
- 设置物理路径指向publish文件夹
-
配置web.config:
<?xml version="1.0" encoding="utf-8"?> <configuration> <location path="." inheritInChildApplications="false"> <system.webServer> <handlers> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" /> </handlers> <aspNetCore processPath=".\StudentGradeManagement.exe" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" /> </system.webServer> </location> </configuration>
6.2 Linux环境部署(Nginx)
-
安装依赖:
sudo apt-get update sudo apt-get install -y aspnetcore-runtime-9.0 nginx
-
创建系统服务:
# /etc/systemd/system/student-grade.service [Unit] Description=Student Grade Management System After=network.target [Service] WorkingDirectory=/var/www/student-grade ExecStart=/usr/bin/dotnet StudentGradeManagement.dll Restart=always RestartSec=10 User=www-data Environment=ASPNETCORE_ENVIRONMENT=Production Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false [Install] WantedBy=multi-user.target
-
配置Nginx:
server { listen 80; server_name your-domain.com; location / { proxy_pass http://localhost:5000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection keep-alive; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } }
七、总结与扩展
7.1 项目回顾
本文构建的学生成绩管理系统实现了以下核心价值:
- 技术先进性:基于. Net 9和Blazor Server,采用分层架构设计
- 功能完整性:覆盖学生管理、课程管理、成绩管理全流程
- 性能优化:通过DATAS GC、组件渲染控制提升系统响应速度
- 安全可靠:基于ASP. Net Core Identity的身份认证与授权
7.2 功能扩展建议
- 移动应用:使用Blazor Hybrid开发跨平台移动应用
- 高级报表:集成RDLC或Syncfusion ReportViewer生成复杂报表
- AI分析:添加机器学习模型预测学生成绩趋势
- 通知系统:集成SignalR实现成绩更新实时推送
通过本文的指导,您已掌握使用Blazor和. Net 9构建企业级Web应用的核心技能。这个成绩管理系统不仅满足了教学管理的基本需求,更为您提供了一个可扩展的架构基础,助力您在. Net生态系统中进一步探索和创新。
附录:完整项目结构
StudentGradeManagement/
├── Application/
│ ├── Dtos/
│ ├── Interfaces/
│ └── Services/
├── Domain/
│ ├── Entities/
│ └── Interfaces/
├── Infrastructure/
│ ├── Data/
│ └── Migrations/
├── Web/
│ ├── Components/
│ ├── Pages/
│ ├── Properties/
│ ├── Services/
│ ├── Shared/
│ ├── wwwroot/
│ ├── App.razor
│ ├── Program.cs
│ └── ...
├── StudentGradeManagement.sln
└── README.md
- 点赞
- 收藏
- 关注作者
评论(0)