Winform和ASP.NET、Web API详解

Winform和ASP.NET、Web API

一、winform基础

1.1 基础学习

1、 winform应用程序是一种智能客户端技术,我们可以使用winform应用程序帮助我们获得信息或者传输信息等。

2、属性

Name:在后台要获得前台的控件对象,需要使用Name属性。

visible:指示一个控件是否可见。

Enabled:指示一个控件是否可用。

3、

事件:发生一件事情。

注册事件:双击控件注册的都是控件默认被选中的那个事件。

触发事件:
4、
在Main函数当中创建的窗体对象,我们称之为这个窗体应用程序的主窗体。

也就意味着,当你将主窗体关闭后,整个应用程序都关闭了。

5、TextBox控件

Wordwrap:指示文本框是否换行。

Passwordchar:让文本框显示一个单一的字符

scollBars:是否显示滚动条

事件:Textchanged当文本框中的内容发生改变的时候触发这个事件。

6、跑马灯练习

abcde

bcdea

cdeab

private void timer1_Tick(object sender, EventArgs e)
        {
            //abcde
            label1.Text = label1.Text.Substring(1) + label1.Text.Substring(0, 1);
        }

7、Timer

在指定的时间间隔内做一件指定的事情

8、单选和多选

RadioButton

CheckBox

默认情况下,在一个窗体中,所有的单选按钮只允许选中一个,可以使用groupbox进行分组

9、MDI窗体的设计:MDI(Multiple Document Interface)多文档界面,与此对应就有单文档界面 (SDI)

  1. 首先确定一个父窗体,将IsMdiContainer设置为true
  2. 创建子窗体,并且设置他们的父窗体
private void 显示子窗体ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //显示子窗体
            Form2 form2 = new Form2();
            form2.MdiParent = this;
            form2.Show();
            Form3 form3 = new Form3();
            form3.MdiParent = this;
            form3.Show();
            Form4 form4 = new Form4();
            form4.MdiParent = this;
            form4.Show();
        }

        private void 横向排列ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            LayoutMdi(MdiLayout.TileHorizontal);
        }

        private void 纵向排列ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            LayoutMdi(MdiLayout.TileVertical);
        }

image-20221008165357562

  1. PictureBox

    (1)主要逻辑代码

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    
    namespace WindowsFormsTransPicture
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            /// <summary>
            /// 点击更换上一张图片
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void button1_Click(object sender, EventArgs e)
            {
                i--;
                if (i<0)
                {
                    i = picturePaths.Length-1;
                }
                pictureBox1.Image = Image.FromFile(picturePaths[i]);
            }
    
            private void Form1_Load(object sender, EventArgs e)
            {
                //设置图片显示模式,不设置可能会看不到  Zoom:缩放
                pictureBox1.SizeMode = PictureBoxSizeMode.Zoom;
                pictureBox1.Image = Image.FromFile(@"C:\Users\kevin\Desktop\Picture\Alan Olav Walker.jpg");
            }
            int i = 0;
            //获得指定文件夹下的所有文件的全路径
            string[] picturePaths = Directory.GetFiles(@"C:\Users\kevin\Desktop\Picture");
            /// <summary>
            /// 点击更换下一张图片
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void button2_Click(object sender, EventArgs e)
            {
                i++;
                if (i == picturePaths.Length)
                {
                    i = 0;
                }
                pictureBox1.Image = Image.FromFile(picturePaths[i]);
            }
        }
    }
    

    (2)实现

    image-20221008174638191

  2. 对话框

    系统自带的一些对话框类:

    • OpenFileDialog 打开文件对话框

      private void btnOpenFile_Click(object sender, EventArgs e)
              {
                  var fileContent = string.Empty;
                  var filePath = string.Empty;
      
                  using (OpenFileDialog openFileDialog = new OpenFileDialog())
                  {
                      openFileDialog.InitialDirectory = "D:\\";
                      openFileDialog.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";
                      openFileDialog.FilterIndex = 2;
                      openFileDialog.RestoreDirectory = true;
      
                      if (openFileDialog.ShowDialog() == DialogResult.OK)
                      {
                          //获取需要打开的文件路径
                          filePath = openFileDialog.FileName;
      
                          //读取文件内容
                          var fileStream = openFileDialog.OpenFile();
      
                          using (StreamReader reader = new StreamReader(fileStream))
                          {
                              fileContent = reader.ReadToEnd();
                          }
                      }
                  }
      
                  MessageBox.Show(fileContent, "当前路径下文件内容: " + filePath, MessageBoxButtons.OK);
              }
      
    • SaveFileDialog 保存文件对话框

      private void btnSaveFile_Click(object sender, EventArgs e)
              {
                  Stream myStream;
                  SaveFileDialog saveFileDialog1 = new SaveFileDialog();
      
                  saveFileDialog1.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";
                  saveFileDialog1.FilterIndex = 2;
                  saveFileDialog1.RestoreDirectory = true;
      
                  if (saveFileDialog1.ShowDialog() == DialogResult.OK)
                  {
                      if ((myStream = saveFileDialog1.OpenFile()) != null)
                      {
                          // Code to write the stream goes here.
                          myStream.Close();
                      }
                  }
              }
      
    • FolderBrowserDialog 目录选择对话框

              private void btnInputSrc_Click(object sender, EventArgs e)
              {
                  FolderBrowserDialog fdlg = new FolderBrowserDialog();
                  if (fdlg.ShowDialog() == DialogResult.OK)
                      InputSrc.Text = fdlg.SelectedPath;
              }
      
              private void bntOutputSrc_Click(object sender, EventArgs e)
              {
                  FolderBrowserDialog fdlg = new FolderBrowserDialog();
                  if (fdlg.ShowDialog() == DialogResult.OK)
                      OutputSrc.Text = fdlg.SelectedPath;
              }
      

    image-20221014151354860

    • ColorDialog颜色选择对话框

    • FontDialog 字体选择对括框

  3. 图标下载:https://www.iconfont.cn

  4. 选取文件,选取目录

                //选取目录
                //string debugPath = null;
                //FolderBrowserDialog fbd = new FolderBrowserDialog();
                //fbd.ShowDialog();
                //debugPath = fbd.SelectedPath;
    
                //选取文件
                OpenFileDialog ofd = new OpenFileDialog();
                ofd.ShowDialog();
                string xmlPath = ofd.FileName;    //路径
                //string fileName = Path.GetFileName(xmlPath);//文件名
    

1.2 练习

1.2.1 学生信息编辑

练习:实现一个学生的信息的编辑器

-学号,姓名,性别,手机号

-将数据保存到文件

-启动时从文件读取

  1. 界面布局

    • 添加需要的控件
    • 修改显示文本Text
    • 手工对齐
    • 修改控件名Name

    image-20221014112358664

  2. 保存功能

    点击保存时,将界面的数据保存到文件中

    • 添加NewtonSoft.json支持

      image-20221014112906938

    • 添加学生类

      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      using System.Threading.Tasks;
      
      namespace StudentInfoEdit
      {
          internal class Student
          {
              public int Id { get; set; }
              public string Name { get; set; }
              public string Sex { get; set; }
              public string Phone { get; set; }
      
              public Student()
              {
              }
      
              public Student(int id, string name, string sex, string phone)
              {
                  Id = id;
                  Name = name;
                  Sex = sex;
                  Phone = phone;
              }
          }
      }
      
    • 添加按钮事件处理,将数据保存为Json,存到文件中

      private void saveButton_Click(object sender, EventArgs e)
              {
                  Student student = new Student();
                  student.Id = Convert.ToInt32(idField.Text.Trim());
                  student.Name = nameField.Text.Trim();
                  student.Sex = sexField.Text.Trim();
                  student.Phone = phoneField.Text.Trim();
      
                  //Json 支持
                  string jsonStr = JsonConvert.SerializeObject(student,Formatting.Indented);
                  ReadWriteTextFile.Write(@"D:\C#学习\WinForm\student.txt", jsonStr,Encoding.UTF8);
                  MessageBox.Show("操作成功");
              }
      
  3. 加载功能

    当程序启动时,自动读取student.txt中的数据

    • 在构造方法中加载

       public Form1()
              {
                  InitializeComponent();
                  
                  //加载数据方法
                  LoadData();
                  
                  //设置下拉列表的值
                  //sexField.Items.Add("男");
                  //sexField.Items.Add("女");
              }
      
    • 读取文件,转成json

      private void LoadData()
              {
                  //读取文件
                  var sutdentInfo = ReadWriteTextFile.Read(@"D:\C#学习\WinForm\student.txt", Encoding.UTF8);
                  //转成Student
                  Student student = JsonConvert.DeserializeObject<Student>(sutdentInfo);
                  //MessageBox.Show(student.Name);
      
                  //将数据显示在界面上
                  idField.Text = student.Id.ToString();
                  nameField.Text = student.Name.ToString();
                  phoneField.Text = student.Phone.ToString();
      
                  //if (student.Sex.ToString().Equals("false"))
                  //{
                  //    student.Sex = "男";
                  //}
                  sexField.Text = student.Sex.ToString();
              }
      
    • 将数据显示到界面

      image-20221014122320944

1.2.2 文件处理

需求:根据22-A-01-001-0001-002列查询的重复语句有多少条,并将条数总数打印出来

  1. 进行Winform页面设计

    image-20221020163648132

  2. 进行代码设计

    (1)KeyValueModel类

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace WinFormsModel
    {
        public class KeyValueModel<K,V>
        {
            public K Key { get; set; }
    
            public V Value { get; set; }
        }
    }
    

    (2)Form1.cs类

    using System.Diagnostics;
    using System.Text;
    
    namespace WinFormsModel
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            private void btnInputSrc_Click(object sender, EventArgs e)
            {
                FolderBrowserDialog fdlg = new FolderBrowserDialog();
                if (fdlg.ShowDialog() == DialogResult.OK)
                    InputSrc.Text = fdlg.SelectedPath;
            }
    
            private void btnOutputSrc_Click(object sender, EventArgs e)
            {
                FolderBrowserDialog fdlg = new FolderBrowserDialog();
                if (fdlg.ShowDialog() == DialogResult.OK)
                    OutputSrc.Text = fdlg.SelectedPath;
            }
            /// <summary>
            /// 打开目录
            /// </summary>
            /// <param name="folderPath">目录路径</param>
            private static void OpenFolder(string folderPath)
            {
                if (string.IsNullOrEmpty(folderPath)) return;
    
                Process process = new Process();
                ProcessStartInfo psi = new ProcessStartInfo("Explorer.exe");
                psi.Arguments = folderPath;
                process.StartInfo = psi;
    
                try
                {
                    process.Start();
                }
                catch
                {
                    throw;
                }
                finally
                {
                    process?.Close();
    
                }
            }
            private void ReadINI()
            {
                string confi = Application.StartupPath + "\\conf.txt";
                if (File.Exists(confi))
                {
                    StreamReader sr = new StreamReader(confi, System.Text.Encoding.UTF8);
                    string st = sr.ReadLine();
                    InputSrc.Text = st;
                    st = sr.ReadLine();
                    OutputSrc.Text = st;
    
                    sr.Close();
                }
            }
    
            private void WriteINI()
            {
                string confi = Application.StartupPath + "\\conf.txt";
    
                StreamWriter sr = new StreamWriter(confi, false, System.Text.Encoding.UTF8);
                sr.WriteLine(InputSrc.Text);
                sr.WriteLine(OutputSrc.Text);
    
                sr.Close();
    
            }
    
            private void Form1_Load(object sender, EventArgs e)
            {
                ReadINI();
            }
    
            private void btnCheck_Click(object sender, EventArgs e)
            {
                if (Directory.Exists(InputSrc.Text) && Directory.Exists(OutputSrc.Text))
                {
                    WriteINI();
                    string input = InputSrc.Text;
                    string output = OutputSrc.Text;
    
                    //得到所有的tsv文件
                    var files = Directory.GetFiles(input, "*.tsv", SearchOption.AllDirectories);
    
                    //定义一个字典,利用字典的键不能重复判断有没有重复的句子编号
                    var dic = new Dictionary<string, int>();
    
                    //定义一个重复句子集合
                    var repeatlist = new List<string>();
    
                    //定义一个repeatKV接收重复的句子编号
                    var repeatKV = new List<KeyValueModel<string, int>>();
                    foreach (string file in files)
                    {
                        //读取文件内容
                        var tsvcontents =File.ReadAllLines(file);
                        //遍历每一行内容
                        for (int i = 1; i < tsvcontents.Length; i++)
                        {
                            //以tab键进行分隔,得到一个内容数组
                            var sub = tsvcontents[i].Split('\t');
                            //取出编号
                            //if (sub[4] != null)
                            //{
                            //    alllist.Add(sub[4]);
                            //}
                            //如果有重复的键,则将键加入dic字典
                            if (dic.ContainsKey(sub[4]) == false) 
                            {
                                dic.Add(sub[4], i);
                            }
                            else
                            {
                                repeatKV.Add(new KeyValueModel<string, int>
                                {
                                    Key = sub[4],
                                    Value = i
                                });
                            }
                        }
                    }
                    var res = from kv in repeatKV
                              group kv by kv.Key into k     //按照Key属性进行分组
                              select new { count = k.Count()+1, key = k.Key };  //k.Key:表示按照哪个属性分的组
    
                    var content = string.Empty;
                    foreach (var k in res)
                    {
                        content = k.key + "\t" + k.count + "\r\n";
                        repeatlist.Add(content);
                        rtbmsg.AppendText(content);
                    }
    
                    //var eyValueModels = IsRepeat(repeatlist);
                    var basePath = Path.Combine(output, "重复编号.txt");
                    //File.WriteAllLines(basePath, repeatlist, Encoding.UTF8);  //不使用异步 + Encoding.UTF8写入生成的编码格式:UTF8-BOM
                    File.WriteAllLines(basePath, repeatlist);  //不使用异步写入生成的编码格式:UTF8
                    //File.WriteAllLinesAsync(basePath, repeatlist);   //使用异步写入生成的编码格式:UTF8
                    //File.WriteAllLinesAsync(basePath, repeatlist, Encoding.UTF8);   //使用异步 + Encoding.UTF8写入生成的编码格式:UTF8-BOM
                    OpenFolder(output);
                }
            }
        }
    }
    
  3. tsv文件内容(部分)

    								ja	en	mn
    22	A	01	22-A-01-001-0001-002	地震...
    22	A	01	22-A-01-001-0001-002	地震...
    ....
    此处省略一万条
    ....
    22	A	01	22-A-01-001-0001-002	地震...
    22	A	01	22-A-01-001-0001-006	地震...
    22	A	01	22-A-01-001-0001-007	地震...
    
  4. 运行测试

    image-20221020164536815

