实验六

目录

  • 实验六
      • 题目要求
      • 环境设置
    • 题意分析
    • 后端·数据库
      • 数据库概述
      • SQL
      • C#中的SQL
    • 前端·ASP.NET Core
      • 前端基础知识·HTML和JavaScript
      • ASP.NET Core概述
      • MVC(模型-视图-控制器,Model-View-Controller)
        • Razor·HTML和C#的结合体
      • 数据从何而来?
    • 完整代码
    • 代码片段分析
      • 后端·建立数据库
      • 前端
        • TestController.cs中
        • Test\\Index.cshtml中
    • 演示
    • 总结
      • 回顾
    • 参考文献

题目要求

基于Asp.Net Core进行网页编程,从数据库中读取学生看书的数据,并展示到网页中。要求先展示列表,然后点击具体项目,进行展示。可进行一定的数据汇总。

环境设置

  1. 操作系统: Windows 10 x64
  2. SDK: .NET Framework 4.7.2
  3. IDE: Visual Studio 2019

题意分析

俗话说,题目越短越困难。还是像以往一样,我们把要求拆解来看,具体有以下几个要求:

①基于Asp.Net Core进行网页开发
②从数据库中读取数据
③将数据展示到网页中
④先展示列表,然后点击具体项目进行展示
⑤可进行一定的数据汇总

可以看到,其中的①和②就是我们最大的核心功能,也正是前端后端的内容。将①②③串接在一起就是实验的主体,④和⑤是附加要求。
对于前端内容①,我们需要掌握Asp.Net Core的基本开发方式,掌握MVC模式(模型-视图-控制器模式)的基本架构和使用,并在这一模式中完成数据库的操作和前端页面的展示。
对于后端内容②,我们需要掌握数据库的基本组织形式和SQL语言的基本使用。本次实验使用开源的MySQL数据库,通过MySQL进行数据库、表的建立和加入数据,建好的数据表供网页直接读取。具体的模式我们将在下面展开。
从ASP.Net Core的性质出发,本次实验使用的是前后端不分离的开发模式,即通过ASP.Net Core现场读取数据库并展示在动态网页之中。当下流行的也是更高效率开发模式是前后端分离的开发模式,即前后端只使用一个例如json的文件进行交互:前端向后端发送请求并获取文件然后解析。限于篇幅本文不再赘述。


后端·数据库

数据库概述

数据库,顾名思义,就是用来存放数据的地方。从精确的定义来看,数据库是指以一定方式存储在一起,能为多个用户共享,具有尽可能小的冗余度,并且与应用程序彼此独立的数据集合,用于存储结构化数据。数据组织有多种数据模型,目前主要的数据模型是关系数据模型,以关系模型为基础的数据库就是关系型数据库,也是目前使用最广泛的数据库。当下流行的各个数据库,例如Oracle、Microsoft SQL Server、Access和MySQL等等,都是关系型数据库。简单来说,关系模型指的就是二维表格模型,而一个关系型数据库就是由二维表及其之间的联系所组成的一个数据组织。

SQL

有了数据库之后,自然而然想到的是如何去操作它。结构化查询语言(Structured Query Language),即SQL,是源自于IBM公司,后被采纳为国际标准的关系型数据库的查询语言。它操作简单易上手,许多数据库都支持,当然包括我们这个实验用到的MySQL数据库。SQL语言包括查询,操纵,定义和控制等几个部分,通过命令动词来实现。我们更为熟知的是CRUD(增查改删,Create Retrieve Update Delete),当然也包括在这些命令中。我们将在接下来详细地使用SQL语言来构建和读取我们的数据库。

C#中的SQL

要在C#中使用SQL对数据库进行操作,可没有这么简单。换句话说,我们不能像在MySQL的命令窗口里输入指令那样直接在C#的控制台里使用SQL,而必须通过程序对数据库进行连接,使用字符串表示命令并通过C#语句调用它们,最后再退出。不同的数据库使用不同的前缀语句,例如MySQL数据库对应的语句是MySqlxxx,并要引入MySql.Data.MySqlClient包;而Microsoft SQL Service使用的语句是OleDbxxx等。我们将在下面的流程里看到它的详细使用方法。


前端·ASP.NET Core

前端基础知识·HTML和JavaScript

为了将数据展示在浏览器中,我们需要运用HTMLJavaScript与用户进行交互。
超文本标记语言(Hyper Text Markup Language),即HTML,是一种标记语言,它通过标记符号来标记要显示的网页中的各个部分。网页文件本身是一种文本文件,通过在文本文件中添加标记符,可以告诉浏览器如何显示其中的内容。浏览器按顺序阅读网页文件,然后根据标记符解释和显示其标记的内容。简单来说,我们通过输入不同的标记来完成不同的功能。而这些标记不同于XML,是已经规定好的,我们运用这些标记的组合就像使用指令一样控制网页的长相。我们将在接下来看见它们的使用方式。
上面提到的HTML只能完成网页的展示部分,但是我们能看见的绝大部分网页都是能够跟用户进行多姿多彩的交互的。这怎么办?为了完成这些交互,我们需要使用JavaScript这个脚本语言。脚本语言的特性是“随调随用”,也就是解释型或者即时编译型语言。它不同于大量的后端语言是编译之后再执行,而是在网页运行的过程中一句一句执行。这样的特性可以帮助我们实现在网页运作中的各种各样的功能。在实际开发中,JS和HTML结合起来完成了展示和人机交互的基本功能。
除了HTML和JavaScript,在前端的开发中时常运用到的还有**CSS(层叠样式表,Cascading Style Sheets)**来完成各种各样绚丽的动态效果。本实验的功能使用不到它,有兴趣的读者可以自行查找资料进行了解。

