WPF开发学生信息管理系统【WPF+Prism+MAH+WebApi】(三)(上)

简介: WPF开发学生信息管理系统【WPF+Prism+MAH+WebApi】(三)(上)

最近通过WPF开发项目,为了对WPF知识点进行总结,所以利用业余时间,开发一个学生信息管理系统【Student Information Management System】。前两篇文章进行了框架搭建和模块划分,以及后台WebApi接口编写,本文在前两篇基础之上,继续深入开发学生信息管理系统的课程管理模块,通过本篇文章,将了解如何进行数据的CRUD操作,将前后端贯穿起来开发。本文仅供学习分享使用,如有不足之处,还请指正。

涉及知识点

在本篇文章中,虽然只介绍一个模块的开发,但是麻雀虽小,五脏俱全,而且掌握一个模块的开发方法,相当于掌握通用的开发方法,就可以举一反三,其他模块的开发也可以水到渠成。涉及到的知识点如下:

  1. WPF开发中TextBlock,TextBox,DataGrid,Combox等控件的基础使用以及数据绑定等操作。
  2. MAH样式的使用,在本示例中MAH主要用于统一页面风格,提高用户体验。
  3. WebApi接口开发,本示例中,WebApi提供接口共客户端使用。
  4. HttpClient使用,主要用于访问服务端提供的接口。

数据访问流程

一般情况下,交付给客户的都是可视化的操作页面,而不是一些只有专业开发人员才能看懂的WebApi接口。为了更好的描述数据访问流程,以具体代码模块为例,如下所示:

数据操作上下文DataContext

关于WebApi和数据库的相关操作,在上一篇文章中已有说明,本文不再赘述,主要侧重于业务代码。关于课程管理模块的数据操作上下文,有两点需要加以说明:

  1. DbContext 是EntityFramwork提供的用于访问数据库中数据表的类,一个DbContext实例表示一个session,可用于查询或保存数据实体对应数据表。
  2. 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。如下所示:

  1. Course视图,主要用于课程的查询,以及新增,修改,删除的链接入口。
  2. AddEditCourse视图,主要用于课程信息的新增和修改,共用一个视图页面。
  3. 删除课程不需要页面,所以没有对应视图。