1.3 问题总结

\r\n和\n的区别:

一、含义不同:

\r是回车符,\n是换行符。在C语言中,除了表示除法和注释符号,换行和转义字符都是使用反斜杠的,所以这里的斜杠应该指的是反斜杠。\r\n是回车加换行,\n是换行。要注意的是\r是回车,操作在当前行,而\n则是跳到下一行。

二、用法不同:

(1)Unix 系统里,每行结尾只有“<换行>”,即“\n”

(2)Windows系统里面,每行结尾是“<回车><换行>”,即“ \r\n”

(3)Mac系统里,每行结尾是“<回车>”,即“\n”

一个直接后果是,Unix/Mac系统下的文件在Windows里打开的话,所有文字会变成一行;而Windows里的文件在Unix/Mac下打开的话,在每行的结尾可能会多出一个^M符号。

关于写文件的时候生成的文件格式问题:

File.WriteAllLines(basePath,list);  //不使用异步写入生成的编码格式:UTF8
File.WriteAllLinesAsync(basePath,list);   //使用异步写入生成的编码格式:UTF8
File.WriteAllLines(basePath,list, Encoding.UTF8);  //不使用异步 + Encoding.UTF8写入生成的编码格式:UTF8-BOM
File.WriteAllLinesAsync(basePath,list, Encoding.UTF8);   //使用异步 + Encoding.UTF8写入生成的编码格式:UTF8-BOM

二、ASP.NET

准备

推荐直接安装VS2022正式版,.NET环境全部自带,最省事儿!

1、 Visual Studio 2022正式版
下载地址: https://visualstudio.microsoft.com/zh-hans/vs/

3个版本,目前都是英文版的
社区版 vs_Community.exe

专业版 vs_professional

企业版 vs_community
社区版不需要激活码

专业版激活码:
TD244-P4NB7-YQ6XK-Y8MMM-YWV2J

企业版激活码:
VHF9H-NXBBB-638P6-6JHCY-88JWH
tips:在线安装时,如果下载不了安装文件,则是需要设置dns为8.8.8.8 114.114.114.114

2、 VS2022是带了.NET6的SDK,但运行时有依赖,建议单独下载Hosting Bundle并安装
下载地址: https://dotnet.microsoft.com/download/dotnet/6.0
dotnet-hosting-6.0.0-win.exe

3、不建议使用2019了,,只有MAC是VS2019

2.1 ASP.NET

ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。

ASP.NET 是新一代 ASP 。它与经典 ASP 是不兼容的,但 ASP.NET 可能包括经典 ASP。

ASP.NET 页面是经过编译的,这使得它们的运行速度比经典 ASP 快。

ASP.NET 具有更好的语言支持,有一大套的用户控件和基于 XML 的组件,并集成了用户身份验证。

ASP.NET 页面的扩展名是 .aspx ,通常是用 VB (Visual Basic) 或者 C# (C sharp) 编写。

在 ASP.NET 中的控件可以用不同的语言(包括 C++ 和 Java)编写。

当浏览器请求 ASP.NET 文件时,ASP.NET 引擎读取文件,编译和执行脚本文件,并将结果以普通的 HTML 页面返回给浏览器。

ASP.NET 支持三种不同的开发模式:
Web Pages(Web 页面)、MVC(Model View Controller 模型-视图-控制器)、Web Forms(Web 窗体):

Web Pages 单页面模式MVC 模型-视图-控制器Web Forms 事件驱动模式
最简单的 ASP.NET 模式。 与 PHP 和经典 ASP 相似。 内置了数据库、视频、图形、社交媒体等模板和帮助器。MVC 将 Web 应用程序分成 3 个不同的组成部分: 模型负责数据 视图负责显示 控制器负责输入传统的 ASP.NET 事件驱动开发模式: 带有服务器控件、服务器事件和服务器代码的网页。

2.2 Web Pages和Razor的简单了解

Web Pages:

Web Pages 是三种创建 ASP.NET 网站和 Web 应用程序的编程模式中的一种。

其他两种编程模式是 Web Forms 和 MVC(Model View Controller 模型-视图-控制器)。

Web Pages 是开发 ASP.NET 网页最简单的开发模式。它提供了一种简单的方式来将 HTML、CSS、JavaScript 和服务器脚本结合起来:

  • 容易学习,容易理解,容易使用
  • 围绕着单一的网页创建
  • 与 PHP 和经典 ASP 相似
  • Visual Basic 或者 C# 的服务器脚本
  • 全 HTML、CSS 和 JavaScript 控制

Web Pages 内置了数据库、视频、图形、社交媒体和其他更多的 Web Helpers,因此很容易扩展。

<!DOCTYPE html>

<html lang="en">
<head>
<meta charset="utf-8" />
<title>Web Pages Demo</title>
</head>
<body>
<h1>Hello Web Pages</h1>
</body>
</html>

现在向实例中添加一些 Razor 代码:

<!DOCTYPE html>

<html lang="en">
<head>
<meta charset="utf-8" />
<title>Web Pages Demo</title>
</head>
<body>
<h1>Hello Web Pages</h1>
<p>The time is @DateTime.Now</p>
</body>
</html>

该页面中包含普通的 HTML 标记,除此之外,还添加了一个 @ 标识的 Razor 代码。

Razor 代码能够在服务器上实时地完成多有的动作,并将结果显示出来。(您可以指定格式化选项,否则只会显示默认项。)

什么是 Razor ?

  • Razor 是一种将基于服务器的代码添加到网页中的标记语法
  • Razor 具有传统 ASP.NET 标记的功能,但更容易使用并且更容易学习
  • Razor 是一种服务器端标记语法,与 ASP 和 PHP 很像
  • Razor 支持 C# 和 Visual Basic 编程语言

主要的 Razor C# 语法规则

  • Razor 代码块包含在 @{ … } 中

  • 内联表达式(变量和函数)以 @ 开头

  • 代码语句用分号结束

  • 变量使用 var 关键字声明

    (1)变量是用来存储数据的。

    (2)一个变量的名称必须以字母字符开头,并且不能包含空格或者保留字符。

    (3)一个变量可以是一个指定的类型,表示它所存储的数据类型。string 变量存储字符串值(“Welcome to RUNOOB.COM”),integer 变量存储数字值(103),date 变量存储日期值,等等。

    (4)变量使用 var 关键字声明,或通过使用类型(如果您想声明类型)声明都可以,但是 ASP.NET 通常能自动确定数据类型。var关键字一般声明为局部变量,全局变量不能用var声明。

    // 使用 var 关键字:
    var greeting = "Welcome to China";
    var counter = 103;
    var today = DateTime.Today;
    
    // 使用数据类型:
    string greeting = "Welcome to China";
    int counter = 103;
    DateTime today = DateTime.Today;
    
  • 字符串用引号括起来

  • C# 代码区分大小写

  • C# 文件的扩展名是 .cshtml

<!-- Single statement block -->
@{ var myMessage = "Hello World"; }

<!-- Inline expression or variable -->
<p>The value of myMessage is: @myMessage</p>

<!-- Multi-statement block -->
@{
var greeting = "Welcome to our site!";
var weekDay = DateTime.Now.DayOfWeek;
var greetingMessage = greeting + " Today is: " + weekDay;
}
<p>The greeting is: @greetingMessage</p>

输出:
The value of myMessage is: Hello World

The greeting is: Welcome to our site! Here in Huston it is: Saturday

2.3 ASP.NET MVC

2.3.1 MVC基础

复杂网站–使用MVC

MVC 是三种 ASP.NET 编程模式中的一种。

MVC 是一种使用 MVC(Model View Controller 模型-视图-控制器)设计创建 Web 应用程序的模式:

  • Model(模型)表示应用程序核心(比如数据库记录列表)。
  • View(视图)显示数据(数据库记录)。
  • Controller(控制器)处理输入(写入数据库记录)。

MVC 模式同时提供了对 HTML、CSS 和 JavaScript 的完全控制。


MVCMVC 模式定义 Web 应用程序 带有三个逻辑层:业务层(模型逻辑)显示层(视图逻辑)输入控制(控制器逻辑

**Model(模型)**是应用程序中用于处理应用程序数据逻辑的部分。
通常模型对象负责在数据库中存取数据。

**View(视图)**是应用程序中处理数据显示的部分。
通常视图是依据模型数据创建的。

**Controller(控制器)**是应用程序中处理用户交互的部分。
通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。

MVC 分层有助于管理复杂的应用程序,因为您可以在一个时间内专门关注一个方面。例如,您可以在不依赖业务逻辑的情况下专注于视图设计。同时也让应用程序的测试更加容易。

MVC 分层同时也简化了分组开发。不同的开发人员可同时开发视图、控制器逻辑和业务逻辑。

添加数据库控制器

数据库控制器可以通过以下几个简单的步骤来创建:

  • 在 Solution Explorer(解决方案资源管理器)中,右击 Controllers 文件夹,选择 AddController

    image-20221009162113948

  • 选择模板:Controller with read/write actions and views, using Entity Framework

    image-20221009162143959

  • 选择模型类:MovieDB (MvcDemo.Models)

  • 选择 data context 类:MovieDBContext (MvcDemo.Models)

  • 选择视图 Razor (CSHTML)

  • 设置控制器名称为 MoviesController

    image-20221009162309708

  • 点击 Add

Visual Web Developer 将创建以下文件:

  • Controllers 文件夹中的 MoviesController.cs 文件
  • Views 文件夹中的 Movies 文件夹

image-20221009162849514


添加数据库视图

在 Movies 文件夹中,会自动创建以下文件:

  • Create.cshtml
  • Delete.cshtml
  • Details.cshtml
  • Edit.cshtml
  • Index.cshtml

MVC项目:

  • M(模型model)–用来传递数据

  • C(控制器Controller)–HTTP请求到了MEB服务器使用控制器来处理这些请求(处理请求,数据处理,生成响应)

​ 作用:

​ (1)在构造函数中注入服务

​ (2)执行动作方法

​ (3)向视图传递数据,HTML、PDF、EXCEL、JOSN

​ (4)将结果附带状态码返回给客户端(例如:200 OK)

image-20221009172802099

  • V (View) --Razor视图(.cshtml文件)

    (1)视图搜索方式:

    /Views/Home/xxx.cshtml
    /Views/Shared/xxx.cshtml
    /Pages/Shared/xxx.cshtml
    

    image-20221009174316956

    启动项目打开该页面

    image-20221009174345379

Areas:什么情况下会用到,一个项目有多个系统,或者一个分前段(客户)后端(网站管理)

image-20221009154639721

ControllerBase基类:

(1)不支持视图

(2)后面WebAPI也是用这个基类

(3) htppContext(请求和响应,包括有关的链接信息、包括一些中间件以及和身份验证和授权User对象)

(4) Request(HTTP请求,请求头,查询字符串,内容类型)

(5)Response()

Controller类:

(1)数据传递–ViewData(键值对的字典–只能在当前请求里面保存数据)/ViewBag是对vViewData的包装好处编写人性化/TempData(跨请求保存数据,生命周期长一点)
(2)处理视图的方法:
View ()返回和动作方法同名的视图

过滤器Filter:
作用–向多个控制器和动作方法添加一些功能的时候,可以使用或者自定义的过滤器。

(1)标注在控制器–对整个控制器产生作用

(2)标注在动作方法上面–对这个动作方法起作用

(3)通过服务的形式配置–全局作用

并行编程在多核CPU

image-20221010100917244

并发编程:榨干CPU,充分利用CPU资源

image-20221010100712442

Thread—》ThreadPool线程池—》Task任务(async await)

异步编程:提高服务器吞吐量(I/0)

image-20221010101211183

//async:1、使标注的方法变成异步方法 2、使调用方法时前面加的await生效 3、异步编程返回值:  无返回值:Task   有返回值:Task<返回值类型>
        public async Task<IActionResult> Index()
        {
            //创建角色
            await roleManager.CreateAsync(new IdentityRole(AdminRole));
            //创建管理员用户
            IdentityUser user = new IdentityUser { UserName = UserEmail ,Email = UserEmail,EmailConfirmed = true};  //EmailConfirmed = true:Email用户验证为true,就可以登录了,否则登录不了
            await userManager.CreateAsync(user,UserEmail);
            //把用户添加到角色
            await userManager.AddToRoleAsync(user,AdminRole);

            //return View();
            //Redirect:重定向跳转页面,也就是直接跳转到指定页面   "/":表示首页
            return Redirect("/");
        }

2.3.2 程序包管理控制台迁移数据库报错

PM> add-migration CreateIdentitySchema
Build started...
Build succeeded.
//第一个错
The Entity Framework tools version '6.0.4' is older than that of the runtime '6.0.9'. Update the tools for the latest features and bug fixes. See https://aka.ms/AAc1fbw for more information.

System.TypeLoadException: Method 'Create' in type 'MySql.Data.EntityFrameworkCore.Query.Internal.MySQLSqlTranslatingExpressionVisitorFactory' from assembly 'MySql.Data.EntityFrameworkCore, Version=8.0.22.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d' does not have an implementation.
...
//第二个错
Method 'Create' in type 'MySql.Data.EntityFrameworkCore.Query.Internal.MySQLSqlTranslatingExpressionVisitorFactory' from assembly 'MySql.Data.EntityFrameworkCore, Version=8.0.22.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d' does not have an implementation.

第一个错的意思是:实体框架工具版本“6.0.4”比运行时版本“6.0.9”更旧。更新工具以获得最新的特性和bug修复。

接下来以管理员身份打开CMD 运行以下命令:

dotnet tool update --global dotnet-ef

image-20221010104451928

重启visual studio即可

如果以上操作还是不行的话,则是因为你导入的包的版本不一致,需要全部更新为6.0.9,如下

image-20221010105812811

连接MySQL
1、通过NuGet添加相关的包:

Pomelo.EntityFrameworkCore.MySql

image-20221010132235781

注:
网上很多资料都是关于引用 MySql.Data.EntityFrameworkCore 连接MySql的,但MySql.Data.EntityFrameworkCore目前好像已经弃用了。引用 MySql.Data.EntityFrameworkCore后,会报以下错误,所以不要安装这个包,安装了的话需要移除,只需要使用上面那个就行了:

MySql.Data.EntityFrameworkCore.Query.Internal.MySQLSqlTranslatingExpressionVisitorFactory' from assembly 'MySql.Data.EntityFrameworkCore, Version=8.0.22.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d' does not have an implementation

2、新增MySql数据库连接配置

在 在appsettings.json 新增数据库连接

{
  //新增数据库连接
  "ConnectionStrings": {
    "DefaultConnection": "Server=127.0.0.1;UserId=root;Password=123456;Database=webmvc"
  },
  //下面这些是原来的不用管
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

3、在Program.cs中注册服务

// 注册服务
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(builder => builder.UseMySql(connectionString, new MySqlServerVersion(new Version(8, 0, 11))));

下面是我的Program.cs代码,有需要的话可以借鉴(此处创建的是一个MVC项目):

using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebMvcMysql.Data;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");

//builder.Services.AddDbContext<ApplicationDbContext>(options =>
//    options.UseSqlServer(connectionString));
builder.Services.AddDbContext<ApplicationDbContext>(builder => builder.UseMySql(connectionString, new MySqlServerVersion(new Version(8, 0, 11))));

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();


var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Home/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();

app.Run();

注: 这里必须指定 ServerVersion,否则无法编译通过,会报第二个错误
4、调试运行,访问页面成功

image-20221010122613317

到这来,连接MySQL 已完成

2.3.3 创建MVC实战项目

  1. 创建项目

    image-20221010133905952

  2. 连接mysql数据库

    1)通过NuGet添加相关的包:

    Pomelo.EntityFrameworkCore.MySql

    image-20221010132235781

    注:
    网上很多资料都是关于引用 MySql.Data.EntityFrameworkCore 连接MySql的,但MySql.Data.EntityFrameworkCore目前好像已经弃用了。引用 MySql.Data.EntityFrameworkCore后,会报以下错误,所以不要安装这个包,安装了的话需要移除,只需要使用上面那个就行了:

    MySql.Data.EntityFrameworkCore.Query.Internal.MySQLSqlTranslatingExpressionVisitorFactory' from assembly 'MySql.Data.EntityFrameworkCore, Version=8.0.22.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d' does not have an implementation
    

    2)新增MySql数据库连接配置

    在appsettings.json 新增数据库连接

    {
      //新增数据库连接
      "ConnectionStrings": {
        "DefaultConnection": "Server=127.0.0.1;UserId=root;Password=123456;Database=webmvc"
      },
      //下面这些是原来的不用管
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft.AspNetCore": "Warning"
        }
      },
      "AllowedHosts": "*"
    }
    
    

    3)在Program.cs中注册服务

    // 注册服务
    var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
    builder.Services.AddDbContext<ApplicationDbContext>(builder => builder.UseMySql(connectionString, new MySqlServerVersion(new Version(8, 0, 11))));
    

    下面是我的Program.cs代码,有需要的话可以借鉴(此处创建的是一个MVC项目):

    using Microsoft.AspNetCore.Identity;
    using Microsoft.EntityFrameworkCore;
    using WebMvcMysql.Data;
    
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
    
    //builder.Services.AddDbContext<ApplicationDbContext>(options =>
    //    options.UseSqlServer(connectionString));
    builder.Services.AddDbContext<ApplicationDbContext>(builder => builder.UseMySql(connectionString, new MySqlServerVersion(new Version(8, 0, 11))));
    
    builder.Services.AddDatabaseDeveloperPageExceptionFilter();
    
    builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    builder.Services.AddRazorPages();
    
    
    var app = builder.Build();
    
    // Configure the HTTP request pipeline.
    if (app.Environment.IsDevelopment())
    {
        app.UseMigrationsEndPoint();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
        app.UseHsts();
    }
    
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    
    app.UseRouting();
    
    app.UseAuthentication();
    app.UseAuthorization();
    
    app.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
    app.MapRazorPages();
    
    app.Run();
    

    注: 这里必须指定 ServerVersion,否则无法编译通过,会报第二个错误

    4)删除系统生成的迁移文件夹,因为系统默认连接的是SQL Server,否则直接执行Update-Database或者进行下面的迁移会报错

    image-20221010152815050

    5)在程序包管理控制台执行命令即可生成以mysql数据库的迁移文件夹,有问题参照 2.3.2

    add-migration CreateIdentitySchema
    

    image-20221010153500339

  3. 启动项目,点击注册

  4. 点击进行迁移

    image-20221010150732016

  5. 刷新页面,并点击最后的蓝色字体进行邮箱确认,否则会登录不进系统

    image-20221010150807931

  6. 迁移成功之后便会在数据库中自动生成下面这些表

    image-20221010151033463

  7. 数据库表分析

    image-20221010152259692

  8. 登录系统

    image-20221010152325081

  9. 访问权限设置

            //在方法名上方添加:[Authorize(Roles ="admin")],表示对方法访问进行授权限制访问(过滤器)
            //需要对应的角色才能登录Index页面
            [Authorize(Roles ="admin")]
            public IActionResult Index()
            {
                return View();
            }
    

    image-20221010154239230

  10. 再次登录系统被拒

    image-20221010154323458

  11. Route过滤器自定义路由

    [Route("error")] //使用过滤器自定义路由
            public IActionResult Error()
            {
                return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
            }
    

    image-20221010163357383