ASP.NET Core概述

动态服务器页面(Active Server Pages),即ASP,是微软公司开发的服务器端脚本环境,可用来创建动态交互式网页并建立强大的web应用程序。当服务器收到对ASP文件的请求时,它会处理包含在用于构建发送给浏览器的HTML网页文件中的服务器端脚本代码。而.NET平台我们已经很熟悉了,是微软开发的开发平台。.NET Core是.NET Framework的新一代版本,是微软开发的第一个具有跨平台(Windows、Mac OSX、Linux)能力的应用程序开发框架。二者结合起来就是ASP.NET Core,它是基于.NET Core的Web开发框架,是对ASP.NET的再开发和优化。有兴趣的读者可以自行查找并了解他们的历史,本文就不再赘述了。

MVC(模型-视图-控制器,Model-View-Controller)

MVC是一种构建Web应用的模式,简单来说就是这个网页或者说Web应用是怎么构建的,怎么运作的。其应用几乎遍及所有的Web框架,而不仅仅是在我们实验用的Asp.Net Core中。就连iOS和Android上的移动应用也是MVC的一个变种。拆开来看,MVC的三个组成部分有如下的功能:

  1. 模型:负责应用程序中用于处理应用程序数据逻辑的部分,通常模型对象负责在数据库中存取数据。在Asp.Net Core中,模型可以分为两种:以xxxItem命名的模型负责记录部分,记录的是保存在数据库中的条目;以xxxViewModel命名的模型负责与视图相结合,发送到浏览器供用户查看。
  2. 视图:是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的。简单来说就是把已经存好的数据呈现给用户。在Asp.Net Core中,视图是用Razor语言书写的,它存储在后缀为.cshtml的特殊文件中。关于Razor语言我们将在下面进行介绍。
  3. 控制器:是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。也就是说控制器是负责人机交互的“窗口”,它处理用户(端)的输入并送交给相应的代码进行处理。

Razor·HTML和C#的结合体

上面提到了Razor这种语言。其实,他就是HTML和C#(也可以是VB)的混合版本。Razor使用“@”符号来区别HTML标记和C#代码。以@开头的变量或是头尾用@标记的均被识别为C#代码。而这C#代码之中还可以嵌套HTML标记,比如放入表单元素之中(<td><td>)。是不是很神奇?它在一定程度上可以取代部分JS代码的功能。我们可以在下面看到这个东西的实际用途。需要注意的是,这个语言是微软特地为ASP系列的网页开发而制造的,纯前端的开发是用不了它的。

数据从何而来?

数据当然是从数据库读取啦。本次实验的需求比较简单,于是我们可以直接把读取数据库的任务交给Controller,即直接在控制器Controller当中读取数据库的内容,然后存到模型Model(xxxItem)里面。在更加专业化,更加层次化结构化的ASP.NET Core开发中,我们会再把MVC改造成不同的层次以达到展示和与后端交流分开的作用。这时候就会出现:一个由控制器和视图构成的表示层,用来处理用户的交互;一个包含了业务逻辑和数据库代码的服务层,跟数据库交流的任务就会交给这个服务层。读者可以自行查找相关的资料并了解,本次实验只和大家交流最简单的开发工作。


完整代码

后端代码(实验六 第二题 后端\Program.cs):建立数据库并写入数据