相关文章
|
3月前
|
C# 开发者 Windows
WPF 应用程序开发:一分钟入门
本文介绍 Windows Presentation Foundation (WPF),这是一种用于构建高质量、可缩放的 Windows 桌面应用程序的框架,支持 XAML 语言,方便 UI 设计与逻辑分离。文章涵盖 WPF 基础概念、代码示例,并深入探讨常见问题及解决方案,包括数据绑定、控件样式与模板、布局管理等方面,帮助开发者高效掌握 WPF 开发技巧。
174 65
|
2月前
|
设计模式 前端开发 C#
使用 Prism 框架实现导航.NET 6.0 + WPF
使用 Prism 框架实现导航.NET 6.0 + WPF
119 10
|
4月前
|
容器 C# Docker
WPF与容器技术的碰撞:手把手教你Docker化WPF应用,实现跨环境一致性的开发与部署
【8月更文挑战第31天】容器技术简化了软件开发、测试和部署流程,尤其对Windows Presentation Foundation(WPF)应用程序而言,利用Docker能显著提升其可移植性和可维护性。本文通过具体示例代码,详细介绍了如何将WPF应用Docker化的过程,包括创建Dockerfile及构建和运行Docker镜像的步骤。借助容器技术,WPF应用能在任何支持Docker的环境下一致运行,极大地提升了开发效率和部署灵活性。
163 1
|
4月前
|
测试技术 C# 开发者
“代码守护者:详解WPF开发中的单元测试策略与实践——从选择测试框架到编写模拟对象,全方位保障你的应用程序质量”
【8月更文挑战第31天】单元测试是确保软件质量的关键实践,尤其在复杂的WPF应用中更为重要。通过为每个小模块编写独立测试用例,可以验证代码的功能正确性并在早期发现错误。本文将介绍如何在WPF项目中引入单元测试,并通过具体示例演示其实施过程。首先选择合适的测试框架如NUnit或xUnit.net,并利用Moq模拟框架隔离外部依赖。接着,通过一个简单的WPF应用程序示例,展示如何模拟`IUserRepository`接口并验证`MainViewModel`加载用户数据的正确性。这有助于确保代码质量和未来的重构与扩展。
116 0
|
4月前
|
前端开发 C# 设计模式
“深度剖析WPF开发中的设计模式应用:以MVVM为核心,手把手教你重构代码结构,实现软件工程的最佳实践与高效协作”
【8月更文挑战第31天】设计模式是在软件工程中解决常见问题的成熟方案。在WPF开发中,合理应用如MVC、MVVM及工厂模式等能显著提升代码质量和可维护性。本文通过具体案例,详细解析了这些模式的实际应用,特别是MVVM模式如何通过分离UI逻辑与业务逻辑,实现视图与模型的松耦合,从而优化代码结构并提高开发效率。通过示例代码展示了从模型定义、视图模型管理到视图展示的全过程,帮助读者更好地理解并应用这些模式。
127 0
|
4月前
|
区块链 C# 存储
链动未来:WPF与区块链的创新融合——从智能合约到去中心化应用,全方位解析开发安全可靠DApp的最佳路径
【8月更文挑战第31天】本文以问答形式详细介绍了区块链技术的特点及其在Windows Presentation Foundation(WPF)中的集成方法。通过示例代码展示了如何选择合适的区块链平台、创建智能合约,并在WPF应用中与其交互,实现安全可靠的消息存储和检索功能。希望这能为WPF开发者提供区块链技术应用的参考与灵感。
70 0
|
4月前
|
开发者 C# Windows
WPF与游戏开发:当桌面应用遇见游戏梦想——利用Windows Presentation Foundation打造属于你的2D游戏世界,从环境搭建到代码实践全面解析新兴开发路径
【8月更文挑战第31天】随着游戏开发技术的进步,WPF作为.NET Framework的一部分,凭借其图形渲染能力和灵活的UI设计,成为桌面游戏开发的新选择。本文通过技术综述和示例代码,介绍如何利用WPF进行游戏开发。首先确保安装最新版Visual Studio并创建WPF项目。接着,通过XAML设计游戏界面,并在C#中实现游戏逻辑,如玩家控制和障碍物碰撞检测。示例展示了创建基本2D游戏的过程,包括角色移动和碰撞处理。通过本文,WPF开发者可更好地理解并应用游戏开发技术,创造吸引人的桌面游戏。
228 0
|
4月前
|
开发者 C# 自然语言处理
WPF开发者必读:掌握多语言应用程序开发秘籍,带你玩转WPF国际化支持!
【8月更文挑战第31天】随着全球化的加速,开发多语言应用程序成为趋势。WPF作为一种强大的图形界面技术,提供了优秀的国际化支持,包括资源文件存储、本地化处理及用户界面元素本地化。本文将介绍WPF国际化的实现方法,通过示例代码展示如何创建和绑定资源文件,并设置应用程序语言环境,帮助开发者轻松实现多语言应用开发,满足不同地区用户的需求。
86 0
|
4月前
|
开发者 C# UED
WPF多窗口应用程序开发秘籍:掌握窗口创建、通信与管理技巧,轻松实现高效多窗口协作!
【8月更文挑战第31天】在WPF应用开发中,多窗口设计能显著提升用户体验与工作效率。本文详述了创建新窗口的多种方法,包括直接实例化`Window`类、利用`Application.Current.MainWindow`及自定义方法。针对窗口间通信,介绍了`Messenger`类、`DataContext`共享及`Application`类的应用。此外,还探讨了布局控件与窗口管理技术,如`StackPanel`与`DockPanel`的使用,并提供了示例代码展示如何结合`Messenger`类实现窗口间的消息传递。总结了多窗口应用的设计要点,为开发者提供了实用指南。
297 0
|
4月前
|
C# Windows IDE
WPF入门实战:零基础快速搭建第一个应用程序,让你的开发之旅更上一层楼!
【8月更文挑战第31天】在软件开发领域,WPF(Windows Presentation Foundation)是一种流行的图形界面技术,用于创建桌面应用程序。本文详细介绍如何快速搭建首个WPF应用,包括安装.NET Framework和Visual Studio、理解基础概念、创建新项目、设计界面、添加逻辑及运行调试等关键步骤,帮助初学者顺利入门并完成简单应用的开发。
163 0