2.3.4 CRUD快速创建

  1. 新建Product类

    namespace WebMvcMysql.Models
    {
        public class Product
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public decimal Price { get; set; }  //价格
            public short Stock { get; set; }  //库存
            public string? ImageUrl { get; set; }  //图片路径
            public IFormFile? FormFile { get; set; }  //图片
        }
    }
    
  2. 将其添加到

    using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore;
    using WebMvcMysql.Models;
    
    namespace WebMvcMysql.Data
    {
        public class ApplicationDbContext : IdentityDbContext
        {
            public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
                : base(options)
            {
    
            }
            //需要在此增加,否则不能生成对应的数据库表
            public DbSet<Product> Products { get; set; }
        }
    }
    

    执行命令:add-migration addproduct

    image-20221010164722782

    报错,具体就是说数据库中没有对应的类型,不能形成映射,所以这个类型不需要映射

    The property 'Product.FormFile' is of an interface type ('IFormFile'). If it is a navigation, manually configure the relationship for this property by casting it to a mapped entity type. Otherwise, ignore the property using the [NotMapped] attribute or 'Ignore' in 'OnModelCreating'.
    

    修改实体类

    using System.ComponentModel.DataAnnotations.Schema;
    
    namespace WebMvcMysql.Models
    {
        public class Product
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public decimal Price { get; set; }  //价格
            public short Stock { get; set; }  //库存
            public string? ImageUrl { get; set; }  //图片路径
            [NotMapped]  //不映射的注解
            public IFormFile? FormFile { get; set; }  //图片
        }
    }
    
  3. 继续

    add-migration addproduct  //执行迁移命令
    
    update-database  //更新数据库命令,不执行此语句数据库中不会有对应的表
    

    image-20221010165519503

    image-20221010165711584

  4. CRUD

  • Index:显示页面
  • Edit:编辑页面
  • Create:创建页面
  • Delete:删除页面
  • Details:明细页面,只能看不能改
  1. 添加数据库控制器:

    1)生成方式一(不建议使用,使用下面的生成方式二去生成):

image-20221010170518804

自动生成

image-20221010170554529

2)生成方式二:

image-20221010170826269

出现如下不用管,重新继续操作一遍就可以了

image-20221010170957844

自动生成如下

image-20221010171210814

  1. 启动项目访问页面,https://localhost:7134/products/index

    格式:https://localhost:7134/控制器名称/页面名称

    image-20221010172511478

  2. 添加Products,点击即可访问

    image-20221010173133623

    image-20221010173204678

2.4 Razor Pages

2.4.1 了解网络开发

1、遵守HTTP
2、https://www.baidu.com/—180.101.49.11(域名–localhost) :443(端口号)(https加密,http明文)5000,5001,资源路径
3、get请求(一般用于资源请求,可以带参数),POST请求(注册,登陆)
4、需要的基础知识HTNL5/CSS3/JavaScript(TypeScript),C#基础进阶、EFCore,Bootstarp

2.4.2 HTTPS安全跳转设置

  1. https:

    image-20221011163530406

  2. http

    image-20221011163622815

  3. 项目链接分析

image-20221011163434245

  1. 在Program.cs中编写代码

    var builder = WebApplication.CreateBuilder(args); //创建默认web主机
    var app = builder.Build();  //创建
    
    //判断是否是开发环境,可以去launchSettings.json文件中查看environmentVariables
    //if (!app.Environment.IsDevelopment())
    //{
    //    app.UseHsts();  //添加安全访问中间件
    //}
    app.UseHttpsRedirection(); //自动跳转到HTTPS,用HTTPS链接去访问
    
    app.MapGet("/", () => "Hello World!");
    
    app.Run();  //运行,阻塞调用
    
  2. 启动项目进行测试,默认是开发环境

    image-20221011164757404

  3. launchSettings.json

    {
      "iisSettings": {
        "windowsAuthentication": false,
        "anonymousAuthentication": true,
        "iisExpress": {
          "applicationUrl": "http://localhost:8836",
          "sslPort": 44311
        }
      },
      "profiles": {
        "RazorPages.web": {
          "commandName": "Project",
          "dotnetRunMessages": true,
          "launchBrowser": true,
          "applicationUrl": "https://localhost:7036;http://localhost:5036",
          "environmentVariables": {
            "ASPNETCORE_ENVIRONMENT": "Development"
            //"ASPNETCORE_ENVIRONMENT": "Production"  //部署上线后设置为Production
          }
        },
        "IIS Express": {
          "commandName": "IISExpress",
          "launchBrowser": true,
          "environmentVariables": {
            "ASPNETCORE_ENVIRONMENT": "Development"
          }
        }
      }
    }
    

    image-20221011165452242

2.4.3 Startup类设置和传递静态文件

  1. 添加类

    image-20221011170000320

  2. 修改类

    using Microsoft.AspNetCore.Builder;
    
    namespace RazorPages.web
    {
        public class Startup
        {
            public void ConfigureServices(IServiceCollection services)
            {
    
            }
            public void Configure(IApplicationBuilder app,IWebHostEnvironment env)
            {
                //判断是否是开发环境,可以去launchSettings.json文件中查看environmentVariables
                if (!env.IsDevelopment())
                {
                    app.UseHsts();  //添加安全访问中间件
                }
                app.UseRouting();  //启动终结点路由(做选择)
    
                app.UseHttpsRedirection(); //自动跳转到HTTPS,用HTTPS链接去访问
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapGet("/", () => "Hello World!");
                });
                
            }
        }
    }
    
  3. 启动项目:在Program.cs写入以下代码

    //var builder = WebApplication.CreateBuilder(args); //创建默认web主机
    //var app = builder.Build();  //创建
    
    判断是否是开发环境,可以去launchSettings.json文件中查看environmentVariables
    //if (!app.Environment.IsDevelopment())
    //{
    //    app.UseHsts();  //添加安全访问中间件
    //}
    //app.UseHttpsRedirection(); //自动跳转到HTTPS,用HTTPS链接去访问
    
    //app.MapGet("/", () => "Hello World!");
    
    //app.Run();  //运行,阻塞调用
    
    
    using RazorPages.web;
    
    //启动项目的方法
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        }).Build().Run();
    
    
  4. 传递静态文件,新建文件夹命名为wwwroot

    image-20221011171651944

  5. 新建一个index.html文件在wwwroot文件夹下

    image-20221011171845397

  6. 编辑index.html文件

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <!--引入bootstrap的CSS样式-->
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rTTiRUKnSWaDu2FjhzWFl8/JuUZMlplyWE/djenb2LoKqkgLGfEGfSrL7XDLoB1M" crossorigin="anonymous">
        <title>小舒的网页</title>
    </head>
    <body>
        <div class="container">
            <h1>这是一个静态页面</h1>
        </div>
    </body>
    </html>
    
  7. 在Startup文件中添加如下代码

    image-20221011172733415

  8. 启动项目访问页面

    image-20221011172759117

  9. 设置默认路由

    app.UseDefaultFiles(); //index.html,default.html
    
    using Microsoft.AspNetCore.Builder;
    
    namespace RazorPages.web
    {
        public class Startup
        {
            public void ConfigureServices(IServiceCollection services)
            {
    
            }
            public void Configure(IApplicationBuilder app,IWebHostEnvironment env)
            {
                //判断是否是开发环境,可以去launchSettings.json文件中查看environmentVariables
                if (!env.IsDevelopment())
                {
                    app.UseHsts();  //添加安全访问中间件
                }
                app.UseRouting();  //启动终结点路由(做选择)
    
                app.UseHttpsRedirection(); //自动跳转到HTTPS,用HTTPS链接去访问
                
                app.UseDefaultFiles(); //index.html,default.html
    
                app.UseStaticFiles();  //添加启动静态文件页面
    
                app.UseEndpoints(endpoints =>
                {   
                    //页面访问路由,可自定义
                    endpoints.MapGet("/xiaoshu", () => "Hello World!");
                });
                
            }
        }
    }
    