using System;
using MySql.Data.MySqlClient;namespace 实验六_第二题_后端
{class Program{static void Main(string[] args){//准备被写入的数据(也可通过控制台让用户自行输入)string[] number = { "2938", "0001", "0002", "0003", "2938", "6666", "0001", "0003", "0003", "2938" };string[] student = { "张辰涛", "杨小刚", "李小芳", "赵小明", "张辰涛", "赖永炫(老师新年快乐!)", "杨小刚", "赵小明", "赵小明", "张辰涛" };string[] sex = { "男", "男", "女", "女", "男", "男", "男", "女", "女", "男", };string[] birth = { "2002.2.20", "2002.12.20", "2002.7.16", "2002.1.5", "2002.2.20", "~", "2002.12.20", "2002.1.5", "2002.1.5", "2002.2.20" };string[] book = { "数据库系统原理及MySql应用教程", "数据结构与算法", "概率论与数理统计", "离散数学", "C#程序设计教程", "Taxi Demand Prediction with LSTM-based Combination Model.", "汇编语言程序设计", "C#程序设计教程", "计算机网络与因特网", "Unity3D游戏开发实战", };//开始操作数据库string connetStr = "server=localhost;port=3306;user=root;password=********; database=*******;";// server=127.0.0.1/localhost 代表本机,端口号port默认是3306可以不写MySqlConnection connect = new MySqlConnection(connetStr);try//CRUD{connect.Open();//打开通道,建立连接string cmd = "";MySqlCommand mycmd = new MySqlCommand(cmd, connect);//清空(测试时使用)cmd = "delete from studentdata";mycmd.CommandText = cmd;mycmd.ExecuteNonQuery();//建表cmd = @"create table if not exists studentdata (number char(4),student char(20),sex char(3),birth char(10),book char(120))";mycmd.CommandText = cmd;mycmd.ExecuteNonQuery();//对于非查询的语句使用这一指令//插入数据for (int i = 0; i < number.Length; i++){//按照列次序依次写入数据cmd = string.Format("insert into studentdata(number, student, sex, birth, book) values(\"{0}\", \"{1}\", \"{2}\", \"{3}\", \"{4}\")", number[i], student[i], sex[i], birth[i], book[i]);mycmd.CommandText = cmd;mycmd.ExecuteNonQuery();}//查询数据cmd = "select * from studentdata";mycmd.CommandText = cmd;MySqlDataReader reader = mycmd.ExecuteReader();while (reader.Read())//有点类似于迭代器,读取直到最后一行返回false{for (int i = 0; i < 5; i++)Console.Write(reader[i].ToString() + " ");Console.WriteLine();}}catch (MySqlException ex){Console.WriteLine(ex.Message);}finally{connect.Close();}Console.WriteLine("Finish...");Console.ReadKey();}}
}

前端代码(实验六 第二题 前端\Controllers\HomeController.cs):初始创立时的页面

using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using test01.Models;namespace test01.Controllers
{public class HomeController : Controller{private readonly ILogger<HomeController> _logger;public HomeController(ILogger<HomeController> logger){_logger = logger;}public IActionResult Index(){return View();}public IActionResult Privacy(){return View();}[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]public IActionResult Error(){return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });}}
}

前端代码(实验六 第二题 前端\Controllers\TestController.cs):自写的控制器,用以读取数据库和存放数据

