资讯动态

基于C#、SQL Server三层架构与Vue/AjaxPro的学生信息管理系统

发布时间:2026/10/9 17:22:51 来源:尧图企业网站定制
简介基于C#与SQLServer构建的学生信息管理系统完整项目源码采用三层架构整合BootStrap、Vue与AjaxPro技术栈面向.NET开发学习者、课程设计及毕业设计人员提供可直接运行的Web管理系统范例可用于理解企业级分层开发思路与前后端协作模式。包内共457个文件包含172个C#源码文件以及aspx页面、JS/CSS前端脚本、dll程序集、数据库备份bak/sql/mdf等可满足从代码阅读到环境部署的全过程参考整体压缩包约11.59MB目录结构包含登录、学生信息、班级、专业等典型模块。已有93人学习下载适合借此理解三层架构分层职责、AjaxPro异步交互与VueBootStrap前端集成等场景。除源码外还附带数据库备份与页面源文件便于直接还原环境、对照学习业务逻辑、界面布局及数据访问实现细节。1. 这个组合看着“混搭”却是学生信息管理系统最稳的落地配置C#、SQL Server、三层架构、Bootstrap、Vue、AjaxPro六个词放一起像把十年前和现在的东西硬拼在一台机器上。Vue和Bootstrap是今天前端的常备选项三层架构是十几年前的标准后端写法AjaxPro更是不少新人听都没听过的AJAX组件。可是这类组合在内部管理系统里活得很好某高校的教务系统、某培训机构的学员登记平台单台服务器扛几百个用户日常录入和查询毫无压力。它的价值是结构清晰、交付快、改起来不伤筋动骨。我按这个顺序往下拆先讲为什么这么选型再落到SQL Server表结构和三层实现然后打通Vue与AjaxPro的交互最后把最容易翻车的五个坑摊开讲结尾给一条验收路径。适合要接手维护老系统的开发者也想从零搭一套学生信息管理系统的朋友。2. 选型先讲道理三层架构、Vue、AjaxPro为什么能拧成一股绳2.1 三层架构不是老古董它把职责边界画死了先把标题里最“老”的三层架构说清楚。表示层、业务逻辑层、数据访问层外加一个实体层是这套系统的主干。表示层不放SQLBLL不写数据库操作DAL不做业务判断这个约束才是三层架构真正的价值而不是单纯把代码拆成三个项目叫三层。推荐的项目结构是这样Visual Studio解决方案里按模块分StudentManage.sln ├── StudentManage.Web // 表示层aspx页面 Vue Bootstrap ├── StudentManage.BLL // 业务逻辑层学号唯一校验、删除前检查 ├── StudentManage.DAL // 数据访问层DbHelper 各表Dal类 ├── StudentManage.Model // 实体层StudentInfo、ClassInfo等 └── StudentManage.Common // 公共工具分页、JSON辅助、密码加密参考依赖顺序Web引用BLLBLL引用DALWeb不直接引用DALModel全项目共享。这样做有两个直接好处默认数据库从本地SQL Server换成服务器只改Web.config里的连接字符串业务规则变更只动BLL不碰页面也不碰SQL。我在某个图像处理Demo项目里吃过亏早期图省事把SQL直接写在aspx页面后置代码里需求一改所有页面翻个遍那种返工成本比重建还高。实体类最简单的长这样public class StudentInfo { public int StudentId { get; set; } public string StudentNo { get; set; } public string Name { get; set; } public int ClassId { get; set; } public string Phone { get; set; } }实体类的作用是跨层传参DAL返回实体BLL操作实体UI展示实体。如果图省事让DAL直接返回DataTable短期看很快可前端一旦用上Vue的数据绑定DataTable的序列化问题会立刻暴露后面专门有一节讲这个坑。2.2 Vue管数据绑定、Bootstrap管外观、AjaxPro管异步通道AjaxPro是老.NET项目里用来替代UpdatePanel的AJAX组件。服务器端把某个类的public static方法标记成AjaxMethod浏览器端自动生成对应的JS代理直接调用返回值自动序列化不需要手动拼JSON也不需要手动管XMLHttpRequest这是它最大的省事之处。交互方式数据格式后端改动量适用场景传统PostBack回发页面刷新 ViewState无但体验差老页面维护AjaxPro异步调用自动序列化对象类加特性即可老项目升级体验Fetch/WebAPIJSON需要新建接口层全新项目在这套组合里Vue做的事情是拿到AjaxPro返回值后更新列表Bootstrap管表格栅格、按钮、弹窗和整体观感AjaxPro管前端到后端的数据通道。三者分工清楚前端渲染逻辑和后端分类互不干扰。在某个实验室的学员选课系统里新增一个查询字段的完整路径是加表字段、DAL加条件、页面加输入框、Vue data加字段、AjaxPro调用时多传一个参数全程不用动BLL因为查询本身没有业务规则。2.3 这套组合适合什么不适合什么适合局域网或内部网络环境下的教务、选课、成绩、学员信息等管理系统。这类系统用户量几百到几千并发不高逻辑以增删改查和简单统计为主维护者通常是懂后端但不想折腾前端工程化的开发者。不适合面向公网的高并发场景、需要复杂权限审计的场景、前端需要频繁迭代的场景。如果确定做一个全新的对外产品别用AjaxPro直接上WebAPI或Minimal API加前端框架别让老技术拖后腿。提示判断标准很简单——你是在维护已经交付的老项目还是从零新建。维护老项目保留AjaxPro少动少错从零新建可以考虑用Dapper或SqlSugar替代手写ADO.NET但整体架构还是保持三层。3. SQL Server表设计与DAL、BLL实现地基决定后期改动的代价3.1 SQL Server核心表怎么建至少五张表外键索引怎么摆学生信息管理系统最核心的数据关系是班级、学生、用户、课程、选课成绩五张表彼此通过外键关联构成最小闭环。建表精简版本如下CREATE TABLE Class ( ClassId INT IDENTITY(1,1) PRIMARY KEY, ClassName NVARCHAR(50) NOT NULL, GradeName NVARCHAR(20) NULL ); CREATE TABLE Student ( StudentId INT IDENTITY(1,1) PRIMARY KEY, StudentNo NVARCHAR(20) NOT NULL, Name NVARCHAR(50) NOT NULL, Gender CHAR(1) NULL DEFAULT 男, Birthday DATE NULL, ClassId INT NOT NULL, Phone NVARCHAR(20) NULL, CreateTime DATETIME DEFAULT GETDATE() ); CREATE TABLE SysUser ( UserId INT IDENTITY(1,1) PRIMARY KEY, Account NVARCHAR(30) NOT NULL UNIQUE, PasswordHash NVARCHAR(200) NOT NULL, RoleName NVARCHAR(20) NOT NULL DEFAULT 教师 ); CREATE TABLE Course ( CourseId INT IDENTITY(1,1) PRIMARY KEY, CourseName NVARCHAR(50) NOT NULL, Credit DECIMAL(3,1) NULL ); CREATE TABLE Score ( ScoreId INT IDENTITY(1,1) PRIMARY KEY, StudentId INT NOT NULL, CourseId INT NOT NULL, ScoreValue DECIMAL(5,2) NOT NULL );几个字段选型的要点IDENTITY(1,1)是自增主键不要用学号当主键学号属于业务数据可能因转班、复学调整一旦当主键所有关联表都要级联更新。姓名、课程名、班级名这类字段必须用NVARCHAR而不是VARCHARVARCHAR在中文排序和长度计算上容易出问题。ScoreValue用DECIMAL不用FLOAT避免浮点精度误差导致成绩数据漂移。索引按照高频查询来建CREATE NONCLUSTERED INDEX IX_Student_ClassId ON Student(ClassId); CREATE NONCLUSTERED INDEX IX_Student_No ON Student(StudentNo);班级是查询最高频的过滤条件学号是搜索最常用的关键字这两个字段建索引能把查询响应降到毫秒级。Score表上的外键我会建因为成绩表必须保证学生存在但BLL层仍要兜底检查数据库约束不是唯一防线。注意如果后期要做“年级班级”统计人数建议在Class表里预留GradeName字段不要通过截取班级名来判断年级这类临时取巧的处理往往变成日后统计报表的坑。3.2 DAL层参数化查询是底线返回List实体而不是DataTable数据访问层的基础是DbHelper把连接管理、命令执行、参数封装集中起来所有Dal类都走这个入口。最小实现如下public class DbHelper { private static readonly string connStr ConfigurationManager.ConnectionStrings[StudentDB].ConnectionString; public static DataTable Query(string sql, params SqlParameter[] parameters) { using (SqlConnection conn new SqlConnection(connStr)) using (SqlCommand cmd new SqlCommand(sql, conn)) { if (parameters ! null) cmd.Parameters.AddRange(parameters); SqlDataAdapter da new SqlDataAdapter(cmd); DataTable dt new DataTable(); da.Fill(dt); return dt; } } public static int Execute(string sql, params SqlParameter[] parameters) { using (SqlConnection conn new SqlConnection(connStr)) using (SqlCommand cmd new SqlCommand(sql, conn)) { if (parameters ! null) cmd.Parameters.AddRange(parameters); conn.Open(); return cmd.ExecuteNonQuery(); } } }这段代码做了三件事统一获取连接字符串、统一用SqlParameter承载参数、统一用using释放连接和命令资源。之后所有Dal类都不需要再写连接打开关闭的重复代码。参数化是必须守住的底线后面避坑章节会给出具体翻车案例。StudentDal查询学生列表的写法public static ListStudentInfo GetStudentList(string keyword, int classId) { string sql SELECT StudentId, StudentNo, Name, Gender, ClassId, Phone FROM Student WHERE 11; var ps new ListSqlParameter(); if (!string.IsNullOrEmpty(keyword)) { sql AND (StudentNo LIKE kw OR Name LIKE kw); ps.Add(new SqlParameter(kw, % keyword %)); } if (classId 0) { sql AND ClassId classId; ps.Add(new SqlParameter(classId, classId)); } DataTable dt DbHelper.Query(sql, ps.ToArray()); var list new ListStudentInfo(); foreach (DataRow row in dt.Rows) { list.Add(new StudentInfo { StudentId Convert.ToInt32(row[StudentId]), StudentNo row[StudentNo].ToString(), Name row[Name].ToString(), Gender row[Gender].ToString(), ClassId Convert.ToInt32(row[ClassId]), Phone row[Phone] DBNull.Value ? : row[Phone].ToString() }); } return list; }这里故意用“WHERE 11”再动态附加条件而不是每次拼完整SQL好处是条件块可以独立增删可读性好。keyword为空时不加LIKE条件classId为0表示不限班级所有用户输入都进SqlParameter而不是字符串拼接。返回List 而不是DataTable的原因很简单AjaxPro序列化DataTable会生成一堆XML结构前端拿到的不是正常数组这个坑在第五章展开讲。3.3 BLL层业务规则放中间层UI不要越过BLL业务规则不是数据库约束的重复而是系统对外行为的规定。最常见的规则有学号在系统内唯一、删除班级前检查是否还有学生、删除学生前检查是否有成绩记录。StudentBll的典型写法public class StudentBll { public static bool IsStudentNoExists(string studentNo, int currentStudentId) { return StudentDal.ExistsByStudentNo(studentNo, currentStudentId); } public static void DeleteStudent(int studentId) { if (ScoreDal.ExistsByStudentId(studentId)) { throw new Exception(该学生已存在成绩记录不能删除请先处理成绩数据); } StudentDal.Delete(studentId); } }DeleteStudent里先检查成绩表有记录就抛异常UI层捕获后提示用户。这个规则写在BLL而不是DAL里因为删除本身是数据操作是否允许删除是业务决定。学号重复校验和保存操作我习惯放在同一个Save方法里先校验再写入避免UI页面自己去承担判断逻辑。提示有人把校验写在aspx页面后置代码或前端JS里。前端加校验能提升体验但后端必须再校验一遍这是安全底线。前端校验只是减少无效请求真正的可靠性依赖BLL。4. 前端交互落地Vue管渲染、Bootstrap管外观、AjaxPro管通道4.1 AjaxPro在WebForms里注册与调用的完整过程AjaxPro的使用分五步每一对应一个文件或配置节点缺一个就出问题。第一步项目引用AjaxPro组件第二步web.config注册HTTP处理程序第三步页面注册类型第四步后台方法标AjaxMethod特性第五步前端JS调用。web.config里的注册是两个节点configuration system.web httpHandlers add verb* pathajaxpro/*.ashx typeAjaxPro.AjaxHandlerFactory, AjaxPro / /httpHandlers /system.web system.webServer handlers add nameAjaxPro verb* pathajaxpro/*.ashx typeAjaxPro.AjaxHandlerFactory, AjaxPro / /handlers /system.webServer /configurationhttpHandlers告诉ASP.NET凡是ajaxpro路径下的请求都交给AjaxHandlerFactory处理。IIS6站点配置在system.web里就够IIS7及以上要看站点运行模式——经典模式用system.web集成模式必须同时配system.webServer否则就会遇到第五章的404。后台页面类的写法public partial class StudentPage : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { AjaxPro.Utility.RegisterTypeForAjax(typeof(StudentPage)); } [AjaxPro.AjaxMethod] public static object GetStudentList(string keyword, int classId) { var list StudentBll.GetStudentList(keyword, classId); return new { code 200, rows list }; } }关键点在于[AjaxPro.AjaxMethod]特性方法必须是public static否则前端代理生成不出来返回值用匿名对象统一包一层code和rows前端判断code就知道请求是否成功。RegisterTypeForAjax必须在页面加载时执行注册之后AjaxPro才会生成对应的JS代理。前端调用StudentPage.GetStudentList(keyword, classId, function (result) { var res result.value; if (res.code 200) { vm.students res.rows; } });AjaxPro的回调函数固定接收result对象result.value才是后端返回的数据不需要手动JSON.parseAjaxPro已经处理好了序列化。注意回调是异步的不能写成“var list StudentPage.GetStudentList(...)”然后继续用这是刚开始用AjaxPro的人最容易犯的错。4.2 Vue在AjaxPro回调里更新页面数据Vue实例在页面加载后初始化data里放列表、查询条件、分页状态methods里放加载方法。推荐写法如下var vm new Vue({ el: #app, data: { students: [], keyword: , classId: 0, pageIndex: 1, pageSize: 10, total: 0 }, methods: { loadStudents: function () { var self this; // 回调函数内的this不是Vue实例 StudentPage.GetStudentList(self.keyword, self.classId, self.pageIndex, self.pageSize, function (result) { var res result.value; if (res.code 200) { self.students res.rows; self.total res.total; } else if (res.code 401) { window.location.href login.aspx; } }); }, search: function () { this.pageIndex 1; // 查询条件变化时回到第一页 this.loadStudents(); } }, created: function () { this.loadStudents(); // 页面初始化后自动加载 } });loadStudents里先用var self this把Vue实例缓存下来回调函数内部再用self访问Vue的data和方法。如果直接写this.students回调里this指向的是AjaxPro内部调用环境几乎必然报undefined。search方法在搜索按钮点击时调用把页码重置为1再重新请求筛完条件不会停留在已失效的高页码上。表格渲染用v-for加Bootstrap的table样式Bootstrap负责布局外观数据渲染完全交给Vue。分页控件在内部管理系统里很常用我让后端同时返回total前端根据total算总页数翻页传pageIndex给后端后端用ROW_NUMBER分页SQL实现具体封装写法见第6章。4.3 登录状态和按钮级权限Session检查要做两层内部管理系统通常用Session保存登录状态。AjaxPro的方法必须自己处理Session过期因为它不会像WebForms页面那样自动跳转登录页。后端统一写法[AjaxPro.AjaxMethod] public static object SaveStudent(StudentInfo student) { if (HttpContext.Current.Session[UserId] null) return new { code 401, message 登录已过期请重新登录 }; StudentBll.Save(student); return new { code 200 }; }每个AjaxMethod第一行都做Session判空返回统一401。前端在回调里统一判断是401就直接跳登录页。不要依赖AjaxPro的异常机制异常被吞掉后前端只能拿到null用户看到的症状是点了没反应。按钮级权限的做法是Session里存角色前端渲染时用v-if判断data: { isAdmin: false }, // 初始化时由后端返回当前用户角色 vm.isAdmin res.isAdmin;button v-ifisAdmin classbtn btn-danger clickdeleteStudent(s.Id)删除/button后端在保存、删除类方法里也应该二次检验角色前端v-if只是隐藏按钮真正能不能操作由后端决定。原则不变前端控制体验后端控制安全。5. 避坑要点AjaxPro、Vue和三层里最常见的五个翻车点5.1 AjaxPro请求404页面找不到类型现象浏览器开发者工具里看到ajaxpro/*.ashx的请求返回404页面报“找不到XXXX类型”或“AjaxPro未注册”。原因IIS运行模式下web.config配置缺了handlers节点或者类没有加[AjaxPro.AjaxMethod]特性又或者方法是private或实例方法而不是public static。解决对照第4.1节的配置检查一遍重点看站点是IIS6还是IIS7集成模式下system.webServer的handlers必须有。方法签名必须是public static。我见过最玄学的情况同一套代码本机IIS Express正常部署到生产IIS就404最后发现生产站点应用池是经典模式配置写在了system.web节点里IIS7集成模式补上system.webServer节点后恢复。5.2 DataSet/DataTable返回前端变成一坨XML嵌套标签现象回调里打印result.value看到的不是对象数组而是一串类似NewDataSet / Table / StudentId / StudentNo的层级结构Vue根本没法渲染。原因AjaxPro会自动序列化返回值但对DataSet、DataTable的处理是XML序列化而不是JSON序列化这是它的历史包袱。只要DAL层返回DataTable前端拿到的就是XML结构。解决DAL层统一返回List实体高版本转实体在DAL内部完成。如果实在绕不开老代码应急方案是手动序列化成JSON字符串再返回public static string GetStudentJson(string keyword) { DataTable dt DbHelper.Query( SELECT StudentId, StudentNo, Name FROM Student WHERE Name LIKE kw, new SqlParameter(kw, % keyword %)); return Newtonsoft.Json.JsonConvert.SerializeObject(dt); }这个写法只作为应急新代码一律走List实体。多层转手最容易出错别给后来维护的人留这种暗坑。5.3 Vue回调里this丢失现象AjaxPro回调函数里写this.students控制台报Cannot read property students of undefined数据不更新。原因回调函数执行时this指向的是AjaxPro的内部调用环境不是Vue实例。这个坑几乎每个人都会踩一次。解决在方法开头用var self this缓存Vue实例回调里全部用self。或者用箭头函数包住回调loadStudents: function () { StudentPage.GetStudentList(this.keyword, this.classId, (result) { // 箭头函数不绑定this顺着外层this指向Vue实例 this.students result.value.rows; }); }箭头函数写法更现代但老项目如果用户终端还有老版本浏览器保守做法还是var self this稳字当头。5.4 SQL注入和拼接字符串翻车现象搜索框输入1 OR 11列表数据异常或接口报错更严重的是输入多语句数据被删改。原因早期代码在DAL里用String.Format或字符串相加拼SQL用户输入直接进了SQL语句。解决全部使用SqlParameter参数化查询。对比两种写法// 反例直接拼接危险 string sql SELECT * FROM Student WHERE Name LIKE % name %; // 正例参数化安全 string sql SELECT * FROM Student WHERE Name LIKE name; var p new SqlParameter(name, % name %);参数化不只是改写法而是让ADO.NET把参数作为值传给SQL Server而不是拼进语句文本从根本上杜绝注入。如果后面换成Dapper、SqlSugar这类轻量ORM它们同样强制参数化这也是老项目DAL逐步迁移时的一个附加值。5.5 登录超时后AjaxPro请求异常前端点了没反应现象系统挂机一小时回来点按钮没反应F12里看到异步请求返回500或者AjaxPro回调拿到null。原因Session默认20分钟过期后端方法里没检查登录状态AjaxPro处理未捕获异常时前端只拿到异常信息页面没有跳转。解决前后端配套处理。后端所有AjaxMethod开头检查Session返回code401前端把判断抽成公共函数function handleResult(result, successCallback) { var res result.value; if (res.code 401) { window.location.href /login.aspx; return; } if (successCallback) successCallback(res); }把401判断从每个页面重复代码里抽出来页面级方法只关心业务逻辑。这样Session过期和用户主动退出走同一条分支不再出现数据没刷出来但页面还挂着的假死状态。另外可以在web.config里调长Session超时时间内部管理系统改成60分钟是常见做法system.web sessionState modeInProc timeout60/sessionState /system.web提示Session超时调长是缓解手段不是根治。根治靠统一的401处理两者配合用户才不会在不知情时丢操作。6. 验收路径与下一步值得做的改造把查询和分页封装成一个方法新系统做完我习惯按这个顺序验收第一把建库脚本拿到一台干净的SQL Server上执行确认不依赖手工建表第二用IIS发布站点应用池选择与项目目标框架匹配的集成模式登录页先跑通第三走一遍完整业务流程登录、学生列表、按班级筛选、按关键词搜索、新增学生、修改学生、删除有成绩记录的学生此时应被拦截、退出登录。这套流程走完核心链路就闭环了。下一步值得做的最小改造是把查询条件和分页参数封装成一个对象让DAL层只有一个分页查询入口。以学生查询为例public class StudentQuery { public string Keyword { get; set; } public int ClassId { get; set; } public int PageIndex { get; set; } 1; public int PageSize { get; set; } 10; } public static PageResultStudentInfo GetStudentPage(StudentQuery query) { string sql SELECT * FROM ( SELECT s.*, ROW_NUMBER() OVER (ORDER BY s.StudentId DESC) AS RowNum FROM Student s WHERE 11) AS T WHERE T.RowNum BETWEEN start AND end; // start (query.PageIndex - 1) * query.PageSize 1 // end query.PageIndex * query.PageSize // 同时再用一个COUNT查询取total }这个改动的价值是把所有查询入口统一成“一个对象加一个方法”前端加筛选条件时只改StudentQuery加字段后端只改SQL拼接不用再为每个页面写不同的分页逻辑。我吃过一次亏新功能上线只测了第一页第二页翻页白屏排查后是分页SQL里BETWEEN参数算错了。从那以后我养成了一个习惯——任何分页改动至少翻到第二页和最后一页各验证一次查询条件也要在非第一页状态下重查一遍。这套组合看着老但结构清楚、维护成本低对内部管理系统来说是高性价比选择。希望你做完这个系统后也能体会到稳定压过新鲜的感觉。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