2.4.4 创建Razor动态页面(C#+HTML)

  1. 准备,文件夹名称必须是Pages,否则会找不到

    image-20221011182531699

  2. 在Startup文件中修改页面访问路由

    image-20221011180924714

  3. 编辑Razor页面

    @page
    @model RazorPages.web.Pages.IndexModel
    @{
        //这里可以写C#代码,还可以写http请求的时候执行的方法,比如OnGet(get请求时执行),OnPost(post请求时执行)
       Model.Name="我是动态页面";
    }
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <!--引入bootstrap的CSS样式-->
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rTTiRUKnSWaDu2FjhzWFl8/JuUZMlplyWE/djenb2LoKqkgLGfEGfSrL7XDLoB1M" crossorigin="anonymous">
        <title>Razor的网页</title>
    </head>
    <body>
        <div class="container">
            <h1>@Model.Name</h1>
        </div>
    </body>
    </html>
    
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.RazorPages;
    
    namespace RazorPages.web.Pages
    {
        public class IndexModel : PageModel
        {
            public string? Name { get; set; }
            public void OnGet()
            {
                
            }
        }
    }
    
  4. 启动项目

    image-20221011182637766

2.5 日志组件 Log4net

2.5.1 日志组件 Log4net

Nlog:http://t.csdn.cn/581eS

Log4net:http://t.csdn.cn/1zAsR

  1. Nuget引入程序包

    • microsoft.extensions.logging.log4net.aspnetcore
    • log4net

    image-20221012133354774

  2. 准备好配置文件 log4net.config

    <?xml version="1.0" encoding="utf-8" ?>
    <log4net>
    	<!-- Define some output appenders -->
    
    	<!--Type 表示用那种类型记录日志-->
    	<appender name="rollingAppender" type="log4net.Appender.RollingFileAppender">
    		<!--表示用文本来记录日志-->
    
    		<file value="log\log.txt" />
    		<!--<file value="E:\Log4\log.txt" />-->
    
    		<!--追加日志内容-->
    		<!--<appendToFile value="false" />-->
    		<appendToFile value="true" />
    
    		<!--防止多线程时不能写Log,官方说线程非安全-->
    		<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
    		<!--最小锁-->
    
    		<!--配置Unicode编码-->
    		<Encoding value="UTF-8" />
    
    		<!--是否只写到一个文件里-->
    		<param name="StaticLogFileName" value="false" />
    
    		<!--当备份文件时,为文件名加的后缀,这里可以作为每一天的日志分别存储不同的文件-->
    		<datePattern value="yyyyMMdd&quot;.txt&quot;" />
    
    		<!--可以为:Once|Size|Date|Composite-->
    		<!--Composite为Size和Date的组合-->
    		<!--<rollingStyle value="Composite" />-->
    		<rollingStyle value="Size" />
    
    		<!--日志最大个数,都是最新的-->
    		<!--rollingStyle节点为Size时,只能有value个日志文件-->
    		<!--rollingStyle节点为Composite时,每天有value个日志-->
    		<maxSizeRollBackups value="2" />
    
    		<!--可用的单位:KB|MB|GB-->
    		<maximumFileSize value="2MB" />
    
    		<!--置为true,当前最新日志文件名永远为file节中的名字-->
    		<staticLogFileName value="true" />
    
    		<!--过滤器-->
    
    		<!--阻止所有事件被记录-->
    		<!--<filter type="log4net.Filter.DenyAllFilter"> </filter>-->
    
    		<!--只有指定等级的日志事件才被记录-->
    		<!--<filter type="log4net.Filter.LevelMatchFilter">
            <param value="FATAL" />
          </filter>-->
    
    		<!--日志等级在指定范围内的事件才被记录-->
    		<!--<filter type="log4net.Filter.LevelRangeFilter">
            <param name="LevelMin" value="ERROR" />
            <param name="LevelMax" value="FATAL" />
          </filter>-->
    
    
    		<layout type="log4net.Layout.PatternLayout">
    			<!--日志输出格式:时间  日志类型  日志内容-->
    			<conversionPattern value="%date [%thread] %-5level %logger - %message%newline"/>
    		</layout>
    	</appender>
    
    	<!-- levels: OFF > FATAL > ERROR > WARN > INFO > DEBUG  > ALL -->
    	<root>
    		<priority value="ALL"/>
    		<level value="ALL"/>
    		<appender-ref ref="rollingAppender" />
    	</root>
    </log4net>
    
  3. 配置读取配置文件生效

    //Nuget引入:
    //1、Log4Net
    //2、microsoft.extensions.logging.log4net.aspnetcore
    builder.Logging.AddLog4Net("CfgFile/log4net.config");
    

    image-20221012134213135

  4. 注入得到log4net实例开始写日志

  • 创建一个日志控制器Log4netController

    using Microsoft.AspNetCore.Mvc;
    
    namespace WebMvc.Controllers
    {
        public class Log4netController : Controller
        {
            private readonly ILogger<Log4netController> _Logger;
            private readonly ILoggerFactory _LoggerFactory;
            public Log4netController(ILogger<Log4netController> logger,ILoggerFactory loggerFactory)
            {
                this._Logger = logger;
                this._Logger.LogInformation($"{this.GetType().Name}被构造了 ---> _Logger");
    
                this._LoggerFactory = loggerFactory;
                ILogger < Log4netController >  _Logger2 =this._LoggerFactory.CreateLogger<Log4netController>();
                _Logger2.LogInformation($"{this.GetType().Name}被构造了  ---> _Logger2");
            }
            public IActionResult Index()
            {
                ILogger<Log4netController> _Logger3 = this._LoggerFactory.CreateLogger<Log4netController>();
                _Logger3.LogInformation($"Index被执行了");
                this._Logger.LogInformation($"Index被执行了");
                return View();
            }
        }
    }
    
  • 创建一个Razor视图

    @{
        ViewData["Title"] = "Index";
    }
    <h1>Index</h1>
    

    image-20221012141818935

  • 访问页面,项目链接+控制器名字+index页面

    image-20221012142005046

  • 生成日志

    image-20221012142147507

  • 项目路径下生成日志文件

    image-20221012142221532

2.5.2 Log4net写Mysql(未实现)

分析:可能是数据库没有连接上,日志一般不写入数据库,我也不深究了,这里仅仅是提供一个思路,在下面的webapi项目中我使用配置文件成功连接上了数据库,有兴趣的可以在下面的内容中学习并结合这里的知识去试一下!

Log4net写Mysql:http://t.csdn.cn/xhWIK,有需要的自己看链接文章即可

  1. 引入nuget程序包Mysql.Data

    image-20221012143931056

  2. 在配置文件 log4net.config添加支持Mysql数据库的配置

    <!--name表示appender的name,随便取   type表示用什么类型记录日志    ADONetAppender代表用数据库记录-->
    	<appender name="AdoNetAppender_Mysql" type="log4net.Appender.ADONetAppender">
    		<param name="ConnectionType" value="MySql.Data.MySqlClient.MySqlConnection, MySql.Data" />
    		<!--指定的数据库连接字符串-->
    		<!--<param name="ConnectionString" value="Server=127.0.0.1;port=3306;UserID=root;password=123456;database=webmvc;charset=utf8;Allow User Variables=True"/>-->
    		<param name="ConnectionString" value="Server=127.0.0.1;port=3306;UserID=root;password=123456;database=webmvc;charset=utf8mb4;Allow User Variables=True"/>
    		<param name="CommandText" value="insert into log(datetime,thread,level,ogger,message,exception) values(@datetime, @thread , @level, @logger, @message,@exception)" />
    
    		<param name="Parameter">
    			<param name="ParameterName" value="@datetime" />
    			<param name="DbType" value="DateTime" />
    			<param name="Layout" type="log4net.Layout.PatternLayout">
    				<param name="ConversionPattern" value="%d{yyyy'-'MM'-'dd HH':'mm':'ss'.'fff}" />
    			</param>
    		</param>
    
    		<param name="Parameter">
    			<param name="ParameterName" value="@thread" />
    			<param name="DbType" value="longtext" />
    			<!--<param name="Size" value="255" />-->
    			<param name="Layout" type="log4net.Layout.PatternLayout">
    				<param name="ConversionPattern" value="%t" />
    			</param>
    		</param>
    		<param name="Parameter">
    			<param name="ParameterName" value="@level" />
    			<param name="DbType" value="longtext" />
    			<!--<param name="Size" value="50" />-->
    			<param name="Layout" type="log4net.Layout.PatternLayout">
    				<param name="ConversionPattern" value="%p" />
    			</param>
    		</param>
    		<param name="Parameter">
    			<param name="ParameterName" value="@logger" />
    			<param name="DbType" value="longtext" />
    			<!--<param name="Size" value="255" />-->
    			<param name="Layout" type="log4net.Layout.PatternLayout">
    				<param name="ConversionPattern" value="%c" />
    			</param>
    		</param>
    		<param name="Parameter">
    			<param name="ParameterName" value="@message" />
    			<param name="DbType" value="longtext" />
    			<!--<param name="Size" value="10000" />-->
    			<param name="Layout" type="log4net.Layout.PatternLayout">
    				<param name="ConversionPattern" value="%m" />
    			</param>
    		</param>
    		<param name="Parameter">
    			<param name="ParameterName" value="@exception" />
    			<param name="DbType" value="longtext" />
    			<!--<param name="Size" value="10000" />-->
    			<param name="Layout" type="log4net.Layout.PatternLayout">
    				<param name="ConversionPattern" value="%m" />
    			</param>
    		</param>
    
    	</appender>
    

    在最后的root中使配置内容生效

    	<!-- levels: OFF > FATAL > ERROR > WARN > INFO > DEBUG  > ALL -->
    	<root>
    		<priority value="ALL"/>
    		<level value="ALL"/>
    		<appender-ref ref="rollingAppender" />
    		//使配置内容生效
    		<appender-ref ref="AdoNetAppender_Mysql" />
    	</root>
    

    image-20221012145616897

三、Web API

3.1 两种不同的WebApi项目创建

3.1.1 如何创建WebApi项目(一)

  1. 选择创建web 应用程序

    image-20221017151841194

  2. 命名

    image-20221017152024009

  3. 选择Web API,点击创建即可

    image-20221017152229955

  4. 生成如下页面

    image-20221019170509414

  5. 点击运行得到如下页面

    image-20221019171625334

3.1.2 如何创建WebApi项目(二)

  1. 直接选择webapi

    image-20221019170748686

  2. 命名

    image-20221019170850232

  3. 下一步创建即可

    image-20221019170909384

  4. 生成如下页面

    image-20221019171338077

  5. 点击运行得到如下页面

    image-20221019171840766

  6. 将上面标记的链接在浏览器打开,得到如下页面内容

    image-20221019171959431

3.1.3 两种WebApi项目的区别

第一种项目包含模型、视图、控制器等,类似于MVC项目(与MVC项目的区别是:MVC项目是带Razor视图的),其Controller类继承的是Controller类

image-20221020190151220

第二种项目只能写Web Api,因为其Controller类继承的是ControllerBase基类,所以只支持写WebApi项目

image-20221020190534637

小知识:

  1. Controller类继承于ControllerBase,是其子类,在Controller类中提供了一些操作View(视图)的方法

    image-20221020190847253

  2. ControllerBase类中没有支持View的方法,所以只能写Web Api

    image-20221020191000519

3.2 Restful、参数传递和返回值

  1. WebAPI是一种用来开发系统间接口、设备接口API的技术,基于Http协议,请求和返回格式结果默认是json格式
    比 WCF 更简单、更通用,比 WebService更节省流量、更简洁。

  2. WebAPI和普通ASP.Net MVC的区别:
    WebAPl是开发接口的技术,用户不会直接和WebAPI打交道,因此WebAPI也不会生成界面

  3. 特点:
    a. webapi的Action方法返回值直接返回对象,专注于数据

​ b. webapi更符合Restful的风格

​ c.有利于独立于lIS部署(selfhost、winform、windows service、控制台)

​ d. Action可以直接声明为async

  1. Restful风格(接口设计按照Http谓词语义设计)

​ 基于“Http谓词语义”进行通讯协议的设计,带来的好处:

​ i.可以为不同类型做不同的权限控制;

​ ii.不再需要“Delete”. “AddNew”这样的Action名字,根据请求的类型就可以判断.

​ iii.返回报文的格式也确定,不用再约定返回状态码,充分利用Http状态码

​ iv.有利于系统优化,浏览器可以自动缓存Get请求

​ v. Get没有副作用,是幂等的(1的任何次方都是1),可以重试

  1. Web API入门+<>RESTED(一个谷歌插件,可自行下载)

a.提交方式

b. 报文头 参数接收

c. 报文体 [FormBody]参数接收

d. 默认路由规则=》api/{controller}/{action}/{id}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;

namespace WebAPITest
{
  public static class WebApiConfig
  {
      public static void Register(HttpConfiguration config)
      {
          // Web API 配置和服务

          // Web API 路由
          config.MapHttpAttributeRoutes();

          config.Routes.MapHttpRoute(
              name: "DefaultApi",
              routeTemplate: "api/{controller}/{id}",        //这是自动生成的!
              //routeTemplate: "api/{controller}/{action}/{id}",//实际开发中使用最多的就是这个,action:方法名
              defaults: new { id = RouteParameter.Optional }
          );
      }
  }
}

image-20221012185105148

  1. Web API的参数,接收参数的方法大概以下几种:

    FromBody  // application/json
    FromForm  //前端的默认消息类型
    FromHeader //从请求头里获取
    FromQuery //从参数里获取
    FromRoute //从路由中获取
    FromServices//这个Jwt篇讲解
    

a. GET

      // 示例:GET api/values
      [HttpGet]
      public IEnumerable<string> Get()
      {
          return new string[] { "value1", "value2" };
      }

      // 示例:GET api/values/5
      [HttpGet]
      public string Get(int id)
      {
          return "value";
      }
  • public string Login([FromUri]LoginModel model)
    public class Login
    {
        public string Name { get; set; }
        public string Pwd { get; set; }
    }
        //GET api/values/login?name=张三&pwd=123456
        [HttpGet]
        public IEnumerable<string> Login([FromUri] Login login)
        {
            return new string[] { login.Name,login.Pwd};
        }

image-20221017161332540

b. POST

      // 示例:POST api/values
      [HttpPost]
      public string Post([FromBody] string value)
      {
          return "添加成功";
      }
  • 接收参数是name=zhangsan&pwd=5

​ i. public string AddNew2(LoginModel model)

​ ii. public string AddNew2([FromBody]LoginModel model)

        //POST api/values/LoginPost1
        [HttpPost]
        public void LoginPost1(Login login)
        {
            
        }
        [HttpPost]
        public void LoginPost2([FromBody] Login login)
        {

        }

image-20221017162633980

  • 提交的数据是ContentType=" application/json”方式提交

    i. 即参数设置成报文体{userName:“admin3”,password:“123”)

    ii. 参数也可以用模型对象

c.PUT

      // 示例:PUT api/values/5
      [HttpPut]
      public string Put(int id, [FromBody] string value)
      {
          return "修改成功" + id;
      } 

d.DELETE

      // 示例:DELETE api/values/5
      [HttpDelete]
      public string Delete(int id)
      {
          return "删除成功";
      }

e.通过自定义路由规则来捕获参数(get、post都适用)

i. WebAPI可以通过[Route]和[RoutePrefix]来自定义路由

ii. [RoutePrefix]作用于Controller例:[RoutePrefix( "api/Person”)]

iii. [Route]作用于Action 例 : [Route(“GetByld2”)]
iv.例如:

  1. [Route(“Login/{phoneNum}/{password}”)] 自定义路由

  2. [HttpPost]

  3. public string Login(string phoneNum, string password)

  4. 可以通过/Login/33/44访问

    //示例:可以通过https://localhost:44386/Login/22/33访问  
    [Route("Login/{phoneNum}/{password}")]
    [HttpPost]
    public string Login(string phoneNum,string password){
        return phoneNum + password;
    }
    

    image-20221012182907451

状态码:

2开头:成功(例:200,有返回结果;204,无返回结果)

3开头:都是错

4开头:服务器报错

5开头:代码报错

Status code

http status code是reponse的一部分,它提供了这些信息:请求是否成功,失败的原因

web api能涉及到的status codes主要是这些:

200: OK

201: Created,创建了新的资源

204:无内容No Content,例如删除成功

400: Bad Request,指的是客户端的请求错误

401:未授权Unauthorized

403:禁止操作Forbidden.验证成功,但是没法访问相应的资源404: Not Found

409:有冲突Conflict.

500: Internal Server Error,服务器发生了错误

  1. Web API的返回值

a.普通类型(默认GET请求)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;

namespace WebAPITest
{
  public static class WebApiConfig
  {
      public static void Register(HttpConfiguration config)
      {
          // Web API 配置和服务

          // Web API 路由
          config.MapHttpAttributeRoutes();

          config.Routes.MapHttpRoute(
              name: "DefaultApi",
              //routeTemplate: "api/{controller}/{id}",        //这是自动生成的!
              //自定义默认路由
              routeTemplate: "api/{controller}/{action}/{id}", //实际开发中使用最多的就是这个,action:方法名
              defaults: new { id = RouteParameter.Optional }
          );
      }
  }
}
      //普通类型
      public string GetString()
      {
          return "";
      }

image-20221012185840671

b. void,这样客户端会得到204的状态码,尽量别这样干(没拿到结果的成功)

c. lHttpActionResult类型

i.可以调用ApiController 中的Ok)、NotFound()、Json(new {a=1,b=2})、Content()、Redirect()等方法。

        //IIHttpActionResult类型
        public IHttpActionResult GetHttpActionResult()
        {
            return Json(new {a=1,b=2,c=3});
        }

d. HttpResponseMessage

i、可以做更精细化的返回内容控制,比如返回二进制文件、设置返回报文头

ii.例如:

[HttpPost]
public HttpResponseMessage Test()
{
  HttpContent httpContent = new StringContent("hello");
  return new HttpResponseMessage { Content= httpContent ,StatusCode= HttpStatusCode.OK};
}
  1. Web API的异常处理

    a. ASP.Net Web API错误处理:对于Action 中的异常,默认就是返回500状态码,报文体是Json格式这样也是最好的

    b.对于“ld不存在”、“年龄不合法”等这类的错误既可以通过自动以状态码的方式返回(不太够用),也可以自定义下面这种类型作为返回值(然后在文档中约定:О代表成功、1代表用户名不能为空、2代表金额超限)

            //Web Api 的异常处理
            //a.默认就是返回500状态码,报文体是Json格式
            public string GetError(int id)
            {
                if (id == 0)
                {
                    throw new Exception("这是一个错误");
                }
                else
                {
                    return "我是" + id;
                }
            }
            //b.业务报错处理,对于“ld不存在”、“年龄不合法”等这类的错误既可以通过自动以状态码的方式返回(不太够用),也可以自定义下面这种类型作为返回值(然后在文档中约定:О代表成功、1代表用户名不能为空、2代表金额超限)
            public class ApiResult<T>
            {
                public int Code { get; set; }
                public string Message { get; set; }
                public T Value { get; set; }
            }
            public ApiResult<string> GetApiResult(int id)
            {
                if (id == 0)
                {
                    return new ApiResult<string> { Message = "出错了" };
                }
                else
                {
                    return new ApiResult<string> { Message = "对了" };
                }
            }
            //c.未处理异常的处理IExceptionFilter、WebApiConfig中config.Filter.Add(new ExceptionFilter());
            public string GetExceptionFilter(int id)
            {
                int a = id / 0;
                return "等于" + a;
            }
    
  2. 将报错信息写入错误文本

    (1)自定义类

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Web;
    using System.Web.Http.Filters;
    
    namespace WebAPITest
    {
        //自定义类继承异常过滤器,在WebApiConfig中实现
        public class ExceptionFilter : IExceptionFilter
        {
            public bool AllowMultiple => false;
    
            public async Task ExecuteExceptionFilterAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
            {
                //throw new NotImplementedException();
                using (StreamWriter writer = File.AppendText("E:/error.txt"))
                {
                    await writer.WriteLineAsync(actionExecutedContext.Exception.ToString());
                }
            }
        }
    }
    

    (2)在WebApiConfig类中实现

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web.Http;
    
    namespace WebAPITest
    {
        public static class WebApiConfig
        {
            public static void Register(HttpConfiguration config)
            {
                // Web API 配置和服务
    
                // Web API 路由
                config.MapHttpAttributeRoutes();
    
                config.Routes.MapHttpRoute(
                    name: "DefaultApi",
                    //routeTemplate: "api/{controller}/{id}",  //默认路由匹配规则
                    routeTemplate: "api/{controller}/{action}/{id}",   //自定义路由匹配规则:实际开发中使用最多的就是这个,action:方法名
                    defaults: new { id = RouteParameter.Optional }
                );
                //异常处理
                config.Filters.Add(new ExceptionFilter());
            }
        }
    }
    

    image-20221017173420820

  3. Web API的多版本管理
    a.旧版接口做一个代码分支,除了进行 bug修改外,旧版本接口不再做改动;新接口代码继续演化升级.在客户端请求的时候带着要请求的接口版本号,在服务器端选择合适的版本代码进行处理。
    b.技术处理方法
    i.(最推荐)不同版本用不同的域名: v1.api.tgh.com 、 v2.api.tgh.com 、 v3…
    ii.在url、报文头等中带不同的版本信息,用Nginx等做反向代理服务器,然后将http://api.tgh.com/api/v1/User/1和http://api.tgh.com/api/v2/User/1转到不同的服务器处理
    iii.多个版本的Controller共处在一个项目中,然后使用[RoutePrefix]或者llttpControllerSelector根据报文头、路径等选择不同的Controller 执行

3.3 基于ASP.NET的Web API

Web API连接mysql数据库

HUI前端框架:http://www.h-ui.net/

  1. 启动 Visual Studio ,在开始页里选择“新建项目”。或者从“文件”菜单里,选择“新建项目”。选择ASP .NET Web 应用程序

    image-20221018110753911

  2. 输入项目名称

    image-20221018111005978

  3. 选择web api创建即可

    image-20221018111058406

  4. 得到如下页面

    image-20221018111145244

  5. 在Model文件夹下新建Users类

    image-20221018111406388

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    
    namespace WebApiMysql.Models
    {
        public class Users
    
        {
            public int UserID { get; set; }
    
            public string UserName { get; set; }
    
            public string UserEmail { get; set; }
    
        }
    }
    
  6. 然后就可以添加控制器了,控制器就是控制层,在“Controllers”下有一个“HomeController.cs”的文件,它是一个传统的 ASP.NET MVC 控制器。它只是负责处理站点的HTML页,跟Web API没有直接关系。这里需要手动添加一个API控制器

    image-20221018112149897

    image-20221018111816926

  7. 给控制器命名

    image-20221018111844268

  8. 向控制器中加入以下代码

    public class UsersController : ApiController
        {
            //MySqlConnection对象是一个数据库连接对象,主要功能是建立与物理数据库的连接;简单来说,就是连接到本机数据库的一个对象
            private static MySqlConnection getMySqlConnection()
            {
                MySqlConnection mysql = new MySqlConnection(ConfigurationManager.ConnectionStrings["MySqlConnection"].ConnectionString);
                return mysql;
            }
            //MySqlCommand对象是一个数据命令对象,主要功能是向数据库发送增删改查操作的语句
            public static MySqlCommand getSqlCommand(String sql, MySqlConnection mysql)
            {
                MySqlCommand mySqlCommand = new MySqlCommand(sql, mysql);
    
                return mySqlCommand;
            }
            // GET api/users/Get
            /// <summary>
            /// 查找所有用户
            /// </summary>
            /// <returns>返回查找到的所有用户集</returns>
            /// <exception cref="HttpResponseException"></exception>
            [HttpGet]
            public IEnumerable<Users> Get()
            {
                List<Users> listUser = new List<Users>();
                MySqlConnection mysql = getMySqlConnection();
                MySqlCommand mySqlCommand = getSqlCommand("select * from user", mysql);
                mysql.Open();
                //用MySqlDataReader对象读取数据
                MySqlDataReader reader = mySqlCommand.ExecuteReader();
                try
                {
                    while (reader.Read())
                    {
                        if (reader.HasRows)
                        {
                            Users user = new Users();
                            user.UserID = reader.GetInt32("UserID");
                            user.UserName = reader.GetString("username");
                            user.UserEmail = reader.GetString("useremail");
                            listUser.Add(user);
                        }
                    }
                }
                catch
                {
                    throw new HttpResponseException(HttpStatusCode.NotFound);
                }
                finally
                {
                    mysql.Close();
                }
                return listUser;
            }
            // GET api/Users/GetUserByID/2
            /// <summary>
            /// 根据id查询用户
            /// </summary>
            /// <param name="id">用户id</param>
            /// <returns>返回一个用户</returns>
            /// <exception cref="HttpResponseException"></exception>
            [HttpGet]
            public Users GetUserByID(int id)
            {
                Users user = new Users();
                MySqlConnection mysql = getMySqlConnection();
                MySqlCommand mySqlCommand = getSqlCommand("select * from USER where UserID=" + id, mysql);
                mysql.Open();
                MySqlDataReader reader = mySqlCommand.ExecuteReader();
                try
                {
                    while (reader.Read())
                    {
                        if (reader.HasRows)
                        {
                            user.UserID = reader.GetInt32(0);
                            user.UserName = reader.GetString(1);
                            user.UserEmail = reader.GetString(2);
                        }
                    }
                    reader.Close();
                }
                catch
                {
                    throw new HttpResponseException(HttpStatusCode.NotFound);
                }
                finally
                {
                    mysql.Close();
                }
    
                return user;
            }
            //GET api/Users/GetUserByName/?username=xx
            /// <summary>
            /// 根据用户名查找用户
            /// </summary>
            /// <param name="userName">用户名</param>
            /// <returns>返回一个查找结果集</returns>
            /// <exception cref="HttpResponseException"></exception>
            [HttpGet]
            public IEnumerable<Users> GetUserByName(string userName)
            {
                List<Users> listuser = new List<Users>();
                MySqlConnection mysql = getMySqlConnection();
                MySqlCommand mySqlCommand = getSqlCommand("select * from USER where username like'%" + userName + "%'", mysql);
                mysql.Open();
                MySqlDataReader reader = mySqlCommand.ExecuteReader();
                try
                {
                    while (reader.Read())
                    {
                        if (reader.HasRows)
                        {
                            Users user = new Users();
                            user.UserID = reader.GetInt32("UserID");
                            user.UserName = reader.GetString("USERNAME");
                            user.UserEmail = reader.GetString("USEREMAIL");
                            listuser.Add(user);
                        }
    
                    }
                    reader.Close();
                }
                catch
                {
                    throw new HttpResponseException(HttpStatusCode.NotFound);
                }
                finally
                {
                    mysql.Close();
                }
                return listuser;
            }
            // POST api/Users/AddUser
            public void Post([FromBody] string value)
            {
            }
            // PUT api/Users/
            public void Put(int id, [FromBody] string value)
            {
            }
            // DELETE api/Users/
            public void Delete(int id)
            {
            }
        }
    

    此处会报错,跟着下面的步骤走就能解决(一键导包:Alt + Enter):

    (1)Users报错,导包即可

    image-20221018112413917

    (2)MySqlConnection报错,安装Mysql.Data包

    image-20221018112710755

    image-20221018112911007

    image-20221018113311935

    (3)ConfigurationManager报错,这里也是导包就行

    image-20221018113455502

  9. UsersController处代码,主要补充了Get方法的逻辑。数据从mysql数据库中获取。例中MySql对应user表中数据如下

    image-20221018113904237

    获取数据共写了三个get方法,第一个是获取所有user数据,第二个根据Users 的ID获取对应Users,第三个根据UserName获取Users。为了能够通过MySql获取数据,需要在Web.config的节点中添加 ;name为connectionString对应名称,connectionString中为连接mysql的配置;代码中通过ConfigurationManager.ConnectionStrings[“name”].ConnectionString获取mysql配置。

    image-20221018114112901

    	<connectionStrings>
    		<!--mysql数据库连接配置-->
    		<add name="MySqlConnection" providerName="System.Data.SqlClient" connectionString="server=127.0.0.1;database=webapi;port=3306;Userid=root;Password=123456"/>
    	</connectionStrings>
    
  10. 完成代码编写后,启动项目即可对进行代码测试。这里使用的是一个谷歌插件RESTED,可自行下载

    image-20221018114320091

    使用Web api进行访问

    image-20221018114548276

    即可查询

    image-20221018114742194

3.4 基于Swagger的Web API

3.4.1 Swagger简单配置

文档注释配置

  1. 配置前

    image-20221019181254434

  2. 开启生成包含API文档的文件功能

    方法一:

    image-20221019183145597

    方法二:

    image-20221019183415148

  3. 在Program类中增加如下代码

    builder.Services.AddSwaggerGen(options =>
    {
        //注释
        var xmlFileName = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
        //第二个参数为是否显示控制器注释,我选择true
        options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory,xmlFileName),true);
    });
    

    image-20221019184532171

  4. 在Controller类中增加文档注释(其他类中增加了也可以看到)

    image-20221019184634218

  5. 启动项目即可看到文档注释

    控制器类注释:

    image-20221019184722119

    实体类注释:

    image-20221019185523512

  6. 注意

    image-20221019190030582

    需要修改为如下才会显示:

    image-20221019190214416