using System;
using System.Collections.Generic;
using MySql.Data.MySqlClient;
using Microsoft.AspNetCore.Mvc;
using test01.Models;namespace test01.Controllers
{public class TestController : Controller{public IActionResult Index(){var contents = new List<TestItem>();string connetStr = "server=localhost;port=3306;user=root;password=********; database=*******;";//server=127.0.0.1/localhost 代表本机,端口号port默认是3306可以不写//这段与后端代码的开始部分是一样的MySqlConnection conn = new MySqlConnection(connetStr);try{conn.Open();//打开通道,建立连接//CRUDstring cmd = "";MySqlCommand mycmd = new MySqlCommand(cmd, conn);//查询数据cmd = "select * from studentdata";mycmd.CommandText = cmd;MySqlDataReader reader = mycmd.ExecuteReader();while (reader.Read()){//对后端的代码进行了改造,把记录存入数组中,最终存入模型之中;注意新对象的创建contents.Add(new TestItem { number = reader[0].ToString(), student = reader[1].ToString(), sex = reader[2].ToString(), birth = reader[3].ToString(), book = reader[4].ToString() });}}catch (MySqlException ex){//暂时还没有想到很好的错误信息处理方式Console.WriteLine(ex);}finally{//关闭连接conn.Close();}//创建新对象并存入,返回给模型return View(new TestViewModel { Contents = contents });}}
}

前端代码(实验六 第二题 前端\Models\TestItem.cs):提供了数据访问的接口

namespace test01.Models
{// 内容实体public class TestItem{public string number { get; set; }public string student { get; set; }public string sex { get; set; }public string birth { get; set; }public string book { get; set; }}
}

前端代码(实验六 第二题 前端\Models\TestViewModel.cs):与视图结合提供给用户

using System.Collections.Generic;namespace test01.Models
{public class TestViewModel{// 内容列表public List<TestItem> Contents { get; set; }}
}

前端代码(实验六 第二题 前端\Views\Home\Index.cshtml):初始创立时的页面

@{ViewData["Title"] = "Home Page";
}<div class="text-center"><h1 class="display-4">Welcome</h1><p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

前端代码(实验六 第二题 前端\Views\Home\Privacy.cshtml):初始创立时的页面

@{ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1><p>Use this page to detail your site's privacy policy.</p>

前端代码(实验六 第二题 前端\Views\Test\Index.cshtml):自写的页面,包含了对模型中数据的筛选和展示

@model TestViewModel
@{ViewData["Title"] = "内容列表";
}<div class="panel panel-default todo-panel"><div class="panel-heading">@ViewData["Title"]</div><!--制作表格--><table id = "student" border="1" cellspacing="0"><thead><tr><td>学号<!--onchang函数用以实现切换选项时就开始筛选和展示数据--><select id="selectNumber" οnchange="ChooseSelect()"><option>*</option></select></td><td>姓名<select id="selectName" οnchange="ChooseSelect()"><option>*</option></select></td><td>性别<select id="selectSex" οnchange="ChooseSelect()"><option>*</option><option>男</option><option>女</option></select></td><td>生日</td><td>书籍</td></tr></thead>@foreach (var item in Model.Contents)//table里的内容基本是写死动不了的,需要通过后序的函数来改造{<tr><!--Razor直接使用C#的变量--><td>@item.number</td><td>@item.student</td><td>@item.sex</td><td>@item.birth</td><td>@item.book</td></tr>}</table><!--JS脚本--><script type="text/javascript">function DeleteForm() {var table = document.getElementById("student")var len = table.rows.lengthfor (var i = len - 1; i > 0; i--)table.deleteRow(i)return}//删除表单以供新表单的展示function ChooseCondition(number, name, sex) {var filteredArray = new Array()for (var i = 0; i < studentData.length; i++) {if ((studentData[i].number == number || number == "*") &&(studentData[i].student == name || name == "*") &&(studentData[i].sex == sex || sex == "*"))//"*"表示通配符,故以或的逻辑写入判定条件之中filteredArray.push(studentData[i])}return filteredArray}//从全局数组中筛选符合条件的记录并返回,交给展示函数展示function CreateForm(res) {var table = document.getElementById("student")for (var i = 0; i < res.length; i++) {var x = table.insertRow(i+1)//添加表格元素x.insertCell(0).innerText = res[i].numberx.insertCell(1).innerText = res[i].studentx.insertCell(2).innerText = res[i].sexx.insertCell(3).innerText = res[i].birthx.insertCell(4).innerText = res[i].book}return}//展示函数function ChooseSelect(){//筛选,获取字符串var onumber = document.getElementById("selectNumber")var number = onumber.options[onumber.selectedIndex].valuevar oname = document.getElementById("selectName")var name = oname.options[oname.selectedIndex].valuevar osex = document.getElementById("selectSex")var sex = osex.options[osex.selectedIndex].valuevar res = ChooseCondition(number, name, sex)//清空表格DeleteForm()//展示筛选出来的列表CreateForm(res)return}//要被调用的总函数,先筛选,再删除,然后展示var studentData = new Array()//全局数组var tableElement = document.getElementById("student")var tableLen = tableElement.rows.length//获取总行数var sselectNumber = document.getElementById("selectNumber")var qNumber = new Array()var sselectName = document.getElementById("selectName")var qName = new Array()for (var i = 1; i < tableLen; i++) {var oneStudent = new Object()//创建一个新对象oneStudent.number = tableElement.rows[i].cells[0].innerText//创建新的选项对象以供插入select中,实测必须每次以新对象插入下拉菜单,否则引起错误(只会出现在一个地方)var opNumber = document.createElement("option")opNumber.innerText = oneStudent.numberif (qNumber.indexOf(opNumber.innerText) < 0) { //查无此人qNumber.push(opNumber.innerText)//插入去重表格中sselectNumber.add(opNumber)//插入}//原理同上oneStudent.student = tableElement.rows[i].cells[1].innerTextvar opName = document.createElement("option")opName.innerText = oneStudent.studentif (qName.indexOf(opName.innerText) < 0) {qName.push(opName.innerText)sselectName.add(opName)}oneStudent.sex = tableElement.rows[i].cells[2].innerTextoneStudent.birth = tableElement.rows[i].cells[3].innerTextoneStudent.book = tableElement.rows[i].cells[4].innerTextstudentData.push(oneStudent)//插入总的全局数组中}</script></div>

代码片段分析

后端·建立数据库

string connetStr = "server=localhost;port=3306;user=root;password=********; database=*******;";
// server=127.0.0.1/localhost 代表本机,端口号port默认是3306可以不写
MySqlConnection connect = new MySqlConnection(connetStr);

由于无法像在控制台那样直接操作数据库,我们只能通过包提供的字符串的形式来通过C#指令对数据库进行连接和其他操作。下面的代码也是相同的道理。

cmd = @"create table if not exists studentdata (number char(4),student char(20),sex char(3),birth char(10),book char(120))";
mycmd.CommandText = cmd;
mycmd.ExecuteNonQuery();//对于非查询的语句使用这一指令

对于非查询的语句,包提供了ExecuteNonQuery()这一指令进行操作,操作的SQL指令就是写在CommandText中的字符串。而查询语句是数据库操作中最常用的语句,且查询的方式丰富多彩,比如下面的这一块代码:

//查询数据
cmd = "select * from studentdata";
mycmd.CommandText = cmd;
MySqlDataReader reader = mycmd.ExecuteReader();
while (reader.Read())
{for (int i = 0; i < 5; i++)Console.Write(reader[i].ToString() + " ");Console.WriteLine();
}

MySQL在C#的包中提供了两种查询语句的方式,其中一种便是上面提供的ExecuteReader()。这一语句的作用是创建一个读取器对象,这个读取器对象就像一个迭代器一样,逐行读出SQL命令获取的数据并以数组的形式返回,所以我们可以通过下标的方式来获取每个字段的内容。当然,上面的代码只是在调试后端的时候用得到,在前端读取数据的时候是要稍加改造的,我们会在后面看到。

前端

TestController.cs中

public IActionResult Index()
{var contents = new List<TestItem>();string connetStr = "server=localhost;port=3306;user=root;password=********; database=*******;";//server=127.0.0.1/localhost 代表本机,端口号port默认是3306可以不写//这段与后端代码的开始部分是一样的MySqlConnection conn = new MySqlConnection(connetStr);try{conn.Open();//打开通道,建立连接//CRUDstring cmd = "";MySqlCommand mycmd = new MySqlCommand(cmd, conn);//查询数据cmd = "select * from studentdata";mycmd.CommandText = cmd;MySqlDataReader reader = mycmd.ExecuteReader();while (reader.Read()){//对后端的代码进行了改造,把记录存入数组中,最终存入模型之中;注意新对象的创建contents.Add(new TestItem { number = reader[0].ToString(), student = reader[1].ToString(), sex = reader[2].ToString(), birth = reader[3].ToString(), book = reader[4].ToString() });}}catch (MySqlException ex){//暂时还没有想到很好的错误信息处理方式Console.WriteLine(ex);}finally{//关闭连接conn.Close();}//创建新对象并存入,返回给模型return View(new TestViewModel { Contents = contents });
}

这一段代码是将前端的内容稍加改造用以读取数据库。不同的地方在于,它将数据先放进一个临时的对象数组,而后将数组传入模型之中存储。实际测试中,return View(new TestViewModel { Contents = contents });这样创建新对象数组的方式是必要的,否则会导致问题,而问题的原因目前仍未究明。Index函数是供页面加载时直接读取的函数,而不用再增加子目录。

Test\Index.cshtml中

<thead><tr><td>学号<!--onchang函数用以实现切换选项时就开始筛选和展示数据--><select id="selectNumber" οnchange="ChooseSelect()"><option>*</option></select></td><td>姓名<select id="selectName" οnchange="ChooseSelect()"><option>*</option></select></td><td>性别<!--性别是固定的,于是直接写死--><select id="selectSex" οnchange="ChooseSelect()"><option>*</option><option>男</option><option>女</option></select></td><td>生日</td><td>书籍</td></tr>
</thead>

上面这段是纯粹的HTML语言,用以创建表头(thead),也就是表格的第0行。其中包含了一些select控件,也就是下拉菜单,它的元素是option,也就是各个选项。控件的onchang函数是在控件的内容改变的时候会触发的函数,函数内容写在了JavaScript脚本之中,我们会在接下来看到。
到这里,数据筛选,也就是要求④的初步思路已经有所窥见了:我们会利用JS脚本,像开发C#程序一样对数据进行筛选,然后再返回给表格显示出来。这些数据都已经是被读取之后放进缓冲区之中的了,而不是现场调取数据库。
仅仅有上面HTML的文本是还不够的,我们还需要将内容一个个添加到表格的cell,也就是单元里面,这时候Razor就派上用场了:

@foreach (var item in Model.Contents)//table里的内容基本是写死动不了的,需要通过后序的函数来改造
{<tr><!--Razor直接使用C#的变量--><td>@item.number</td><td>@item.student</td><td>@item.sex</td><td>@item.birth</td><td>@item.book</td></tr>
}

前面加了@符号的都是C#的语法或者变量,而带<>的都是HTML语法。可以看到,C#的变量被嵌入到了单元格之中,而单元格又被嵌入到了foreach循环之中,变相达到了动态创建表格的效果。当然,以上功能通过JavaScript应该也是能够达到的,但是要用JS和C#导出的数据打交道就显得有些麻烦了,Razor正好弥合了这二者的空缺;接下来我们会看到纯HTML条件下JS发挥的作用。我们接着往下看。

var studentData = new Array()//全局数组
var tableElement = document.getElementById("student")
var tableLen = tableElement.rows.length//获取总行数var sselectNumber = document.getElementById("selectNumber")
var qNumber = new Array()
var sselectName = document.getElementById("selectName")
var qName = new Array()for (var i = 1; i < tableLen; i++) {var oneStudent = new Object()//创建一个新对象oneStudent.number = tableElement.rows[i].cells[0].innerText//创建新的选项对象以供插入select中,实测必须每次以新对象插入下拉菜单,否则引起错误(只会出现在一个地方)var opNumber = document.createElement("option")opNumber.innerText = oneStudent.numberif (qNumber.indexOf(opNumber.innerText) < 0) { //查无此人qNumber.push(opNumber.innerText)//插入去重表格中sselectNumber.add(opNumber)//插入}//原理同上oneStudent.student = tableElement.rows[i].cells[1].innerTextvar opName = document.createElement("option")opName.innerText = oneStudent.studentif (qName.indexOf(opName.innerText) < 0) {qName.push(opName.innerText)sselectName.add(opName)}oneStudent.sex = tableElement.rows[i].cells[2].innerTextoneStudent.birth = tableElement.rows[i].cells[3].innerTextoneStudent.book = tableElement.rows[i].cells[4].innerTextstudentData.push(oneStudent)//插入总的全局数组中
}

JS脚本要写在<script><script>标签之中。这一段文字上下代码的顺序跟写在程序的是相反的,因为从逻辑上来看,应当先实现的是完成所有数据的汇总之后展示,而后点击下拉列表进行筛选。前者是在网页运行时依据脚本的编写次序来运行,而后者要通过调用函数来实现。更好的处理方式是将函数的实现放在HTML的头部,也就是<head></head>标签之中。
现在来分析一下这部分脚本的作用。在先前的代码中已经通过Razor将数据库内容放到HTML表格里了,但是这些内容没有进入JS的存储空间中,所以我们要通过HTML表格这一中介将数据读到一个全局数组变量里面,再对它进行筛选。全局数组里面是存储的数据是JS对象,而JS的对象接触过JSON(JavaScript Object Notation)的同学就很熟悉了:一个{}中囊括了一些键值对,其中键就是属性,值就是这个属性对应的值,刚好拿来装学生的字段信息。我们一边将每行的字段从表格之中找出来,然后存到对象里面(比如oneStudent.number = tableElement.rows[i].cells[0].innerText),一边将这些选项放进下拉框select当中。这当中已经包含了去重的过程为了达到去重的效果,我们为要筛选的字段开一个辅助数组,比如var qNumber = new Array(),将没有插入过select下拉框的对象插入其中,同时又插入下拉框中。其中用到的indexOf函数事实上承担了筛选的作用,若其返回-1则表示该对象不在数组之中。
值得一提的是,JavaScript是一种弱类型语言,因而所有变量都以var声明。提到这个是因为经常使用到的document.getElementById()是功能非常强大的函数,能够在页面范围内根据Id值抓取不同的对象。这个Id值就好像C#中的变量名,可以用以标识不同种类的控件。
接下来的部分就全是JS函数部分了,也是这个实验最终的部分。

function DeleteForm() {var table = document.getElementById("student")var len = table.rows.lengthfor (var i = len - 1; i > 0; i--)table.deleteRow(i)return
}//删除表单以供新表单的展示function ChooseCondition(number, name, sex) {var filteredArray = new Array()for (var i = 0; i < studentData.length; i++) {if ((studentData[i].number == number || number == "*") &&(studentData[i].student == name || name == "*") &&(studentData[i].sex == sex || sex == "*"))//"*"表示通配符,故以或的逻辑写入判定条件之中filteredArray.push(studentData[i])}return filteredArray
}//从全局数组中筛选符合条件的记录并返回,交给展示函数展示function CreateForm(res) {var table = document.getElementById("student")for (var i = 0; i < res.length; i++) {var x = table.insertRow(i+1)//添加表格元素x.insertCell(0).innerText = res[i].numberx.insertCell(1).innerText = res[i].studentx.insertCell(2).innerText = res[i].sexx.insertCell(3).innerText = res[i].birthx.insertCell(4).innerText = res[i].book}return
}//展示函数

以上就是三个子功能函数的内容。首先是将表格的内容全部删除。我们能够在此大胆删掉表格的原因是我们已经将所有的内容按照对象加入了全局数组之中。删除的原理是获取表格所有的行,并且逐行删除。这里要注意两点:其一,从尾部开始删起,以防止循环变量下标和行数的改变影响删除次序(想象一下跳着删);其二,第0行是表头,真正的内容是从第1行开始的,删除时要注意i>0
第二个函数的内容是从外部获取要筛选的内容并进行筛选,返回一个筛选完毕的数组。这个判定的信息由总的函数提供,来自于select框的选定内容,提取的方式在下面的代码可以看见。在select下拉框中有一个特殊的符号"*",也就是通配符。通配符表示不对当前字段的内容做筛选限制。那么它在判定条件之中是如何表现的呢?显然,它对于传入的任何条件都返回真,自然而然地想到用或条件来实现。而将这些带或逻辑的判定表达式用与运算符连接起来就是总的判定条件了,也就是代码中如下的部分。

if((studentData[i].number == number || number == "*") &&(studentData[i].student == name || name == "*") &&(studentData[i].sex == sex || sex == "*"))

第三个函数则是创建新的表格并将返回的筛选过的数组插入其中。插入的方式是逐行插入,每行中逐单元插入,也就是逐列插入。这些table控件自带的函数形式上比较简单,这里就不再赘述了,能掌握使用方式就行了。
接下来就是把这些子函数全部拼装起来,变成一个供调用的总函数:

function ChooseSelect()
{//筛选,获取字符串var onumber = document.getElementById("selectNumber")var number = onumber.options[onumber.selectedIndex].valuevar oname = document.getElementById("selectName")var name = oname.options[oname.selectedIndex].valuevar osex = document.getElementById("selectSex")var sex = osex.options[osex.selectedIndex].valuevar res = ChooseCondition(number, name, sex)//清空表格DeleteForm()//展示筛选出来的列表CreateForm(res)return
}//要被调用的总函数,先筛选,再删除,然后展示

以前两行为例,由于select只能获取到被选中项的下标,我们需要通过下标从options数组中来获取被选中的option。而option是一个对象,我们要的筛选方式是通过字符串来筛选,于是通过value值获取它们的文字。注意,这只能在未在标签中指定value值的时候使用,若已指定value值,可使用innerText来获取。获取各个select框里用户要求的筛选条件之后将它们传入ChooseCondition函数中,再运行各个子函数就能达到我们想要的目标了。
那么,这个函数将在什么时候调用呢?我们回到最开始制造表格时的代码:

<select id="selectNumber" οnchange="ChooseSelect()">

这个onchange函数就是用以调用最终的ChooseSelect()函数,来实现在每次完成任意一个下拉框的改变之后改变整张表格的内容。

至此。我们层层解析,步步为营,终于达到了实验要求的目标。接下来进入本实验,也是我们最后一个实验的特色环节:

演示

这是本实验的特色环节。这次实验大部分在于前端,而我们又不像之前那样直接使用窗体进行开发而需要借助浏览器展示。这是一个所见即所得的项目,我们将从数据库开始,逐步展示我们这次实验的所有界面。
在运行了后端代码之后,我们直接到数据库窗口(cmd),通过select * from studentdata指令查看已经插好的数据库(表):

可以看到,数据已经被写入数据库了。我们返回DOS界面,将目录转移到项目文件夹下,输入指令dotnet watch run运行整个项目。当然,也可以在VS中直接启动,在IIS中运行。


可以看到,在运行web应用之后直接进入了home界面,我们在地址栏中追加/test来实现对TestController的Index函数的访问,进而展示到浏览器之中。

可以看到我们已经进入了含有表格的页面,这里展示了所有的数据。我们可以通过调整不同的筛选条件选择我们想要的条目


如果选择互斥的条件的话……

可以看到它变成了一张空表,情理之中。
至此,我们已经完成了这个实验所有内容的展示。


总结

通过本次实验,我们了解了关系型数据库的基本组织形式,掌握了SQL语言的基本内容和使用方式,并完成了往其中添加数据和查找数据的功能。在前端内容方面,我们学习了HTML、JavaScript等前端基础知识,了解了Asp.Net Core的基本组织结构和MVC开发模式。通过以上知识完成了一个简单Web应用的开发。

当然这个实验是非常简单的MVC开发模式,与真正专业化的开发相比还难以望其项背。比如,就算在前后端不分离的场景下,应该也存在某种方式能够让前端给后端发送指令请求并查询数据库而后返回,而不是使用通过JS操作缓存区的数据这样效率较低的方式。数据库的优势就在于强大的数据组织能力。其次,本实验本可以分层完成,即前文提到的分为表示层和服务层等,这样能够使得程序更加模块化。最后,我们可以通过Asp.Net Core提供的其他方式完成对数据库更高效的访问,而非使用MySql.Data.MySqlClient这一包。

回顾

通过这学期的C#学习,我们从最开始的控制台界面开发,到窗体应用开发,到委托事件反射,再到多线程和互斥,最后到Web应用开发,可以说是学到了不少的东西。笔者也从一个一无所知的小白,一路走到了具有一定开发基础的研习者了。当然,软件开发的途径远不止这些,软件开发也绝非C#这一条路,但是通过本学期的C#学习,我们能够领略到软件开发的魅力和基本模式,基本知识。软工路且长,希望日后能够学到更多深刻,实用,有趣的开发内容。在文章的最后,要感谢一直陪伴和指导我们的赖永炫教授,尽心尽力的助教老师,身边各位亲爱的同学们,以及在崎岖道路上不断攀登的自己。
以上,就是本次实验报告的,
所有内容。
祝阅读到此的各位小伙伴2022年快乐!


参考文献

李辉 等.数据库系统原理及MySQL应用教程:机械工业出版社,2015

李春葆,曾平,喻丹丹.C#程序设计教程(第3版):清华大学出版社,2015

赖永炫.厦门大学移动计算与数据分析实验室,Mobile Computing & Data Analysis

Copyright @ 2021, CSDN: ForeverMeteor, all rights reserved.

C#实际案例分析(第六弹)相关推荐

  1. 物流信息管理系统MySQL设计_案例分析第六章:物流管理系统的数据库设计(六个基本步骤)案例分析...

    物流管理信息系统的数据库设计 (案例分析) 系统名称:物流管理信息系统(数据库管理系统) 一.需求分析 需求分析是整个数据库设计过程的基础,要收集数据库所有用户的信息内容和处理要求,并加以规格化和分析 ...

  2. 微观经济学案例分析(六)

    6.1 完全竞争市场的短期均衡:春节的洗车服务涨价 案例内容 <楚天金报>2013年2月3日报道:今年春节假日,武汉洗车价格普涨到每台 60元,不少网友发帖抱怨洗车店"趁节打劫& ...

  3. SWT 重启案例分析(六)

    极力推荐文章:欢迎收藏 Android 干货分享 本篇文章主要介绍 Android 开发中的部分知识点,通过阅读本篇文章,您将收获以下内容: 一.拷贝大文件,IO wait 高,导致SWT重启 二.高 ...

  4. 阅读《大型网站技术架构:核心原理与案例分析》第五、六、七章

    阅读<大型网站技术架构:核心原理与案例分析>第五.六.七章,结合我们的系统,分析如何增加相应的功能,提高系统的可用性和易用性. 这三章主要讲述的是网站的可用性.伸缩性和可扩展性. 高可用架 ...

  5. 六、朴素贝叶斯案例分析

    1.朴素贝叶斯案例分析 朴素贝叶斯案例分析的内容有: 项目概述:屏蔽社区留言板的侮辱性言论 项目实战:朴素贝叶斯案例的实现 数据集信息 朴素贝叶斯案例的数据包含6条样本,具体有3个正样本和3个负样本, ...

  6. 【Python的自学之路】(六):案例分析第四课-小工具2.0

    目录 序言 背景 思路 代码及解析 跋文 序言 小工具1.0版本,备份oracle存储过程的小工具上篇文章已经都了解完毕了,下面继续进行功能升级,2.0版本-表数据的导出功能. 背景 案例分析第四课- ...

  7. [系统安全] 二十三.逆向分析之OllyDbg动态调试复习及TraceMe案例分析

    您可能之前看到过我写的类似文章,为什么还要重复撰写呢?只是想更好地帮助初学者了解病毒逆向分析和系统安全,更加成体系且不破坏之前的系列.因此,我重新开设了这个专栏,准备系统整理和深入学习系统安全.逆向分 ...

  8. 培智学校计算机教学案例,希沃电子白板在培智语文课堂教学中的运用案例分析——以课例《四季的衣服》为例...

    内容摘要:随着信息技术的不断发展,希沃电子白板在培智课堂教学中得到了广泛的推广与运用,为学生提供了丰富的学习资源,将其融入培智教学,能为学生呈现出一种富有趣味性和知识性的课堂,能调动学生积极性,提高学 ...

  9. 个人作业二-软件案例分析

    个人作业-软件案例分析-音乐软件 项目 内容 这个作业属于哪个课程 2023年春季软件工程(罗杰 任健) 这个作业的要求在哪里 个人作业-软件案例分析 我在这个课程的目标是 学习软件工程方法,提升解决 ...

  10. Python+审计实务与案例分析库 毕业设计-附源码211526

    Python审计实务与案例分析库 摘 要 21世纪的今天,随着社会的不断发展与进步,人们对于信息科学化的认识,已由低层次向高层次发展,由原来的感性认识向理性认识提高,管理工作的重要性已逐渐被人们所认识 ...

最新文章

  1. 美国动物园小鹿“撞脸”韩国艺人 粉丝众筹为其取名
  2. C/C++-标准输入/输出重定向为文件输入/输出
  3. MySQL B+树索引和哈希索引的区别
  4. 【已修正】SAP中各个环境的简介
  5. Yii2 mongodb 扩展的where的条件增加大于 小于号
  6. 安装Linux系统的一些问题记载
  7. HAproxy负载均衡动静分离实现及配置详解
  8. 2008秋季-计算机软件基础-0917课堂用例(1)
  9. chrome插件“京东商品佣金助手”之项目介绍(一)
  10. 80 多个免费编程字体,你喜欢哪种?
  11. 物联网和互联网之间,主要有什么关系?
  12. android根据经纬度获取位置,Android获取经纬度
  13. 面向对象使用python-docx模块制作格式化文本(奖状生成器)
  14. 《演说之禅》读书笔记
  15. 物联网离线语音控制智能家居系统设计(三):ESP8266(Arduino IDE)连接阿里云物联网平台
  16. dingo php,laravel 中安装 Dingo API 基本设置
  17. Zookeeper实现注册中心
  18. JAVA设计模式第三讲:结构型设计模式
  19. Vue中watch、computed、updated的区别
  20. Node课程(3,2,1,8,3)

热门文章

  1. python 取域名的两种方式
  2. 服装工业新消费·PLM
  3. 某短视频(dy)创作者平台发布视频JS逆向学习(1)
  4. 海关跨境电商平台数据实时获取接口对接走过的坑(海关165,179号公告)
  5. 微擎视频小程序4.3.0 视频音频图文发布平台
  6. eclipse设置字体大小和main快捷键
  7. JVM深度学习系列之内存使用细节(三)
  8. uniapp通过点击事件click跳转到指定页面
  9. 光机电一体化综合实训系统
  10. m1发卡器支持java_友我科技发布M1卡通用版的发卡充值软件