C#实际案例分析(第六弹)

实验六

目录

  • 实验六
      • 题目要求
      • 环境设置
    • 题意分析
    • 后端·数据库
      • 数据库概述
      • 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标记,比如放入表单元素之中()。是不是很神奇?它在一定程度上可以取代部分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 _logger;

        public HomeController(ILogger 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();

            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();//打开通道,建立连接

                //CRUD
                string 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 Contents { get; set; }
    }
}

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

@{
    ViewData["Title"] = "Home Page";
}


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

@{
    ViewData["Title"] = "Privacy Policy";
}

@ViewData["Title"]

Use this page to detail your site's privacy policy.

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

@model TestViewModel
@{
    ViewData["Title"] = "内容列表";
}

@ViewData["Title"]
@foreach (var item in Model.Contents)//table里的内容基本是写死动不了的,需要通过后序的函数来改造 { }
学号 姓名 性别 生日 书籍
@item.number @item.student @item.sex @item.birth @item.book

代码片段分析

后端·建立数据库

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();

    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();//打开通道,建立连接

        //CRUD
        string 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中


    
    	学号
    		
    		
    	

    	姓名
    		
    	

    	性别
            
    		
    	

    	生日
    	

    	书籍
    	
    

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

@foreach (var item in Model.Contents)//table里的内容基本是写死动不了的,需要通过后序的函数来改造
{
	
		
		@item.number
		@item.student
		@item.sex
		@item.birth
		@item.book
	
}

前面加了@符号的都是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.number
    if (qNumber.indexOf(opNumber.innerText) < 0) { //查无此人
        qNumber.push(opNumber.innerText)//插入去重表格中
        sselectNumber.add(opNumber)//插入
    }

    //原理同上
    oneStudent.student = tableElement.rows[i].cells[1].innerText
    var opName = document.createElement("option")
    opName.innerText = oneStudent.student
    if (qName.indexOf(opName.innerText) < 0) {
        qName.push(opName.innerText)
        sselectName.add(opName)
    }

    oneStudent.sex = tableElement.rows[i].cells[2].innerText
    oneStudent.birth = tableElement.rows[i].cells[3].innerText
    oneStudent.book = tableElement.rows[i].cells[4].innerText

    studentData.push(oneStudent)//插入总的全局数组中
}

JS脚本要写在

你可能感兴趣的:(c#,开发语言,后端,前端)