返回数据的时间格式化

  1. 安装包microsoft.aspnetcore.mvc.newtonsoftjson

    image-20221019183604727

  2. 格式化之前:

    image-20221019183914526

    进行格式化:

    (1)在Program类中增加如下代码

    builder.Services.AddControllers().AddNewtonsoftJson(options =>
    {
        options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
    });
    

    image-20221019184017524

    (2)格式化后

    image-20221019184138893

Api版本管理

  1. 下图中可以构建不同的版本

    image-20221020100210839

  2. 在Program类中添加如下代码

    //生成多个文档显示
        typeof(ApiVersions).GetEnumNames().ToList().ForEach(version =>
        {
            //添加文档介绍
            options.SwaggerDoc(version, new Microsoft.OpenApi.Models.OpenApiInfo
            {
                Title = $"项目名",
                Version = version,
                Description = $"项目名:{version}版本"
            });
        });
    

    image-20221020101344513

    app.UseSwaggerUI(options =>
        {
            //options.SwaggerEndpoint($"/swagger/V1/swagger.json",$"版本选择:V1");
            //如果只有一个版本也要和上方保持一致
            typeof(ApiVersions).GetEnumNames().ToList().ForEach(version =>
            {
                //切换版本操作
                //参数一时使用的哪个json文件,参数二就是个名{字
                options.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"版本选择:{version}");
            });
        });
    

    image-20221020102008942

  3. 最后在控制器方法上表明它是属于哪个版本的特性

    [ApiExplorerSettings(GroupName ="V1")]  //如果不加,则默认两个版本都有;加上就是某个版本独有
    

    image-20221020102939452

    image-20221020103102113

  4. 切换回单版本

    修改一下代码就行:

        //单版本显示
        options.SwaggerDoc("V1", new Microsoft.OpenApi.Models.OpenApiInfo
        {
            Title = $"项目名",
            Version = "V1",
            Description = $"项目名:V1版本"
        });
    
        //生成多个版本显示,获取枚举类中的每个枚举的名字
        //typeof(ApiVersions).GetEnumNames().ToList().ForEach(version =>
        //{
        //    //添加文档介绍
        //    options.SwaggerDoc(version, new Microsoft.OpenApi.Models.OpenApiInfo
        //    {
        //        Title = $"项目名",
        //        Version = version,
        //        Description = $"项目名:{version}版本"
        //    });
        //});
        
        //单版本显示
        options.SwaggerEndpoint($"/swagger/V1/swagger.json",$"版本选择:V1");
    
        //生成多个版本显示,如果只有一个版本要和上方保持一致
        //typeof(ApiVersions).GetEnumNames().ToList().ForEach(version =>
        //{
        //    //切换版本操作
        //    //参数一时使用的哪个json文件,参数二就是个名字
        //    options.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"版本选择:{version}");
        //});
    

    image-20221020103555321

