最近通过WPF开发项目,为了对WPF知识点进行总结,所以利用业余时间,开发一个学生信息管理系统【Student Information Management System】。前两篇文章进行了框架搭建和模块划分,以及后台WebApi接口编写,本文在前两篇基础之上,继续深入开发学生信息管理系统的课程管理模块,通过本篇文章,将了解如何进行数据的CRUD操作,将前后端贯穿起来开发。本文仅供学习分享使用,如有不足之处,还请指正。
涉及知识点
在本篇文章中,虽然只介绍一个模块的开发,但是麻雀虽小,五脏俱全,而且掌握一个模块的开发方法,相当于掌握通用的开发方法,就可以举一反三,其他模块的开发也可以水到渠成。涉及到的知识点如下:
- WPF开发中TextBlock,TextBox,DataGrid,Combox等控件的基础使用以及数据绑定等操作。
- MAH样式的使用,在本示例中MAH主要用于统一页面风格,提高用户体验。
- WebApi接口开发,本示例中,WebApi提供接口共客户端使用。
- HttpClient使用,主要用于访问服务端提供的接口。
数据访问流程
一般情况下,交付给客户的都是可视化的操作页面,而不是一些只有专业开发人员才能看懂的WebApi接口。为了更好的描述数据访问流程,以具体代码模块为例,如下所示:
数据操作上下文DataContext
关于WebApi和数据库的相关操作,在上一篇文章中已有说明,本文不再赘述,主要侧重于业务代码。关于课程管理模块的数据操作上下文,有两点需要加以说明:
- DbContext 是EntityFramwork提供的用于访问数据库中数据表的类,一个DbContext实例表示一个session,可用于查询或保存数据实体对应数据表。
- DbSet表示一个对具体数据表中数据的操作映射,如增加,删除,修改,查询等,都是可通过DbSet类型完成。
关于DataContext具体代码,如下所示:
namespace SIMS.WebApi.Data { public class DataContext:DbContext { public DbSet<UserEntity> Users { get; set; } public DbSet<MenuEntity> Menus { get; set; } public DbSet<RoleEntity> Roles { get; set; } public DbSet<UserRoleEntity> UserRoles { get; set; } public DbSet<RoleMenuEntity> RoleMenus { get; set; } /// <summary> /// 学生 /// </summary> public DbSet<StudentEntity> Students { get; set; } /// <summary> /// 班级 /// </summary> public DbSet<ClassesEntity> Classes { get; set; } /// <summary> /// 课程 /// </summary> public DbSet<CourseEntity> Courses { get; set; } /// <summary> /// 成绩 /// </summary> public DbSet<ScoreEntity> Scores { get; set; } public DataContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<UserEntity>().ToTable("Users"); modelBuilder.Entity<MenuEntity>().ToTable("Menus"); modelBuilder.Entity<StudentEntity>().ToTable("Students"); modelBuilder.Entity<RoleEntity>().ToTable("Roles"); modelBuilder.Entity<UserRoleEntity>().ToTable("UserRoles"); modelBuilder.Entity<RoleMenuEntity>().ToTable("RoleMenus"); } } }
数据服务Service
数据服务是对DataContext操作的封装,使得业务更加明确,当然如果通过控制器直接访问DataContext,省掉数据服务,也可以实现对应功能,只是加入数据服务层,使得结构更加清晰。
数据服务接口ICourseAppService,提供了五个接口,包括查询课程列表,查询当个课程信息,新增课程,修改课程,删除课程,具体如下所示:
namespace SIMS.WebApi.Services.Course { public interface ICourseAppService { /// <summary> /// 查询列表 /// </summary> /// <param name="courseName"></param> /// <param name="teacher"></param> /// <param name="pageNum"></param> /// <param name="pageSize"></param> /// <returns></returns> public PagedRequest<CourseEntity> GetCourses(string courseName,string teacher, int pageNum,int pageSize); /// <summary> /// 通过id查询课程信息 /// </summary> /// <param name="id"></param> /// <returns></returns> public CourseEntity GetCourse(int id); /// <summary> /// 新增课程 /// </summary> /// <param name="course"></param> /// <returns></returns> public int AddCourse(CourseEntity course); /// <summary> /// 修改课程 /// </summary> /// <param name="course"></param> /// <returns></returns> public int UpdateCourse(CourseEntity course); /// <summary> /// 删除课程 /// </summary> /// <param name="id"></param> public int DeleteCourse(int id); } }
数据服务接口实现类CourseAppService,对数据服务接口ICourseAppService的实现,即通过操作DataContext来访问数据库中的课程表,如下所示:
namespace SIMS.WebApi.Services.Course { public class CourseAppService : ICourseAppService { private DataContext dataContext; public CourseAppService(DataContext dataContext) { this.dataContext = dataContext; } public int AddCourse(CourseEntity course) { var entry = dataContext.Courses.Add(course); dataContext.SaveChanges(); return 0; } public int DeleteCourse(int id) { var entity = dataContext.Courses.FirstOrDefault(x => x.Id == id); if (entity != null) { dataContext.Courses.Remove(entity); dataContext.SaveChanges(); } return 0; } /// <summary> /// 根据ID获取单个课程 /// </summary> /// <param name="id"></param> /// <returns></returns> public CourseEntity GetCourse(int id) { var entity = dataContext.Courses.FirstOrDefault(r => r.Id == id); return entity; } /// <summary> /// 获取课程列表 /// </summary> /// <param name="courseName"></param> /// <param name="teacher"></param> /// <param name="pageNum"></param> /// <param name="pageSize"></param> /// <returns></returns> public PagedRequest<CourseEntity> GetCourses(string courseName, string teacher, int pageNum, int pageSize) { IQueryable<CourseEntity> courses = null; if (!string.IsNullOrEmpty(courseName) && !string.IsNullOrEmpty(teacher)) { courses = dataContext.Courses.Where(r => r.Name.Contains(courseName) && r.Teacher.Contains(teacher)).OrderBy(r => r.Id); } else if (!string.IsNullOrEmpty(courseName)) { courses = dataContext.Courses.Where(r => r.Name.Contains(courseName)).OrderBy(r => r.Id); } else if (!string.IsNullOrEmpty(teacher)) { courses = dataContext.Courses.Where(r => r.Teacher.Contains(teacher)).OrderBy(r => r.Id); } else { courses = dataContext.Courses.Where(r => true).OrderBy(r => r.Id); } int count = courses.Count(); List<CourseEntity> items; if (pageSize > 0) { items = courses.Skip((pageNum - 1) * pageSize).Take(pageSize).ToList(); } else { items = courses.ToList(); } return new PagedRequest<CourseEntity>() { count = count, items = items }; } public int UpdateCourse(CourseEntity course) { dataContext.Courses.Update(course); dataContext.SaveChanges(); return 0; } } }
WebApi接口控制器
控制器是对数据服务的公开,每一个控制器的方法表示一个Action,即表示一个客户端可以访问的入口。具体如下所示:
namespace SIMS.WebApi.Controllers { /// <summary> /// 课程控制器 /// </summary> [Route("api/[controller]/[action]")] [ApiController] public class CourseController : ControllerBase { private readonly ILogger<CourseController> logger; private readonly ICourseAppService courseAppService; public CourseController(ILogger<CourseController> logger, ICourseAppService courseAppService) { this.logger = logger; this.courseAppService = courseAppService; } [HttpGet] public PagedRequest<CourseEntity> GetCourses( int pageNum, int pageSize, string? courseName=null, string? teacher=null) { return courseAppService.GetCourses(courseName,teacher,pageNum,pageSize); } /// <summary> /// 获取课程信息 /// </summary> /// <param name="id"></param> /// <returns></returns> [HttpGet] public CourseEntity GetCourse(int id) { return courseAppService.GetCourse(id); } /// <summary> /// 新增课程 /// </summary> /// <param name="course"></param> /// <returns></returns> [HttpPost] public int AddCourse(CourseEntity course) { return courseAppService.AddCourse(course); } /// <summary> /// 修改课程 /// </summary> /// <param name="course"></param> /// <returns></returns> [HttpPut] public int UpdateCourse(CourseEntity course) { return courseAppService.UpdateCourse(course); } /// <summary> /// 删除课程 /// </summary> /// <param name="id"></param> [HttpDelete] public int DeleteCourse(int id) { return courseAppService.DeleteCourse(id); } } }
当服务运行起来后,Swagger还每一个控制器都进行归类,可以清晰的看到每一个接口对应的网址,课程管理模块对应的接口如下所示:
客户端接口访问类HttpUtil
在学生信息系统开发的过程中,发现所有的接口访问都是通用的,所以对接口访问功能提取成一个HttpUtil基类【包括GET,POST,PUT,DELETE等功能】,其他具体业务再继承基类,并细化具体业务即可。HttpUtil代码如下所示:
namespace SIMS.Utils.Http { /// <summary> /// HTTP访问基类 /// </summary> public class HttpUtil { private static readonly string absoluteUrl = "https://localhost:7299/"; /// <summary> /// get方式 /// </summary> /// <param name="url"></param> /// <param name="param"></param> /// <returns></returns> public static string Get(string url, Dictionary<string, object> param) { string p=string.Empty; if (param != null && param.Count() > 0) { foreach (var keypair in param) { if (keypair.Value != null) { p += $"{keypair.Key}={keypair.Value}&"; } } } if (!string.IsNullOrEmpty(p)) { p=p.TrimEnd('&'); } var httpClient = new HttpClient(); var response = httpClient.GetAsync($"{absoluteUrl}{url}?{p}",HttpCompletionOption.ResponseContentRead).Result; string strJson = string.Empty; Stream stream = response.Content.ReadAsStream(); using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)) { strJson = reader.ReadToEnd(); } return strJson; } /// <summary> /// POST方式 /// </summary> /// <param name="url"></param> /// <param name="param"></param> /// <returns></returns> public static string Post<T>(string url, T t) { var content = JsonConvert.SerializeObject(t); var httpContent = new StringContent(content,Encoding.UTF8,"application/json"); var httpClient = new HttpClient(); var response = httpClient.PostAsync($"{absoluteUrl}{url}", httpContent).Result; var respContent = response.Content.ReadAsStringAsync().Result; return respContent; } public static string Put<T>(string url, T t) { var content = JsonConvert.SerializeObject(t); var httpContent = new StringContent(content,Encoding.UTF8,"application/json"); var httpClient = new HttpClient(); var response = httpClient.PutAsync($"{absoluteUrl}{url}", httpContent).Result; var respContent = response.Content.ReadAsStringAsync().Result; return respContent; } public static string Delete(string url, Dictionary<string, string> param) { string p = string.Empty; if (param != null && param.Count() > 0) { foreach (var keypair in param) { p += $"{keypair.Key}={keypair.Value}&"; } } if (!string.IsNullOrEmpty(p)) { p = p.TrimEnd('&'); } var httpClient = new HttpClient(); var response = httpClient.DeleteAsync($"{absoluteUrl}{url}?{p}").Result; var respContent = response.Content.ReadAsStringAsync().Result; return respContent; } public static T StrToObject<T>(string str) { var t = JsonConvert.DeserializeObject<T>(str); return t; } } }
然后课课程接口访问通用类CourseHttpUtil继承了HttpUtil,以对应课程信息的操作。如下所示:
namespace SIMS.Utils.Http { public class CourseHttpUtil:HttpUtil { /// <summary> /// 通过id查询课程信息 /// </summary> /// <param name="id"></param> /// <returns></returns> public static CourseEntity GetCourse(int id) { Dictionary<string, object> data = new Dictionary<string, object>(); data["id"] = id; var str = Get(UrlConfig.COURSE_GETCOURSE, data); var course = StrToObject<CourseEntity>(str); return course; } public static PagedRequest<CourseEntity> GetCourses(string? courseName, string? teacher, int pageNum, int pageSize) { Dictionary<string, object> data = new Dictionary<string, object>(); data["courseName"] = courseName; data["teacher"] = teacher; data["pageNum"] = pageNum; data["pageSize"] = pageSize; var str = Get(UrlConfig.COURSE_GETCOURSES, data); var courses = StrToObject<PagedRequest<CourseEntity>>(str); return courses; } public static bool AddCourse(CourseEntity course) { var ret = Post<CourseEntity>(UrlConfig.COURSE_ADDCOURSE, course); return int.Parse(ret) == 0; } public static bool UpdateCourse(CourseEntity course) { var ret = Put<CourseEntity>(UrlConfig.SCORE_UPDATESCORE, course); return int.Parse(ret) == 0; } public static bool DeleteCourse(int Id) { Dictionary<string, string> data = new Dictionary<string, string>(); data["Id"] = Id.ToString(); var ret = Delete(UrlConfig.COURSE_DELETECOURSE, data); return int.Parse(ret) == 0; } } }
客户端操作
经过前面四个部分的开发,客户端就可以与数据接口进行交互,展示数据到客户端。客户端所有的开发,均采用MVVM模式进行。
在课程管理模块中,根据功能区分,主要包含两个View视图及对应的ViewModel。如下所示:
- Course视图,主要用于课程的查询,以及新增,修改,删除的链接入口。
- AddEditCourse视图,主要用于课程信息的新增和修改,共用一个视图页面。
- 删除课程不需要页面,所以没有对应视图。