3.4.2 入口文件和配置文件

Program类(入口文件)

从上到下,依次执行

从 C# 9 开始,无需在应用程序项目中的Program显式包含 Main 方法。

IOC容器:

new object();

var x = new object();

x.xxx;

有了IOC就不需要new对象了,直接提前进行注入

依赖注入:

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="logger"></param>
        public WeatherForecastController(ILogger<WeatherForecastController> logger)
        {
            _logger = logger;
        }

直接给构造函数传一个需要的参数,由IOC容器去创建,我直接使用就行了

入口文件中注册服务就是使用的这种思想!

使用配置文件

appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "a": "1",
  "User": {
    "UserName": "张三",
    "Age": 18
  }
}

在Program类中编写如下代码

var Logging = builder.Configuration["Logging"];   //获取一级目录
var AllowedHosts = builder.Configuration["AllowedHosts"];   //获取AllowedHosts的值
var Default = builder.Configuration["Logging:LogLevel:Default"];  //获取Logging:LogLevel:Default的值
var a = builder.Configuration["a"]; //获取a的值
var inta = builder.Configuration.GetValue<int>("a"); //获取a的值并转换为int类型
//获取并序列化为某某类型   Configuration.GetSection("xxx").Get<某某类型>();
var user = builder.Configuration.GetSection("User").Get<User>();

image-20221020155549226

新建User类

public class User
    {
        public string UserName { get; set; }
        public int Age { get; set; }
    }

在控制器类中进行注入

image-20221020155850124

3.4.3 三层架构

三层架构就是为了符合“高内聚,低耦合”思想,把各个功能模块划分为表示层(UI)、业务逻辑层(BLL)和数据访问层(DAL)三层架构,各层之间采用接口相互访问,并通过对象模型的实体类(Model)作为数据传递的载体,不同的对象模型的实体类一般对应于数据库的不同表,实体类的属性与数据库表的字段名一致。

image-20221020172956016

根据以上依赖模型依次添加项目引用(这里做一个示范):

image-20221020173300656

image-20221020173328704

3.4.5 Web Api 实战

3.4.5.1 Web Api连接mysql数据库
  1. 建一个web api项目,项目开始之前需要导入的包,不导入包可能迁移不会成功(必须导入!!!)

    image-20221021183316746

  2. 根据DB First方法(详见本人博客:http://t.csdn.cn/SWVoM 11.3.2)连接数据库(如有问题可私信博主,有时间可提供帮助)

    image-20221021110405939

  3. 需要用到的ApplicationDbContext类和实体类

    using Microsoft.EntityFrameworkCore;
    
    namespace Models
    {
        /// <summary>
        /// 数据库上下文类
        /// </summary>
        public class ApplicationDbContext : DbContext
        {
            //主要是框架在用,有时候用户也可能用到
            public ApplicationDbContext()
            {
    
            }
            //DbContextOptions:选项模式,实现参数动态化
            public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
            {
    
            }
            /// <summary>
            /// 数据表
            /// </summary>
            public DbSet<User> user { get; set; }  //这个属性叫什么名字,数据库中的表就叫什么名字
            public DbSet<WeatherForecast> weatherForecast { get; set; }  //这个属性叫什么名字,数据库中的表就叫什么名字
        }
    }
    
    namespace Models
    {
        public class User
        {
            public int Id { get; set; }
            public string UserName { get; set; }
            public int Age { get; set; }
        }
    }
    
    namespace Models
    {
        public class WeatherForecast
        {
            public int Id { get; set; }
            /// <summary>
            /// 时间
            /// </summary>
            public DateTime Date { get; set; }
            /// <summary>
            /// 摄氏度
            /// </summary>
            public int TemperatureC { get; set; }
            /// <summary>
            /// 华氏度
            /// </summary>
            public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
            /// <summary>
            /// 总结
            /// </summary>
            public string? Summary { get; set; }
        }
    }
    
  4. 迁移生成数据库表之后,在数据库中增加字段信息以便后续查询

    image-20221021110518416

  5. 新建一个DbTestController webapi控制器

    using Microsoft.AspNetCore.Mvc;
    using Models;
    
    // For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
    
    namespace WebApiStudy.Controllers
    {
        [Route("api/[controller]")]
        [ApiController]
        public class DbTestController : ControllerBase
        {
            //Db就相当于一个数据库,可以通过  Db.user 获取数据库中的表
            private ApplicationDbContext Db { get;}
            /// <summary>
            /// 生成一个构造函数,创建一个数据库对象db,ApplicationDbContext就是一个数据库对象创建一个数据库对象db,ApplicationDbContext就是一个数据库对象
            /// </summary>
            /// <param name="db"></param>
            public DbTestController(ApplicationDbContext db)
            {
                Db = db;
            }
    
            //查询单个用户
            [HttpGet]
            public List<User> GetUsers()
            {
                var query = Db.user.Where(s => s.Id > 0)
                        .Where(s => s.UserName == "小雨").ToList();
                return query;
            }
        }
    }
    
  6. 启动项目执行查询,根据以下步骤即可查询到数据库中对应的内容

    image-20221021111350967

    image-20221021111408732

    image-20221021111433286

    image-20221021111531271

  7. 根据Request URL也可以进行查询,下面提供两种访问方式

    image-20221021112426870

    image-20221021112527262

    image-20221021112610823

3.4.5.2 Web Api CRUD实现
  1. 编写响应实体类

    namespace WebApiStudy.Models
    {
        public class ResultModel
        {
            //返回结果
            public bool result { get; set; }
            //错误信息
            public string message { get; set; }
    
            public ResultModel(bool result, string message)
            {
                this.result = result;
                this.message = message;
            }
        }
    }
    
  2. 编写DbTestController类的web api

    using Microsoft.AspNetCore.Mvc;
    using Models;
    using WebApiStudy.Models;
    
    // For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
    
    namespace WebApiStudy.Controllers
    {
        [Route("api/[controller]")]   //可以自定义路由
        [ApiController]
        public class DbTestController : ControllerBase
        {
            //Db就相当于一个数据库,Db = webapistudy,可以通过  Db.user 获取数据库中的表
            private ApplicationDbContext Db { get;}
            /// <summary>
            /// 生成一个构造函数,创建一个数据库对象db,ApplicationDbContext就是一个数据库对象
            /// </summary>
            /// <param name="db"></param>
            public DbTestController(ApplicationDbContext db)
            {
                Db = db;
            }
    
            //查询指定的单个用户  api/DbTest/GetUser
            /// <summary>
            /// 查询名字为小雨的用户信息
            /// </summary>
            /// <returns></returns>
            [Route("[action]")]  //可以自定义路由
            [HttpGet]
            public List<User> GetUser()
            {
                var user = Db.user.Where(s => s.Id > 0)
                        .Where(s => s.UserName == "小雨").ToList();
                return user;
            }
            
            /// <summary>
            /// 查询所有用户
            /// </summary>
            /// <returns></returns>
            [Route("[action]")]  //可以自定义路由
            [HttpGet]
            public List<User> GetAllUser()
            {
                var user = Db.user.Where(s => s.Id > 0)
                        .Where(s => s.UserName!=null).ToList();
                return user;
            }
    
            // HttpPost api/DbTest/GetUserByID/1
            /// <summary>
            /// 根据id查询用户
            /// </summary>
            /// <param name="id">用户id</param>
            /// <returns></returns>
            [Route("[action]/{id}")]   //可以自定义路由
            [HttpPost]
            public IQueryable<User> GetUserByID(int id)
            {
                var user = Db.user.Where(s => s.Id == id);
                return user;
            }
            
            // HttpPost api/DbTest/GetUserByName/name
            /// <summary>
            /// 根据Name查询用户
            /// </summary>
            /// <param name="name">用户名字</param>
            /// <returns></returns>
            [Route("[action]/{name}")]   //可以自定义路由
            [HttpPost]
            public IQueryable<User> GetUserByName(string name)
            {
                var user = Db.user.Where(s => s.UserName == name);
                return user;
            }
            
            /// <summary>
            /// 修改用户年龄
            /// </summary>
            /// <param name="name">输入用户名</param>
            /// <param name="age">修改用户年龄</param>
            /// <returns></returns>
            [Route("[action]/{name}")]   //可以自定义路由
            [HttpPatch]
            public ResultModel EditUser(string name,int age)
            {
                if (name != null)
                {
                    var user = Db.user.Where(s => s.Id > 0)
                        .Where(s => s.UserName.Contains(name)).Select(s => new User { UserName = s.UserName, Id = s.Id, Age = age }).ToList().FirstOrDefault();
                    if (user != null)
                    {
                        Db.user.Update(user);
                        Db.SaveChanges();
                        return new ResultModel(true, "修改成功");
                    }
                    else
                    {
                        return new ResultModel(false, "修改失败,没有找到对应的用户");
                    }
                }
                return new ResultModel(false, "修改失败,输入的用户名为空");
            }
            
            /// <summary>
            /// 添加用户
            /// </summary>
            /// <param name="name"></param>
            /// <param name="age"></param>
            /// <returns></returns>
            [Route("[action]")]   //可以自定义路由
            [HttpPost]
            public ResultModel AddUser(string name, int age)
            {
                Db.user.AddRange(new User
                {
                    UserName = name,
                    Age = age
                });
                Db.SaveChanges();
                return new ResultModel(true, "添加成功");
            }
            
            /// <summary>
            /// 根据id删除用户
            /// </summary>
            /// <param name="id"></param>
            /// <returns></returns>
            [Route("[action]")]   //可以自定义路由
            [HttpPost]
            public ResultModel DeleteUser(int id)
            {
                var user = Db.user.Where(s => s.Id == id).ToList().FirstOrDefault();
                if (user == null)
                {
                   return new ResultModel(false, "删除失败,没有找到对应的用户");
                }
                else
                {
                    Db.user.Remove(user);
                    Db.SaveChanges();
                    return new ResultModel(true, "删除成功");
                }
            }
        }
    }
    
  3. 启动项目即可访问对应的Api

    image-20221021160453901

  4. 升级版

    using Microsoft.AspNetCore.Mvc;
    using Models;
    using WebApiStudy.Models;
    
    // For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
    
    namespace WebApiStudy.Controllers
    {
        [Route("api/[controller]")]   //可以自定义路由
        [ApiController]
        public class DbTestController : ControllerBase
        {
            //Db就相当于一个数据库,Db = webapistudy,可以通过  Db.user 获取数据库中的表
            private ApplicationDbContext Db { get;}
            /// <summary>
            /// 生成一个构造函数,创建一个数据库对象db,ApplicationDbContext就是一个数据库对象
            /// </summary>
            /// <param name="db"></param>
            public DbTestController(ApplicationDbContext db)
            {
                Db = db;
            }
    
            //查询指定的单个用户  api/DbTest/GetUser
            /// <summary>
            /// 查询名字为小雨的用户信息
            /// </summary>
            /// <returns></returns>
            [Route("[action]")]  //可以自定义路由
            [HttpGet]
            public List<User> GetUser()
            {
                var user = Db.user.Where(s => s.Id > 0)
                        .Where(s => s.UserName == "小雨").ToList();
                return user;
            }
            /// <summary>
            /// 查询所有用户
            /// </summary>
            /// <returns></returns>
            [Route("[action]")]  //可以自定义路由
            [HttpGet]
            public List<User> GetAllUser()
            {
                var user = Db.user.Where(s => s.Id > 0)
                        .Where(s => s.UserName!=null).ToList();
                return user;
            }
    
            /// <summary>
            /// 根据id查询用户
            /// </summary>
            /// <param name="id">用户id</param>
            /// <returns></returns>
            //[Route("[action]/{id}")]   //可以自定义路由         HttpPost api/DbTest/GetUserByID/1
            [Route("[action]")]   //可以自定义路由                HttpPost api/DbTest/GetUserByID?id=1
            [HttpGet]
            public List<User> GetUserByID_Get(int id)
            {
                var user = Db.user.Where(s => s.Id == id).ToList();
                return user;
            }
            /// <summary>
            /// 根据id查询用户
            /// </summary>
            /// <param name="id">用户id</param>
            /// <returns></returns>
            //[Route("[action]/{id}")]   //可以自定义路由         HttpPost api/DbTest/GetUserByID/1
            [Route("[action]")]   //可以自定义路由                HttpPost api/DbTest/GetUserByID?id=1
            [HttpPost]
            public List<User> GetUserByID_Post(int id)
            {
                var user = Db.user.Where(s => s.Id == id).ToList();
                return user;
            }
            // HttpPost api/DbTest/GetUserByName/xiaoming
            /// <summary>
            /// 根据Name查询用户
            /// </summary>
            /// <param name="name">用户名字</param>
            /// <returns></returns>
            [Route("[action]/{name}")]   //可以自定义路由
            [HttpPost]
            public IQueryable<User> GetUserByName(string name)
            {
                var user = Db.user.Where(s => s.UserName == name);
                return user;
            }
    
            /// <summary>
            /// 修改用户年龄
            /// </summary>
            /// <param name="name">输入用户名</param>
            /// <param name="age">修改用户年龄</param>
            /// <returns></returns>
            [Route("[action]/{name}")]   //可以自定义路由
            [HttpPatch]
            public ResultModel EditUser(string name,int age)
            {
                if (name != null)
                {
                    var user = Db.user.Where(s => s.Id > 0)
                        .Where(s => s.UserName.Contains(name)).Select(s => new User { UserName = s.UserName, Id = s.Id, Age = age }).ToList().FirstOrDefault();
                    if (user != null)
                    {
                        Db.user.Update(user);
                        Db.SaveChanges();
                        return new ResultModel(true, "修改成功");
                    }
                    else
                    {
                        return new ResultModel(false, "修改失败,没有找到对应的用户");
                    }
                }
                return new ResultModel(false, "修改失败,输入的用户名为空");
            }
    
            /// <summary>
            /// 添加用户
            /// </summary>
            /// <param name="name"></param>
            /// <param name="age"></param>
            /// <returns></returns>
            [Route("[action]")]   //可以自定义路由
            [HttpPost]
            public ResultModel AddUser1(string name, int age)
            {
                Db.user.AddRange(new User
                {
                    UserName = name,
                    Age = age
                });
                Db.SaveChanges();
                return new ResultModel(true, "添加成功");
            }
            /// <summary>
            /// 添加用户
            /// </summary>
            /// <param name="name"></param>
            /// <param name="age"></param>
            /// <returns></returns>
            [Route("[action]")]   //可以自定义路由
            [HttpPost]
            public ResultModel AddUser2(User user)
            {
                Db.user.AddRange(new User
                {
                    UserName = user.UserName,
                    Age = user.Age
                });
                Db.SaveChanges();
                return new ResultModel(true, "添加成功");
            }
            /// <summary>
            /// 根据id删除用户
            /// </summary>
            /// <param name="id"></param>
            /// <returns></returns>
            [Route("[action]")]   //可以自定义路由
            [HttpDelete]
            public ResultModel DeleteUserByID(int id)
            {
                var user = Db.user.Where(s => s.Id == id).ToList().FirstOrDefault();
                if (user == null)
                {
                   return new ResultModel(false, "删除失败,没有找到对应的用户");
                }
                else
                {
                    Db.user.Remove(user);
                    Db.SaveChanges();
                    return new ResultModel(true, "删除成功");
                }
            }
        }
    }
    
  5. 启动项目即可访问对应的API

    image-20221021175636562

3.4.5.3 小结

post和get请求的区别:

一、功能不同

1、get是从服务器上获取数据。

2、post是向服务器传送数据。

二、过程不同

1、get是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到。

2、post是通过HTTP post机制,将表单内各个字段与其内容放置在HTML HEADER内一起传送到ACTION属性所指的URL地址。用户看不到这个过程。

三、获取值不同

1、对于get方式,服务器端用Request.QueryString获取变量的值。

2、对于post方式,服务器端用Request.Form获取提交的数据。

四、传送数据量不同

1、get传送的数据量较小,不能大于2KB。

2、post传送的数据量较大,一般被默认为不受限制。但理论上,IIS4中最大量为80KB,IIS5中为100KB。

五、安全性不同

1、get安全性非常低。

2、post安全性较高。

3.4.6 依赖注入的生命周期

开发中用的较多的是Transient、AddScoped

Transient
瞬态
瞬态模式,服务在每次请求时被创建你今天相了十次亲,每个都是不同的人

AddScoped
区域模式
作用域模式,服务在每次请求时被创建,整个请求过程中都贯穿使用这个创建的服务
你今天出去相了十次亲,今天这十个都是同一个人

AddSingleton
单例模式
单例模式,服务在第一次请求时被创建,其后的每次请求都沿用这个已创建的服务。
你每天都相亲十次,永远都是那同一个人

3.4.7 数据校验(FluentValidation)

官网:https://fluentvalidation.net/

  1. Nuget安装

    FluentValidation.AspNetCore
    
  2. 在Program中进行依赖注入

    //在Program中进行依赖注入,注册服务
    builder.Services.AddFluentValidation(options =>
    {
        options.RegisterValidatorsFromAssembly(Assembly.GetExecutingAssembly());
    });
    
  3. 使用AddUser2方法进行验证

     /// <summary>
            /// 添加用户
            /// </summary>
            /// <param name="name"></param>
            /// <param name="age"></param>
            /// <returns></returns>
            [Route("[action]")]   //可以自定义路由
            [HttpPost]
            public ResultModel AddUser2(User user)
            {
                Db.user.AddRange(new User
                {
                    UserName = user.UserName,
                    Age = user.Age
                });
                Db.SaveChanges();
                return new ResultModel(true, "添加成功");
            }
    
  4. FluentValidation符合开发的单一职责标准

    使用方式:创建一个类UserValidator,继承AbstractValidator 泛型类,T为需要验证的实体类,甚至可以操作数据库,只需要构造函数中,然后进行数据的查询和对比

    Must()自定义验证规则,使用RuleFor对每个字段单独做校验

    //例子:
    public class WeChatInfoInsertDtovalidator : AbstractValidator<WeChatInfoInsertDto> {
            //可以直接在这个构造函数注入数据库上下文
            public WeChatInfoInsertDtovalidator() {
                //对每个字段进行单独校验
                RuleFor(it => it.Appid)
                .NotNull().withMessage("AppId不能为空")
                .Must(v => v.contains(" ")).withMessage("AppId不能包含空格");
                RuleFor(it => it.AppSecret)
                .NotNull().withMessage("Appseret不能为空格")
                .Must(v => v.contains(" ")).withMessage("Appseret不能包含空格");
                RuleFor(it => it.code)
                .NotNull().withMessage("编号不能为空")
                .Must(v => v.Contains("")).withMessage("编号不能包含空格");
            }
        }
    
    using FluentValidation;
    using Models;
    
    namespace WebApiStudy.Validators
    {
        public class UserValidator : AbstractValidator<User>
        {
            public UserValidator()
            {
                RuleFor(it => it.UserName)
                    .NotNull()
                    .Must(v=> v.Contains("xxx"))
                    .WithMessage("必须包含xxx");
            }
        }
    }
    
  5. 启动项目,重新进行AddUser2方法api调用

    Request body

    {
      "id": 0,
      "userName": "string",
      "age": 0
    }
    

    Response body

    {
      "errors": {
        "UserName": [
          "必须包含xxx"
        ]
      },
      "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
      "title": "One or more validation errors occurred.",
      "status": 400,
      "traceId": "00-53e12fe3d84c5c1acb1335a1fb805364-6dc373e559739a8e-00"
    }
    

3.4.8 跨域

  1. 配置跨域

    #region 配置跨域
    builder.Services.AddCors(c =>
    {
        c.AddPolicy("Cors", policy =>
        {
            policy.AllowAnyOrigin()
                  .AllowAnyHeader()   //Ensures that the policy allows any header
                  .AllowAnyMethod();
        });
    });
    #endregion
    
  2. 使用跨域(注意命名需要一致:Core)

    app.UseCors("Cors");
    

    image-20221024104753962

3.4.9 JWT认证

参考学习博文:http://t.csdn.cn/PZGZi

3.4.9.1 什么是JWT

在介绍JWT之前,我们先来回顾一下利用token进行用户身份验证的流程:

  1. 客户端使用用户名和密码请求登录
  2. 服务端收到请求,验证用户名和密码
  3. 验证成功后,服务端会签发一个token,再把这个token返回给客户端
  4. 客户端收到token后可以把它存储起来,比如放到cookie中
  5. 客户端每次向服务端请求资源时需要携带服务端签发的token,可以在cookie或者header中携带
  6. 服务端收到请求,然后去验证客户端请求里面带着的token,如果验证成功,就向客户端返回请求数据

这种基于token的认证方式相比传统的session认证方式更节约服务器资源,并且对移动端和分布式更加友好。其优点如下:

  • 支持跨域访问:cookie是无法跨域的,而token由于没有用到cookie(前提是将token放到请求头中),所以跨域后不会存在信息丢失问题

  • 无状态:token机制在服务端不需要存储session信息,因为token自身包含了所有登录用户的信息,所以可以减轻服务端压力

  • 更适用CDN:可以通过内容分发网络请求服务端的所有资料

  • 更适用于移动端:当客户端是非浏览器平台时,cookie是不被支持的,此时采用token认证方式会简单很多

  • 无需考虑CSRF:由于不再依赖cookie,所以采用token认证方式不会发生CSRF,所以也就无需考虑CSRF的防御

而JWT就是上述流程当中token的一种具体实现方式,其全称是JSON Web Token,官网地址:https://jwt.io/

通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT

token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。

JWT的认证流程如下:

  1. 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口,这个过程一般是一个POST请求。建议的方式是通过SSL加密的传输(HTTPS),从而避免敏感信息被嗅探

  2. 后端核对用户名和密码成功后,将包含用户信息的数据作为JWT的Payload,将其与JWT Header分别进行Base64编码拼接后签名,形成一个JWT Token,形成的JWT Token就是一个如同lll.zzz.xxx的字符串

  3. 后端将JWT Token字符串作为登录成功的结果返回给前端。前端可以将返回的结果保存在浏览器中,退出登录时删除保存的JWT Token即可
    前端在每次请求时将JWT Token放入HTTP请求头中的Authorization属性中(解决XSS和XSRF问题)

  4. 后端检查前端传过来的JWT Token,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等

  5. 验证通过后,后端解析出JWT Token中包含的用户信息,进行其他逻辑操作(一般是根据用户信息得到权限等),返回结果

image-20221024111031908

3.4.9.2.为什么要用JWT

1、传统Session认证的弊端
我们知道HTTP本身是一种无状态的协议,这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,认证通过后HTTP协议

不会记录下认证后的状态,那么下一次请求时,用户还要再一次进行认证,因为根据HTTP协议,我们并不知道是哪个用户发出的请求,

所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在用户首次登录成功后,在服务器存储一份用户登录的信息,这份登录信

息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,

这是传统的基于session认证的过程

image-20221024111058340

然而,传统的session认证有如下的问题:

  • 每个用户的登录信息都会保存到服务器的session中,随着用户的增多,服务器开销会明显增大

  • 由于session是存在与服务器的物理内存中,所以在分布式系统中,这种方式将会失效。虽然可以将session统一保存到Redis中,但是这样做无疑增加了系统的复杂性,对于不需要redis的应用也会白白多引入一个缓存中间件

  • 对于非浏览器的客户端、手机移动端等不适用,因为session依赖于cookie,而移动端经常没有cookie

  • 因为session认证本质基于cookie,所以如果cookie被截获,用户很容易收到跨站请求伪造攻击。并且如果浏览器禁用了cookie,这种方式也会失效

  • 前后端分离系统中更加不适用,后端部署复杂,前端发送的请求往往经过多个中间件到达后端,cookie中关于session的信息会转发多次

  • 由于基于Cookie,而cookie无法跨域,所以session的认证也无法跨域,对单点登录不适用

2、JWT认证的优势
对比传统的session认证方式,JWT的优势是:

  • 简洁:JWT Token数据量小,传输速度也很快

  • 因为JWT Token是以JSON加密形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持

  • 不需要在服务端保存会话信息,也就是说不依赖于cookie和session,所以没有了传统session认证的弊端,特别适用于分布式微服务

  • 单点登录友好:使用Session进行身份认证的话,由于cookie无法跨域,难以实现单点登录。但是,使用token进行认证的话, token可以被保存在客户端的任意位置的内存中,不一定是cookie,所以不依赖cookie,不会存在这些问题

  • 适合移动端应用:使用Session进行身份认证的话,需要保存一份信息在服务器端,而且这种方式会依赖到Cookie(需要 Cookie 保存 SessionId),所以不适合移动端

因为这些优势,目前无论单体应用还是分布式应用,都更加推荐用JWT token的方式进行用户认证

3、JWT结构

JWT由3部分组成:标头(Header)、有效载荷(Payload)和签名(Signature)。在传输的时候,会将JWT的3部分分别进行Base64编码后用.进

行连接形成最终传输的字符串

JWTString = Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret)

JWTString=Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header)+“.”+base64UrlEncode(payload),secret)

image-20221024112054230

1.Header
JWT头是一个描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存

{
“alg”: “HS256”,
“typ”: “JWT”
}
2.Payload
有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择

iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
除以上默认字段外,我们还可以自定义私有字段,一般会把包含用户信息的数据放到payload中,如下例:

{
“sub”: “1234567890”,
“name”: “Helen”,
“admin”: true
}
请注意,默认情况下JWT是未加密的,因为只是采用base64算法,拿到JWT字符串后可以转换回原本的JSON数据,任何人都可以解读其内容,因此不要构建隐私信息字段,比如用户的密码一定不能保存到JWT中,以防止信息泄露。JWT只是适合在网络中传输一些非敏感的信息

3.Signature
签名哈希部分是对上面两部分数据签名,需要使用base64编码后的header和payload数据,通过指定的算法生成哈希,以确保数据不会被篡改。首先,需要指定一个密钥(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用header中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名

HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret)
HMACSHA256(base64UrlEncode(header)+“.”+base64UrlEncode(payload),secret)

在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用.分隔,就构成整个JWT对象

image-20221024112122240

注意JWT每部分的作用,在服务端接收到客户端发送过来的JWT token之后:

header和payload可以直接利用base64解码出原文,从header中获取哈希签名的算法,从payload中获取有效数据
signature由于使用了不可逆的加密算法,无法解码出原文,它的作用是校验token有没有被篡改。服务端获取header中的加密算法之后,利用该算法加上secretKey对header、payload进行加密,比对加密后的数据和客户端发送过来的是否一致。注意secretKey只能保存在服务端,而且对于不同的加密算法其含义有所不同,一般对于MD5类型的摘要加密算法,secretKey实际上代表的是盐值

4、JWT的种类
其实JWT(JSON Web Token)指的是一种规范,这种规范允许我们使用JWT在两个组织之间传递安全可靠的信息,JWT的具体实现可以分为以下几种:

nonsecure JWT:未经过签名,不安全的JWT
JWS:经过签名的JWT
JWE:payload部分经过加密的JWT

1.nonsecure JWT
未经过签名,不安全的JWT。其header部分没有指定签名算法

{
“alg”: “none”,
“typ”: “JWT”
}
并且也没有Signature部分

2.JWS
JWS ,也就是JWT Signature,其结构就是在之前nonsecure JWT的基础上,在头部声明签名算法,并在最后添加上签名。创建签名,是保证jwt不能被他人随意篡改。我们通常使用的JWT一般都是JWS

为了完成签名,除了用到header信息和payload信息外,还需要算法的密钥,也就是secretKey。加密的算法一般有2类:

对称加密:secretKey指加密密钥,可以生成签名与验签
非对称加密:secretKey指私钥,只用来生成签名,不能用来验签(验签用的是公钥)
JWT的密钥或者密钥对,一般统一称为JSON Web Key,也就是JWK

到目前为止,jwt的签名算法有三种:

HMAC【哈希消息验证码(对称)】:HS256/HS384/HS512
RSASSA【RSA签名算法(非对称)】(RS256/RS384/RS512)
ECDSA【椭圆曲线数据签名算法(非对称)】(ES256/ES384/ES512)

3.4.9.3 C#中使用JWT

参考学习链接:https://gitee.com/laozhangIsPhi/Blog.Core,使用其中JwtHelper类进行修改了一下,有需要可自行使用

  1. JwtHelper类创建
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace WebApiStudy.Common
{
    public class JwtHelper
    {

        /// <summary>
        /// 颁发JWT字符串
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public static string IssueJwt(TokenModelJwt tokenModel)
        {
            //var claims = new Claim[] //old
            var claims = new List<Claim>
                {
                 /*
                 * 特别重要:
                   1、这里将用户的部分信息,比如 uid 存到了Claim 中,如果你想知道如何在其他地方将这个 uid从 Token 中取出来,请看下边的SerializeJwt() 方法,或者在整个解决方案,搜索这个方法,看哪里使用了!
                   2、你也可以研究下 HttpContext.User.Claims ,具体的你可以看看 Policys/PermissionHandler.cs 类中是如何使用的。
                 */
                new Claim("UserId",tokenModel.UserId.ToString()),
                new Claim("UserName",tokenModel.UserName),
                
                //new Claim(ClaimTypes.Role,tokenModel.Role),//为了解决一个用户多个角色(比如:Admin,System),用下边的方法
               };

            // 可以将一个用户的多个角色全部赋予;

            if (!string.IsNullOrWhiteSpace(tokenModel.Role))
            {
                claims.AddRange(tokenModel.Role.Split(',').Select(s => new Claim(ClaimTypes.Role, s)));
                claims.Add(new Claim("Role", tokenModel.Role));
            }

            //秘钥 (SymmetricSecurityKey 对安全性的要求,密钥的长度太短会报出异常)
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenModel.Secret));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            var jwt = new JwtSecurityToken(
                issuer: tokenModel.Issuer,
                audience: tokenModel.Audience,
                expires: DateTime.Now.AddSeconds(tokenModel.Expires),
                signingCredentials: creds,
                claims: claims
                );

            var jwtHandler = new JwtSecurityTokenHandler();
            var token = jwtHandler.WriteToken(jwt);

            return token;
        }

        /// <summary>
        /// 不检查有效性并解析
        /// </summary>
        /// <param name="jwtStr"></param>
        /// <returns></returns>
        public static TokenModelJwt SerializeJwt(string jwtStr)
        {
            //不校验,直接解析token
            var jwtToken = new JwtSecurityTokenHandler().ReadJwtToken(jwtStr);
            var tokenJwt = JsonConvert.DeserializeObject<TokenModelJwt>(jwtToken.Payload.SerializeToJson());

            return tokenJwt;
        }

        /// <summary>
        /// 令牌
        /// </summary>
        public class TokenModelJwt
        {
            public int UserId { get; set; }
            public string UserName { get; set; }
            public string Issuer { get; set; }
            public string Audience { get; set; }
            public string Secret { get; set; }
            public int Expires { get; set; }
            public string Role { get; set; }
        }
    }
}

报错解决:

image-20221024121018081

image-20221024121213488

image-20221024122425371

  1. 在配置文件中写上我们需要的配置文件

    "Jwt": {
        "Secret": "sghkadhkabhahjbdskanladhnjsfbdsknadnkjsb", //不要太短,建议16位+
        "Issuer": "xiaoshu",
        "Audience": "xiaoshu"
      }
    

    image-20221024132403830

  2. 新建一个JwtController控制器

    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using WebApiStudy.Common;
    using static WebApiStudy.Common.JwtHelper;
    
    namespace WebApiStudy.Controllers
    {
        [Route("api/[controller]")]
        [ApiController]
        public class JwtController : ControllerBase
        {
            //需要读取配置文件,创建一个构造器
            public JwtController(IConfiguration configuration)
            {
                Configuration = configuration;
            }
            private IConfiguration Configuration { get; }
            [HttpPost]
            public string CreateToken()
            {
                var tokenModel = Configuration.GetSection("Jwt").Get<TokenModelJwt>();
                tokenModel.UserName = "张三";
                tokenModel.UserId = 1;
                tokenModel.Role = "Admin";
    
                return JwtHelper.CreateJwt(tokenModel);
            }
        }
    }
    
  3. 启动项目,获取jwt字符串

    image-20221024134556153

  4. 在Program中添加jwt配置代码,然后运行项目

    #region jwt配置
        options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
        {
            Description = "在下框中输入请求头中需要添加的Jwt授权Token:Bearer Token",
            Name = "Authorization",
            In = ParameterLocation.Header,
            Type = SecuritySchemeType.ApiKey,
            BearerFormat =  "Jwt",
            Scheme = "Bearer"
        });
        options.AddSecurityRequirement(new OpenApiSecurityRequirement
        {
            {
                new OpenApiSecurityScheme
                {
                    Reference = new OpenApiReference
                    {
                        Type = ReferenceType.SecurityScheme,
                        Id = "Bearer"
                    }
                },new string[] { }
            }
        });
        #endregion
    

    image-20221024140052906

  5. 然后在program中再加上Jwt的验证(注册服务)

    #region jwt验证中间件
    builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            var tokenModel = builder.Configuration.GetSection("Jwt").Get<TokenModelJwt>();
            var secretByte = Encoding.UTF8.GetBytes(tokenModel.Secret);
            options.TokenValidationParameters = new TokenValidationParameters()
            {
                ValidateIssuer = true,
                ValidIssuer = tokenModel.Issuer,
    
                ValidateAudience = true,
                ValidAudience = tokenModel.Audience,
    
                ValidateLifetime = true,
    
                IssuerSigningKey = new SymmetricSecurityKey(secretByte)
            };
            options.Events = new JwtBearerEvents
            {
                OnChallenge = context =>
                {
                    return Task.FromResult(0);
                },
                OnForbidden = context =>
                {
                    return Task.FromResult(0);
                }
            };
        });
    #endregion
    

    安装以下包:

    image-20221024140543670

    在最后加上

    //jwt身份验证中间件
    app.UseAuthentication();
    

    image-20221024143331645

  6. 在JwtController控制器中新增一个方法来测试是否授权成功

    /// <summary>
            /// 解析token
            /// </summary>
            /// <returns></returns>
            [HttpGet]
            [Authorize]
            public IActionResult DeToken()  //[FromHeader]:从请求头中获取数据
            {
                return Ok();
            }
    
  7. 启动项目,先获取jwt字符串

    image-20221024144025035

  8. 进行jwt认证

    image-20221024144148265

    输入格式:Bearer+(空格)+jwt字符串,点击授权即可,然后关闭状态栏

    image-20221024144357606

    image-20221024144436397

  9. 测试是否授权成功

    image-20221024145243771

  10. 解析token并返回数据

    /// <summary>
            /// 解析token并返回数据
            /// </summary>
            /// <returns></returns>
            [HttpGet]
            [Authorize]
            public IActionResult GetJwtToken([FromHeader] string Authorization)  //[FromHeader]:从请求头中获取数据
            {
                //Bearer :Bearer后面接了一个空格
                var token = JwtHelper.SerializeJwt(Authorization.Replace("Bearer ", ""));  
                return Ok(token);
            }
    
  11. 启动项目重新进行授权并返回数据

image-20221024151404255

  1. 如果修改角色

    /// <summary>
            /// 解析token并返回数据
            /// </summary>
            /// <returns></returns>
            [HttpGet]
            [Authorize(Roles ="xiaoshu")]
            public IActionResult GetJwtToken([FromHeader] string Authorization)  //[FromHeader]:从请求头中获取数据
            {
                var token = JwtHelper.SerializeJwt(Authorization.Replace("Bearer ", ""));
                return Ok(token);
            }
    

    则会报错403:角色权限不符合

    image-20221024151831140

3.4.10 前端数据接收规范

  1. 新建一个用户数据接收的类(不能用实体类去接收,因为它涉及到数据库的一些操作不能随意更改)

    namespace WebApiStudy
    {
        /// <summary>
        /// 接收前端数据的类,可指定接收什么样的
        /// </summary>
        public class LoginDto
        {
            public string Code { get; set; }
            public string Password { get; set; }
        }
    }
    
  2. 在JwtController中增加一个参数,用于接收前端传的参数

            [HttpPost]
            public string CreateToken(LoginDto dto)
            {
                var tokenModel = Configuration.GetSection("Jwt").Get<TokenModelJwt>();
                tokenModel.UserName = "张三";
                tokenModel.UserId = 1;
                tokenModel.Role = "Admin";
    
                return JwtHelper.CreateJwt(tokenModel);
            }
    
  3. 启动项目,即可看到规定需要传入哪些值

    image-20221024154813510

3.4.11 简单使用缓存

  1. 依赖注入

    //进行缓存的依赖注入
    builder.Services.AddMemoryCache();
    

    image-20221024161057106

  2. 新建一个缓存控制器

    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Caching.Memory;
    
    namespace WebApiStudy.Controllers
    {
        [Route("api/[controller]")]
        [ApiController]
        public class MemoryCacheController : ControllerBase
        {
            private readonly IMemoryCache cache;
    
            //通过构造函数的方法获取
            public MemoryCacheController(IMemoryCache cache)
            {
                this.cache = cache;
            }
            [HttpPost]
            public string Set(string Name)
            {
                var key = Guid.NewGuid().ToString();
                cache.Set(key, Name,TimeSpan.FromSeconds(100));  //TimeSpan.FromSeconds(100):缓存有效期
                return key;
            }
            [HttpGet]
            public string Get(string key)
            {
                return cache.Get(key).ToString();
            }
        }
    }
    
  3. 通过Name生成一个GUID

    image-20221024161731151

  4. 从缓存中通过GUID获取Name

    image-20221024161812659

  5. 当缓存有效期过期时

    image-20221024162321270

3.4.12 文件上传

  1. 编写一个控制器

    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    
    namespace WebApiStudy.Controllers
    {
        [Route("api/[controller]")]
        [ApiController]
        public class FileController : ControllerBase
        {
            [HttpPost]
            public bool Upload(IFormFile file)
            {
                var filename = file.FileName;
                var path = @"D:\C#学习\Test\";
                using (var stream = new FileStream(path + filename, FileMode.Create))
                {
                    file.CopyTo(stream);
                }
                return true;
            }
        }
    }
    
  2. 执行进行复制文件操作

    image-20221024163752797

3.4.13 AutoMapper的简单使用

功能:对象与对象之间的映射

  1. 安装automapper.extensions.microsoft.dependencyinjection

    image-20221024170004352

  2. 准备工作

    创建一个User映射类

    namespace WebApiStudy.Dto
    {
        /// <summary>
        /// User映射类
        /// </summary>
        public class UserDto
        {
            public int Id { get; set; }
        }
    }
    

    创建一个MapperProfile类

    using AutoMapper;
    using Models;
    using WebApiStudy.Dto;
    
    namespace WebApiStudy.Profiles
    {
        public class MapperProfile:Profile
        {
            public MapperProfile()
            {
                CreateMap<User, UserDto>();
            }
        }
    }
    

    在program中进行依赖注入

    //进行AutoMapper依赖注入
    builder.Services.AddAutoMapper(typeof(WebApiStudy.Profiles.MapperProfile));
    

    新建MapperController控制器

    using AutoMapper;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using Models;
    using WebApiStudy.Dto;
    
    namespace WebApiStudy.Controllers
    {
        [Route("api/[controller]")]
        [ApiController]
        public class MapperController : ControllerBase
        {
            public MapperController(IMapper mapper)
            {
                Mapper = mapper;
            }
    
            public IMapper Mapper { get; }
            [HttpPost]
            public UserDto Test(User user)
            {
                //User, UserDto:第一个参数是源,第二个参数是目标
                return Mapper.Map<User, UserDto>(user);
            }
        }
    }
    
  3. 进行测试并查看返回结果

    image-20221024171849363

    image-20221024171906081

  4. 复杂映射

    using AutoMapper;
    using Models;
    using WebApiStudy.Dto;
    
    namespace WebApiStudy.Profiles
    {
        public class MapperProfile:Profile
        {
            public MapperProfile()
            {
                //CreateMap<User, UserDto>();
                //复杂映射
                //CreateMap<User, UserDto>().ForMember(dest=> dest.Id,opt=>opt.MapFrom(user => user.Id+"名字"));
                CreateMap<User, UserDto>().ForMember(dest=> dest.Id,opt=>opt.MapFrom(user => user.Id+1));
            }
        }
    }
    

3.4.14 ISS发布

excel导入导出学习链接:https://www.cnblogs.com/codelove/p/15117226.html

ISS发布学习链接:【Net 6 WebApi简单入门-哔哩哔哩】 https://b23.tv/KthBIg9

3.4.15 问题小结

关于构造函数中的参数创建属性还是字段的探讨:

image-20221024174957317

  1. 创建属性

     public JwtController(IConfiguration configuration)
            {
                Configuration = configuration;
            }
            private IConfiguration Configuration { get; }
    
  2. 创建字段

    private readonly IConfiguration configuration;
    
            //需要读取配置文件,创建一个构造器
            public JwtController(IConfiguration configuration)
            {
                this.configuration = configuration;
            }
    
  3. 总结:

    • 两种方法创建的都是一个IConfiguration对象:configuration,并且都是只读的

    • 区别:

      (1)属性是项目一启动就开辟空间(在内存中指向一个地址),没有赋值的时候为null;如果不赋值,编译不会报错,但是运行会报值空异常

      (2)字段是需要调用构造函数的时候才会去给它开辟空间顺便就赋值了,不调用的时候为null

  • 0
    点赞
  • 2
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:数字20 设计师:CSDN官方博客 返回首页
评论

打赏作者

江南、寻你

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值