语法篇

基础背景知识

c# 编写的应用程序必须放置于.NET 环境中才能正常允许。C# 代码最终会被编译为中间语言 "(MSIL)"

类库是以.dll 结尾,不能直接运行,而启动项目是以.exe 结尾可以直接运行。

语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System;

namespace MyApp
{
class Test1
{
static void Main(string[] args)
{
Console.Write("入口点A。");
Console.Read();
}
}
class Test2
{
static void Main(string[] args)
{
Console.Write("入口点B。");
Console.Read();
}
}
}

主项目也叫启动项目,可以通过该设置方式设置启动项目。

基本语法

面向过程

Debug 类

语法格式
1
System.Diagnostics.Debug.WriteLine("测试debug情况");

变量与常量

语法格式
1
2
3
4
5
6
7
8
9
      int x = default(int);
int y = default;//0,是简写方式
var z;//必须得初始化
z = 20;
Console.WriteLine($"{x}--{y},{z}");
//常量
const int NUMBER = 200;//一般命名方式用大写

int @int = 20;//关键字前得用@

注释

summary 注释:ctrl+m

多行注释:ctrl+/

单行注释:ctrl+k

小驼峰和大驼峰

语法格式
1
2
3
4
/*
小驼峰 myName --第一个小写后面大写
大驼峰 MyName
*/

As 和 is

语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//as只能用于引用类型转换,值类型不行,需要有继承关系才行

Object o = new Object();
dynamic d = o as Student;
Console.WriteLine(o is Object);

using System;

namespace MyApp
{
class Program
{
static void Main(string[] args)
{
object obj = "abcd";
//object obj = 0.085;
//object obj = 1000;
if(obj is double v)
{
Console.WriteLine("双精度数值:{0}", v);
}
if(obj is int x)
{
Console.WriteLine("整数值:{0}", x);
}
if(obj is string s)
{
Console.WriteLine("字符串:{0}", s);
}
}
}
}

类型转换

语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//Convert
//BitConvert是基础类型到字节数组
// double与字节数组互转
double dval = 9.2716d;
Console.WriteLine("double数值:{0}", dval);
// 把double数值转为字节数组
byte[] data = BitConverter.GetBytes(dval);
Console.WriteLine("把双精度数值转为字节数组:\n{0}", string.Join(" ", data));
// 把字节数组转回double类型数值
Console.WriteLine("把字节数组转回double数值:{0}", BitConverter.ToDouble(data, 0));

// 输出字节数组的字符串形式
ulong ulval = 1236548009200356U;
// 获取ulong数值的字节数组
data = BitConverter.GetBytes(ulval);
// 输出此字节数组的字符串
Console.WriteLine("\n字节数组的字符串形式:\n{0}", BitConverter.ToString(data));

// 将包含4个字节的数组转为32位整数
byte[] bytes = { 7, 9, 100, 1 };
int int32val = BitConverter.ToInt32(bytes, 0);
Console.WriteLine("\n将字节数组转为int数值:{0}", int32val);

Console.Read();

自定义转换

implicit 和 explicit 不能重复

implicit
语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using System;

namespace MyApp
{

public class Student
{
public int No { get; set; }
public string Name { get; set; }
/// <summary>
/// Student类隐式转换为String
/// </summary>
public static implicit operator string(Student stu)
{
return string.Format("{0} - {1}", stu.No, stu.Name);
}
}

class Program
{
static void Main(string[] args)
{
// 实例化Student对象
Student stud = new Student();
// 给属性赋值
stud.No = 2013052;
stud.Name = "Yan";
// 可以把Student的实例直接赋值给string变量
string str = stud;
Console.WriteLine("学员信息:\n{0}", str);

Console.Read();
}
}
}

explicit
语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using System;

namespace MyApp
{

public class DoSum
{
#region 私有字段
private int m_val1, m_val2, m_val3;
#endregion
public DoSum(int val1, int val2, int val3)
{
m_val1 = val1;
m_val2 = val2;
m_val3 = val3;
}
/// <summary>
/// 如果要把DoSum转换为int类型,需要强制转换
/// </summary>
public static explicit operator int(DoSum ds)
{
return ds.m_val1 + ds.m_val2 + ds.m_val3;
}
}

class Program
{
static void Main(string[] args)
{
// 实例化DoSum类
DoSum sum = new DoSum(2, 5, 3);
// 将DoSum对象强制转换为int类型
int isum = (int)sum;
// 输出转换后的值
Console.WriteLine(isum);

Console.Read();
}
}
}

创建对象的三种方式

语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Student
{
public int Id { get; set; }
public string Name { get; set; }

public Student(int id, string name)
{
this.Id = id;
this.Name = name;
}

}
static void Main(string[] args)
{
var s1 = new Student(1, "t1");
Console.WriteLine($"{s1.Id}--{s1.Name}");

//下面这两个都需要有构造函数才行
var type = typeof(Student);
var constructor = type.GetConstructor(new Type[] { typeof(int),typeof(string) });
//得是小写的object而不是大写的
var s2 = constructor.Invoke(new object?[] { 2,"t2"}) as Student;
Console.WriteLine($"{s2.Id}--{s2.Name}");

var s3 = Activator.CreateInstance(typeof(Student),3,"t3") as Student;
Console.WriteLine($"{s3.Id}--{s3.Name}");
}

方法

语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
stringExt.Test(p2:"21",p1:"12");
//可选参数必须放到最后面否则出问题,可选参数是会加上中括号的
public void Test(string p1,string p2="234")
{
Console.WriteLine(p1+p2);
}
public void Test(int a,int b)=>a+b


//本地函数
static void Main(string[] args)
{
void Print(string message)
{
Console.WriteLine($"{message}正在打印");
}
string message = Console.ReadLine();
Print(message);
}

Out 、ref、使用元组组合多个返回值

语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static dynamic Add(int a,int b,out int c)
{
dynamic result = a * b;
c = a + b;
return result;
}
static void Main(string[] args)
{
var temp = 10;
var number = 20;
dynamic result = Add(temp,number,out int c);
Console.WriteLine($"{result},{c}");//200 30
}


public (string,int) GetValue(){
return ("apple",5);
}
(string,int) value = stu.GetValue();
Console.WriteLine($"{value.Item1}--{value.Item2}");//apple-5


public (string Name, int Number) GetValue()
{
return (Name:"hello",Number: 2);
}
var fruit = stu.GetValue();
Console.WriteLine($"{fruit.Name},{fruit.Number}");

Params

语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void sum(params int[] a)
{
foreach(int i in a)
Console.WriteLine(i);
/*
1
2
3
4
5
*/
}
static void Main(string[] args)
{
sum(1,2,3,4,5);
}

Switch

语法格式
1
2
3
4
5
6
7
8
int day = Convert.ToInt32(Console.ReadLine());
string week = day switch
{
1 => "星期一",
2 => "星期二",
_ => "星期莫"
};
Console.WriteLine(week);

字符串 @输出转义符、字符和字符串转换

语法格式
1
2
3
4
5
string s = @"C:\\www.baidu.com";
Console.WriteLine (s);//C:\\www.baidu.com

string str = new String(new char[] { 'A', 'B', 'C' });
Console.WriteLine(str);

字符串 Null

语法格式
1
2
3
4
5
6
7
8
string s1 = null;//s1和s2是一样的,为null,没有分配空间,而s3和s4是分配了空间只是为空
string s2;
string s3 = "";
string s4 = string.Empty;

//允许比较未知数据类型的值
if (ReferenceEquals(str, null))
Console.WriteLine("str is null");

typeof 和 sizeof

语法格式
1
Console.WriteLine($"{sizeof(int)},{ typeof(Object)}");//4 System.Object

?和??(合并运算符)

语法格式
1
2
3
4
5
6
7
   Object a = new Object();
a = null;
dynamic b = a ?? 30;
Console.WriteLine(b);//null的话是后面,不然是前面

int? c = null;//加上?就可以赋值null,不然会出错
Console.WriteLine(c);

字符串

语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//拼接的四种方式
/*
""+xxx;
StringBuilder方式更高效对于频繁拼接的话
String.join("/",xxx);
String.Concat(xxx,xxx);//连接字符串
String.Remove(开始索引,删除几个)
String.Split('xxx')以什么为分隔符
String.Trim()首尾空格去除
String.SubString(开始索引,取出几个字符)//取出字串
xxx.PadLeft(多长,用什么填充(默认是空格))
*/

//加上@可以分成两行输出
Console.WriteLine($@"你的名字
{firstname},{lastname}");


//TryParse
//xxx.TryParse(xxx,out xxx) outxxx失败为0
string strDouble = "16K.51F";
// 转化为double值
double convDouble;
if (double.TryParse(strDouble, out convDouble))
{
Console.WriteLine("将字符串“{0}”转化为double值:{1}", strDouble, convDouble);
}
else
{
Console.WriteLine("将字符串“{0}”转化为double值:{1}", strDouble, "转化失败");
}

//indexofAny(xxx,数组)
string testStr = "check my app";
// 要查找的条件
char[] patts = { 'y', 'a' };
// 查找char数组中的元素
int index1 = testStr.IndexOfAny(patts);
// 输出
Console.WriteLine("在{0}中查找{1}的索引:{2}", testStr, string.Join(",", patts), index1);


//填充字符串
string sampleStr = "table"; //测试字符串

// 1、用空格填充左侧,字符串右对齐
Console.WriteLine("调用PadLeft方法填充空格:" + sampleStr.PadLeft(10));

// 2、用字符'#'填充字符串左侧
Console.WriteLine("调用PadLeft方法填充#:" + sampleStr.PadLeft(10, '#'));

// 3、填充字符串右侧,字符串左对齐
Console.WriteLine("调用PadRight方法填充空格:" + sampleStr.PadRight(10));

// 4、用字符'#'填充字符串的右侧
Console.WriteLine("调用PadRight方法填充#:" + sampleStr.PadRight(10, '#'));

区域性相关的信息

语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 设置控制台窗口内字符的缓冲区域
Console.SetBufferSize(950, 860);
// 基于区域性
CultureInfo[] culs = CultureInfo.GetCultures(CultureTypes.SpecificCultures);
Console.WriteLine("{0,-18}{1,-50}{2,-45}", "标记", "显示名称", "英文名称");
Console.WriteLine("----------------------------------------------------------------------------------------------------------------------------");
foreach (CultureInfo c in culs)
{
Console.WriteLine("{0,-18}{1,-50}{2,-45}", c.Name, c.DisplayName, c.EnglishName);
}

Console.WriteLine("****************************************************************************************************");

// 基于语言
CultureInfo[] ntculs = CultureInfo.GetCultures(CultureTypes.NeutralCultures);
Console.WriteLine("{0,-18}{1,-50}{2,-45}", "标记", "显示名称", "英文名称");
Console.WriteLine("----------------------------------------------------------------------------------------------------------------------------");
foreach (CultureInfo c in ntculs)
{
Console.WriteLine("{0,-18}{1,-50}{2,-45}", c.Name, c.DisplayName, c.EnglishName);
}

Console.Read();

字符串格式化

语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
System.Globalization.CultureInfo zh_cul = System.Globalization.CultureInfo.GetCultureInfo("zh");
System.Globalization.CultureInfo en_cul = System.Globalization.CultureInfo.GetCultureInfo("en");
System.Globalization.CultureInfo ja_cul = System.Globalization.CultureInfo.GetCultureInfo("ja");

// 用于测试的时间
DateTime dt = new DateTime(2014, 8, 9, 17, 53, 40);
string strFormat = ""; //格式

/*------------------------------------- 标准格式 -----------------------------------*/
Console.WriteLine("************************* 标准格式 *************************");
// 长日期
strFormat = "D";
Console.WriteLine("---------- 长日期 ----------");
Console.WriteLine("{0,-8}{1}", zh_cul.DisplayName, dt.ToString(strFormat, zh_cul));
Console.WriteLine("{0,-8}{1}", en_cul.DisplayName, dt.ToString(strFormat, en_cul));
Console.WriteLine("{0,-8}{1}", ja_cul.DisplayName, dt.ToString(strFormat, ja_cul));

// 短日期
strFormat = "d";
Console.WriteLine("\n---------- 短日期 ----------");
Console.WriteLine("{0,-8}{1}", zh_cul.DisplayName, dt.ToString(strFormat, zh_cul));
Console.WriteLine("{0,-8}{1}", en_cul.DisplayName, dt.ToString(strFormat, en_cul));
Console.WriteLine("{0,-8}{1}", ja_cul.DisplayName, dt.ToString(strFormat, ja_cul));

// 完整格式(长时间)
strFormat = "F";
Console.WriteLine("\n---------- 完整格式(长时间) ----------");
Console.WriteLine("{0,-8}{1}", zh_cul.DisplayName, dt.ToString(strFormat, zh_cul));
Console.WriteLine("{0,-8}{1}", en_cul.DisplayName, dt.ToString(strFormat, en_cul));
Console.WriteLine("{0,-8}{1}", ja_cul.DisplayName, dt.ToString(strFormat, ja_cul));

// 完整格式(短时间)
strFormat = "f";
Console.WriteLine("\n---------- 完整格式(短时间) ----------");
Console.WriteLine("{0,-8}{1}", zh_cul.DisplayName, dt.ToString(strFormat, zh_cul));
Console.WriteLine("{0,-8}{1}", en_cul.DisplayName, dt.ToString(strFormat, en_cul));
Console.WriteLine("{0,-8}{1}", ja_cul.DisplayName, dt.ToString(strFormat, ja_cul));

/*-----------------------------------自定义格式------------------------------------------*/
/*------------------------------------- 自定义格式 -----------------------------------*/
Console.WriteLine("\n\n*********************** 自定义格式 *************************");
// 一周中的一天的全称
strFormat = "dddd";
Console.WriteLine("-------- 一周中某天的全称 --------");
Console.WriteLine("{0,-8}{1}", zh_cul.DisplayName, dt.ToString(strFormat, zh_cul));
Console.WriteLine("{0,-8}{1}", en_cul.DisplayName, dt.ToString(strFormat, en_cul));
Console.WriteLine("{0,-8}{1}", ja_cul.DisplayName, dt.ToString(strFormat, ja_cul));

// 一个月中的一天
strFormat = "%d";
Console.WriteLine("\n-------- 一个月中的一天 --------");
Console.WriteLine("{0,-8}{1}", zh_cul.DisplayName, dt.ToString(strFormat, zh_cul));
Console.WriteLine("{0,-8}{1}", en_cul.DisplayName, dt.ToString(strFormat, en_cul));
Console.WriteLine("{0,-8}{1}", ja_cul.DisplayName, dt.ToString(strFormat, ja_cul));

// 某个月的全称
strFormat = "MMMM";
Console.WriteLine("\n-------- 某个月的全称 --------");
Console.WriteLine("{0,-8}{1}", zh_cul.DisplayName, dt.ToString(strFormat, zh_cul));
Console.WriteLine("{0,-8}{1}", en_cul.DisplayName, dt.ToString(strFormat, en_cul));
Console.WriteLine("{0,-8}{1}", ja_cul.DisplayName, dt.ToString(strFormat, ja_cul));

// 自定义日期时间格式
strFormat = "yyyy年M月d日 HH时mm分ss秒";
Console.WriteLine("\n------ 自定义日期时间格式 ------");
Console.WriteLine("自定义日期时间格式:" + dt.ToString(strFormat));

Console.Read();

字符串复合格式化

语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 在Console.WriteLine应运中使用复合格式化
double price = 5.6d;
int q = 20;
Console.WriteLine("单价:{0:C}\n数量:{1}\n总价:{2:C}", price, q, price * q);

// 在String.Format方法中使用复合格式化
DateTime dt = new DateTime(2011, 5, 9);
double d = 0.125d;
string resultStr = string.Format("在{0:D}买入,当前涨幅为{1:P2}", dt, d);
Console.WriteLine("\n" + resultStr);

// 对齐方式
Console.WriteLine("\n");
Console.WriteLine("左对齐,宽度为10:[{0,-10}]", "case");
Console.WriteLine("右对齐,宽度为13:[{0,13}]", "zhang");

Console.Read();

生成随机数

语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//public virtual int Next(int minValue,int maxValue);//包含minValue,不包含maxValue

Console.SetWindowSize(1, 1);
Console.SetBufferSize(35, 20);
Console.SetWindowSize(35, 20);

// 实例化一个Random实例
Random rand = new Random();
// 产生一个随机整数
Console.WriteLine("产生随机整数:{0}", rand.Next());

//使用种子,这样每次都可以生成基于相同的序列
var random = new Random(12345);
Console.WriteLine("使用种子 12345 生成的随机数:");
for (int i = 0;i<5;i++)
Console.WriteLine(random.Next(1,101));

Console.WriteLine("\n产生一个介于100和999之间的随机整数数组:");
// 创建一个int数组
int[] arrNums = (int[])Array.CreateInstance(typeof(int), 5);
// 用随机数填充数组
int arrLen = arrNums.Length; //数组中元素的个数
for (int i = 0; i < arrLen; i++)
{
/*
* 该方法重载的原型为:
* int Next(int minValue, int maxValue)
* minValue限制所产生的随机数的最小值,
* maxValue限制所产生的随机数的最大值。
* 产生的随机数大于或等于minValue,
* 并小于maxValue。
* 如下面调用,minValue参数为100,
* maxValue参数为1000,由于所产生的随机数
* 不包括上限的值,即不包含1000,这样使得
* 产生的随机数可以包含999
*/
arrNums[i] = rand.Next(100, 1000);
}
Console.Write(string.Join(" ", arrNums));

// 产生一个随机的double值
Console.WriteLine("\n\n产生一个随机的双精度数值:{0:G4}", rand.NextDouble());

Console.Read();

常见的时间计算

语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//DateTime是时间点,TimeSpan是时间段 


Console.SetWindowSize(32, 18);
Console.SetBufferSize(32, 18);

// DateTime运算
Console.WriteLine("--------- DateTime --------- ");
// 2010年3月21日 20时3分22秒
DateTime dtime = new DateTime(2010, 3, 21, 20, 3, 22);
// 加上75分钟
DateTime resDtime = dtime.AddMinutes(75);
Console.WriteLine("将{0:F}加上75分钟后:{1:F}", dtime, resDtime);
// 减去8000秒
resDtime = dtime.AddSeconds(-8000);
Console.WriteLine("将{0:F}减去8000秒:{1:F}", dtime, resDtime);

// TimeSpan运算
Console.WriteLine("\n--------- TimeSpan --------");
// 60000分钟
double mins = 60000d;
TimeSpan tsp = TimeSpan.FromMinutes(mins);
// 总共有多少秒
Console.WriteLine("{0:N0}分钟中有{1:N0}秒", mins, tsp.TotalSeconds);
// 总共有多少小时
Console.WriteLine("{0:N0}分钟中有{1:N0}小时", mins, tsp.TotalHours);
// 总共有多少天
Console.WriteLine("{0:N0}分钟中有{1:N0}天", mins, tsp.TotalDays);

Console.Read();

日期格式化

语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
DateTime dt = new DateTime(2017, 4, 1, 13, 16, 32, 108);
/*
yyyy:四位数年份
MM:两位数月份(01-12)
dd:两位数日期(01-31)
HH:24小时制的小时(00-23)
mm:两位数的分钟(00-59)
ss:两位数的秒(00-59)
fff:三位数的毫秒(000-999)
*/
//得用冒号
dynamic result = string.Format("{0:yyyy-MM-dd HH:mm:ss.fff}", dt);
Console.WriteLine(result);

集合

语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//Find(new Predicate<string>(xxx));

using System;
using System.Collections.Generic;

namespace MyApp
{
class Program
{
static void Main(string[] args)
{
List<string> list = new List<string>();
// 向列表中添加元素
list.Add("star");
list.Add("start");
list.Add("are");
list.Add("fill");
list.Add("green");
list.Add("ask");
list.Add("car");
list.Add("desk");
list.Add("let");

// 找查列表中带有字符串“ar”的第一个元素
string resstr = list.Find(new Predicate<string>(FindStringFirst));
Console.WriteLine("第一个包含“ar”的元素:{0}", resstr);

/*--------------------------------------------------------------------------------*/
List<int> intlist = new List<int>();
// 向列表中添加元素
intlist.AddRange(new int[] { 90, 17, 8, 5, 33, 61, 12, 11, 35 });
// 找出列表中的所有偶数
List<int> resultNums = intlist.FindAll(new Predicate<int>(FindAllInt));
Console.WriteLine("列表中的偶数有:{0}", string.Join(" ", resultNums.ToArray()));

Console.Read();
}

private static bool FindAllInt(int n)
{
if ((n % 2) == 0)
{
return true;
}
return false;
}

private static bool FindStringFirst(string obj)
{
if (obj.IndexOf("ar") != -1)
{
return true;
}
return false;
}
}
}

HashSet

IsSubsetOf–子集
IsSupersetOf–父集
Overlaps–交集
UnionWith–并集
RemoveWhere(new Predicate(xxx))
语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
using System;
using System.Collections.Generic;

namespace MyApp
{
class Program
{
static void Main(string[] args)
{
/*------------------------------------------ 添加和删除元素 -----------------------------------------*/
// 实例化
HashSet<int> set1 = new HashSet<int>();
// 向集合中添加3个元素
set1.Add(5000);
set1.Add(-100);
set1.Add(75);
// 输出集合中的元素
Console.Write("set1中的元素:\n");
foreach (int x in set1)
{
Console.Write(" {0}", x);
}
// 集合中已包含元素75,我们再添加一次
set1.Add(75);
// 再次输出集合中的元素
Console.Write("\n向集合set1中再次添加75后:\n");
foreach (int x in set1)
{
Console.Write(" {0}", x);
}
// 删除集合中的-100
set1.Remove(-100);
// 再次输出集合中的元素
Console.Write("\n从集合set1中删除元素-100后:\n");
foreach (int x in set1)
{
Console.Write(" {0}", x);
}

/*----------------------------------------- 条件删除 ---------------------------------------*/
HashSet<string> set2 = new HashSet<string>(new string[]{
"move", "head", "tree", "bad", "read", "full"
});
// 输出集合中的元素
Console.Write("\n\n集合set2中的元素:\n");
foreach (string s in set2)
{
Console.Write(" {0}", s);
}
// 删除以“d”结尾的元素
set2.RemoveWhere(new Predicate<string>(OnDeleteItem));
// 输出删除后集合中的元素
Console.Write("\n删除set2中以“d”结尾的元素后:\n");
foreach (string s in set2)
{
Console.Write(" {0}", s);
}

/*------------------------------------------ 集合运算 ------------------------------------------*/
HashSet<int> set3 = new HashSet<int>();
// 添加元素
set3.Add(10);
set3.Add(13);
set3.Add(15);
HashSet<int> set4 = new HashSet<int>();
// 添加元素
set4.Add(13);
set4.Add(15);
bool b = set3.IsSubsetOf(set4);
Console.Write("\n\nset3{0}set4的子集。\n", b ? "是" : "不是");
b = set3.IsSupersetOf(set4);
Console.Write("set3{0}set4的父集。", b ? "是" : "不是");

/*------------------------------------------------ 交集与并集 -------------------------------------------*/
HashSet<byte> set5 = new HashSet<byte>(new byte[] { 5, 7, 22, 43 });
HashSet<byte> set6 = new HashSet<byte>(new byte[] { 7, 20, 5, 53 });
// 判断是否为交集
b = set5.Overlaps(set6);
// 输出结果
Console.Write("\n\nset5与set6{0}交集。", b ? "存在" : "不存在");
// 合并两个集合
set5.UnionWith(set6);
// 输出合并后的元素
Console.Write("\n将set6合并到set5后;\n");
foreach (byte bt in set5)
{
Console.Write(" {0}", bt);
}


Console.Read();
}

private static bool OnDeleteItem(string obj)
{
if (obj.EndsWith("d"))
{
return true;
}
return false;
}
}
}

字典

HashTable 与 Dictionary
语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//xxx.add(xxx.xxx,xxx)

//Dictionary用KeyValuePair,HashTable用DictionaryEntry

using System;
using System.Collections;

namespace MyApp
{
public class EmpInfo
{
/// <summary>
/// 员工编号
/// </summary>
public string EmpID { get; set; }
/// <summary>
/// 员工姓名
/// </summary>
public string EmpName { get; set; }
/// <summary>
/// 员工年龄
/// </summary>
public int EmpAge { get; set; }
}

class Program
{
static void Main(string[] args)
{
Hashtable hstb = new Hashtable();
// 向字典中添加三位员工的信息
// 以员工编号作为键
EmpInfo emp1 = new EmpInfo();
emp1.EmpID = "C-1001";
emp1.EmpName = "小胡";
emp1.EmpAge = 29;
hstb.Add(emp1.EmpID, emp1);
EmpInfo emp2 = new EmpInfo()
{
EmpID = "C-1002",
EmpName = "老李",
EmpAge = 28
};
hstb.Add(emp2.EmpID, emp2);
EmpInfo emp3 = new EmpInfo
{
EmpID = "C-1003",
EmpName = "小张",
EmpAge = 21
};
hstb.Add(emp3.EmpID, emp3);

// 通过键获取某个元素
EmpInfo empGet = (EmpInfo)hstb["C-1002"];
if (empGet != null)
{
Console.WriteLine("从字典中取出编号为C-1002的员工信息:");
Console.WriteLine("员工编号:{0}\n员工姓名:{1}\n员工年龄:{2}\n", empGet.EmpID, empGet.EmpName, empGet.EmpAge);
}

// 修改字典中某个元素
// 把编号为C-1003的员工的年龄改为25
// 获取元素的引用
EmpInfo empModif = (EmpInfo)hstb["C-1003"];
// 修改属性
if (empModif != null)
{
empModif.EmpAge = 25;
}
// 把编号为C-1001的员工信息替换为另一位员工的信息
if (hstb.ContainsKey("C-1001"))
{
hstb["C-1001"] = new EmpInfo { EmpID = "C-1001", EmpName = "小林", EmpAge = 23 };
}

// 输出字典中所有元素的信息
Console.Write("\n");
foreach (DictionaryEntry emt in hstb)
{
string key = (string)emt.Key;
EmpInfo info = (EmpInfo)emt.Value;
Console.WriteLine("--------------------------------");
// 输出键
Console.WriteLine(key);
// 输出值
Console.Write("员工编号:{0}\n员工姓名:{1}\n员工年龄:{2}\n", info.EmpID, info.EmpName, info.EmpAge);
Console.WriteLine("--------------------------------");
}

Console.Read();
}
}
}

Dictionary<int,string> keyValuePairs = new Dictionary<int,string>();
keyValuePairs.Add(3, "three");
keyValuePairs.Add(4, "four");
foreach (var item in keyValuePairs)
{
Console.WriteLine($"{item.Key}--{item.Value}");
}

自定义排序

两种方式

Comparer类派生,有静态 Default 属性,默认排序方式

IComparer接口,必须实现 Compare 方法。

语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
using System;
using System.Collections.Generic;

namespace MyApp
{

#region 定义类型
public class Student
{
/// <summary>
/// 学生姓名
/// </summary>
public string Name { get; set; }
/// <summary>
/// 学生年龄
/// </summary>
public int Age { get; set; }
/// <summary>
/// 学号
/// </summary>
public string ID { get; set; }
}
#endregion

#region 自定义的比较器
public class OrderByAge : IComparer<Student>
{
/// <summary>
/// 对两个Student对象进行比较
/// </summary>
/// <param name="x">要进行比较的第一个Student对象</param>
/// <param name="y">要进行比较的第二个Student对象</param>
/// <returns>如果两个对象中有一个或者都为null,则无法进行比较,视为两对象相等;如果x的Age属性与y的Age属性相等,则视为两个对象相等;如果x的Age属性比y的Age属性大,则视为x大于y;否则x小于y</returns>
public int Compare(Student x, Student y)
{
if (x == null || y == null)
{
return 0;
}
if (x.Age < y.Age)
{
return -1;
}
else if (x.Age == y.Age)
{
return 0;
}
return 1;
}
}

#endregion

class Program
{
static void Main(string[] args)
{
// 准备数据
List<Student> students = new List<Student>();
students.Add(new Student { ID = "100022", Name = "小王", Age = 19 });
students.Add(new Student { ID = "100023", Name = "小曾", Age = 20 });
students.Add(new Student { ID = "100024", Name = "小李", Age = 27 });
students.Add(new Student { ID = "100025", Name = "小宁", Age = 26 });
students.Add(new Student { ID = "100026", Name = "小成", Age = 21 });
students.Add(new Student { ID = "100027", Name = "小陈", Age = 17 });
students.Add(new Student { ID = "100028", Name = "小黄", Age = 25 });
students.Add(new Student { ID = "100029", Name = "小顾", Age = 22 });

// 排序前输出
Console.Write("排序前:\n");
foreach (Student stu in students)
{
Console.WriteLine("学号:{0},姓名:{1},年龄:{2}", stu.ID, stu.Name, stu.Age);
}

// 使用自定义方案进行排序
students.Sort(new OrderByAge());

// 排序后再次输出
Console.WriteLine("\n排序后:");
foreach (Student stu in students)
{
Console.WriteLine("学号:{0},姓名:{1},年龄:{2}", stu.ID, stu.Name, stu.Age);
}

Console.Read();
}
}
}

控制台应用程序

获取文本信息

语法格式
1
2
3
4
 public override string ToString()
{
return "编号:" + No.ToString() + ",产品名称:" + Name + ",日期:" + ProductDate.ToShortDateString();
}

获取键盘输入

Read

Read (), 每次只读入一个字符,Ctrl+Z 返回 - 1

ReadKey

ReadKey (),读取用户输入的字符
ReadKey 返回 ConsoleKeyInfo 实例, 包含三个属性:Key(返回 ConsoleKey 枚举)、keyChar(直接返回 Unicode 字符)、Modifiers (返回 ConsoleModifiers 枚举值,表示用户是否按下了 Ctrl、Alt、Shift)

ReadLine

ReadLine () 如果按下 Ctrl+Z+Enter 键会返回 Null

命令行参数

两种方式
语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 第一种
if (args.Length > 0)
{
Console.WriteLine("通过Main方法的参数获取到的命令行参数:");
string parms = string.Join(", ", args);
Console.WriteLine(parms);
}

//Environment.GetCommandLineArgs()中的第一个元素是当前可执行文件的路径,
//Array.Copy(原数组,原数组的开始索引,目标数组,目标数组的起始索引,长度);

// 第二种
var paramters = Environment.GetCommandLineArgs();
Console.WriteLine("\n通过Environment.GetCommandLineArgs方法获取到的命令行参数:");
if (paramters.Length > 1)
{
// 因为返回的数组中
// 第一个元素为.exe文件的路径
// 因此去掉第一个元素
string[] s = new string[paramters.Length - 1];
// 从第二个元素开始复制
Array.Copy(paramters, 1, s, 0, paramters.Length - 1);
string pst = string.Join(", ", s);
Console.WriteLine(pst);
}

控制台窗口的外观

三个属性
Title
BackgroundColor
ForegroundColor–文本颜色也就是前置颜色
语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 修改窗口标题文本
Console.Title = "我的应用程序";
// 将文本颜色改为绿色
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("第一行文本"); // 输出
// 将文本背景色改为蓝色
Console.BackgroundColor = ConsoleColor.Blue;
Console.WriteLine("第二行文本"); // 输出
// 将背景色改为白色
Console.BackgroundColor = ConsoleColor.White;
// 将文本颜色改为黑色
Console.ForegroundColor = ConsoleColor.Black;
Console.WriteLine("第三行文本"); // 输出
// 将文本颜色还原为灰色
Console.ForegroundColor = ConsoleColor.Gray;
// 将背景色还原为黑色
Console.BackgroundColor = ConsoleColor.Black;

控制台窗口的大小和位置

注意点:

缓冲区大小比窗口大小要大,要先设置窗口大小为 1,1 之后再重新设置。

WindowTop 以行为单位,WindowLeft 以列为单位。

ConsoleKey.RightArrow// 右边

Console.WindowLeft < (Console.BufferWidth - Console.WindowWidth)

Console.SetWindowPosition(Console.WindowLeft + 1, Console.WindowTop);

语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 输出测试文本
Console.WriteLine("笙,是中国汉族古老的吹奏乐器,它是世界上最早使用自由簧的乐器,并且对西洋乐器的发展曾经起过积极的推动作用。1978年,中国湖北省随县曾侯乙墓出土了2400多年前的几支匏笙,这是中国目前发现的最早的笙。殷代(公元前1401--前1122年)的甲骨文中已有“和”(小笙)的名称。春秋战国时期,笙已非常流行,它与竽并存,在当时不仅是为声乐伴奏的主要乐器,而且也有合奏、独奏的形式。南北朝到隋唐时期,竽、笙仍并存应用,但竽一般只用于雅乐,逐渐失去在历史上的重经作用,而笙却在隋唐的燕乐九部乐、十部乐中的清乐、西凉乐、高丽乐、龟兹乐中均被采用。当时笙的形制主要有十九簧、十七簧、十三簧。唐代又有了十七簧的义管笙,在十七簧之外,另备两根义管,需要时,再把它临时装上去。早期的笙为竹制,后来改为铜制。明清时期,民间流传的笙有方、圆、大、小各种不同的笙的形制。");
Console.WriteLine("笙是我国古老的簧管乐器,历史悠久,能奏和声。它以簧、管配合振动发音,簧片能在簧框中自由振动,是世界上最早使用自由簧的乐器。史记补三皇本记记载:“女娲氏风姓,有神圣之德,代宓仪、立号曰女希氏,作笙黄。”可之,女娲当时并无笙,只作笙中簧,此时的笙簧许是以竹、木片所制,只可发出高低不同的音。到唐、虞、夏、商诸朝代,发展以数根的竹簧之管参差插入葫芦之干壳内制成(今云贵边疆的苗、傜民族,仍存用此种笙)。远在3000多年前的商代,我国就已有了笙的雏型。在出土的殷(公元前1401~前1122)墟甲骨文中有“和”的记载。“和”即是后世小笙的前身。《尔雅·释乐》记载:“大笙谓之巢,小者谓之和。”在我国古代乐器分类中,笙为匏类乐器。《诗经》的《小雅.鹿鸣》写道:“我有嘉宾,鼓瑟吹笙。吹笙鼓簧,承筐是将”,可见笙在当时已经很流行了。");


// 在修改缓冲区域大小前先缩小窗口区域
Console.SetWindowSize(1, 1);//得先设置为1,1不然默认窗口是要比缓冲区大会报错,因为要求缓冲区比窗口要大
// 设置缓冲区域的大小
Console.SetBufferSize(82, 25);
// 再次设置窗口的区域大小
Console.SetWindowSize(30, 8);

// 捕捉键盘输入
ConsoleKeyInfo keyInfo;
do
{
//捕获一个键盘输入。true 参数表示不在控制台上显示按下的键。
keyInfo = Console.ReadKey(true);

// 判断按下的键
switch (keyInfo.Key)
{
case ConsoleKey.RightArrow: //右
if (Console.WindowLeft < (Console.BufferWidth - Console.WindowWidth))
{
Console.SetWindowPosition(Console.WindowLeft + 1, Console.WindowTop);
}
break;
case ConsoleKey.LeftArrow: //左
if (Console.WindowLeft > 0)
{
Console.SetWindowPosition(Console.WindowLeft - 1, Console.WindowTop);
}
break;
case ConsoleKey.UpArrow: //上
if (Console.WindowTop > 0)
{
Console.SetWindowPosition(Console.WindowLeft, Console.WindowTop - 1);
}
break;
case ConsoleKey.DownArrow: //下
if (Console.WindowTop < (Console.BufferHeight - Console.WindowHeight))
{
Console.SetWindowPosition(Console.WindowLeft, Console.WindowTop + 1);
}
break;
}
} while (keyInfo.Key != ConsoleKey.Escape);

响应 CancelKeyPress 事件

语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 // 声明一个静态字段
static bool running = false;
static void Main(string[] args)
{
// 注册事件处理程序
Console.CancelKeyPress += OnCancel;
running = true;
// 进入循环
while (running)
{
Console.WriteLine("正在运行……");
}
}

private static void OnCancel(object sender, ConsoleCancelEventArgs e)
{
Console.WriteLine("程序即将退出");
// 修改静态字段的值
running = false;
}

数组

定义方式

语法格式
1
2
3
4
5
xxx.Length/xxx.LongLength;
//foreach是实现了IEnumerable接口
string[] name = new string[]{xxx,xxx};
string[] name = {xxx,xxx};
//数组是引用类型,如果数组中包含引用类型,初始化为Null

复制数组

语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//复制数组
//实例.copyto(目标数组,索引),只能一维数组,不能多维数组[,]
//Array.copy(源数组,目标数组,长度)


// 复制一维数组
int[] srcArr = new int[6] { 1, 2, 3, 4, 5, 6 };
int[] disArr = new int[8];

// 复制
srcArr.CopyTo(disArr, 0);
// 或者 Array.Copy(srcArr, disArr, srcArr.Length);
Array.Copy(srcArr, disArr, srcArr.Length);
// 输出
Console.Write("复制一维组数后目标数组中的元素:\n");
foreach (int b in disArr)
{
Console.Write(b + " ");
}
/**********************************************************************/
// 复制二维数组
int[,] srcArrd = new int[3, 2]
{
{ 30, 50 }, { 35, 16 }, { 27, 19 }
};
int[,] disArrd = new int[3, 2];
// 复制
Array.Copy(srcArrd, disArrd, disArrd.Length);
// 输出
Console.Write("\n\n复制二维数组后目标数组中的元素:\n");
foreach (int i in disArrd)
{
Console.Write(i + " ");
}

/******************************************************************************/
// 复制嵌套数组
int[][] srcArrm = new int[][]
{
new int[] { 0, 2, 7},
new int[] { 9, 12, 21}
};
int[][] disArrm = new int[2][];

// 复制
srcArrm.CopyTo(disArrm, 0);
//或者 Array.Copy(srcArrm, disArrm, disArrm.Length);
//Array.Copy(srcArrm, disArrm, disArrm.Length);
// 输出
Console.Write("\n\n复制嵌套数组后目标数组中的元素:\n");

//循环遍历嵌套数组
foreach (int[] x in disArrm)
{
foreach (int y in x)
{
Console.Write(y + " ");
}
}

查找元素

查找元素索引
语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//在数组中查找元素
//查找元素的索引
public delegate bool Predicate<T>(T obj);

// 用于测试的数组
string[] testArr = new string[]
{
/*0*/"ask", /*1*/"check", /*2*/"ask", /*3*/"food", /*4*/"ink"
};
// check是数组的第二个元素,索引为1
int index1 = Array.IndexOf(testArr, "check");
Console.WriteLine("check元素的索引:{0}", index1);

// 数组中存在两个ask,索引分别为0和2
// LastIndexOf方法只返回最后一个ask的索引2
int index2 = Array.LastIndexOf(testArr, "ask");
Console.WriteLine("测试数组中有两个ask元素,LastIndexOf方法返回的索引:{0}", index2);

// 通过自定义方式,查找以k结尾的元素
// 第一个元素ask就是k结尾,已满足条件,不再往下查找
// 因此返回第一个ask的索引0
int index3 = Array.FindIndex(testArr, new Predicate<string>(FindProc));
Console.WriteLine("\nFindIndex方法查找以k结尾的元素的索引:{0}", index3);

// 自定义方式查找以k结尾的元素
// 测试数组中索引0、1、2、4四处的元素都以k结尾
// 但FindLastIndex只返回最后一个匹配项ink的索引4
int index4 = Array.FindLastIndex(testArr, new Predicate<string>(FindProc));
Console.WriteLine("FindLastIndex方法查找以k结尾的元素的索引:{0}", index4);
查找元素本身
语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//查找元素本身 
// 声明数组变量
int[] arr = { 3, 6, 35, 10, 9, 13 };

// Find方法只返回匹配的第一个元素
// 数组中第一个小于10的元素是3,故返回3
int result1 = Array.Find(arr, new Predicate<int>(FindCallback));
Console.WriteLine("Find方法查找小于10的元素:{0}", result1);

// FindLast方法返回匹配元素的最后一项
// 数组中最后一个小于10的元素是9,故返回9
int result2 = Array.FindLast(arr, new Predicate<int>(FindCallback));
Console.WriteLine("FindLast方法查找小于10的元素:{0}", result2);

// FindAll方法返回所有匹配的元素
// 数组中3、6、9都小于10
// 因此,返回一个由3、6、9三个元素组成的数组
int[] result3 = Array.FindAll(arr, new Predicate<int>(FindCallback));
Console.WriteLine("\nFindAll方法查找所有小于10的元素:");
foreach (int x in result3)
{
Console.Write(x + " ");
}

private static bool FindCallback(int val)
{
if (val < 10)
return true;
return false;
}

var result = Array.FindIndex(ints,new Predicate<int>(x=>x==3));

反转数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 反转数组
Array.Reverse(arr);

// 反转后再次输出
Console.WriteLine("\n反转后数组的元素及次序:");
for (int n = 0; n < arr.Length; n++)
{
Console.WriteLine("[{0}] - {1}", n, arr[n]);
}
//反转数组
//Array.Reverse(xxx);

// 定义数组
byte[] arr = new byte[] { 5, 6, 7, 8 };
Console.WriteLine("反转前数组的元素及次序:");
for (int i = 0; i < arr.Length; i++)
{
Console.WriteLine("[{0}] - {1}", i, arr[i]);
}

更改数组大小

1
2
3
4
5
6
7
8
9
10
//更改数组的大小
public static void Resize<T>(ref T[] array,int newSize);//方法是通过替换旧数组修改数组的大小,哈希值是会不同的。
// 创建数组实例
int[] arr = new int[3];
// 修改大小前输出
Console.WriteLine("数组的大小:{0},哈希值为{1}", arr.Length, arr.GetHashCode());
// 修改数组的大小
Array.Resize<int>(ref arr, 7);
// 修改大小后再次输出
Console.WriteLine("数组的大小:{0},哈希值为{1}", arr.Length, arr.GetHashCode());

多维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System;

namespace ArrayApplication
{
class MyArray
{
static void Main(string[] args)
{
/* 一个带有 5 行 2 列的数组 */
int[,] a = new int[5, 2] {{0,0}, {1,2}, {2,4}, {3,6}, {4,8} };

int i, j;
char[,] arr2 = { { 'a', 'b', 'c', 'd' }, { 'e', 'f', 'g', 'h' }, { 'i', 'j', 'k', 'l' } };
// 获取数组的行数和列数
int rowCount = list.GetLength(0); // 获取行数,第一维度
int colCount = list.GetLength(1); // 获取列数,第二维度

/* 输出数组中每个元素的值 */
for (i = 0; i < 5; i++)
{
for (j = 0; j < 2; j++)
{
Console.WriteLine("a[{0},{1}] = {2}", i, j, a[i,j]);
}
}
Console.ReadKey();
}
}
}

交错数组 / 数组的数组 / 嵌套数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//交错数组是数组的数组。交错数组是一维数组

//使用 Length 属性可以快速获取数组的总元素数量,适用于所有类型的数组。
//使用 GetLength 方法可以获取多维数组的特定维度的长度,更加精确。
int[][] arr = new int[2][]
{
new int[]{ 10,20,30},
new int[]{ 40,50,60,70}
};
for (int i = 0; i < arr.Length;i++)
{
//arr[i].GetLength(0)
for (int j = 0; j < arr[i].Length; j++)
Console.WriteLine("{0},{1} = {2}", i,j, arr[i][j]);
Console.WriteLine(" ");
}
Console.WriteLine(arr.Rank);//1

// 三层嵌套的数组
int[][][] ints = new int[3][][] //第一层
{
new int[2][] //第二层
{
new int[] { 20, 32, 2 }, //第三层
new int[] { 1, 11, 29, 6 } //第三层
},
new int[2][] //第二层
{
new int[] { 27, 26, 17 }, //第三层
new int[] { 199 } //第三层
},
new int[2][] //第二层
{
new int[] { 40, 74, 81 }, //第三层
new int[] { 120, 95 } //第三层
}
};
// 输出数组
Console.WriteLine(ints.GetType().Name);
for (int i = 0; i < ints.Length; i++) //第一层
{
Console.WriteLine(" {0}", ints[i].GetType().Name);
for (int j = 0; j < ints[i].Length; j++) //第二层
{
Console.WriteLine(" {0}", ints[i][j].GetType().Name);
Console.Write(" ");
for (int k = 0; k < ints[i][j].Length; k++) //第三层
{
Console.Write("{0} ", ints[i][j][k]);
}
Console.WriteLine();
}
}

Array 类

1
2
3
4
5
6
7
8
int[] list = { 34, 72, 13, 44, 25, 30, 10 };

Console.Write("原始数组: ");
foreach (int i in list)
{
Console.Write(i + " ");
}
Console.Write();

动态数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 //初始为4,以后成倍递增
ArrayList arrayList = new ArrayList();
arrayList.Add(1);
arrayList.Add(2);
arrayList.Add(3);
arrayList.Add(4);
arrayList.Add(5);
Console.WriteLine($"{arrayList.Capacity},{arrayList.Count}");//8 5


//RemoveAt,Remove
ArrayList list = new ArrayList();
// 添加int类型对象
list.Add(100);
// 添加double类型对象
list.Add(20.977d);
// 添加string类型对象
list.Add("hello");
// 添加long类型对象
list.Add(9800000L);

// 可以通过索引取得元素
// 要进行类型转换
// 取出来的元素的类型要与放入时对应
Console.WriteLine("[0] - {0}", (int)list[0]);
Console.WriteLine("[3] - {0}", (long)list[3]);
// 输出元素总个数
Console.WriteLine("元素个数:{0}", list.Count);
// 删除最后一个元素
list.RemoveAt(list.Count - 1);
// 删除后再次输出元素个数

结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

//结构是值类型(System.ValueType派生),类是引用类型(System.Object派生)
struct Test_Struct{
public int x;
/*
public Test_Struct(){//不能有无参构造
}
*/
public Test_Struct(int value){
x = value;
}
// 结构不能继承、派生,但可以实现接口
//struct Sub_Test_Struct:Test_Struct{}
}

//字段不能设定初始值
//可以忽略new运算符,但是忽略后只能使用字段,其他无法使用;类不可以忽略new
struct Teacher
{
public int _age = 20;
public int Id { get; set; }
public string Name { get; set; }
public void Read()
{
Console.WriteLine("read book");
}
public Teacher()
{
Console.WriteLine("hello,teacher");
}
}
Teacher t1;
t1._age = 20;
t1.Name = "30";
//t1.Read();
Console.WriteLine($"{t1._age}");

枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//枚举只支持这几类:int、uint、byte、sbyte、short、ushort、long、ulong(其实也就是int、byte、long、short这四类),默认是int类型

enum longEnum:Long {L1}//sizeof(longEnum)为8个字节

enum Day { Sun, Mon, Tue, Wed, Thu, Fri, Sat };//枚举得在主函数外部
static void Main(string[] args){
dynamic s = Convert.ToInt32(day.tueday);
Console.WriteLine(s);
}

//获取枚举的值类型,Enum为基类,
enum Test : ushort
{
Value1 = 100,
Value2 = 101,
Value3 = 103
}

//GetValues方法返回的数组类型不是固定的,是动态决定的,使用var关键字

//public static Array GetValues(Type enumType);
var values = Enum.GetValues(typeof(Test));
// 输出这些枚举值
foreach (ushort v in values)
{
Console.Write(v + "\t");
}

//获取枚举中各成员的名称
//两个方法:Enum.GetName,Enum.GetNames
//public static string? GetName(Type enumType, object value);
//public static string[] GetNames(Type enumType);

var name = Enum.GetName(typeof(Test),Test.Value1);
Console.WriteLine(name);

var names = Enum.GetNames(typeof(Test));
foreach (var item in names)
{
Console.Write(item + "\t");
}

//枚举的位运算
//首先需要Flag特性,包含用&,去掉先用~然后再与运算.
[Flags]
enum Test
{
/// <summary>
/// 二进制0
/// </summary>
None = 0,
/// <summary>
/// 二进制1
/// </summary>
Music = 1,
/// <summary>
/// 二进制10
/// </summary>
Video = 2,
/// <summary>
/// 二进制100
/// </summary>
Text = 4
}
// 变量v相当于二进制111
Test v = Test.Music | Test.Text | Test.Video;
// 检查变量v中是否包含Text
if ((v & Test.Text) == Test.Text)
{
Console.WriteLine("变量v中包含了Text。");
}
else
{
Console.WriteLine("变量v中不包含Text。");
}
// 从v中去掉Music
v = v & ~Test.Music;
// 检查一下是否还含有Music
if ((v & Test.Music) == Test.Music)
{
Console.WriteLine("变量v中仍含有Music的值。");
}
else
{
Console.WriteLine("变量v中不包含Music的值。");
}

Console.Read();

//如果用Flag特性的话,转换为带有Flag特性的可以转换,不带Flag特性的话是原值7

// 001 | 010 | 100 == 111,十进制值为7
int testValue = 7;
/*
* 无法从组合的值中识别出A枚举
*/
Console.WriteLine((A)testValue);
/*
* 可以从组合数值中识别出B枚举的值
*/
Console.WriteLine((B)testValue);

常用 API

StringBuilder

1
2
3
4
5
6
7
8
9
/*
StringBuilder对象维护缓冲区以容纳对字符串的扩展。
如果空间可用,则会将新数据追加到缓冲区;否则,将分配一个新的更大的缓冲区,将原始缓冲区中的数据复制到新缓冲区,然后将新数据追加到新缓冲区。
最大的容量是INT类型的最大值
*/

StringBuilder stringBuilder = new StringBuilder("abc", 40);
stringBuilder.Append(new char[] { 'D','E','F'});
Console.WriteLine(stringBuilder);//abcDEF

正则表达式(需要再次学习)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/*
常用方法
Regex.Match: 查找字符串中第一个匹配的正则表达式。
Regex.Matches: 查找字符串中所有匹配的正则表达式,返回一个 MatchCollection。
Regex.Replace: 替换字符串中匹配的正则表达式。
Regex.IsMatch: 检查字符串是否与正则表达式匹配。
*/

using System;
using System.Text.RegularExpressions;

class Program
{
static void Main()
{
// 测试手机号
string phoneNumber = "13812345678"; // 示例手机号
if (IsValidPhoneNumber(phoneNumber))
{
Console.WriteLine($"{phoneNumber} 是有效的手机号。");
}
else
{
Console.WriteLine($"{phoneNumber} 不是有效的手机号。");
}

// 测试游戏账号
string gameAccount = "Player123"; // 示例游戏账号
if (IsValidGameAccount(gameAccount))
{
Console.WriteLine($"{gameAccount} 是有效的游戏账号。");
}
else
{
Console.WriteLine($"{gameAccount} 不是有效的游戏账号。");
}
}

// 验证手机号
static bool IsValidPhoneNumber(string phoneNumber)
{
// 中国大陆手机号正则表达式
string pattern = @"^1[3-9]\d{9}$";
return Regex.IsMatch(phoneNumber, pattern);
}

// 验证游戏账号
static bool IsValidGameAccount(string gameAccount)
{
// 游戏账号正则表达式(字母开头,长度6-16,允许字母、数字和下划线)
string pattern = @"^[a-zA-Z][a-zA-Z0-9_]{5,15}$";
return Regex.IsMatch(gameAccount, pattern);
}
}


string input = "1851 1999 1950 1905 2003";
string pattern = @"(?<=19)\d{2}\b";

foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(match.Value);



using System;
using System.Text.RegularExpressions;

namespace RegExApplication
{
class Program
{
private static void showMatch(string text, string expr)
{
Console.WriteLine("The Expression: " + expr);
MatchCollection mc = Regex.Matches(text, expr);
foreach (Match m in mc)
{
Console.WriteLine(m);
}
}
static void Main(string[] args)
{
string str = "A Thousand Splendid Suns";

Console.WriteLine("Matching words that start with 'S': ");
showMatch(str, @"\bS\S*");
Console.ReadKey();
}
}
}


using System;
using System.Text.RegularExpressions;

namespace RegExApplication
{
class Program
{
private static void showMatch(string text, string expr)
{
Console.WriteLine("The Expression: " + expr);
MatchCollection mc = Regex.Matches(text, expr);
foreach (Match m in mc)
{
Console.WriteLine(m);
}
}
static void Main(string[] args)
{
string str = "make maze and manage to measure it";

Console.WriteLine("Matching words start with 'm' and ends with 'e':");
showMatch(str, @"\bm\S*e\b");
Console.ReadKey();
}
}
}


using System;
using System.Text.RegularExpressions;

namespace RegExApplication
{
class Program
{
static void Main(string[] args)
{
string input = "Hello World ";
string pattern = "\\s+";
string replacement = " ";
Regex rgx = new Regex(pattern);
string result = rgx.Replace(input, replacement);

Console.WriteLine("Original String: {0}", input);
Console.WriteLine("Replacement String: {0}", result);
Console.ReadKey();
}
}
}

格式化字符串

1
2
3
4
5
6
7
8
9
10
11
     int a = 30;
int b = 49;
dynamic result = string.Format("{0},{1}", a, b);
Console.WriteLine(result);

MyStruct my = new MyStruct();
my.X = 20;
Console.WriteLine(string.Format($"{my.X}"));
Console.WriteLine(string.Format("{0}",my.X));
Console.WriteLine("{0}",my.X);
Console.WriteLine($"{my.X}");

补充知识

泛型

1
2
3
4
5
6
7
8
       List<int> list = new List<int>();
list.Add(1);
list.Add(2);
list.Add(3);
foreach(int i in list)
{
Console.WriteLine(i);
};

顶级语句

注意点:
  • 一个项目中,只允许一个代码文件使用顶层语句。
  • 由于顶层语句在编译时自动生成入口点方法 Main,所以在同一个项目中不能再选择其他入口点方法(即编译器选项中不能使用 /main 或 - main 开头)
1
2
3
using System
int age = 20;
Console.WriteLine(age);

record

record 与 class 的区别在于相等比较的计算不同,如果相同的属性 class 在相等比较运算符上会给出 false, 而 record 会给出 true.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
record bird(string name,string sex) { }
static void Main(string[] args)
{
bird bird = new bird("小黄","公");
//这种的只能在完整版里才可以
bird bird = new bird{
name = "小黄鸭"
sex = "男";
}
}

public record Person
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
Person p1 = new();
p1.Id = 1;
p1.Name = "dog";
p1.Age = 20;

Person p2 = new();
p2.Id = 1;
p2.Name = "dog";
p2.Age = 20;

if (p1 == p2)//record会自动计算,会给出true,而class会给出false
{
Console.WriteLine("是同一个人");
}
else {
Console.WriteLine("不是同一个人");
}

Global

​ global using System;

异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
       //得先抛出throw才能进行捕获
class DivideByZeroCustomException : Exception
{
public DivideByZeroCustomException(string message) : base(message)
{
}
}
static void Main(string[] args)
{

try
{
int number = Convert.ToInt32(Console.ReadLine());
int shuzi = Convert.ToInt32(Console.ReadLine());
if (shuzi == 0)
throw (new DivideByZeroCustomException("爆零异常"));
int result = number / shuzi;//这个只能在没有错误的情况下才能运行,放前面会有错误
Console.WriteLine(result);
}
catch (DivideByZeroCustomException e)
{
Console.WriteLine(e.Message);
}

finally{
Console.WriteLine("请重新输入");
};
}




//when关键字后面是异常的筛选条件
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyApp
{
class Program
{
static void Main(string[] args)
{
try
{
//DoSomething(null, null);
DoSomething("abc", null);
}
catch (ArgumentException ex) when (ex.ParamName == "y")
{
Console.WriteLine(ex.Message);
}
catch
{
Console.WriteLine("其他异常信息。");
}

Console.Read();
}

static void DoSomething(string x, string y)
{
if (string.IsNullOrWhiteSpace(x))
{
throw new ArgumentException("参数不能为空。", "x");
}
if (string.IsNullOrWhiteSpace(y))
{
throw new ArgumentException("参数不能为空。", "y");
}
}
}

}

命名空间

作用:避免名称冲突

不是本项目的需要先引用目标命名空间所在程序集

NameSpace

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using System;
namespace first_space
{
class namespace_cl
{
public void func()
{
Console.WriteLine("Inside first_space");
}
}
}
namespace second_space
{
class namespace_cl
{
public void func()
{
Console.WriteLine("Inside second_space");
}
}
}
class TestClass
{
static void Main(string[] args)
{
first_space.namespace_cl fc = new first_space.namespace_cl();
second_space.namespace_cl sc = new second_space.namespace_cl();
fc.func();
sc.func();
Console.ReadKey();
}
}

using

  1. 起别名

    1
    2
    using p1 = System.Math;
    var v1 = p1.PI;
  2. 引入命名空间

  3. 释放空间

  4. 静态成员访问

    1
    2
    3
    4
    5
    using static System.Math;
    var v1 = PI;

    using static System.Console;
    WriteLine();//只能放为静态成员,实例成员无法访问

预处理器指令

  • #region #endregion

  • #warning 和 #error

  • #lines

  • #pragma

文件的输入和输出

1
2
3
4
5
6
7
FileStream fileStream = new FileStream("C://Users//Administrator//Desktop//test.text", FileMode.Create, FileAccess.ReadWrite, FileShare.Write);
for (int i = 1; i <= 10; i++)
fileStream.WriteByte((byte)i);
fileStream.Position = 0;//要重0开始不然会有问题
for (int i = 0; i <= 10; i++)
Console.WriteLine(fileStream.ReadByte() + " ");
fileStream.Close();

不安全代码

设置

image-20250419194945461

使用指针检索数据值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;
namespace UnsafeCodeApplication
{
class Program
{
public static void Main()
{
unsafe
{
int var = 20;
int* p = &var;
Console.WriteLine("Data is: {0} " , var);
//使用 ToString() 方法检索存储在指针变量所引用位置的数据
Console.WriteLine("Data is: {0} " , p->ToString());
Console.WriteLine("Address is: {0} " , (int)p);
}
Console.ReadKey();
}
}
}

传递指针作为方法的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using System;
namespace UnsafeCodeApplication
{
class TestPointer
{
public unsafe void swap(int* p, int *q)
{
int temp = *p;
*p = *q;
*q = temp;
}

public unsafe static void Main()
{
TestPointer p = new TestPointer();
int var1 = 10;
int var2 = 20;
int* x = &var1;
int* y = &var2;

Console.WriteLine("Before Swap: var1:{0}, var2: {1}", var1, var2);
p.swap(x, y);

Console.WriteLine("After Swap: var1:{0}, var2: {1}", var1, var2);
Console.ReadKey();
}
}
}

使用指针访问数组元素

fixed (int *ptr = list), 指针变量在数组中内存固定,其余地方不固定,需要使用 fixed () 来固定

C# 中声明的变量在内存中的存储受垃圾回收器管理;因此一个变量(例如一个大数组)有可能在运行过程中被移动到内存中的其他位置。如果一个变量的内存地址会变化,那么指针也就没有意义了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using System;
namespace UnsafeCodeApplication
{
class TestPointer
{
//不安全代码使用unsafe
//*(ptr+i),(int)(ptr+i)--输出地址
/*
指针变量 p,因为它在内存中不是固定的,但是数组地址在内存中是固定的,所以您不能增加数组 p。
需要使用指针变量访问数组数据,使用 fixed 关键字来固定指针
*/
public unsafe static void Main()
{
int[] list = {10, 100, 200};
//不用加分号
fixed(int *ptr = list)

/* 显示指针中数组地址 */
for ( int i = 0; i < 3; i++)
{
Console.WriteLine("Address of list[{0}]={1}",i,(int)(ptr + i));
Console.WriteLine("Value of list[{0}]={1}", i, *(ptr + i));
}
Console.ReadKey();
}
}
}

// 使用 stackalloc 在栈上分配一个 int 数组
int* stackArray = stackalloc int[5];

// 初始化 stackArray
for (int i = 0; i < 5; i++)
{
stackArray[i] = i + 1; // 将值 1, 2, 3, 4, 5 存储到 stackArray 中
}
// 输出 stackArray 的值
Console.WriteLine("Values from stackArray:");
for (int i = 0; i < 5; i++)
{
Console.WriteLine(stackArray[i]);
}

面向对象

基础使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
      class picture
{
public int x;
public int y;
public picture(int x, int y)
{
this.x = x;
this.y = y;
}
public void out_print()
{
Console.WriteLine("hello picture");
}
}
class circle:picture
{
public circle(int x,int y) : base(x, y) { }
public void subprint()
{
base.out_print();
}
}

//两种都可以
picture p1 = new picture(){};
picture p1 = new picture{};

//拷贝构造函数,只获取一个参数,该参数必须具有包容类型
//可以方便地克隆一个对象实例,创建内容一样的新实例
public Employee(Employee original){}

抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//抽象类是可以有非抽象成员和构造函数的,带有访问修饰符的;而接口是没有访问修饰符,默认是public,而且不能有具体的实现,它主要起到一种规范的作用,不能有构造函数。
class Animal
{
public virtual void Work()
{
Console.WriteLine("animal is work!");
}
}
abstract class Dog:Animal
{
public abstract override void Work();
}
class Cat:Dog
{
public override void Work()
{
Console.WriteLine("Cat is Work!");
}
}

密封类

1
2
3
4
5
6
7
8
9
//无法继承
sealed class school
{

}
class home : school
{

}

静态类

单例模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/*
1.阻止实例化private 类名(){}
2.声明一个对象 private static 类名 变量名 = null ---static是为了static构造函数内访问
3.static 类名 (){
变量名 = new 类名();
}
4.提供对外的访问接口,只能是get访问器
public static MyDbContext DbContext
{

get { return dbContext; }
}
*/
//单例实例化
class MyDbContext
{
//单例是指程序中只有一个对象
public string UserName { get; set; }
public string Password { get; set; }
private static MyDbContext dbContext = null;//先声明一个对象

//阻止别人去实例化
private MyDbContext()
{

}
//对外暴露一个出口
public static MyDbContext DbContext
{

get { return dbContext; }
}
static MyDbContext()
{
dbContext = new MyDbContext();
}
}

MyDbContext.DbContext.UserName = "王五";
MyDbContext.DbContext.Password = "123456";
静态构造方法
1
2
3
4
5
6
class point{
static readonly int age = 20;
static point{
Console.WriteLine("xxx");
}
}

只读字段

1
2
3
4
5
6
7
8
9
public readonly struct Test
{
//如果有初始值的化得配有构造函数才行
public readonly int id;
//public Test()
//{
// id = 20;
//}
}

字段和属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class test
{
string _name;//字段
int _id;
//只读属性可以使用lambda表达式来代替,或者只设置get属性就行.
public string Name =>"hello";
public string Name{get;set;}="hello";
public string Name{get;}


public string Name//属性
{
get { return _name; }//get和set里面的设置是字段并且为私有的,而对外的获取设置是属性而不是字段
set { _name = value; }
}
public int ID { get; set; }
public void print_1()
{
Console.WriteLine($"我叫:{_name},编号为{ID}");//tom 10如果_id则为0
}
}
//enum day { monday,tueday=2,thirsday,weekday};
static void Main(string[] args)
{
test t1 = new test();
t1.Name = "tom";
t1.ID = 10;
Console.WriteLine($"{t1.Name},{t1.ID}");
t1.print_1();
}

Init 初始化器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//Init初始化器可以赋值,但只能在构造方法里
/*point p1 = new point{
X = 33,
Y= 44
};

*/
Animal animal = new Animal()
{
X = 30,
Y = 40
};
public Animal(int x, int y) {
this.X = x;
this.Y = y;
}
public int X { get; init; }
public int Y { get; init; }



class init
{
public int x;
public int y;
public int X { get; init; }
public int Y { get; init; }
public init(int x,int y)
{
this.x = x;
this.y = y;
}
public int setValue(int a, int b)
{
x = a+10;
y = b+10;
return x + y;
}

}
public record point
{
public int X { get; init; }
public int Y { get; init; }
}
static void Main(string[] args)
{
init init = new init(10,20);
Console.WriteLine($"{init.x},{init.y}");
dynamic result = init.setValue(30, 40);
Console.WriteLine(result);
point p1 = new point
{
X = 33,
Y = 44
};
Console.WriteLine($"{p1.X},{p1.Y}");
}

封装

Protected

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//protected 的,只能在类内部或派生类中访问。
public class BaseClass
{
protected int protectedValue = 42;

protected void ProtectedMethod()
{
Console.WriteLine("Protected method in BaseClass");
}
}

public class DerivedClass : BaseClass
{
public void AccessProtectedMembers()
{
Console.WriteLine("Accessing protectedValue: " + protectedValue);
ProtectedMethod();
}
}

class Program
{
static void Main()
{
DerivedClass derived = new DerivedClass();
derived.AccessProtectedMembers();

// 以下代码将无法访问 protectedValue 和 ProtectedMethod
// Console.WriteLine(derived.protectedValue); // 错误
// derived.ProtectedMethod(); // 错误
}
}

Internal

一个项目就是一个程序集

可以通过创建类库,保护级别为 internal,然后在主函数的解决方案那边添加类库的引用,从而验证是错误的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
internal 修饰符表示该成员可以在同一程序集(Assembly)中的任何类中访问,
但不能从其他程序集访问。换句话说,internal 成员在同一项目中是可见的,
但在不同项目中不可见。
*/
internal class InternalClass
{
internal int internalValue = 100;

internal void InternalMethod()
{
Console.WriteLine("Internal method in InternalClass");
}
}

class Program
{
static void Main()
{
InternalClass internalClass = new InternalClass();
Console.WriteLine("Accessing internalValue: " + internalClass.internalValue);
internalClass.InternalMethod();
}
}

Protected Internal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//protected internal,表示该成员可以在同一程序集中的任何类中访问,或者在派生类中访问
public class BaseClass
{
protected internal int protectedInternalValue = 200;
}

public class DerivedClass : BaseClass
{
public void AccessProtectedInternal()
{
Console.WriteLine("Accessing protectedInternalValue: " + protectedInternalValue);
}
}

class Program
{
static void Main()
{
BaseClass baseClass = new BaseClass();
// 在同一程序集中的其他类可以访问 protectedInternalValue
Console.WriteLine("Accessing protectedInternalValue: " + baseClass.protectedInternalValue);

DerivedClass derived = new DerivedClass();
derived.AccessProtectedInternal();
}
}

多态

virtual 和 override、new

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 //new看左边,其余看右边
//new是用来隐藏基类的成员,默认B继承A的话,方法是选择B的,而不是A的,默认是会隐藏的。
class test{
public virtual void print_2()
{
Console.WriteLine("hello test!");
}
public void run()
{
Console.WriteLine("test is run");
}
}
class subTest():test
{
public override void print_2()
{
Console.WriteLine("hello subtest!");
}
public new void run()
{
Console.WriteLine("subtest is run");
}
}
static void Main(string[] args)
{
test t1 = new test();
t1.print_2();// hello test!
t1.run();//test is run
test t2 = new subTest();
t2.print_2();// hello subtest!
t2.run();//test is run
}

静态多态

方法重载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void sum(int a,int b)
{
Console.WriteLine(a+b);
}
public static void sum(double a, double b,double c)
{
Console.WriteLine(a + b + c);
}
static void Main(string[] args)
{
sum(10, 20);
sum(1.2, 3.4, 5.6);
}
//构造函数重载
public Teacher()
{
Console.WriteLine("hello,teacher");
}
public Teacher(string name)
{
Console.Write()
}
//ref,out重载,只能ref或者Out搭配一个普通传参,不能ref和out在一起
运算符重载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Person
{
public int age;
public int money;
public Person(int age, int money)
{
this.age = age;
this.money = money;
}
public static Person operator+(Person p1, Person p2)
{
Person p = new Person(40,60);
p.age = p1.age + p2.age;
p.money = p1.money + p2.money;
return p;
}
}
static void Main(string[] args)
{
Person p1 = new Person(20, 40);
Person p2 = new Person(30, 50);
Person p3 = p1 + p2;
Console.WriteLine("{0},{1}",p3.age,p3.money);
}

动态多态

1
2
3
4
5
6
7
8
/*
父类类型 对象名称 = new 子类构造器;
接口 对象名称 = new 实现类构造器;

动态多态性包括虚函数和抽象类实现
*/
Animal a = new Dog();
a.run(); // 后续业务行为随对象而变,后续代码无需修改

继承

派生类的可访问性不应该比基类高。这样会避免访问冲突。

定义接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//在定义接口的时候默认前边加上一个大写的I        
interface myface
{
void face();
}
//接口也可以有默认实现
public interface IRepository<T> where T:class
{
// 接口也可以有默认实现
int Add(T model)
{
Console.WriteLine("添加了一条数据");
return 1;
}
int Update(T model);
int Delete(dynamic id);
T GetModel(dynamic id);
IList<T> GetList(string condition);
}
class myclassface:myface
{
public void face()
{
Console.WriteLine("myface is green");
}
}
static void Main(string[] args)
{
myface myface = new myclassface();
myface.face();
}

接口继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//接口是干活的(做什么),类是下定义的,是什么
interface myinterface
{
void run();
}
interface mysubinterface:myinterface
{
void work();
}
class completeInterface : mysubinterface
{
public void run()
{
Console.WriteLine("interface is run");
}
public void work()
{
Console.WriteLine("interface is work");
}
}

显示接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
       //显示接口不能有Public,而且方法得是xxx.paint()这样,不能统一用Paint()
interface myinter
{
void paint();
}
interface mysubinter
{
void paint();
}
class mycomplete : myinter, mysubinter
{
public void paint()
{
Console.WriteLine("mycomplete is painting");
}
void myinter.paint()
{
Console.WriteLine("myinter is painting");
}
void mysubinter.paint()
{
Console.WriteLine("mysubinter is painting");
}
}
static void Main(string[] args)
{

//这样的方法虽然可行,但是创造了两个独立的实例,无法直接通过类来调用,最好的方式是创建类,对类进行转换来调用(因为有时候需要涉及到类的一些数据,比如私有字段,调用同一个实例不会造成数据的不统一。)
((myinter)mycomplete).paint();
((mysubinter)mycomplete).paint();

mycomplete mycomplete = new mycomplete();
mycomplete.paint();
myinter myinter = mycomplete;
myinter.paint();
mysubinter mysubinter = mycomplete;
mysubinter.paint();
/*
mycomplete is painting
myinter is painting
mysubinter is painting
*/
}

高级语法

单元测试

安装包以及引入的包

安装包
  1. NUnit
  2. Microsoft.NET.Test.Sdk
  3. NUnit3TestAdapter
引入包

​ using NUnit.Framework;

项目属性修改,不然会显示 Main 入口不对

基础使用

onetimesetup 不输出结果,断言.that (4,Is.equalto (2+3)) 错误会显示结果,正确没有结果。

test 的话需要先运行所有测试,之后才会有√进行单独的单元测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
namespace C__Senior_Study
{
[TestFixture]
internal class MyTest
{
[SetUp]
public void init()
{
Console.WriteLine("我正在运行");
}
[OneTimeSetUp]
public void onetime()
{
Console.WriteLine("我只运行一次");
}
int i = 0;
[Test]
public void run()
{
i++;
Console.WriteLine($"{i}正在running");
}
[Test]
public void Test_Assert()
{
Assert.That(4,Is.EqualTo(2+3));
try
{
int result = i / 0;
Console.WriteLine(result);
}
catch (Exception ex)
{
Assert.Fail($"{ex.Message}");
}
}
}
}

断言

作用:检验代码的执行是否符合预期的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//第一种方式

//下面代码用于禁用用户界面模式
System.Diagnostics.DefaultTraceListener listener = null;
// 从Debug.Listeners中取出DefaultTraceListener实例
listener = System.Diagnostics.Debug.Listeners[0] as System.Diagnostics.DefaultTraceListener;
if (listener != null)
{
// 禁用用户界面模式
listener.AssertUiEnabled = false;
}

//第二种方式
//修改应用程序的配置文件 <应用程序文件名>.config

使用日志文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//Debug版本
//Release版本,文件体积小效率高


/*

* 要输出调试信息,请打开项目属性窗口
* 切换到“生成”选项卡
* 在右侧用户界面中选中“定义DEBUG常量”
* 然后再重新运行项目

*/
Debug.WriteLine("\n***************************");
Debug.WriteLine("{0:T} 输出了调试信息。", DateTime.Now);
Debug.WriteLine("***************************\n");


DefaultTraceListener listener = null;
listener = Trace.Listeners[0] as DefaultTraceListener;
// 查找已有的 DefaultTraceListener
DefaultTraceListener defaultTraceListener = Trace.Listeners.OfType<DefaultTraceListener>().FirstOrDefault();
if (listener != null)
{
listener = new DefaultTraceListener();
Trace.Listeners.Add(listener);
}
listener.LogFileName = "example.log";

反射

GetMethod、GetConstructor 都与 invoke 有关

var Obj = assembly.CreateInstance(「MyStudyProcess_C#Senior.MyTest」,false);// 命名空间。类名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyStudyProcess_C_Senior
{
[AttributeUsage(AttributeTargets.All,AllowMultiple =true)]
internal class MyDescriptionAttribute:Attribute
{
public string Name { get; set; }
public string Description { get; set; }
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[MyDescription(Name ="这是一个学生类")]
internal class StudentInfo
{
private int _StudentId;//字段
public int ID { get; set; }//属性
public string Name { get; set; }
public int Age { get; set; }

//属性是对字段的封装
private int _CourseId;
public int CourseId
{
get
{
return _CourseId;
}
set
{
_CourseId = value;
}
}

public void Run()
{
Console.WriteLine($"我叫{Name},今年{Age},去晨跑");
}
private void Run2()
{
Console.WriteLine($"我叫{Name},是私有方法");
}

public StudentInfo()
{

}
public StudentInfo(string Name ,int Age)
{
this.Name = Name;
this.Age = Age;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
using NUnit.Framework; 


[Test]
public void ReflectTest()
{
//生成type类对象
var type = typeof(StudentInfo);
Console.WriteLine($"{type.Name},{type.FullName}");
StudentInfo studentInfo = new();
var type_1 = studentInfo.GetType() ;
Console.WriteLine($"{type_1.Name},{type_1.FullName}");

//获取属性
var properties = type.GetProperties();
foreach(var prop in properties)
{
Console.WriteLine($"{prop.Name},{prop.PropertyType}");
}
//操作属性
var stu = Activator.CreateInstance(type);
var Prop = type.GetProperty("Name");
Prop.SetValue(stu,"任我行");
var value = Prop.GetValue(stu);
Console.WriteLine(value);

//获取字段
var fields = type.GetFields(BindingFlags.Instance|BindingFlags.NonPublic);
foreach (var field in fields)
{
Console.WriteLine($"{field.Name},{field.FieldType}");
}
//操作字段
var Field = type.GetField("_StudentId",BindingFlags.Instance|BindingFlags.NonPublic);
Field.SetValue(stu, 10);
var Field_Value = Field.GetValue(stu);
Console.WriteLine(Field_Value);

//获取命名空间
var NameSpace = type.Namespace;
Console.WriteLine($"命名空间:{NameSpace}");
Console.WriteLine($"基类:{type.BaseType}");
Console.WriteLine($"测试委托是否是类:{typeof(Action).IsClass}");

//创建对象
var obj = Activator.CreateInstance(type) as StudentInfo;
Console.WriteLine($"{obj.ID},{obj.Name},{obj.Age}");

//得有构造函数
var obj_tp = Activator.CreateInstance(type,"万宏伟",27) as StudentInfo;
Console.WriteLine($"{obj_tp.ID},{obj_tp.Name},{obj_tp.Age}");

//无参的对象,私有构造函数不行
var Constructor = type.GetConstructor(Type.EmptyTypes);
var obj_1 = Constructor.Invoke(null);
Console.WriteLine($"{obj.ID},{obj.Name},{obj.Age}");

var ConstructorInfo = type.GetConstructor(new Type[] { }) ;
var obj_2 = Constructor.Invoke(null) as StudentInfo;
Console.WriteLine($"{obj_2.ID},{obj_2.Name},{obj_2.Age}");

//有参的对象
var Constructor_Info = type.GetConstructor(new Type[] { typeof(string),typeof(int)});
var obj_3 = Constructor_Info.Invoke(new object?[] { "万宏伟", 20 }) as StudentInfo;
Console.WriteLine($"{obj_3.ID},{obj_3.Name},{obj_3.Age}");

//操作方法
var methods = type.GetMethods(BindingFlags.Instance|BindingFlags.NonPublic|BindingFlags.Public);
foreach(var method in methods)
{
Console.WriteLine($"{method}");
}
//操作单个方法
var Method = type.GetMethod("Run2",BindingFlags.Instance|BindingFlags.NonPublic);
Method.Invoke(stu,null);
//Method.Invoke(stu,new Object?[]{30});

//获取程序集信息
var assembly = Assembly.Load("MyStudyProcess_C#Senior");
var assembly_1 = Assembly.LoadFile(@"E:\c#学习\MyStudyProcess_C#Senior\MyStudyProcess_C#Senior\bin\Debug\net8.0\MyStudyProcess_C#Senior.dll");
var Obj = assembly.CreateInstance("MyStudyProcess_C#Senior.MyTest",false);
var location = Assembly.GetExecutingAssembly().Location;
var File = Assembly.GetExecutingAssembly().GetName().Name;
var Version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
Console.WriteLine($"assembly:{assembly},assembly_1:{assembly_1},Obj:{Obj},location:{location},File:{File},Version:{Version}");

//操作特性
var desc = type.GetCustomAttribute<MyDescriptionAttribute>(false);
Console.WriteLine($"{desc.Name}");
}

扩展知识

扩展方法

自定义扩展方法

1、静态类

2、静态方法

3、参数列表前加上 this

扩展方法向现有类型 “添加” 方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。

扩展方法是一种静态方法,但可以像扩展类型上的实例方法一样进行调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
     //自定义扩展方法
internal static class ExtendMethod
{
public static int ParseInt(this string str)
{
if (String.IsNullOrEmpty(str))
return 0;
int result = 0;
if (!int.TryParse(str, out result))
return 0;
return result;
}
}

[Test]
public void ExtendTest()
{
string str = "123";
Console.WriteLine(str.ParseInt());
int[] ints = { 1, 2, 3 };
var result = ints.OrderBy(g=>g);
foreach (var item in result)
{
Console.WriteLine(item);
}
}

[AttributeUsage(AttributeTargets.All)]
public class TypeInfoAttribute : Attribute
{
public string Description { get; set; }
}

[TypeInfo(Description = "这是我们定义的枚举类型。")]
enum TestEnum { One = 1, Two, Three }

[TypeInfo(Description = "这是我们定义的一个类。")]
public class Goods { }

class Program
{
static void Main(string[] args)
{
// 用Type类的GetCustomAttributes方法可以获取指定类型上附加的特性列表
// 返回一个object类型的数组,数组中的每个元素表示一个特性类的实例
// GetCustomAttributes方法的其中一个重载可以将一个Type作为参数传递
// 该Type表示要获取的特性的类型,typeof运算符返回某个类型的一个Type
// 本例中我们要获取TypeInfoAttribute特性列表
// 由于上面定义TestEnum枚举和Goods类时,只应用了一个TypeInfoAttribute特性
// 因此获取到的特性实例数组的元素个数总为1

object[] attrs = typeof(TestEnum).GetCustomAttributes(typeof(TypeInfoAttribute), false);
if (attrs.Length > 0)
{
TypeInfoAttribute ti = (TypeInfoAttribute)attrs[0];
Console.WriteLine("TestEnum枚举的描述信息:{0}", ti.Description);
}

attrs = typeof(Goods).GetCustomAttributes(typeof(TypeInfoAttribute), false);
if (attrs.Length > 0)
{
TypeInfoAttribute ti = (TypeInfoAttribute)attrs[0];
Console.WriteLine("Goods类的描述信息:{0}", ti.Description);
}

Console.Read();
}
}

#region 扩展方法
public static class ObjectToStringExt
{
public static string ObjToStr<T>(this T obj)
{
string ret_str = string.Empty;
// 使用反射技术获取属性信息
System.Reflection.PropertyInfo[] props = obj.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
// 循环取得每个属性的信息
foreach (System.Reflection.PropertyInfo p in props)
{
try
{
string propName = p.Name; //属性名
object propValue = p.GetValue(obj); //属性值
string propValStr = "";
if (propValue != null)
{
propValStr = propValue.ToString();
}
ret_str += propName + " : " + propValStr + "\n";
}
catch { continue; /* 如果发生错误,则跳过本次循环 */ }
}
ret_str = "类型" + obj.GetType().Name + "的相关信息:\n" + ret_str;
return ret_str; // 返回结果
}
}
#endregion

特性

自定义特性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//特性是附加在类型上的一些扩展信息
[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Struct)]
public class AuthorAttribute : System.Attribute
{
private string name;
public double version;
public AuthorAttribute(string name)
{
this.name = name;
version = 1.0;
}
}

[Author("P. Ackerman", version = 1.1)]
class SampleClass
{
// P. Ackerman's code goes here...
}
[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Struct,
AllowMultiple = true) // multiuse attribute]
public class AuthorAttribute : System.Attribute

[Author("P. Ackerman", version = 1.1)]
[Author("R. Koch", version = 1.2)]
class SampleClass
{
// P. Ackerman's code goes here...
// R. Koch's code goes here...
}

//return只能这样设置,但是参数只需要前面添加即可
[return:MarshalAs(UnmanagedType.SysInt)]
public static int GetValue([In]int a,[Optional]int b) { return 0; }
预定义特性

.Net 框架提供了三种预定义特性:

  • AttributeUsage
  • Conditional–条件,#define xxx
  • Obsolete–可以用旧的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public readonly string Url;


[Conditional("DEBUG")]
public static void Message(string msg)
{
Console.WriteLine(msg);
}
//这个预定义特性标记了不应被使用的程序实体。它可以让您通知编译器丢弃某个特定的目标元素。例如,当一个新方法被用在一个类中,但是您仍然想要保持类中的旧方法,您可以通过显示一个应该使用新方法,而不是旧方法的消息,来把它标记为 obsolete(过时的)。
//大白话就是给一个提示告诉你改用什么新东西来代替
[Obsolete("Don't use OldMethod, use NewMethod instead", true)]
static void OldMethod()
{
Console.WriteLine("It is the old method");
}


#define BUG//这个只能放在第一排,加上这个相当于条件编译,选择对应的BUG的特性运行
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.Threading;
using System.Diagnostics;


namespace helloworld
{
public class Myclass
{
[Conditional("BUG")]
public static void M1(string msg)
{
Console.WriteLine(msg);
}
[Conditional("deBUG")]
public static void M2(string msg)
{
Console.WriteLine(msg);
}
[Conditional("BUG")]
public static void M3(string msg)
{
Console.WriteLine(msg);
}
[Conditional("kk")]
public static void M4(string msg)
{
Console.WriteLine(msg);
}
}
class Program
{
static void Main(string[] args)
{
Myclass.M1("456");
Myclass.M2("845");
Myclass.M3("dadasd");
Myclass.M4("456xcvc");
Console.WriteLine("Main thread ID is:" );
Console.ReadKey();
}
}
}

创建并使用自定义特性包含四个步骤:

  • 声明自定义特性
  • 构建自定义特性
  • 在目标程序元素上应用自定义特性
  • 通过反射访问特性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 一个自定义特性 BugFix 被赋给类及其成员
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]


[DeBugInfo(55, "Zara Ali", "19/10/2012",
Message = "Return type mismatch")]
public double GetArea()
{
return length * width;
}
[DeBugInfo(56, "Zara Ali", "19/10/2012")]
public void Display()
{
Console.WriteLine("Length: {0}", length);
Console.WriteLine("Width: {0}", width);
Console.WriteLine("Area: {0}", GetArea());
}

索引器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
using System;
namespace IndexerApplication
{
class IndexedNames
{
/*
private string[] namelist = new string[size];
static public int size = 10;
public IndexedNames()
{
for (int i = 0; i < size; i++)
{
namelist[i] = "N. A.";
}
}*/
private string[] list;
public int size;
public IndexedNames(int size){
this.size = size;
list = new string[size];
for (int i = 0; i < size; i++)
{
namelist[i] = "N. A.";
}
}
public string this[int index]
{
get
{
string tmp;

if( index >= 0 && index <= size-1 )
{
tmp = namelist[index];
}
else
{
tmp = "";
}

return ( tmp );
}
set
{
if( index >= 0 && index <= size-1 )
{
namelist[index] = value;
}
}
}
public int this[string name]
{
get
{
int index = 0;
while(index < size)
{
if (namelist[index] == name)
{
return index;
}
index++;
}
return index;
}

}

static void Main(string[] args)
{
IndexedNames names = new IndexedNames();
names[0] = "Zara";
names[1] = "Riz";
names[2] = "Nuha";
names[3] = "Asif";
names[4] = "Davinder";
names[5] = "Sunil";
names[6] = "Rubic";
// 使用带有 int 参数的第一个索引器
for (int i = 0; i < IndexedNames.size; i++)
{
Console.WriteLine(names[i]);
}
// 使用带有 string 参数的第二个索引器
Console.WriteLine(names["Nuha"]);
Console.ReadKey();
}
}
}

序列化

二进制序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

using CommonLib;

namespace AppWriteObject
{
public partial class Form1 : Form
{
SaveFileDialog saveDlg = null;
public Form1()
{
InitializeComponent();
saveDlg = new SaveFileDialog();
saveDlg.Filter = "bin文件(*.bin)|*.bin";
}

private void button1_Click(object sender, EventArgs e)
{
if (DialogResult.OK == saveDlg.ShowDialog())
{
textBox1.Text = saveDlg.FileName;
}
}

private void btnSave_Click(object sender, EventArgs e)
{
// 创建Person类的实例
Person ps = new Person();
// 为属性赋值
ps.Name = txtName.Text;
ps.No = Convert.ToInt32(upnNo.Value);
ps.Birthday = dateTimePicker1.Value;
// 将对象进行序列化
FileStream outStream = null;
bool isDone = false;
try
{
outStream = File.OpenWrite(textBox1.Text);
// 实例化BinaryFormatter
BinaryFormatter formatter = new BinaryFormatter();
// 序列化
formatter.Serialize(outStream, ps);
isDone = true;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
// 关闭文件流
if (outStream != null)
outStream.Close();
}
if (isDone)
{
MessageBox.Show("序列化已经完成。");
}
}
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

using CommonLib;

namespace AppReadObject
{
public partial class Form1 : Form
{
OpenFileDialog openDlg = null;
public Form1()
{
InitializeComponent();
openDlg = new OpenFileDialog();
openDlg.Filter = "bin文件(*.bin)|*.bin";
}

private void button1_Click(object sender, EventArgs e)
{
if (DialogResult.OK == openDlg.ShowDialog())
{
textBox1.Text = openDlg.FileName;
}
}

private void button2_Click(object sender, EventArgs e)
{
FileStream inStream = null;
Person man = null;
try
{
inStream = File.OpenRead(textBox1.Text);
BinaryFormatter bf = new BinaryFormatter();
// 反序列化
man = (Person)bf.Deserialize(inStream);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
// 关闭文件流
if (inStream != null)
{
inStream.Close();
}
}
if (man != null)
{
this.lblName.Text = man.Name;
this.lblNo.Text = man.No.ToString();
this.lblBirthdt.Text = man.Birthday.ToLongDateString();
}
}
}
}

XML 序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.IO;
using System.Xml.Serialization;

namespace MyApp
{
class Program
{
static void Main(string[] args)
{
MemoryStream ms = new MemoryStream();
// 创建Student实例
Student stu = new Student();
// 向属性赋值
stu.No = 100;
stu.Name = "小钟";
stu.City = "重庆";
// 序列化为XML文档
XmlSerializer xsz = new XmlSerializer(typeof(Student));
xsz.Serialize(ms, stu);
xsz = null;

// 输出XML文档
Console.WriteLine("序列化后的XML文档如下:");
string xml = Encoding.UTF8.GetString(ms.ToArray());
Console.WriteLine(xml);

// 进行反序列化
Console.WriteLine("\n=================================================");
// 将读写指针移到流的开始位置
ms.Position = 0L;
xsz = new XmlSerializer(typeof(Student));
// 还原对象状态
Student stu2 = (Student)xsz.Deserialize(ms);
// 输出反序列化后的对象实例中各属性的值
Console.WriteLine("学员编号:{0}\n学员姓名:{1}\n城市:{2}", stu2.No, stu2.Name, stu2.City);

Console.Read();
}
}


public class Student
{
/// <summary>
/// 学员姓名
/// </summary>
public string Name { get; set; }

/// <summary>
/// 学员编号
/// </summary>
public int No { get; set; }

/// <summary>
/// 城市
/// </summary>
public string City { get; set; }
}
}

自定义 xml 文档的节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Xml.Serialization;

namespace MyApp
{
class Program
{
static void Main(string[] args)
{
System.IO.MemoryStream ms = new System.IO.MemoryStream();
// 创建Product类的实例
Product prd = new Product();
// 为属性赋值
prd.ProdName = "样品";
prd.ProdSize = 69.771d;
// XML序列化
XmlSerializer xmllzer = new XmlSerializer(typeof(Product));
xmllzer.Serialize(ms, prd);
// 输出XML文档
Console.WriteLine("序列化后生成的XML文档如下:");
string xml = Encoding.UTF8.GetString(ms.ToArray());
Console.WriteLine(xml);

// 反序列化,并用TaskItem类来还原
ms.Position = 0L;
xmllzer = new XmlSerializer(typeof(TaskItem));
TaskItem titem = (TaskItem)xmllzer.Deserialize(ms);
// 输出反序列化后的结果
Console.WriteLine("\n===========================================");
if (titem != null)
{
Console.WriteLine("反序列化得到的TaskItem实例信息:");
string msg = string.Format("任务名称:{0}\n尺寸:{1}", titem.ItemName, titem.ItemSize);
Console.WriteLine(msg);
}
// 关闭流
ms.Close();

Console.Read();
}
}

[XmlRoot("info")]
public class Product
{
[XmlElement("name")]
public string ProdName { get; set; }

[XmlElement("size")]
public double ProdSize { get; set; }
}

[XmlRoot("info")]
public class TaskItem
{
[XmlElement("name")]
public string ItemName { get; set; }

[XmlElement("size")]
public double ItemSize { get; set; }
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Xml.Serialization;

namespace MyApp
{
class Program
{
static void Main(string[] args)
{
MemoryStream ms = new MemoryStream();
// 创建Product实例
Product prd = new Product
{
ProdName = "样品",
ProdSize = 123.5775d
};
// XML序列化
XmlSerializer xmlslz = new XmlSerializer(typeof(Product));
xmlslz.Serialize(ms, prd);
// 显示生成的XML文档
string xmldoc = Encoding.UTF8.GetString(ms.ToArray());
ms.Close(); //关闭流
Console.WriteLine(xmldoc);

Console.Read();
}
}

[XmlRoot("info")]
public class Product
{
[XmlAttribute("name")]
public string ProdName { get; set; }

[XmlAttribute("size")]
public double ProdSize { get; set; }
}
}

数据协定

数据交互过程对象不一定相同类型,必须遵循数据协定来定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.IO;
using System.Runtime.Serialization;

namespace MyApp
{
class Program
{
static void Main(string[] args)
{
using (MemoryStream ms = new MemoryStream())
{
// 实例化TestDemo对象
TestDemo td = new TestDemo();
// 为成员赋值
td.Value1 = 199;
td.Value2 = 30000L;
td.Value3 = 500.375d;
// 序列化
DataContractSerializer dtc = new DataContractSerializer(typeof(TestDemo));
dtc.WriteObject(ms, td);
// 显示序列化后生成的XML文档
string xml = Encoding.UTF8.GetString(ms.ToArray());
Console.Write("序列化生成的文档如下:\n");
Console.WriteLine(xml);

Console.WriteLine("================================");
// 反序列化
// 用Sample类型反序列化
ms.Position = 0L;
dtc = new DataContractSerializer(typeof(Sample));
Sample sap = (Sample)dtc.ReadObject(ms);
if (sap != null)
{
Console.WriteLine("类型 - Sample,反序列化结果:");
Console.WriteLine("Number1 = {0}\nNumber2 = {1}\nNumber3 = {2}", sap.Number1, sap.Number2, sap.Number3);
}
// 用Task类型来反序列化
ms.Position = 0L;
dtc = new DataContractSerializer(typeof(Task));
Task tk = (Task)dtc.ReadObject(ms);
if (tk != null)
{
Console.WriteLine("\n类型 - Task,反序列化结果:");
Console.WriteLine("Base1 = {0}\nBase2 = {1}\nBase3 = {2}", tk.Base1, tk.Base2, tk.Base3);
}
}

Console.Read();
}
}

#region 数据协定
[DataContract(Name = "Sample", Namespace = "http://sample")]
public class TestDemo
{
[DataMember(Name = "Number1")]
public short Value1 = 0;
[DataMember(Name = "Number2")]
public long Value2 = 0L;
[DataMember(Name = "Number3")]
public double Value3 = 0d;
}
#endregion

#region 类型
/// <summary>
/// 该类的类名与数据协定的名字相同,公共属性的名字与协定成员的名称相同,因此可以进行反序列化
/// </summary>
[DataContract(Namespace = "http://sample")]
public class Sample
{
[DataMember]
public short Number1 { get; set; }
[DataMember]
public long Number2 { get; set; }
[DataMember]
public double Number3 { get; set; }
}

/// <summary>
/// 该类虽然类型名称和成员名称与TestDemo不同,但是它的协定名称以及类成员的数据类型都与TestDemo类相同,所以可被反序列化
/// </summary>
[DataContract(Name = "Sample", Namespace = "http://sample")]
public class Task
{
[DataMember(Name = "Number1")]
public short Base1;
[DataMember(Name = "Number2")]
public long Base2;
[DataMember(Name = "Number3")]
public double Base3;
}

#endregion
}

JSon 序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;

namespace MyApp
{
class Program
{
static void Main(string[] args)
{
using (MemoryStream mostream = new MemoryStream())
{
DemoA da = new DemoA { StringA = "test1", StringB = "test2" };
// 序列化
DataContractJsonSerializer djs = new DataContractJsonSerializer(typeof(DemoA));
djs.WriteObject(mostream, da);
// 显示JSON对象
Console.WriteLine("序列化生成的JSON对象:");
string json = Encoding.UTF8.GetString(mostream.ToArray());
Console.WriteLine(json);
// 反序列化
Console.WriteLine("======================");
mostream.Position = 0L;
djs = new DataContractJsonSerializer(typeof(DemoB));
DemoB db = (DemoB)djs.ReadObject(mostream);
if (db != null)
{
Console.WriteLine("StringValue1 = {0}\nStringValue2 = {1}", db.StringValue1, db.StringValue2);
}
}

Console.Read();
}
}

[DataContract(Name = "Demo")]
public class DemoA
{
[DataMember(Name = "Item1")]
public string StringA { get; set; }
[DataMember(Name = "Item2")]
public string StringB { get; set; }
}

[DataContract(Name = "Demo")]
public class DemoB
{
[DataMember(Name = "Item1")]
public string StringValue1 { get; set; }
[DataMember(Name = "Item2")]
public string StringValue2 { get; set; }
}
}

JsonSerializer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
using System;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;

namespace MyApp
{
class Program
{
static void Main(string[] args)
{
// 创建对象实例
Demo x = new Demo();
// 给属性赋值
x.PropA = -8075;
x.PropB = "实验文本";
x.PropC = true;

// 序列化,并获得序列化生成的JSON文本
JsonSerializerOptions options = new()
{
// 输出带进缩格式的文本
WriteIndented = true,
Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs)
};
string json = JsonSerializer.Serialize(x, options);
Console.WriteLine("序列化后:\n{0}", json);

// 反序列化
Demo y = JsonSerializer.Deserialize<Demo>(json);
// 输出反序列化后各属性的值
StringBuilder builder = new();
builder.AppendFormat("{0}:{1}\n", nameof(Demo.PropA), y.PropA);
builder.AppendFormat("{0}:{1}\n", nameof(Demo.PropB), y.PropB);
builder.AppendFormat("{0}:{1}\n", nameof(Demo.PropC), y.PropC);
Console.WriteLine("\n反序列化后:");
Console.WriteLine(builder);
}
}

#region 自定义类型
public class Demo
{
public int PropA { get; set; }
public string PropB { get; set; }
public bool PropC { get; set; }
}
#endregion
}

自定义转换器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
using System;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using static System.Console;

namespace MyApp
{
class Program
{
static void Main(string[] args)
{
// 实例化对象
News x = new News();
// 设置属性值
x.ID = 5133;
x.Title = "测试标题";
x.Content = "测试正文";

// 序列化
string json = JsonSerializer.Serialize(x);
WriteLine($"序列化后:\n{json}\n");

// 反序列化
News y = JsonSerializer.Deserialize<News>(json);
WriteLine("反序列化后:");
WriteLine($"{nameof(News.ID)}{y.ID}");
WriteLine($"{nameof(News.Title)}{y.Title}");
WriteLine($"{nameof(News.Content)}{y.Content}");
}
}

#region 转换器
public sealed class B64Converter : JsonConverter<string>
{
public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
string value = default;
if (!reader.TryGetBytesFromBase64(out byte[] data))
{
// 无法读取时使用空字符串
value = string.Empty;
}
else
{
// 读出原字符串
value = Encoding.UTF8.GetString(data);
}
return value;
}

public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
{
byte[] data = Encoding.UTF8.GetBytes(value);
writer.WriteBase64StringValue(data);
}
}
#endregion

#region 定义类型
public class News
{
public uint ID { get; set; }

[JsonConverter(typeof(B64Converter))]
public string Title { get; set; }

[JsonConverter(typeof(B64Converter))]
public string Content { get; set; }
}
#endregion
}

自定义属性名称
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace MyApp
{
class Program
{
static void Main(string[] args)
{
// 初始化对象
Pet v = new()
{
Id = 205,
Name = "Jack",
Age = 2
};
// 序列化
JsonSerializerOptions opt = new JsonSerializerOptions
{
WriteIndented = true
};
string json = JsonSerializer.Serialize(v, opt);
Console.WriteLine(json);
}
}

public class Pet
{
[JsonPropertyName("_pet_id")]
public long Id { get; set; }

[JsonPropertyName("_pet_name")]
public string Name { get; set; }

[JsonPropertyName("_pet_age")]
public int Age { get; set; }
}
}

在线 JSON 校验格式化工具(Be JSON)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using Newtonsoft.Json;

[Test]
public void JsonTest()
{
Site site = new Site()
{
ID = 1,
Name = "Jack",
Url = "www.baidu.com"
};
var json = JsonConvert.SerializeObject(site);
Console.WriteLine(json);

List<Site> sites = new List<Site>();
sites.Add(new Site() {
ID = 2,
Name = "Tom",
Url = "www.bilibili.com"
});
sites.Add(new Site()
{
ID = 3,
Name = "Peter",
Url = "www.Miscroft.com"
});
var Json_List = JsonConvert.SerializeObject(sites);
Console.WriteLine(Json_List);

var str = "{\"ID\":1,\"Name\":\"Jack\",\"Url\":\"www.baidu.com\"}";
var Site = JsonConvert.DeserializeObject<Site>(str);
Console.WriteLine($"{Site.ID},{Site.Name},{Site.Url}");

var List_str = "[{\"ID\":2,\"Name\":\"Tom\",\"Url\":\"www.bilibili.com\"},{\"ID\":3,\"Name\":\"Peter\",\"Url\":\"www.Miscroft.com\"}]";
var Sites = JsonConvert.DeserializeObject<List<Site>>(List_str);
foreach(var site1 in Sites)
{
Console.WriteLine($"{site1.ID},{site1.Name},{site1.Url}");
}
}

泛型

由于泛型委托 xxx的类型参数 T 是固定的,也就是不可变体,所以无法隐式转换.

public delegate TResult Func<in T1,in T2,out TResult>(T1 arg1,T2 arg2);

协变

out 只能用于接口

父类 = 子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//定义两个类
public class Animal
{
public virtual void Run()
{
Console.WriteLine("动物在跑");
}
}
public class Dog:Animal
{
public override void Run()
{
Console.WriteLine("狗在跑");
}
}

//定义协变接口
public interface IFactory<out T> // out 协变 只能应用于interface
{
T Create();//逆变这边就用不了
}

//实现接口
public class FactoryImpl<T>:IFactory<T> where T:new()
{
public T Create()
{
return new T();
}
}

[Test]
public void Test3()
{
IFactory<Dog> iFactory = new FactoryImpl<Dog>();
IFactory<Animal> parentFactory = iFactory; // 协变
Animal animal = parentFactory.Create();
animal.Run();// 输出结果:狗在跑
}


逆变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//通知的一个接口
public interface INotification
{
public string Message { get; }
}
// 关于通知接口的抽象实现。
public abstract class Notification : INotification
{
public abstract string Message { get; }
}

//通知抽象类的具体实现
public class MainNotification : Notification
{
public override string Message => "您有一封新的邮件";
}

//逆变
public interface INotifier<in T> where T : INotification
{
void Notifier(T notification);
}

public class Notifier<T> : INotifier<T> where T : INotification
{
public void Notify(T notification)
{
Console.WriteLine(notification.Message);
}
}

[Test]
public void Test4()
{
INotifier<INotification> notifier = new Notifier<INotification>();
INotifier<MainNotification> mailNotifier = notifier; // 逆变
mailNotifier.Notify(new MainNotification());
}

泛型的应用

手写 ORM 框架(很难)

ORM 框架,对象关系映射。

  • 从老师提供 utils 文件夹中将 DbHelper 拷贝至当前你的项目中
  • nuget 引用:
    • 1:Microsoft.Extensions.Configuration,
    • 2:System.Data.SqlClient
  • 封装 ORM 框架
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public class DbContext<T> where T : class, new()
{
/// <summary>
/// 添加功能
/// </summary>
/// <param name="t">要添加的对象</param>
public void Add(T t)
{
// insert into Student(.属性1,属性2,属性3..) values(.'属性值1','属性值2','属性值3'..)
StringBuilder sql = new StringBuilder($"insert into{typeof(T).Name}(");
// 跳过第一个属性
var propertyInfos = typeof(T).GetProperties().Skip(1);
var propNames = propertyInfos.Select(p => p.Name).ToList();
sql.Append(string.Join(",", propNames));
sql.Append(") values('");
List<string> values = new List<string>();
foreach (var propertyInfo in propertyInfos)
{
// 获取属性值
values.Add(propertyInfo.GetValue(t).ToString());
}
sql.Append(string.Join("','", values));
sql.Append("')");
DbHelper.ExecuteNonQuery(sql.ToString());
}
public List<T> GetList()
{
return DbHelper.GetList<T>($"select * from {typeof(T).Name}");
}
public T GetModel(dynamic id)
{
var pk = GetPrimaryKey().Name; //获取主键的名称
//获取一条记录
return DbHelper.GetList<T>($"select * from {typeof(T).Name} where {pk}=@id",new SqlParameter(pk, id)).First();
}
public int Update(T model)
{
var tp = typeof(T);
var pk = GetPrimaryKey(); //获取主键
var props = tp.GetProperties().ToList();
//获取所有的属性名称(除主键)
var propNames = props.Where(p => !p.Name.Equals(pk)).Select(p => p.Name).ToList();
//update 表 set 字段1=@字段1,字段2=@字段2, where 主键名=主键值
string sql = $"update {tp.Name} set ";
foreach (var propName in propNames)
{
sql += $"{propName}=@{propName},";
}
sql = sql.Remove(sql.Length - 1);
sql += $" where {pk.Name}=@{pk.Name}";
List<SqlParameter> list = new();
foreach (var prop in props)
{
SqlParameter parameter = new SqlParameter(prop.Name,prop.GetValue(model));
list.Add(parameter);
}
return DbHelper.ExecuteNonQuery(sql, list.ToArray());
}
public int Delete(dynamic id)
{
//delete from 表名 where 主键名=@主键值
var pk = GetPrimaryKey().Name;
return DbHelper.ExecuteNonQuery($"delete from {typeof(T).Name} where {pk}=@{pk}", new SqlParameter(pk,id));
}
/// <summary>
/// 获取主键
/// </summary>
/// <returns></returns>
public PropertyInfo GetPrimaryKey()
{
var props = typeof(T).GetProperties();
foreach (var propertyInfo in props)
{
//获取特性
var attrs =
propertyInfo.GetCustomAttributes(typeof(KeyAttribute), false);
if (attrs.Length > 0)
{
return propertyInfo;
}
}
return props[0]; // 如果没有Key 特性,就让第一个属性当作主键
}
}
DataTable List

DataTable 转换成 List

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
private static List<T> ToList<T>(DataTable dt) where T : class, new()
{
Type t = typeof(T);
PropertyInfo[] propertys = t.GetProperties();
List<T> lst = new List<T>();
string typeName = string.Empty;
foreach (DataRow dr in dt.Rows)
{
T entity = new T();
foreach (PropertyInfo pi in propertys)
{
typeName = pi.Name;
if (dt.Columns.Contains(typeName))
{
if (!pi.CanWrite) continue;
object value = dr[typeName];
if (value == DBNull.Value) continue;
if (pi.PropertyType == typeof(string))
{
pi.SetValue(entity, value.ToString(), null);
}
else if (pi.PropertyType == typeof(int) || pi.PropertyType == typeof(int?))
{
pi.SetValue(entity, int.Parse(value.ToString()), null);
}
else if (pi.PropertyType == typeof(DateTime?) || pi.PropertyType == typeof(DateTime))
{
pi.SetValue(entity, DateTime.Parse(value.ToString()),null);
}
else if (pi.PropertyType == typeof(float))
{
pi.SetValue(entity, float.Parse(value.ToString()),null);
}
else if (pi.PropertyType == typeof(double))
{
pi.SetValue(entity, double.Parse(value.ToString()),null);
}
else
{
pi.SetValue(entity, value, null);
}
}
}
lst.Add(entity);
}
return lst;
}

泛型集合

存在的问题

1
2
3
4
5
6
7
8
9
10
//1、存取数据需要进行装箱拆箱
//2、数据类型转换存在隐患

ArrayList arrylist = new ArrayList() { 14, "hello", 29.7, true};
arrylist.Add("world");// object
double dsum = 0;
foreach(var item in arrylist)
{
dsum += Convert.ToDouble(item); // 出现异常
}

性能对比

非泛型集合性能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[Test]
public void Test1()
{
Stopwatch watch = new Stopwatch();
watch.Start();
ArrayList arrayList = new();
for (int i = 0; i < 2000000; i++)
{
arrayList.Add(i); // 装箱
}
long sum = 0;
foreach (var item in arrayList)
{
sum += Convert.ToInt64(item);
}
watch.Stop();
Console.WriteLine("非泛型集合耗时(ms):"+watch.ElapsedMilliseconds);//239
}
泛型集合性能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[Test]
public void Test1()
{
Stopwatch watch = new Stopwatch();
watch.Start();
var arrayList = new List<int>();
for (int i = 0; i < 2000000; i++)
{
arrayList.Add(i);
}
long sum = 0;
foreach (var item in arrayList)
{
sum += Convert.ToInt64(item);
}
watch.Stop();
Console.WriteLine("泛型集合耗时(ms):"+watch.ElapsedMilliseconds);//19
}

I/O 文件流

Stream 流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 小文件读取方式
[Test]
public void Test2()
{
var currentDirectory = Environment.CurrentDirectory; // 当前debug 路径
using FileStream fileStream = new (Path.Combine(currentDirectory,
"hello.txt"), FileMode.Open, FileAccess.Read);
using StreamReader reader = new(fileStream);
var content = reader.ReadToEnd(); // 小文件读取方式,100M算小吗?
Console.WriteLine(content);
}

// 大文件 读取方式
[Test]
public void Test3()
{
var currentDirectory = Environment.CurrentDirectory; // 当前debug 路径
using FileStream fileStream = new (Path.Combine(currentDirectory,
"hello.txt"), FileMode.Open, FileAccess.Read);
// 将hello.txt 文件内容写入到hello_back.txt中
using FileStream backupStream = new (Path.Combine(currentDirectory,
"hello_back.txt"), FileMode.OpenOrCreate, FileAccess.Write);
using StreamReader reader = new(fileStream,Encoding.UTF8);
using StreamWriter writer = new(backupStream);
// 每次只读10B
char[] buffer = new char[10];
while (!reader.EndOfStream)
{
// count 表示实际读取的长度,
int count = reader.Read(
buffer, // 从 fileStream 对象读取数据,填充至buffer 缓冲区
0, // 从 buffer 数组的第0个位置开始写
buffer.Length); // 每次从fileStream中读取的长度
writer.Write(buffer);// 将读取到的数量写入到backupStream流中
}
}

内存映射文件

内存映射文件可以单独存放于内存中,也可以把磁盘上的文件映射为内存文件来进行处理.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

using System.IO.MemoryMappedFiles;

namespace MyApp1
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
MemoryMappedFile mnMappFile = null;
// 文件大小
const int FILE_SIZE = 1000;
public MainWindow()
{
InitializeComponent();
this.Loaded += (a, b) =>
{
// 创建内存映射文件
mnMappFile = MemoryMappedFile.CreateNew("my_file", FILE_SIZE);
};
this.Unloaded += (c, d) =>
{
// 销毁内存映射文件
mnMappFile.Dispose();
};
}

private void OnClick(object sender, RoutedEventArgs e)
{
if (txtInput.Text == "")
{
return;
}

using (MemoryMappedViewStream streamout = mnMappFile.CreateViewStream())
{
// 将字符串转化为字节数组
byte[] buffer = Encoding.Default.GetBytes(txtInput.Text);
// 如果要写入的字节数组的长度大于
// 内存映射文件分配的大小
// 则忽略后面的字节
int ncount = (buffer.Length > FILE_SIZE) ? FILE_SIZE : buffer.Length;
// 先写入内容的长度
streamout.Write(BitConverter.GetBytes(ncount), 0, 4);
// 接着写入内容正文
streamout.Write(buffer, 0, ncount);
}
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO.MemoryMappedFiles;
using System.IO;

namespace MyApp2
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private void OnClick(object sender, RoutedEventArgs e)
{
MemoryStream ms = new MemoryStream();
try
{
using (MemoryMappedFile file = MemoryMappedFile.OpenExisting("my_file"))
{
using (MemoryMappedViewStream stream = file.CreateViewStream())
{
// 先读出内容的长度
byte[] bufferlen = new byte[4];
stream.Read(bufferlen, 0, 4);
// 获取长度
int len = BitConverter.ToInt32(bufferlen, 0);
// 声明一个与内容长度相等的字节数组
byte[] buffer = new byte[len];
// 把内容读到字节数组中
stream.Read(buffer, 0, len);
// 将读到的内容写入内存流
ms.Write(buffer, 0, buffer.Length);
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
// 从内存流中读出字符串
tbRead.Text = Encoding.Default.GetString(ms.ToArray());
// 释放内存流
ms.Dispose();
}
}
}

独立存储

独立存储也叫隔离存储,是一个由操作系统提供的、可以存储文件和目录的一个特殊区域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.IO.IsolatedStorage;

namespace MyApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private Bitmap CreateBitmap()
{
// 创建图像
Bitmap bmp = new Bitmap(200, 200);
using (Graphics g = Graphics.FromImage(bmp))
{
Pen pen = new Pen(Color.Purple, 6f);
g.DrawEllipse(pen, new Rectangle(50, 50, 100, 100));
pen.Color = Color.Red;
g.DrawEllipse(pen, new Rectangle(10, 10, 180, 180));
pen.Dispose();
}
return bmp;
}

private void button1_Click(object sender, EventArgs e)
{
try
{
// 获取独立存储区
using (IsolatedStorageFile isostore = IsolatedStorageFile.GetUserStoreForAssembly())
{
// 如果文件存在,则删除
if (isostore.FileExists("1.png"))
{
isostore.DeleteFile("1.png");
}
using (IsolatedStorageFileStream stream = isostore.CreateFile("1.png"))
{
Bitmap bmp = CreateBitmap();
// 将位图保存到流中
bmp.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
// 释放位图对象
bmp.Dispose();
}
}
MessageBox.Show("文件已保存。");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}

private void button2_Click(object sender, EventArgs e)
{
pictureBox1.Image = null;
try
{
using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForAssembly())
{
if (store.FileExists("1.png"))
{
// 读取文件
IsolatedStorageFileStream stream = store.OpenFile("1.png", FileMode.Open);
// 从文件流创建图像
Image img = Image.FromStream(stream);
stream.Close();
pictureBox1.Image = img;
}
}
}
catch (IsolatedStorageException ex)
{
MessageBox.Show(ex.Message);
}
}

private void button3_Click(object sender, EventArgs e)
{
try
{
using (IsolatedStorageFile store=IsolatedStorageFile.GetUserStoreForAssembly())
{
store.Remove();
MessageBox.Show("独立存储区已删除。");
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
}

委托

  • 1、委托的本质就是一个 Sealed 类
  • 2、继承自 System.MulticastDelegate:Delegate
  • 3、委托里面内置了 3 个方法:Invoke (),BeginInvoke (),EndInvoke ()。

重要结论

  1. 参数列表和返回值称为方法签名
  2. 委托的签名要与方法的签名保持一致
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
//访问修饰符 delegate 返回值 委托名(参数列表)
public delegate void SpeakDel();
//声明一个带参数的委托
public delegate void SpeakDel2(int i);
//声明一个带返回值的委托
public delegate string SpeakDel3();

{
Console.WriteLine("===========无参无返回值===========");
//声明一个委托的实例,并告诉这个委托要去干什么(执行方法)
speak = SpeakChinese;,m
SpeakDel speak = new SpeakDel(SpeakChinese);//也可也直接为SpeakDel speak = SpeakChinese
// SpeakDel speak = SpeakChinese
//调用委托
speak();
}
{
Console.WriteLine("====带参数的委托========");
SpeakDel2 speak = new SpeakDel2(Speak);
int j = 1;
speak(j);
}
{
Console.WriteLine("====带返回值的委托========");
SpeakDel3 speak = new SpeakDel3(SpeakEnglish);
Console.WriteLine(speak());
}

static string SpeakEnglish()
{
return "I can speak English";
}
static void Speak(int i)
{
switch (i)
{
case 1:
Console.WriteLine("我会说中文");
break;
case 2:
Console.WriteLine("I can speak English!");
break;
default:
Console.WriteLine("其他语言不会");
break;
}
}
static void SpeakChinese()
{
Console.WriteLine("我会说中文");
}
static void Speak2()
{
Console.WriteLine("我后来学习了韩语");
}

// 反射可以看清一个类型的本质
var isClass = typof(SpeakDel).IsClsss // true







public delegate void MyDelegate();
public static void Test1() { Console.WriteLine("方法一"); }
public static void Test2() { Console.WriteLine("方法二"); }

public static void MyTest(MyDelegate d)
{
if (d != null)
{
d();
}
d = Test2;
}
static void Main(string[] args)
{
MyDelegate de = Test1;
MyTest(de);
de();
//都是方法一,传递过去的只是自我复制,也就是值传递,不改变外部的。
}

弃元、匿名方法与 Lambda 表达式

弃元
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
string val = "13.0077";
if(double.TryParse(val,out double _))
{
Console.WriteLine("{0}是有效的double值",val);
}
else
{
Console.WriteLine("{0}不是有效的double值", val);
}


using System.Collections;
using System.ComponentModel;
using System.Data.SqlTypes;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
namespace MyTest_Knowledge
{
class Program
{

// 表示程序主体是否执行循环
static bool looping = false;
static void Main(string[] args)
{
Console.CancelKeyPress += (_, _) => looping = false;

// 修改looping变量的值
looping = true;
// 进入循环
while (looping)
{
// 循环内不执行任何操作
}
}
}
}

匿名方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
delegate void SpeakDelegate(string nickName,string lang);
delegate void SpeakDelegate1();
delegate string SpeakDelegate2(string nickName,string lang);
[Test]
public void TestAnonymous()
{
// 有参无返回值
SpeakDelegate del = delegate(string nickName, string lang)
{
Console.WriteLine($"我是{nickName},我会说{lang}");
};

del("任我行","汉语");
// 无参无返回值
SpeakDelegate1 del1 = delegate
{
Console.WriteLine("我是中国人,愿祖国早日统一");
};
del1();
// 有参有返回值
SpeakDelegate2 del2 = delegate(string name, string lang)
{
return $"我是{name},我会说{lang}";
};
Console.WriteLine(del2("张三","中文"));
}
Lambda 表达式
  • Lambda 表达式是一个匿名函数,用它可以高效简化代码,常用作委托,回调
  • Lambda 表达式都使用运算符 => ,所以当你见到这个符号,基本上就是一个 Lambda 表达式
  • Lambda 运算符的左边是输入参数 (), => ,右边是表达式或语句块
  • Lambda 表达式,是可以访问到外部变量的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
delegate void SpeakDelegate(string nickName,string lang);
delegate void SpeakDelegate1();
delegate string SpeakDelegate2(string nickName,string lang);
delegate void SpeakDelegate3(string nickName);
[Test]
public void TestLambda()
{
// 有参无返回值
SpeakDelegate del = (nickName, lang)=>
{
Console.WriteLine($"我是{nickName},我会说{lang}");
};
del("任我行","汉语");
// 无参无返回值
SpeakDelegate1 del1 = ()=>
{
Console.WriteLine("我是中国人,愿祖国早日统一");
};
del1();
// 有参有返回值 ,如果方法体中只有return 语句,则可以简写如下
SpeakDelegate2 del2 = (name, lang) => $"我是{name},我会说{lang}";
Console.WriteLine(del2("张三","中文"));
SpeakDelegate2 del2_1 = (name, lang) =>
{
Console.WriteLine("委托其实功能非常强大");
return $"我是{name},我会说{lang}";
};
Console.WriteLine(del2_1("张三","中文"));
// 一个参数,并演示可访问外部变量
int age = 18;
SpeakDelegate3 del3 = n =>
{
Console.WriteLine($"人家今年才{age},我的芳名叫:{n}");
};
del3("小芳");
}

public void Start(int a,int b)=> a+b;

系统内置委托

Action

Action 无返回值的内置委托,有 16 个重载方法:

  • Action 声明无参数委托
  • Action 声明有一个参数委托
  • Action 声明有 2 个参数委托
  • Action 声明有 3 个参数委托
  • Action 委托输入参数个数最多 16 个。

备注

1、它有 16 重载方法

2、有 16 个输入参数

3、 Action 的返回类型是 Void

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void SayHello(string str)
{
Console.WriteLine(str);
}
[Test]
public void TestAction()
{
// 普通用法,string 作为参数类型
Action<string> action = SayHello;
action("hi, 小度");
// 无参,Lambda用法
Action action1 = () => { Console.WriteLine("hi,小爱"); };
action1();
Action<string> action2 = n => { Console.WriteLine($"hi,{n}"); };
action2("小度");
// 两个参数
Action<int,int> action3 = (x,y) => { Console.WriteLine($"长方形的面积是:
{x*y}"); };
action3(3,5);
}
Func<T,TResult>

Func 有返回值的内置委托,有 17 个重载方法:

  • 1、Func
  • 2、Func<T,TResult>
  • 3、 Func<T1,T2,TResult>
  • 4、 Func<T1,T2,T3,TResult>
  • 5 、Func<T1,…,TResult>

备注

1、它有 17 个重载方法

2、有 T1 到 T16 个输入参数

3、最后一个参数 Tresult 代表返回类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[Test]
public void TestFunc()
{
// 无参有返回值
Func<string> f1 = () => "hello,我是任我行码农场";
Console.WriteLine(f1.Invoke());// invoke 方法 与 f1() 拥有同样的效果,都是同
步调用
// 一个参数
Func<int, int> f2 = x =>
{
Console.WriteLine($"我的年龄是{x + 1}");
return x + 1;
};
f2(18);
// 两个参数
Func<int, int, int> f3 = Max;
Console.WriteLine(f3(5,6));
// 参数是int,返回值是string
Func<int, string> f4 = i => i.ToString();
}

委托异步调用

特点:

  • 异步调用,即在线程池分配的子线程中执行委托,因此执行时不会阻塞调用委托的线程,该线程在 调用后不等委托结束继续向下执行。
  • 委托结束时,如果有返回值,子线程将返回值传递给调用线程;
  • 委托方法执行结束后,如果有回调函数,则在子线程中继续执行回调函数,直到回调函数结束委托 才结束。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 测试异步调用
[Test]
public void TestAsync()
{
Action action = () =>
{
Console.WriteLine("委托开始执行-------");
Thread.Sleep(3000);
Console.WriteLine("-------委托执行结束");
};
var result= action.BeginInvoke(ar =>
{
Console.WriteLine("我是委托执行完事之后的回调函数");
},null);
Console.WriteLine("主程序已经执行了其他业务");
Console.WriteLine("等待委托执行完");
action.EndInvoke(result);
Console.WriteLine("程序结束");
}

但是上述代码会抛出异常 System.PlatformNotSupportedException : Operation is not supported on this platform.

原因如下:

异步编程模型 (APM)(使用 IAsyncResult 和 BeginInvoke) 不再是异步调用的优选方法。从.NET Framework 4.5 开始,推荐的异步模型是基于任务的异步模式 (TAP)。因此,而且由于异步委托的 实现取决于远程处理,但.NET Core 中 是不存在的功能,BeginInvoke 和 EndInvoke 委托调用不支 持 .NET Core

使用任务实现异步调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Test]
public static void Test_Asny()
{
Action action = () =>
{
Console.WriteLine("委托开始执行-------");
Thread.Sleep(3000);
Console.WriteLine("-------委托执行结束");
};
//Task task = Task.Run(action);
Task task = new Task(action);
task.Start();
Console.WriteLine("主程序已经执行了");
Console.WriteLine("等待委托执行完");
Task.WaitAll(task);
Console.WriteLine("程序结束");
}

event 事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
//为了允许派生类重写引发事件的代码,会在类中声明一个受保护的方法,习惯上命名为On<事件名>
//public delegate void EventHandler(object sender,System.EventArgs e);
//Object类型表示引发事件的对象,谁引发了事件,通常将this传递过去,另一个参数是标准事件处理程序的签名。
//public delegate void EventHanlder<TEventArgs>(object sender,TEventArgs e);

using System;

namespace EventDemo
{
/*
sender参数代表触发事件的控件对象。它是一个指向事件源对象的引用,简单来说就是发生事件的那个对象。例如,如果是一个按钮的点击事件,sender就代表那个被点击的按钮。通过将sender转换为相应的控件类型,可以获取到触发事件的控件的具体信息。
EventArgs e是一个包含事件数据的类的基类。这个参数用于传递与事件相关的附加信息,比如用户点击的位置、键盘按下的键等。对于不同类型的事件,EventArgs可以有不同的派生类来携带特定的信息
*/
// 定义一个委托类型,用于事件处理程序
//EventArgs e就是用来传递信息的,sender就是发生事件的对象
public delegate void NotifyEventHandler(object sender, EventArgs e);

// 发布者类
public class ProcessBusinessLogic
{
// 声明事件
public event NotifyEventHandler ProcessCompleted;

// 触发事件的方法
protected virtual void OnProcessCompleted(EventArgs e)
{
ProcessCompleted?.Invoke(this, e);
}

// 模拟业务逻辑过程并触发事件
public void StartProcess()
{
Console.WriteLine("Process Started!");

// 这里可以加入实际的业务逻辑

// 业务逻辑完成,触发事件
OnProcessCompleted(EventArgs.Empty);
}
}

// 订阅者类
public class EventSubscriber
{
public void Subscribe(ProcessBusinessLogic process)
{
process.ProcessCompleted += Process_ProcessCompleted;
}

private void Process_ProcessCompleted(object sender, EventArgs e)
{
Console.WriteLine("Process Completed!");
}
}

class Program
{
static void Main(string[] args)
{
ProcessBusinessLogic process = new ProcessBusinessLogic();
EventSubscriber subscriber = new EventSubscriber();

// 订阅事件
subscriber.Subscribe(process);

// 启动过程
process.StartProcess();

Console.ReadLine();
}
}
}



using System;

namespace MyApp
{
#region 事件参数
public class KeyPressedEventArgs : EventArgs
{
public KeyPressedEventArgs(ConsoleKey key)
{
PressedKey = key;
}
public ConsoleKey PressedKey { get; private set; }
}
#endregion

public class MyApp
{
// 捕捉按键的事件
public event EventHandler<KeyPressedEventArgs> KeyPressed;

// 通过该方法引发事件
protected virtual void OnKeyPressed(KeyPressedEventArgs e)
{
//不需要if判断的话,可以直接写this.SpaceKeyPressed?.invoke();
if (this.KeyPressed != null)
{
this.KeyPressed(this, e);
}
}

public void Start()
{
while (true)
{
ConsoleKeyInfo keyinfo = Console.ReadKey();
// 如果按下了Esc键,则退出循环
if (keyinfo.Key == ConsoleKey.Escape)
{
break;
}
// 引发事件
OnKeyPressed(new KeyPressedEventArgs(keyinfo.Key));
}
}
}

class Program
{
static void Main(string[] args)
{
MyApp app = new MyApp();
app.KeyPressed += app_KeyPressed;
app.Start();
}

// 响应处理事件
static void app_KeyPressed(object sender, KeyPressedEventArgs e)
{
Console.WriteLine("已按下了{0}键。", e.PressedKey.ToString());
}
}
}

事件的本质

通过反射我们可以得出结果:

  • IsClass: true
  • 父类是 MulticastDelegate,成员方法 Invoke,BeginInvoke,EndInvoke
  • IsSealed: true
  • 结论:事件是一个委托,并且还是一个特殊的密封类。

事件与委托的区别

  • 事件只能在类的内部进行触发,不能在类的外部进行触发。而委托在类的内部和外部都可触发;
  • 委托一般用于回调,而事件一般用于外部接口。在观察者模式中,被观察者可在内部声明一个事件
  • 作为外部观察者注册的接口。
  • 事件只能通过 +=,-= 方式 绑定 / 解绑方式
  • 事件是一个特殊的委托,查看反编译工具之后的代码,发现事件是一个 private 委托

观察者模式

错误写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Heater
{
private int temperature; // 水温
// 烧水
public void BoilWater()
{
for (int i = 0; i <= 100; i++)
{
temperature = i;
if (temperature > 95)
{
MakeAlert(temperature);
ShowMsg(temperature);
}
}
}
// 发出语音警报
private void MakeAlert(int param) {
Console.WriteLine("Alarm:嘀嘀嘀,水已经 {0} 度了:" , param);
}
// 显示水温
private void ShowMsg(int param) {
Console.WriteLine("Display:水快开了,当前温度:{0}度。" , param);
}
}
[TestFixture]
public class ObserverMainTest
{
[Test]
public void TestObserver()
{
Heater ht = new Heater();
ht.BoilWater();
}
}

正确写法

Observer 设计模式中主要包括如下两类对象:

  1. Subject:监视对象(被观察者),它往往包含着其他对象所感兴趣的内容。在本范例中,热水器

    就是一个监视对象,它包含的其他对象所感兴趣的内容,就是 temprature 字段,当这个字段的值

    快到 100 时,会不断把数据发给监视它的对象。

  2. Observer:观察者,它监视 Subject,当 Subject 中的某件事发生的时候,会告知 Observer,而

    Observer 则会采取相应的行动。在本范例中,Observer 有警报器和显示器,它们采取的行动分别

    是发出警报和显示水温。

在本例中,事情发生的顺序应该是这样的:

  1. 警报器和显示器告诉热水器,它对它的温度比较感兴趣 (注册)。
  2. 热水器知道后保留对警报器和显示器的引用。
  3. 热水器进行烧水这一动作,当水温超过 95 度时,通过对警报器和显示器的引用,自动调用警报器

的 MakeAlert () 方法、显示器的 ShowMsg () 方法。

类似这样的例子是很多的,GOF (设计模式) 对它进行了抽象,称为 Observer 设计模式:Observer**** 设计

模式是为了定义对象间的一种一对多的依赖关系,以便于当一个对象的状态改变时,其他依赖于它的对

象会被自动告知并更新。Observer 模式是一种松耦合的设计模式。

观察者模式,也叫发布订阅模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/// <summary>
/// 热水器
/// </summary>
class Heater
{
private int _temperature; // 水温
public event Action<int>? BoilEvent;
// 烧水
public void BoilWater()
{
for (int i = 0; i <= 100; i++)
{
_temperature = i;
if (_temperature > 95)
{
//触发事件
BoilEvent?.Invoke(_temperature);
}
}
}
}
/// <summary>
/// 警报器
/// </summary>
public class Alter
{
// 发出语音警报
public void MakeAlert(int temperature) {
Console.WriteLine("Alarm:嘀嘀嘀,水已经 {0} 度了:" , temperature);
}
}
/// <summary>
/// 显示器
/// </summary>
public class Display
{
// 显示水温
public void ShowMsg(int temperature) {
Console.WriteLine("Display:水快开了,当前温度:{0}度。" , temperature);
}
}
[TestFixture]
public class ObserverMainTest
{
[Test]
public void TestObserver()
{
Heater ht = new Heater();
ht.BoilEvent += new Alter().MakeAlert;
ht.BoilEvent += new Display().ShowMsg;
ht.BoilWater();
}
}

表达式树

Expression

​ 提供一种基类,表示表达式树节点的类派生自该基类。 它还包含用来创建各种节点类型 (表达式类型) 的

static 工厂方法。 这是一个 abstract 类。

应用场景:
  1. 自定义 ORM 框架查询条件 (用来检查用户信息合法性)
  2. 规则引擎
常用表达式
1. 常量表达式 **-ConstantExpression**
1
2
3
4
5
6
var constantExpression = Expression.Constant(5);
Expression<Func<int>> exp = Expression.Lambda<Func<int>>
(constantExpression);
var compile = exp.Compile();
var result =compile.Invoke();
Console.WriteLine(result);
2. 参数表达式 **-ParameterExpression**
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Expression<Func<int,int>> exp = x => x;
// Expression<Func<int,int,int>> exp = (x,y) => x+y;
// 创建一个参数表达式
ParameterExpression parameterX = Expression.Parameter(typeof(int));
ParameterExpression parameterY = Expression.Parameter(typeof(int));
// 二元表达式===> x+y
BinaryExpression binaryExpression = Expression.Add(
parameterX, // 左树
parameterY // 右树
);
// 将Body 主体转换成表达式树
// Expression<Func<int,int,int>> exp = (x,y) => x+y;
var expression = Expression.Lambda<Func<int, int,int>>(binaryExpression,
parameterX,parameterY);// 一定不要忘记把参数传进来(告诉Body主体,我的参数是什么)
// Func<int,int,int> func = (x,y) => x+y;
var compile = expression.Compile();
var result = compile.Invoke(5,6);
Console.WriteLine(result);
3. 二叉(二元)表达式 **-BinaryExpression**
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Expression<Func<int,int, int>> exp = (x,y) => x + y - 5;
// x:参数表达式
var parameterX = Expression.Parameter(typeof(int),"x");
var parameterY = Expression.Parameter(typeof(int),"y");
// x+y
var add = Expression.Add(parameterX, // 左边
parameterY);// 右边
// 5: 常量表达式
var constantExpression = Expression.Constant(5,typeof(int));
// 创建二元表达式: x + y - 5
var binaryExpression = Expression.Subtract(add,constantExpression);
// 构造表达式树
var expression = Expression.Lambda<Func<int,int, int>>
(binaryExpression,parameterX,parameterY);
var compile = expression.Compile();
var result = compile.Invoke(10,15);
Console.WriteLine(result);
4. Lambda**** 表达式
1
2
3
4
5
6
7
8
9
10
11
12
13
// Expression<Func<int, int>> exp = x => x + 5;
ConstantExpression constant = Expression.Constant(5);
// 参数表达式,并指定参数的类型
ParameterExpression parameter = Expression.Parameter(typeof(int));
// 相当于 5+x ,x 代表任意参数
BinaryExpression binary = Expression.Add(
constant, // 左树
parameter); // 右树
// lambda 表达式
Expression<Func<int,int>> expression = Expression.Lambda<Func<int, int>>
(binary, parameter);
var func = expression.Compile(); // 将lambda 表达式 编译为委托
Console.WriteLine( func.Invoke(10));// 执行结果 5+10 = 15
5. 成员表达式 **-MemberExpression**
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 成员表达式
[Test]
public void TestMember()
{
// Expression<Func<Person, int>> exp = person => person.Id * 5;
// 创建person 参数对象
var parameterExpression =
Expression.Parameter(typeof(Person),"person");
// 获取参数中的成员属性 person.Id,属性名一定要在Person中存在
var memberExpression = Expression.Property(parameterExpression,"Id");
// 常量表达式 5
var constantExpression = Expression.Constant(5);
// 得到二元表达式 person.Id * 5
var binaryExpression =
Expression.Multiply(memberExpression,constantExpression);
// 构造表达式树 person => person.Id + 5;
var expression = Expression.Lambda<Func<Person,int>>
(binaryExpression,parameterExpression);
var compile = expression.Compile();
var result = compile.Invoke(new Person{Id=88,Name = "任我行码农场"});
Console.WriteLine(result);
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
6. 方法表达式 **-MethodCallExpression**
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Expression<Func<Pepole, int>> exp = x => x.Id.ToString();
var parameter = Expression.Parameter(typeof(Pepole), "x");
MemberExpression memberExpression = Expression.Property(parameter,"Id");
// 方法调用表达式
MethodCallExpression methodCallExpression =
Expression.Call(memberExpression,"ToString",null);
var expression = Expression.Lambda<Func<Pepole, String>>
(methodCallExpression,parameter);
Func<Pepole,String> func = expression.Compile();
Pepole p = new Pepole() {Id = 10, Name = "任我行"};
Console.WriteLine(func.Invoke(p));

//// 创建 MyTest 的实例
//var instanceExpression = Expression.New(typeof(MyTest));
//调用静态方法
Expression.Call(typeof(MyTest).GetMethod("Sum"), parameterExpression);
//// 调用实例方法 Sum
//var methodExpression = Expression.Call(instanceExpression,typeof(MyTest).GetMethod("Sum"), parameterExpression);
7. 成员初始化表达式 **-MemberBinding**
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[Test]
public void TestBinding()
{
var type = typeof(Person);
var idConst = Expression.Constant(30);
var nameConst = Expression.Constant("任我行");
// 创建对象
var personObj = Expression.New(type);
PropertyInfo idProp = type.GetProperty("Id");
// 成员初始化: 相当于 Id=30
MemberAssignment idBind = Expression.Bind(idProp,idConst);
PropertyInfo nameProp =type.GetProperty("Name");
// 成员初始化: 相当于 Name="任我行"
MemberAssignment nameBind =Expression.Bind(nameProp,nameConst);
List<MemberBinding> list = new List<MemberBinding>();
list.Add(idBind);
list.Add(nameBind);
// 成员初始化 new Person{Id=30,Name="任我行"}
var memberInitExpression = Expression.MemberInit(personObj,list);
var expression = Expression.Lambda<Func<Person>>(memberInitExpression);
var compile = expression.Compile();
var person = compile.Invoke();
Console.WriteLine($"id={person.Id},name={person.Name}");
}
表达式实战(很难)
MyTest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
        // 反射实现自动映射
[Test]
public void TestDto()
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < 100000; i++)
{
Class p = new() { Id = i, Name = $"万宏伟{i}" };
// 通过反射得到的自动映射
var ClassDto = p.MapTo2<Class, ClassDto>();

}
stopwatch.Stop();
Console.WriteLine($"反射自动映射耗时:{stopwatch.ElapsedMilliseconds / 1000.0} s");
}
// 表达式树的方式实现自动映射功能
[Test]
public void TestExpressionAutoMap()
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < 100000; i++)
{
Class p = new() { Id = i, Name = $"万宏伟{i}" };
// 通过表达式树得到的自动映射
var ClassDto = p.MapTo<Class, ClassDto>();

}
stopwatch.Stop();
Console.WriteLine($"表达式树自动映射耗时:{stopwatch.ElapsedMilliseconds / 1000.0} s");

}


// 数据传输对象
public class ClassDto
{
public int Id { get; set; }
public string Name { get; set; }
}

public class Class
{
public int Id { get; set; }
public string Name { get; set; }
}
1. AutoMap 自动映射框架
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
public static class AutoMapperUtils
{
/// <summary>
/// 自动转换
/// </summary>
/// <param name="source">原对象数据</param>
/// <typeparam name="TSource">原始类型</typeparam>
/// <typeparam name="TDesc">目标转换类型</typeparam>
/// <returns></returns>
public static TDesc MapTo<TSource, TDesc>(this TSource source)
where TSource:class,new()
where TDesc:class,new()
{
var sourceType = typeof(TSource);
var descType = typeof(TDesc);
// 参数对象
var param = Expression.Parameter(sourceType,"p");
// 所有属性成员绑定列表
List<MemberBinding> list = new List<MemberBinding>();
foreach (var item in descType.GetProperties())
{
// 原始成员属性表达式
var sourceMemberExp =
Expression.Property(param,sourceType.GetProperty(item.Name));
// 将原始成员属性 与 目标属性成员 建立绑定关系
var memberBind = Expression.Bind(item,
sourceMemberExp);
list.Add(memberBind);
}
// 属性成员初始化表达式
MemberInitExpression memberInitExpression =
Expression.MemberInit(Expression.New(descType), list);
// 建立最终可执行的关系表达式
Expression<Func<TSource, TDesc>> expression = Expression.Lambda<Func<TSource, TDesc>>(memberInitExpression,param);
// 执行
return expression.Compile().Invoke(source);
}
}




using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace My_Senior_C_
{
public static class ExpressionExtends
{
/// <summary>
/// 将源数据对象自动映射为目标数据对象(反射方式实现)
/// </summary>
/// <param name="source">源对象</param>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <typeparam name="TDest">目标数据类型</typeparam>
/// <returns></returns>
public static TDest MapTo2<TSource, TDest>(this TSource source) where TSource : class, new() where TDest : class, new()
{
TDest dest = new TDest();

var destType = typeof(TDest);
var sourceType = typeof(TSource);

// 两个类型的属性列表
var destProps = destType.GetProperties();
var sourceProps = sourceType.GetProperties();

foreach (var sourceProp in sourceProps)
{
// 得到目标属性
var dectProp = destProps.FirstOrDefault(p => p.Name.Equals(sourceProp.Name));
// 获取源属性的值给目标属性进行赋值
dectProp.SetValue(dest, sourceProp.GetValue(source));
}

return dest;
}

/// <summary>
/// 将源数据对象自动映射为目标数据对象(表达式树的方式实现)
/// </summary>
/// <param name="source">源对象</param>
/// <typeparam name="TSource">源数据类型</typeparam>
/// <typeparam name="TDest">目标数据类型</typeparam>
/// <returns></returns>
public static TDest MapTo<TSource, TDest>(this TSource source)
where TSource : class, new() where TDest : class, new()
{
// Expression<Func<Person, PersonDto>> exp = person => new PersonDto {Id = person.Id, Name = person.Name};
var destType = typeof(TDest);
var sourceType = typeof(TSource);


// 1. 创建一个参数表达式
var parameterExpression = Expression.Parameter(sourceType, "person");
// 2. 获取所有的源类型的属性
var sourceProps = sourceType.GetProperties();

List<MemberBinding> list = new(); // 保存所有属性的绑定关系
foreach (var sourceProp in sourceProps)
{
// 3. 创建成员表达式 person.Id 或者 person.Name
var memberExpression = Expression.Property(parameterExpression, sourceProp);
// 4. 目标成员绑定 Id= person.Id 或者 Name = person.Name
var memberBinding = Expression.Bind(destType.GetProperty(sourceProp.Name), memberExpression);
list.Add(memberBinding);
}

// 5. 创建对象表达式:new PersonDto()
var destExpression = Expression.New(destType);
// 6. 成员初始化表达式:new PersonDto {Id = person.Id, Name = person.Name}
var memberInitExpression = Expression.MemberInit(destExpression, list);
// 7. 创建最终的表达式
var expression = Expression.Lambda<Func<TSource, TDest>>(memberInitExpression, parameterExpression);
var compile = expression.Compile();
return compile.Invoke(source);
}
}
}

方法测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Test]
public void TestAutoMap()
{
Pepole pepole = new() {Id = 1, Name = "任我行码农场"};
var dto = pepole.MapTo<Pepole,PepoleDto>();
Console.WriteLine($"id={dto.Id},Name={dto.Name}");
}
class Pepole
{
public int Id { get; set; }
public string Name { get; set; }
}
class PepoleDto
{
public int Id { get; set; }
public string Name { get; set; }
}
2. ORM 条件表达式

EF 框架中 一般可以将 Expression 表达式作为查询条件,但如果是自定义的 ORM 框架,我们该如何将

Expression 表达式生成可执行的 SQL 呢?

  • 新建 Asp.net Mvc 项目,搭建 EF 框架

(1)Nuget 安装: microsoft.entityframeworkcore,

microsoft.entityframeworkcore.sqlserver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Table("product")]
public class Product
{
[Key]
public int Id { get; set; }
public string? PName { get; set; }
public decimal? Price { get; set; }
}
public class MyDbContext:DbContext
{
public MyDbContext(DbContextOptions<MyDbContext>
options):base(options)
{
}
public virtual DbSet<Product> Product { get; set; }
}
  • 在 Program 中加下如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var builder = WebApplication.CreateBuilder(args);
    ILoggerFactory efLogger = LoggerFactory.Create(builder =>
    {
    builder.AddFilter((category, level) => category ==
    DbLoggerCategory.Database.Command.Name && level ==
    LogLevel.Information).AddConsole(); // 创建打印控制台的日志
    });
    builder.Services.AddControllersWithViews();
    builder.Services.AddScoped<MyDbContext>();
    builder.Services.AddDbContext<MyDbContext>(p =>
    {
    p.UseSqlServer(builder.Configuration.GetConnectionString("Sql"));
    p.UseLoggerFactory(efLogger); // 打印EF执行的SQL
    });

    appsettings.json 配置如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    "Logging": {
    "LogLevel": {
    "Default": "Information",
    "Microsoft.AspNetCore": "Warning"
    }
    },
    "AllowedHosts": "*",
    "ConnectionStrings": {
    "Sql": "server=.;uid=sa;pwd=123456;database=unit21"
    }
    }
  • 自定义类 CustomerVisitor 继承至 ExpressionVisitor

    假设你要执行的表达式如下:

    1
    2
    Expression<Func<Product, Boolean>> exp = p =>
    p.Id > 0 && p.PName.Contains("vip");

    ExpressionVisitor 可以解析 Expression 表达式中每一个节点的执行过程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    public class CustomerVisitor : ExpressionVisitor
    {
    // 因为表达式是从右至左进行解析
    // 先进去的条件后面生成,使用栈刚好可以满足条件
    private Stack<string> _condition = new ();
    public String GetCondition()
    {
    string condition = string.Concat(_condition.ToArray());
    _condition.Clear();
    return condition;
    }
    // ([p].[Id] > 0) AND ([p].[PName] LIKE N'%vip%')
    public override Expression? Visit(Expression? node)
    {
    return base.Visit(node);
    }
    // 解析二元表达式,
    protected override Expression VisitBinary(BinaryExpression node)
    {
    _condition.Push(")");
    // 表达式是从右到左进行解析
    base.Visit(node.Right);
    _condition.Push(GetOperation(node.NodeType));
    base.Visit(node.Left);
    _condition.Push("(");
    return node;
    }
    // 常量表达式
    protected override Expression VisitConstant(ConstantExpression
    node)
    {
    var nodeValue = node.Value;
    if (nodeValue!=null)
    {
    if (nodeValue is string or DateTime)
    {
    _condition.Push($"'{node.Value.ToString().Replace("'","")}'");
    }
    else
    {
    _condition.Push(nodeValue.ToString());
    }
    }
    return node;
    }
    // 解析方法
    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
    base.Visit(node.Object); // 方法调用者,如
    p.PName.Contains("vip") 代表着 p.PName 就是方法的调用者
    base.Visit(node.Arguments[0]); // 参数:本例中代表:vip
    var arg = _condition.Pop(); // 拿出参数的值
    var member = _condition.Pop(); // 属性名
    switch (node.Method.Name)
    {
    case "Contains":
    _condition.Push($"{member} like '% {arg.Replace("'","")}%'");
    break;
    case "Equals":
    _condition.Push($"=");
    base.Visit(node.Arguments[0]);
    break;
    default:
    throw new NotSupportedException($"暂不支持{node.Method.Name}");
    }
    return node;
    }
    // 解析参数
    protected override Expression
    VisitParameter(ParameterExpression node)
    {
    return base.VisitParameter(node);
    }
    // 解析成员
    protected override Expression VisitMember(MemberExpression node)
    {
    _condition.Push($"[{node.Member.Name}]");
    return node;
    }
    /// <summary>
    /// 将节点类型 转换为 SQL 运算符
    /// </summary>
    /// <param name="eType"></param>
    /// <returns></returns>
    private string GetOperation(ExpressionType eType)
    {
    return eType switch
    {
    ExpressionType.OrElse => " OR ",
    ExpressionType.Or => "|",
    ExpressionType.AndAlso => " AND ",
    ExpressionType.And => "&",
    ExpressionType.GreaterThan => ">",
    ExpressionType.GreaterThanOrEqual => ">=",
    ExpressionType.LessThan => "<",
    ExpressionType.LessThanOrEqual => "<=",
    ExpressionType.NotEqual => "<>",
    ExpressionType.Add => "+",
    ExpressionType.Subtract => "-",
    ExpressionType.Multiply => "*",
    ExpressionType.Divide => "/",
    ExpressionType.Modulo => "%",
    ExpressionType.Equal => "=",
    _=>""
    };
    }
    }
  • 测试调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    Expression<Func<Product, Boolean>> exp = p =>
    p.Id > 0 && p.PName.Contains("vip");
    CustomerVisitor visitor = new CustomerVisitor();
    visitor.Visit(exp);
    var condition = visitor.GetCondition





    using System.Diagnostics;
    using System.Linq.Expressions;
    using Microsoft.AspNetCore.Mvc;
    using WebApplication_ExpressionDemo.Models;

    namespace WebApplication_ExpressionDemo.Controllers
    {
    public class HomeController : Controller
    {
    private readonly ILogger<HomeController> _logger;
    private readonly MyDbContext _myDbContext;
    public HomeController(ILogger<HomeController> logger, MyDbContext myDbContext)
    {
    _logger = logger;
    _myDbContext = myDbContext;
    }

    public IActionResult Index()
    {
    Expression<Func<Product, Boolean>> exp = p => p.Id > 0 && p.PName.Contains("vip");
    CustomerVisitor visitor = new CustomerVisitor();
    visitor.Visit(exp); // 解析表达式
    Console.WriteLine(visitor.sql);
    return View();
    }

    public IActionResult Privacy()
    {
    return View();
    }

    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
    public IActionResult Error()
    {
    return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
    }
    }
    }

系统信息管理

管理服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ServiceProcess;

namespace MyApp
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{

#region 操作服务的命令
/// <summary>
/// 暂停命令
/// </summary>
public static readonly RoutedUICommand PauseCommand = new RoutedUICommand("暂停", "pause", typeof(MainWindow));
/// <summary>
/// 恢复运行命令
/// </summary>
public static readonly RoutedUICommand ContinueCommand = new RoutedUICommand("恢复", "continue", typeof(MainWindow));
/// <summary>
/// 停止命令
/// </summary>
public static readonly RoutedUICommand StopCommand = new RoutedUICommand("停止", "stop", typeof(MainWindow));
/// <summary>
/// 启动命令
/// </summary>
public static readonly RoutedUICommand StartCommand = new RoutedUICommand("启动", "start", typeof(MainWindow));
#endregion

public MainWindow()
{
InitializeComponent();
}

#region 命令事件处理方法
/*------------------------ 暂停 ------------------------*/
private void OnPauseCanexecute(object sender, CanExecuteRoutedEventArgs e)
{
ServiceController scl = e.Parameter as ServiceController;
if (scl == null)
{
e.CanExecute = false;
return;
}
scl.Refresh();
if (scl.Status == ServiceControllerStatus.Running && scl.CanPauseAndContinue)
{
// 在服务正在运行并且支持暂停时允许操作
e.CanExecute = true;
}
else
{
e.CanExecute = false;
}
}
private void OnPauseExecuted(object sender, ExecutedRoutedEventArgs e)
{
ServiceController scl = e.Parameter as ServiceController;
if (scl == null) return;
scl.Refresh();
if (scl.CanPauseAndContinue)
{
try
{
scl.Pause(); //暂停
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
/*------------------------ 恢复 ----------------------*/
private void OnContinueCanexecute(object sender, CanExecuteRoutedEventArgs e)
{
ServiceController scl = e.Parameter as ServiceController;
if (scl == null)
{
e.CanExecute = false;
return;
}
scl.Refresh();
if (scl.Status == ServiceControllerStatus.Paused && scl.CanPauseAndContinue)
{
// 当服务已暂停后才允许继续
e.CanExecute = true;
}
else
{
e.CanExecute = false;
}
}
private void OnContinueExecuted(object sender, ExecutedRoutedEventArgs e)
{
ServiceController scl = e.Parameter as ServiceController;
if (scl == null)
{
return;
}
scl.Refresh();
if (scl.CanPauseAndContinue)
{
try
{
scl.Continue(); //恢复服务
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
/*------------------------- 停止 -----------------------*/
private void OnStopCanexecute(object sender, CanExecuteRoutedEventArgs e)
{
ServiceController scl = e.Parameter as ServiceController;
if (scl == null)
{
e.CanExecute = false;
return;
}
scl.Refresh();
if (scl.Status != ServiceControllerStatus.Stopped && scl.CanStop)
{
// 当服务处于非停止状态时才允许停止
e.CanExecute = true;
}
else
{
e.CanExecute = false;
}
}
private void OnStopExecuted(object sender, ExecutedRoutedEventArgs e)
{
ServiceController scl = e.Parameter as ServiceController;
if (scl == null)
{
return;
}
scl.Refresh();
if (scl.CanStop)
{
try
{
scl.Stop(); //停止服务
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
/*----------------------------- 启动 ------------------------------*/
private void OnStartCanexecute(object sender, CanExecuteRoutedEventArgs e)
{
ServiceController scl = e.Parameter as ServiceController;
if (scl == null)
{
e.CanExecute = false;
return;
}
scl.Refresh();
if (scl.Status == ServiceControllerStatus.Stopped)
{
// 只有服务被停止后才需要启动
e.CanExecute = true;
}
else
{
e.CanExecute = false;
}
}
private void OnStartExecuted(object sender, ExecutedRoutedEventArgs e)
{
ServiceController scl = e.Parameter as ServiceController;
if (scl == null) return;
scl.Refresh();
// 启动服务
try
{
scl.Start();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
#endregion
}

public static class ServiceData
{
public static ServiceController[] GetData()
{
return ServiceController.GetServices().OrderBy(s=>s.DisplayName).ToArray();
}
}
}

WMI 查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Management;

namespace MyApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
txtHDskID.Clear();
txtMacAddr.Clear();
/*
* 磁盘信息 - Win32_DiskDrive类
* 网卡信息 - Win32_NetworkAdapter类
*/
// 使用ManagementObjectSearcher来进行搜索
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher())
{
try
{
// 查询1:查找物理硬盘
WqlObjectQuery q1 = new WqlObjectQuery("SELECT * FROM Win32_DiskDrive WHERE MediaType = \"Fixed hard disk media\"");
searcher.Query = q1;
// 获取结果
var mc = searcher.Get();
// 读出序列号
foreach (var mob in mc)
{
string str = mob["SerialNumber"] as string;
if (string.IsNullOrWhiteSpace(str) == false)
{
txtHDskID.Text = str;
break;
}
}
// 查询2:查找物理网卡
WqlObjectQuery q2 = new WqlObjectQuery("SELECT * FROM Win32_NetworkAdapter WHERE PhysicalAdapter = True");
searcher.Query = q2;
// 获取结果
mc = searcher.Get();
// 提取MAC地址
foreach (var mob in mc)
{
string str = mob["MACAddress"] as string;
if (!string.IsNullOrEmpty(str))
{
txtMacAddr.Text = str;
break;
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
}
}

读取系统参数

Environment 类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyApp
{
class Program
{
static void Main(string[] args)
{
string msg = string.Empty;
msg += "系统版本:" + Environment.OSVersion.VersionString + "\n";
msg += "计算机名:" + Environment.MachineName + "\n";
msg += "处理器个数:" + Environment.ProcessorCount.ToString() + "\n";
msg += "是否为64操作系统:" + (Environment.Is64BitOperatingSystem ? "是" : "否") + "\n";
msg += "当前登录的用户名:" + Environment.UserName + "\n";
msg += "CLR版本:" + Environment.Version.ToString() + "\n";
msg += "桌面的物理路径:" + Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + "\n";
msg += "“图片”库的物理路径:" + Environment.GetFolderPath(Environment.SpecialFolder.MyPictures) + "\n";
msg += "---------- 当前用户的环境变量列表 ----------\n";
var vars = Environment.GetEnvironmentVariables(EnvironmentVariableTarget.User);
foreach (var k in vars.Keys)
{
msg += k.ToString() + " = " + vars[k].ToString() + "\n";
}
// 输出字符串
Console.WriteLine(msg);

Console.Read();
}
}
}

SystemInformation 类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MyApp
{
public partial class MainForm:Form
{
private Label lbMsg;

private void InitializeComponent()
{
this.lbMsg = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// lbMsg
//
this.lbMsg.Dock = System.Windows.Forms.DockStyle.Fill;
this.lbMsg.Location = new System.Drawing.Point(0, 0);
this.lbMsg.Margin = new System.Windows.Forms.Padding(3);
this.lbMsg.Name = "lbMsg";
this.lbMsg.Padding = new System.Windows.Forms.Padding(8);
this.lbMsg.Size = new System.Drawing.Size(332, 327);
this.lbMsg.TabIndex = 0;
//
// MainForm
//
this.ClientSize = new System.Drawing.Size(332, 327);
this.Controls.Add(this.lbMsg);
this.Name = "MainForm";
this.Text = "MainForm";
this.ResumeLayout(false);

}

public MainForm()
{
InitializeComponent();
Load += OnFormLoad;
}

private void OnFormLoad(object sender, EventArgs e)
{
StringBuilder strBd = new StringBuilder();
strBd.AppendLine("窗口标题栏高度:" + SystemInformation.CaptionHeight.ToString());
strBd.AppendLine("鼠标滑轮的滚动量:" + SystemInformation.MouseWheelScrollLines.ToString() + "行");
/*
* 屏幕的工作区域是指桌面上除去任务栏区域的剩余空间
*/
Rectangle workarerect = SystemInformation.WorkingArea;
strBd.AppendFormat("屏幕工作区域的大小:{0}×{1}\n", workarerect.Width, workarerect.Height);
ScreenOrientation orl = SystemInformation.ScreenOrientation;
string orstr = null;
switch (orl)
{
case ScreenOrientation.Angle0:
orstr = "旋转0度";
break;
case ScreenOrientation.Angle180:
orstr = "旋转180度";
break;
case ScreenOrientation.Angle270:
orstr = "旋转270度";
break;
case ScreenOrientation.Angle90:
orstr = "旋转90度";
break;
default:
orstr = "未知";
break;
}
strBd.AppendLine("屏幕方向:" + orstr);
/*
* 与电池相关的信息仅用于笔记本电脑或移动设备
*/
strBd.AppendLine("电池剩余电量百分比:" + SystemInformation.PowerStatus.BatteryLifePercent.ToString("P0"));
strBd.AppendLine("是否有可用的网络连接:" + (SystemInformation.Network ? "是" : "否"));
strBd.AppendLine("显示器个数:" + SystemInformation.MonitorCount.ToString());
Size msize = SystemInformation.PrimaryMonitorSize;
strBd.AppendFormat("主显示器的屏幕大小:{0}×{1}\n", msize.Width, msize.Height);

lbMsg.Text = strBd.ToString();
}
}
}

写入事件日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
 EventLog[] logs = EventLog.GetEventLogs();
foreach (var item in logs)
{
Console.WriteLine("{0} - {1}", item.Log, item.LogDisplayName);
}

Console.Read();





using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;

namespace MyApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
FormClosing += Form1_FormClosing;
}

void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (EventLog.SourceExists("MyApp"))
{
EventLog.DeleteEventSource("MyApp");
}
}

private void button1_Click(object sender, EventArgs e)
{
EventLog.WriteEntry("MyApp", "这是第一条日志。");
}

private void button2_Click(object sender, EventArgs e)
{
EventLog.WriteEntry("MyApp", "这是第二条日志。");
}

private void Form1_Load(object sender, EventArgs e)
{
// 创建日志来源
if (!EventLog.SourceExists("MyApp"))
{
EventLog.CreateEventSource("MyApp", "Application");
}
}
}
}

进程与线程

进程操作

Process 类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System.Diagnostics;
using Process myProcess = new Process();
// 打开一个记事本
myProcess.StartInfo.FileName = "notepad.exe";
myProcess.StartInfo.CreateNoWindow = true;
myProcess.Start();



string url = "http://www.163.com";
// 启动进程
ProcessStartInfo info = new(url);
// 必须设置该属性,如果是False调用浏览器进程会发生错误
info.UseShellExecute = true;
Process.Start(info);
获取进程列表
1
2
3
4
5
6
7
Console.WriteLine("Pid\t\t进程\t\t\t\t\t内存\t\t优先级");
var ps = Process.GetProcesses();
foreach (var p in ps)
{
var memory = Math.Round(p.WorkingSet64 / 1024.0/1024.0, 2); // 四舍五入,保留两位小数点
Console.WriteLine($" {p.Id}\t\t{p.ProcessName}\t\t\t\t\t{memory}Mb\t\t{p.BasePriority}");
}
重定向输入 / 输出流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
if (string.IsNullOrWhiteSpace(txtCmd.Text))
{
return;
}

ProcessStartInfo start = new ProcessStartInfo();
// 文件名
start.FileName = "cmd.exe";
// 启动参数
start.Arguments = string.Format("/C \"{0}\"", txtCmd.Text);
// 不显示窗口
start.CreateNoWindow = true;
// 禁用UseShellExecute
start.UseShellExecute = false;
// 重定向输出流
start.RedirectStandardOutput = true;
try
{
// 启动进程
Process p = Process.Start(start);
// 获取结果
txtOutPut.Text = p.StandardOutput.ReadToEnd();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}

多线程

.net 中如何实现多线程
  1. 线程肯定也是要执行一段代码的。所以要产生一个线程,必须要先为这个线程写一段它即将要执行的一段代码(方法),相当于是找个人来帮忙做事
  2. 线程启动时,通过委托来调用该方法(委托的好处),
  3. 线程启动时,调用传过来的委托,委托就会执行相应的方法,实现线程执行方法。

多线程开发

1. 单线程带来的问题

​ 如果主线程需要执行某个占时很多的任务,如果此时再想对这个 UI 操作其他任务,就需要排队等待或者

出现无响应的情况。

1
2
3
4
5
6
7
8
9
// 点击单线程
private void btnSingle_Click(object sender, EventArgs e)
{
for (int i = 0; i < 99999; i++)
{
txtNumber.Text = i.ToString();
}
MessageBox.Show("循环完毕");
}

​ 在点击按钮的之后,UI 线程已经处于阻塞状态,界面无法拖动,只有当循环结束之后,我们才可以对界

面进行下一步操作。

2. 使用多线程解决问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 点击单线程
private void btnSingle_Click(object sender, EventArgs e)
{
CountTime();
}
//计数方法
private void CountTime()
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < int.MaxValue; i++)
{
}
watch.Stop();
Console.WriteLine($"执行时间:{watch.ElapsedMilliseconds/1000.0} s");
MessageBox.Show("循环完毕");
}
// 多线程解决UI卡死问题
private void btnMulty_Click(object sender, EventArgs e)
{
// 创建一个线程,并告诉这个线程需要做什么
Thread thread = new Thread(CountTime);
thread.Start();// 告诉线程,可以开始干活了,具体执行时间由CPU决定
}
3. 前后台线程

​ Thread 对象在创建时,默认为前台线程,也就是 IsBackground = false 。前端线程的特点是:

  • 只有当所有的前台线程都关闭,程序才会真正的退出。
  • 即使主线程关了,前台线程也要将它所执行的任务执行完成才会退出。
1
2
3
4
5
6
7
8
9
// 多线程解决UI卡死问题
private void btnMulty_Click(object sender, EventArgs e)
{
// 创建一个线程,并告诉这个线程需要做什么
Thread thread = new Thread(CountTime);
// 设置为后台线程
thread.IsBackground = true;
thread.Start();// 告诉线程,可以开始干活了
}
4. 线程之间参数传递
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 传递一个参数方式
Thread singleParamThread = new Thread(obj =>
{
Console.WriteLine("传过来的这个参数值是:"+obj);
});
singleParamThread.Start("张三");
// 传递多个参数的方式
Thread moreParamThread = new Thread(obj =>
{
Student student = obj as Student;
Console.WriteLine($"Id={student.Id},Name={student.Name}");
});
moreParamThread.Start(new Student(10,"李四"));
record Student(int Id, string Name);

线程安全

跨线程控件调用
问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 修改文本框的内容
void ChangeTxt()
{
for (int i = 0; i < 2000; i++)
{
var a = Convert.ToInt32(txtNumber.Text);
a++;
// 不允许跨线程操作控件
txtNumber.Text = a.ToString();
}
}
// 线程安全问题
private void btnSafe_Click(object sender, EventArgs e)
{
// 创建一个线程,并告诉这个线程需要做什么
Thread thread = new Thread(ChangeTxt);
// 设置为后台线程
thread.IsBackground = true;
thread.Start();// 告诉线程,可以开始干活了
}
解决方案 1
1
2
3
4
5
6
public Form1()
{
InitializeComponent();
// 取消跨线程的检查(有安全性问题)
CheckForIllegalCrossThreadCalls = false;
}

​ CheckForIllegalCrossThreadCalls =true; 的时候 .net 会对程序使用 UI 控件进行安全检查 ,避免跨线程

导致的死锁、状态不对的 bug 。而 CheckForIllegalCrossThreadCalls =false;.net 不会对程序使用 UI 控件

进行安全检查,全靠程序员自己避免以上 bug,实际上还是多个线程对界面控件操作

解决方案 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 修改文本框的内容
void ChangeTxt()
{
for (int i = 0; i < 2000; i++)
{
var a = Convert.ToInt32(txtNumber.Text);
a++;
// 使用委托解决跨线程调用问题
this.Invoke(() =>
{
// 不允许跨线程操作控件
txtNumber.Text = a.ToString();
});
}
}
线程并发
抛出问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 修改文本框的内容
void ChangeTxt()
{
for (int i = 0; i < 2000; i++)
{
var a = Convert.ToInt32(txtNumber.Text);
a++;
Console.WriteLine($"当前线程:{Thread.CurrentThread.Name},a={a}");
// 使用委托解决跨线程调用问题
this.Invoke(() =>
{
// 不允许跨线程操作控件
txtNumber.Text = a.ToString();
});
}
}
// 两个线程同时去计数
private void btnSafe_Click(object sender, EventArgs e)
{
Thread thread = new Thread(ChangeTxt);
thread.Name = "thread1";
thread.IsBackground = true;
thread.Start();
Thread thread2 = new Thread(ChangeTxt);
thread2.Name = "thread2";
thread2.IsBackground = true;
thread2.Start();
}
lock 关键字解决并发

作用: lock 关键字可以用来确保代码块完成运行,而不会被其他线程中断。它可以把一段代码定义为

互斥段(critical section),互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待。这

是通过在代码块运行期间为给定对象获取互斥锁来实现的。在多线程中,每个线程都有自己的资源,但

是代码区是共享的,即每个线程都可以执行相同的函数。这可能带来的问题就是几个线程同时执行一个

函数,导致数据的混乱,产生不可预料的结果,因此我们必须避免这种情况的发生。

缺点: 多线程中频繁使用 lock 会造成性能损耗。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private static readonly object _lock = new(); // 最好是只读的object类型,此变量只为lock锁用
// 修改文本框的内容
void ChangeTxt()
{
for (int i = 0; i < 2000; i++)
{
lock (_lock) // lock 关键字中的代码段,可以保证每次只能有一个线程进入访问
{
var a = Convert.ToInt32(txtNumber.Text);
a++;
Console.WriteLine($"当前线程:{Thread.CurrentThread.Name},a={a}");
// 使用委托解决跨线程调用问题
this.Invoke(() =>
{
// 不允许跨线程操作控件
txtNumber.Text = a.ToString();
});
}
}
}
注意事项及原理
  • 2.1**** 注意事项

    当同步对共享资源的线程访问时,请锁定专用对象实例(例如,private static readonly object Lock =

    new ();)或另一个不太可能被代码无关部分用作 lock 对象的实例。 避免对不同的共享资源使用相同的

    lock 对象实例,因为这可能导致死锁或锁争用。

  • 具体而言,避免将以下对象用作 lock 对象

    • this(调用方可能将其用作 lock)。
    • Type 实例(可以通过 typeof 运算符或反射获取)。
    • 字符串实例,包括字符串文本,(这些可能是暂存的)。
    • 尽可能缩短持有锁的时间,以减少锁争用。
    • 在 lock 语句的正文中不能使用 await 运算符。
    • 特别要注意,不要用值类型
  • 2.2**** 原理 (以下内容比较浅显,太深究内容一篇文章写不完)

    • Q1**:** 大家会注意到,为什么要在 lock 的圆括号里放一个引用类型 object?为什么不可以放一个值类型如 int?

      • A1**:** 因为如果使用了值类型例如 int 作为 lock 锁定的对象,lock 圆括号中的入参是 object 类型当传入了值类型会对传入的对象类型进行转换,那么在 IL 层面会对值类型进行一次装箱(box)操作。那么这种情况下就不具备 lock 锁定需要用到专用对象的稳定性了。

        • ​ IL_0002:ldloc.0

        • ​ IL_0003:box [mscorlib]System.Int32

      • A2**:** 值类型 一般都在线程函数自己的栈里,每个线程局部栈是不一样的,互相之间不会有影响,所以不用锁定一个特例,引用类型值类型字段在堆里,但可以通过 lock 那个引用类型对象就可以实现了

Monitor 监视器

首先 lock Minitor 有什么区别呢?

​ 其实 lock 在 IL 代码中会被翻译成 Monitor。也就是 Monitor.Enter (obj) 和 Monitor.Exit (obj).

​ 微软很照护我们,给了我们语法糖 Lock, 对的,语言糖确实减少了我们不必要的劳动并且让代码更可观,但是如果我们要精细的控制,则必须使用原生类,这里要注意一个问题就是 “锁住什么” 的问题,一般情况下我们锁住的都是静态对象,我们知道静态对象属于类级别,当有很多线程共同访问的时候,那个静态对象对多个线程来说是一个,不像实例字段会被认为是多个。

1
2
3
lock(obj)
{
}

等价于:

1
2
3
4
5
6
7
8
9
10
try
{
Monitor.Enter(obj)
}
catch()
{}
finally
{
Monitor.Exit(obj)
}

所以 lock 能做的,Monitor 肯定能做,Monitor 能做的,lock 不一定能做。那么 Monitor 额外的功能呢?

  1. Monitor.TryEnter (obj,timespan)----timeout 之后,就不执行这段代码了。lock 可是一直会死等的。

  2. 还有 Monitor.Wait () 和 Monitor.Pulse ()。在 lock 代码里面如果调用了 Monitor.Wait (),会放弃对资源的所有权,让别的线程 lock 进来。然后别的线程代码里 Pulse 一下(让原线程进入到等待队列),然后在 Wait 一下释放资源,这样原线程的就可以继续执行了(代码还堵塞在 wait 那句话呢)。也就是说,必须两个或多个线程共同调用 Wait 和 Pulse,把资源的所有权抛来抛去,才不会死锁

生产者消费者模式(需要反复学习)

​ 请用线程结合队列模拟生产者 - 消费者模式,需求描述如下:

  1. 生产者只在仓库未满时进行生产苹果手(最多生产 10 部)并放入队列,仓库满时生产者进程被阻塞
  2. 消费者只在仓库非空时进行消费,仓库为空时消费者进程被阻塞
  3. 当消费者发现仓库为空时通知生产者生产
  4. 当生产者发现仓库满时通知消费者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
ProduceConsumer pc = new ProduceConsumer();
Thread produceThread = new Thread(ProduceJob);
produceThread.Name = "生产者线程";
produceThread.Start();
Thread consumerThread = new Thread(ConsumerJob);
consumerThread.Name = "消费者线程";
consumerThread.Start();
void ProduceJob()
{
int i = 0;
while (true)
{
pc.Produce(i++);
Thread.Sleep(new Random().Next(1000)); //模拟生产所消耗的时间
}
}
void ConsumerJob()
{
while (true)
{
pc.Consumer();
Thread.Sleep(new Random().Next(1000)); //模拟消费所消耗的时间
}
}
/// <summary>
/// 生产者与消费者
/// </summary>
public class ProduceConsumer
{
private static readonly object Lock = new();
private readonly Queue<int> _queue = new Queue<int>();
/// <summary>
/// 生产
/// </summary>
public void Produce(int i)
{
Monitor.Enter(Lock);
_queue.Enqueue(i); // 生产东西,向队列中添加一件商品
Console.WriteLine($"{Thread.CurrentThread.Name} 生产了:{i}");
if (_queue.Count>=10)
{
Monitor.Pulse(Lock); // 通知用于消费的线程去消费
Monitor.Wait(Lock); // 释放 用于生产的线程锁资源
}
Monitor.Exit(Lock);
}
/// <summary>
/// 消费
/// </summary>
public void Consumer()
{
Monitor.Enter(Lock);
if (_queue.Count==0)
{
Monitor.Pulse(Lock); // 通知 用于生产的线程去生产
Monitor.Wait(Lock); // 释放 用于消费的线程锁资源
}
Console.WriteLine($"{Thread.CurrentThread.Name} 消费了:{_queue.Dequeue()}");
Monitor.Exit(Lock);
}
}
Semaphore 信号量
案例

需求描述,某公共厕所共有 3 个蹲位 (3 个信号量),突然来了 10 个人,请使用 Semaphore 来模拟此场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 3个蹲位,0个被占,最大计数为3
Semaphore semaphore = new Semaphore(3,// 3个信号量(3个蹲位,也就是还没人占用)
3);// 这个3 表示最多3个线程(3个人)访问共享资源(上厕所)
var random = new Random();
// 创建10个线程来代表 10个人排队上厕所情形
for (int i = 1; i <= 10; i++)
{
Thread thread = new Thread(() =>
{
semaphore.WaitOne(); // 表示有个人占坑了,信号量减一
Console.WriteLine($"{Thread.CurrentThread.Name} 正上厕所...");
// 模拟上厕所的时间(1-3秒之间)
Thread.Sleep(random.Next(1000,3000));
Console.WriteLine($"{Thread.CurrentThread.Name} 出来了");
semaphore.Release(); // 表示用完厕所了,释放资源,信号量加1
});
thread.Name = $"第{i}个人";
thread.Start();
}
线程等待,挂起,唤醒,终止
1. 线程等待 ****Join

在继续执行标准的 COM 和 SendMessage 消息泵处理期间,阻止调用线程,直到由该实例表示的线程

终止。

大白话就是,将执行权交给当前的实例线程,直到该线程的任务完成。

不等待

1
2
3
4
5
6
7
8
9
10
11
12
var rand = new Random();
for (int i = 0; i < 3; i++)
{
Thread thread = new Thread(() =>
{
Thread.Sleep(rand.Next(1000));
Console.WriteLine($"{Thread.CurrentThread.Name} 任务完成");
});
thread.Name = $"线程{i}";
thread.Start();
}
Console.WriteLine("我不会等待,我直接执行了");

Join**** 等待

1
2
3
4
5
6
7
8
9
10
11
12
13
var rand = new Random();
for (int i = 0; i < 3; i++)
{
Thread thread = new Thread(() =>
{
Thread.Sleep(rand.Next(1000));
Console.WriteLine($"{Thread.CurrentThread.Name} 任务完成");
});
thread.Name = $"线程{i}";
thread.Start();
thread.Join();// 等待任务完成
}
Console.WriteLine("我最后才执行");
2. 挂起,唤醒,终止
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
   private Thread globalThread= null;
private void btnStart_Click(object sender, EventArgs e)
{
globalThread = new Thread(() =>
{
for (int i = 0; i < 100; i++)
{
progressBar1.Invoke((Action) (() =>
{
Thread.Sleep(100);
progressBar1.Value++;
}));
}
});
globalThread.IsBackground = true;
globalThread.Start();
}
//下面三种方法实际过程中已经过时了
//暂停线程
private void btnStop_Click(object sender, EventArgs e)
{
globalThread.Suspend();//挂起就是暂停
}
private void btnContine_Click(object sender, EventArgs e)
{
globalThread.Resume(); //线程继续(唤醒)
}
// 线程终止之后,不可再被唤醒
private void btnAbort_Click(object sender, EventArgs e)
{
globalThread.Abort();
}
线程池
1
2
3
4
5
6
7
8
9
10
11
12
[Test]
public void TestPool()
{
ThreadPool.QueueUserWorkItem(PrintInfo, new Student(10, "任我行"));
}
// stateInfo 表示线程执行所传递的参数
void PrintInfo(object? stateInfo)
{
var student = stateInfo as Student;
Console.WriteLine($"id={student.Id},name={student.Name}");
}
record Student(int Id, string Name);

异步编程

Task

创建任务的三种方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 创建任务1,但未执行
var task1 = new Task(() =>
{
Console.WriteLine("任务1执行了..");
});
// 创建任务2,自动执行
var task2 = Task.Factory.StartNew((obj) =>
{
Console.WriteLine("任务2执行了,接收到的参数:"+obj);
},"任我行码农场");
// 创建任务3,自动执行
var task3 = Task.Run(() =>
{
Console.WriteLine("任务3执行了..");
});
// 任务1执行
task1.Start();
Task.WaitAll(task1,task2,task3); // 等待所有任务执行完
Console.WriteLine("所有的任务执行完毕");
任务等待的四种方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Step3.Unit9
{
public class TaskTest
{
// 创建任务 3种方式
[Test]
public void TestCreate1()
{
// 1.创建,但是不会执行
Task task1 = new Task(() =>
{
Thread.Sleep(1000);
Console.WriteLine("我是任务1");
});
task1.Start();// 执行任务

Thread.Sleep(2000);
}

[Test]
public void TestCreate2()
{
// 创建任务,并自动执行
var task = Task.Factory.StartNew(() =>
{
Console.WriteLine("我是任务2");
});

Thread.Sleep(200);
}

// 传递参数
[Test]
public void TestCreate2WithParam()
{
// 创建任务,并自动执行
var task = Task.Factory.StartNew(x =>
{
Console.WriteLine("我是任务2,参数值是:" + x);
}, 30); // 30 是传给任务的参数,如果需要传递多个参数,可以将这些参数封装到一个对象中,也就是直接传递这个对象

Thread.Sleep(200);
}

[Test]
public void TestCreate3()
{
// 创建任务并直接执行(用得最多)
Task task = Task.Run(() =>
{
Console.WriteLine("我是任务3,");
});
Thread.Sleep(200);
}

[Test]
public void TestCreate3WithReturn()
{

Task<int> task = Task.Run(() =>
{
Console.WriteLine("我是任务3,");
return 30;
});
// 获取返回值
Console.WriteLine(task.Result); // 等待任务执行完毕

}

// 要求:任务执行顺序1-4
[Test]
public async Task TestWait()
{
// 方式一: 调用任务对象的Wait()方法
// 方式二:调用任务对象的Result 属性(任务必须有返回值)
// 方式三:使用await 关键字(await 关键字出现的所在方法必须是async 关键字修改的方法)
// 方式四:使用Task.WaitAll(任务1,任务2.....)

var task1 = Task.Run(() =>
{
Console.WriteLine("任务1");
return 1;
});
// task1.Wait(); // 等待任务执行完毕
//Console.WriteLine(task1.Result);// Result就是任务的返回值
// var result1 = await task1; // 如果等待任务有返回值,则可以在前面定义变量来接收它的返回值

var task2 = Task.Run(() =>
{
Console.WriteLine("任务2");
return 2;
});
// task2.Wait();
//Console.WriteLine(task2.Result);
// var result2 = await task2;


var task3 = Task.Run(() =>
{
Console.WriteLine("任务3");
return 3;
});
//task3.Wait();
//Console.WriteLine(task3.Result);
// var result3 = await task3;

var task4 = Task.Run(() =>
{
Console.WriteLine("任务4");
return 4;
});
//task4.Wait();
//Console.WriteLine(task4.Result);
// var result4 = await task4;

Task.WaitAll(task1, task2, task3, task4);// 一次性等待多个任务执行的话,这种方式,任务执行顺序无法保证

Console.WriteLine("所有的任务都执行完毕");
}

// 延续任务

[Test]
public void TestContinueTask()
{
var task1 = Task.Run(() =>
{
Console.WriteLine("任务1");
return 1;
});
var task2 = Task.Run(() =>
{
Console.WriteLine("任务2");
return 2;
});

// 需求:当任务1,任务2 都执行完毕之后,我再执行任务3
//Task.WaitAll(task1, task2);
//var task3 = Task.Run(() =>
//{
// Console.WriteLine("任务3");
// return 3;
//});
//task3.Wait();


var task3 = Task.WhenAll(task1, task2).ContinueWith(t =>
{
Console.WriteLine("延续任务3");
Console.WriteLine("t=" + string.Join(",", t.Result)); // t.Result:task1, task2 任务的返回值
});
task3.Wait();


Console.WriteLine("所有的任务都执行完毕");
}


// 任务取消
[Test]
public void TestCancle()
{
// 创建一个取消任务对象
CancellationTokenSource source = new CancellationTokenSource();
// CancellationToken token = source.Token; // 获取取消任务的令牌

var task1 = Task.Run(() =>
{
for (int i = 0; i < 10; i++)
{
if (!source.IsCancellationRequested) // 如果任务没有被取消,则执行如下代码
{
Console.WriteLine($"第{i}次执行");
Thread.Sleep(1000);
}

}
});

// 需求:如果5钞钟还未执行完毕,任务就需要被取消掉
//Thread.Sleep(5000);
//// 取消任务
//source.Cancel();

source.CancelAfter(5000);// 5秒之后自动取消


// 取消任务之后的回调函数
var register = source.Token.Register(() =>
{
Console.WriteLine("任务已被取消。。。。");
});
task1.Wait();


Console.WriteLine("程序执行结束");

}
}
}

执行延续任务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Task task1 = new Task(() => {
Thread.Sleep(500);
Console.WriteLine("线程1执行完毕!");
});
task1.Start();
Task task2 = new Task(() => {
Thread.Sleep(1000);
Console.WriteLine("线程2执行完毕!");
});
task2.Start();
//task1,task2执行完了后执行后续操作
var task3 = Task.WhenAll(task1, task2).ContinueWith((t) => {
Thread.Sleep(100);
Console.WriteLine("执行后续操作完毕!");
});
Task.WaitAll(task1, task2, task3);
Console.WriteLine("主线程执行完毕!");
任务取消

Task 中有一个专门的类 CancellationTokenSource 来取消任务执行 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CancellationTokenSource source = new CancellationTokenSource();
int index = 0;
//开启一个task执行任务
Task task1 = new Task(() =>
{
while (!source.IsCancellationRequested)
{
Thread.Sleep(1000);
Console.WriteLine($"第{++index}次执行,线程运行中...");
}
});
task1.Start();
//五秒后取消任务执行
Thread.Sleep(5000);
//source.Cancel()方法请求取消任务,IsCancellationRequested会变成true
source.Cancel();

​ CancellationTokenSource 的功能不仅仅是取消任务执行,我们可以使用 source.CancelAfter (5000) 实现 5 秒后自动取消任务,也可以通过 source.Token.Register (Action action) 注册取消任务触发的回调函数,即任务被取消时注册的 action 会被执行 .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
CancellationTokenSource source = new CancellationTokenSource();
//注册任务取消的事件
source.Token.Register(() =>
{
Console.WriteLine("任务被取消后执行xx操作!");
});
int index = 0;
//开启一个task执行任务
Task task1 = new Task(() =>
{
while (!source.IsCancellationRequested)
{
Thread.Sleep(1000);
Console.WriteLine($"第{++index}次执行,线程运行中...");
}
});
task1.Start();
//延时取消,效果等同于Thread.Sleep(5000);source.Cancel();
source.CancelAfter(5000);
task1.Wait();
await async

异步方法

异步方法必须用 async 修饰,方法名一般以 Async 结尾,这是一种命名规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var task = Test1Async();
var task2 =Test2Async();
await Task.Delay(2000); // 等待了2秒钟,可以换成 Task.WaitAll(task, task2); 看看效果
stopwatch.Stop();
Console.WriteLine($"执行总耗时:{stopwatch.ElapsedMilliseconds/1000.0} s");
async Task Test1Async()
{
await Task.Delay(2000); // await 出现的地方,方法必须有async 修饰
Console.WriteLine("Test1 任务执行完成");
}
async Task Test2Async()
{
await Task.Delay(2000); // await 出现的地方,方法必须有async 修饰
Console.WriteLine("Test2 任务执行完成");
}



// See https://aka.ms/new-console-template for more information
using System.Diagnostics;

Console.WriteLine("Hello, World!");

Stopwatch watch = new Stopwatch();
watch.Start();

// 以下三个方法都会以异步的方式进行执行
var task1 = Test1Async();
var task2 = Test2Async();
var task3 = Test3Async();
//Task.WaitAll(task1, task2, task3); // 等待所有任务执行完毕


// 如果你下一行代码必须要用到上一个异步方法的结果,那么我们可以使用await 关键来等待结果
// 以下三个方法等同于:同步方法
//var result1 = await Test1Async(); // await作用是:等待任务执行完毕,相当于是同步方法
//await Test2Async(); // 没有返回值,最好不要加await
//await Test3Async();





watch.Stop();
Console.WriteLine($"耗时:{watch.ElapsedMilliseconds/1000.0} s");


async Task<int> Test1Async()
{
//Thread.Sleep(1000); // 不要用这个来代表等待,如果用了,此方法的异步功效将失效
await Task.Delay(1000);// 等待一秒钟,await关键字出现的地方,一定是async 关键字修饰的方法
Console.WriteLine("Test1");
return 10;
}

async Task Test2Async()//无返回值得用Task
{
await Task.Delay(1000);
Console.WriteLine("Test2");
}

async Task Test3Async()
{
await Task.Delay(1000);
Console.WriteLine("Test3");
}
异步等待

如果某些方法的返回值,你需要立马得到结果才可以进行下一步操作时,可以使用 await 完成异步方法的完成。

等待结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 因为马上要用到这个任务的结果,所以必须要用await 等待结果
Product product = await GetProductAsync();
List<Order> result = await GetOrdersByProductAsync(product.Id);
// 获取商品信息
async Task<Product> GetProductAsync()
{
return new Product(1, "vip", 600);
}
// 根据商品Id获取所有订单数据
async Task<List<Order>> GetOrdersByProductAsync(int productId)
{
List<Order> Orders = new List<Order>
{
new Order(1,1,1,600,"任我行"),
new Order(2,2,1,300,"李四"),
new Order(3,1,1,600,"张三"),
};
return Orders.Where(p => p.ProductId == productId).ToList();
}
record Product(int Id,string ProductName,decimal Price);
record Order(int Id,int ProductId,int Count,int TotalPrice,string UserName);

也可以这样拿结果

1
2
var productTask = GetProductAsync(); // 不等待结果
List<Order> result = await GetOrdersByProductAsync(productTask.Result.Id);

productTask.Result 其实也是等待结果的作用。

Thread Task**** 的区别(面试点)

  1. Task 是基于 Thread 的,是比较高层级的封装(它是在线程池的基础之上), Task 最终还是需要 Thread 来执行

  2. Task 默认使用后台线程执行, Thread 默认使用前台线程

  3. Task 可以有返回值, Thread 没有返回值 , 虽然 Thread 可以通过 Start 方法参数来进行返回值处理,但十分不便。

  4. Task 可以执行后续操作, Thread 不能执行后续操作

  5. Task 可取消任务执行, Thread 不行

  6. 异常传播 , Thread 在父方法上获取不到异常,而 Task 可以。

并行任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//Parallel.invoke(xxx,xxx,xxx);
//Parallel.foreach();

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace MyApp
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private void OnClick(object sender, RoutedEventArgs e)
{
int num1 = default(int);
if (!int.TryParse(txtInput1.Text, out num1))
{
MessageBox.Show("请输入第一个基数。");
return;
}
int num2 = default(int);
if (!int.TryParse(txtInput2.Text, out num2))
{
MessageBox.Show("请输入第二个基数。");
return;
}
int num3 = default(int);
if (!int.TryParse(txtInput3.Text, out num3))
{
MessageBox.Show("请输入第三个基数。");
return;
}

// 声明用于并行操作的委托实例
Action act1 = () =>
{
int sum = 0;
for (int x = 1; x <= num1; x++)
{
sum += x;
}
// 显示结果
this.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => run1.Text = sum.ToString()));
};
Action act2 = () =>
{
int sum = 0;
for (int x = 1; x <= num2; x++)
{
sum += x;
}
// 显示结果
this.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => run2.Text = sum.ToString()));
};
Action act3 = () =>
{
int sum = 0;
for (int x = 1; x <= num3; x++)
{
sum += x;
}
// 显示结果
this.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => run3.Text = sum.ToString()));
};

// 开始执行并行任务
Parallel.Invoke(act1, act2, act3);
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO;

namespace MyApp
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private void OnClick(object sender, RoutedEventArgs e)
{
if (string.IsNullOrWhiteSpace(txtDir.Text))
{
MessageBox.Show("请输入有效的目录名。");
return;
}
// 如果目录不存在,先创建目录
if (!Directory.Exists(txtDir.Text))
{
Directory.CreateDirectory(txtDir.Text);
}

int fileNum = 0;
if (!int.TryParse(txtFileNum.Text, out fileNum))
{
MessageBox.Show("请输入文件数量。"); return;
}
long fileSize = 0L;
if (long.TryParse(txtSize.Text, out fileSize) == false)
{
MessageBox.Show("请输入正确的文件大小。");
return;
}
// 产生文件名列表
List<string> fileNames = new List<string>();
for (int n = 0; n < fileNum; n++)
{
// 文件名
string _filename = "file_" + (n + 1).ToString();
// 目录的绝对路径
string _dirpath = System.IO.Path.GetFullPath(txtDir.Text);
// 组建新文件的全路径
string fullPath = System.IO.Path.Combine(_dirpath, _filename);
fileNames.Add(fullPath);
}
txtDisplay.Clear();
// 用于产生随机字节
Random rand = new Random();

// 用于执行任务的委托实例
Action<string> act = (file) =>
{
FileInfo fileInfo = new FileInfo(file);
// 如果文件存在,则删除
if (fileInfo.Exists)
fileInfo.Delete();
// 将数据写入文件
byte[] buffer = new byte[fileSize];
rand.NextBytes(buffer);
using (FileStream fs = fileInfo.Create())
{
BinaryWriter sw = new BinaryWriter(fs);
sw.Write(buffer);
sw.Close();
sw.Dispose();
}
// 显示结果
string msg = string.Format("文件{0}已成功写入。\n", fileInfo.Name);
this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(() => txtDisplay.AppendText(msg)));
};

// 启动循环任务
Parallel.ForEach<string>(fileNames, act);
}
}
}

Threadlocal

多个线程共享一个变量,但每个线程都希望具有独立的数据版本。

1
2
3
4
5
6
7
8
9
10
11
static Random rand = new Random();
static ThreadLocal<int> local = new ThreadLocal<int>();

static void Dowork()
{
// 赋值
local.Value = rand.Next();
// 取值
Console.WriteLine($"当前线程ID:{Thread.CurrentThread.ManagedThreadId}{nameof(local)}的值:{local.Value}。");
}

AsyncLocal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 声明线程间共享变量
static ThreadLocal<int> local = new ThreadLocal<int>();
static void Main(string[] args)
{
Task t = RunAsync();
t.Wait(); //等待任务完成
Console.Read();
}

static async Task RunAsync()
{
local.Value = 1000; //赋值
Console.WriteLine("异步等待前local变量的值:{0},当前线程ID:{1}。", local.Value,Thread.CurrentThread.ManagedThreadId);
await Task.Delay(60); //异步等待
Console.WriteLine("异步等待后local变量的值:{0},当前线程ID:{1}。", local.Value,Thread.CurrentThread.ManagedThreadId);
}

//等待后会换线程,之前的值会找不到
//异步等待前local变量的值:1000,当前线程ID:1。
//异步等待后local变量的值:0,当前线程ID:4。


//Task异步等待上下文之间数据同步而提供的。
static AsyncLocal<string> local = new AsyncLocal<string>();

static void Main(string[] args)
{
RunAsync().Wait();
Console.Read();

}
static async Task RunAsync()
{
local.Value = "context data";
Console.WriteLine("异步等待前,local变量的值:{0}", local.Value);
await Task.Delay(100);
Console.WriteLine("异步等待后,local变量的值:{0}", local.Value);
}

通道

通道内部自动维护者一个线程安全的队列,数据的写入与读取可以在不同的线程上完成。

Channel

CreateUnbounded–没有容量限制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static async Task Main(string[] args)
{
// 创建通道实例
Channel<string> cnl = Channel.CreateUnbounded<string>();

// 在新线程上写入数据
_ = Task.Run(async () =>
{
// 写入四个字符串类型的值
await cnl.Writer.WriteAsync("item-1");
await cnl.Writer.WriteAsync("item-2");
await cnl.Writer.WriteAsync("item-3");
await cnl.Writer.WriteAsync("item-4");
// 通知数据消费者数据写入完毕
cnl.Writer.Complete();
});

// 读出通道中的数据
while(true)
{
try
{
string x = await cnl.Reader.ReadAsync();
Console.WriteLine($"从通道中读出:{x}");
}
catch(ChannelClosedException)
{
Console.WriteLine("通道已关闭");
break; // 跳出循环
}
}
}
CreateBounded–有容量限制,背压模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 通道队列的容量为3
BoundedChannelOptions options = new(3);
// 设置背压模式
options.FullMode = BoundedChannelFullMode.DropOldest;
// 创建通道
Channel<int> mychan = Channel.CreateBounded<int>(options);

// 向通道写入数据
Task tw = Task.Run(async () =>
{
for(int n = 1; n < 7; n++)
{
await mychan.Writer.WriteAsync(n);
Console.WriteLine("已写入:{0}", n);
}
// 写入完成
mychan.Writer.Complete();
});

// 从通道读取数据
Task tr = Task.Run(async () =>
{
// 等待2秒再开始读取
await Task.Delay(2000);
await foreach(int item in mychan.Reader.ReadAllAsync())
{
Console.WriteLine("从通道读出:{0}", item);
}
});

Task.WaitAll(tw, tr);
}

网络编程(已经操作过一遍,后续还需要学习)

Socket

客户端代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public partial class ClientFrm : Form
{
public ClientFrm()
{
InitializeComponent();
}
//创建一个Socket(手机),IPv4,流式类型,使用Tcp传输协议(三次握手)
Socket socket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
private void btnConnect_Click(object sender, EventArgs e)
{
//准备服务端需要绑定的IP+端口(网络结点)
EndPoint point = new IPEndPoint(IPAddress.Parse(txtIP.Text),
Convert.ToInt32(txtPort.Text));
//与服务端建立连接
socket.Connect(point);
txtMsg.AppendText("客户端连接成功!!!\r\n");
Task.Run(() => Receive());
}
void Receive()
{
while (true)//不停的接收服务端发送过来的消息
{
//准备一个2M的缓冲区,用于接收服务端发送过来的消息
var buffer = new byte[1024 * 1024 * 2];
//接收消息,len:实际接收的长度,
int len = socket.Receive(buffer);
txtMsg.Invoke((Action) (() =>
{
txtMsg.AppendText("服务端说了:" + Encoding.UTF8.GetString(buffer) + "\r\n");
}));
}
}
private void btnSend_Click(object sender, EventArgs e)
{
socket.Send(System.Text.Encoding.UTF8.GetBytes(txtSendMsg.Text));
txtMsg.AppendText($"我说了:{txtSendMsg.Text}!!!\r\n");
}
}
服务端代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
public partial class ServerFrm : Form
{
public ServerFrm()
{
InitializeComponent();
}
//创建一个Socket(手机),IPv4,流式类型,使用Tcp传输协议(三次握手)
Socket socket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
private void btnStart_Click(object sender, EventArgs e)
{
//准备服务端需要绑定的IP+端口(网络结点)
EndPoint point = new IPEndPoint(IPAddress.Parse(txtIP.Text),
Convert.ToInt32(txtPort.Text));
//绑定
socket.Bind(point);
//设置一个监听队列(10:代表我一次只能和10个客户端进行通讯)
socket.Listen(10);
txtMsg.AppendText("服务端启动成功!!!\r\n");
Task.Run(() => Connect());
}
void Connect()
{
while (true)
{
//建立与客户端的连接(开始打电话)
var clientSocket = socket.Accept();
txtMsg.Invoke((Action)(() =>
{
txtMsg.AppendText($"客户端连接成功{clientSocket.RemoteEndPoint}\r\n");
lbOnline.Items.Add(new ClientInfo()
{
ClientSocket = clientSocket,
RemoteEndPoint = clientSocket.RemoteEndPoint.ToString()
});
}));
//DialogResult result= MessageBox.Show("你确定要关闭吗?", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
// if (result == DialogResult.Yes)
// {
// //就是点的是Yes
// }
Thread thread = new Thread(Receive);
thread.IsBackground = true;
thread.Start(clientSocket);
}
}
void Receive(object obj)
{
//不停的接收客户端发送过来的消息
while (true)
{
var clientSocket = obj as Socket;
//准备一个2M的缓冲区,用于接收服务端发送过来的消息
var buffer = new byte[1024 * 1024 * 2];
//接收消息,len:实际接收的长度,
int len = clientSocket.Receive(buffer);
txtMsg.Invoke((Action)(() =>
{
txtMsg.AppendText($"{clientSocket.RemoteEndPoint}说了:" + Encoding.UTF8.GetString(buffer) + "\r\n");
}));
}
}
//发送消息
private void btnSend_Click(object sender, EventArgs e)
{
var info = lbOnline.SelectedItem as ClientInfo;
if (info == null)
{
MessageBox.Show("请选择一个需要通讯的客户端");
return;
}
//把字符串转换为 byte[]字节数组
var buffer = System.Text.Encoding.UTF8.GetBytes(txtSendMsg.Text);
//发送消息
info.ClientSocket.Send(buffer);
txtMsg.AppendText($"服务端说了:{txtSendMsg.Text}\r\n");
}
}
class ClientInfo
{
public string RemoteEndPoint { get; set; }
public Socket ClientSocket { get; set; }
}

常用工具类(还没完整学习,后续还需要学习)

1. 压缩与解压缩

  1. 将老师提供的 utils 文件夹 中的 ZipHelper.cs 类 复制到当前你的项目中
  2. ZipHelper 依赖于 ICSharpCode.SharpZipLib.dll ,通过 Nuget 可将其引用进来
  3. 创建一个 winform 项目,布局如下
选择单文件
1
2
3
4
5
6
7
8
9
10
11
12
private void btnSelectFile_Click(object sender, EventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
// 过滤文件类型
openFileDialog.Filter = @"文本文件|*.txt|PDF文件|*.pdf|图片文
件|*.png;*.jpg;*.gif;*.jpeg|所有文件|*.*";
var result = openFileDialog.ShowDialog();
if (result == DialogResult.OK)//打开文件对话框
{
txtFilePath.Text = openFileDialog.FileName;//获取选中文件的路径
}
}
压缩单文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void btnSingleCompare_Click(object sender, EventArgs e) 
{
ZipHelper helper = new ZipHelper();
//实例化一个保存文件对话框
SaveFileDialog saveFileDialog = new SaveFileDialog();
//过滤器,要求保存格式为zip
saveFileDialog.Filter = @"压缩文件|*.zip";
//设置保存文件的默认名称
saveFileDialog.FileName = DateTime.Now.ToString("yyyyMMddHHmmss");
//设置保存文件默认的后缀名
saveFileDialog.DefaultExt = "zip";
if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
//saveFileDialog.FileName:保存文件的路径
helper.ZipFile(txtFilePath.Text,saveFileDialog.FileName);
MessageBox.Show("压缩完成");
}
}
选择多文件
1
2
3
4
5
6
7
8
9
10
11
OpenFileDialog open = new OpenFileDialog();
//过滤文件为文本文件和doc
open.Filter = @"文本文件|*.txt|PDF文件|*.pdf|图片文件|*.png;*.jpg;*.gif;*.jpeg|
所有文件|*.*";
open.Multiselect = true;//设置为多选
if (open.ShowDialog() == DialogResult.OK)
{
//获取选中的多个文件
var fileNames = open.FileNames;
fileList.Items.AddRange(fileNames);
}
压缩多文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void btnCompareAll_Click(object sender, EventArgs e)
{
ZipHelper helper = new ZipHelper();
List<string> files = new List<string>();//保存待压缩文件的路径
foreach (string item in fileList.Items)
{
files.Add(item);
}
SaveFileDialog saveFileDialog = new SaveFileDialog();
//过滤器,要求保存格式为zip
saveFileDialog.Filter = @"压缩文件|*.zip";
//设置保存文件的默认名称
saveFileDialog.FileName = DateTime.Now.ToString("yyyyMMddHHmmss");
//设置保存文件默认的后缀名
saveFileDialog.DefaultExt = "zip";
if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
helper.ZipManyFilesOrDictorys(files, saveFileDialog.FileName,null);
MessageBox.Show("压缩完成");
}
}
解压文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void btnUnZip_Click(object sender, EventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = @"压缩文件|*.zip";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
// 文件夹浏览器
FolderBrowserDialog folderBrowser = new FolderBrowserDialog();
if (folderBrowser.ShowDialog() == DialogResult.OK)
{
ZipHelper zipHelper = new ZipHelper();
// 解压文件
zipHelper.UnZip(openFileDialog.FileName,folderBrowser.SelectedPath);
MessageBox.Show("解压完成");
}
}
}
全部压缩
1
2
3
4
5
6
7
8
9
10
11
12
13
// 全部压缩
private void button5_Click(object sender, EventArgs e)
{
SaveFileDialog dialog = new SaveFileDialog();
dialog.FileName = DateTime.Now.ToString("yyyyMMddHHmmss"); // 默认名称
dialog.DefaultExt = "zip"; // 默认后缀
if (dialog.ShowDialog() == DialogResult.OK)
{
ZipHelper helper = new ZipHelper();
var files = listBox1.Items.Cast<string>();
helper.ZipManyFilesOrDictorys(files, dialog.FileName, null);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.IO.Compression;

namespace MyApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

#region 压缩/解压缩
/// <summary>
/// 压缩文件
/// </summary>
/// <param name="sourceFile">源文件</param>
/// <param name="distFile">目标文件</param>
private async Task CompressFileAsync(string sourceFile, string distFile)
{
FileInfo srcFileinfo = new FileInfo(sourceFile);
FileInfo disFileinfo = new FileInfo(distFile);
// 排除隐藏文件
if ((srcFileinfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
return;
// 排除系统文件
if (srcFileinfo.Attributes.HasFlag(FileAttributes.System))
return;
// 排除压缩文件
if (srcFileinfo.Attributes.HasFlag(FileAttributes.Compressed))
return;

FileStream inStream = null;
FileStream outStream = null;
try
{
// 打开文件流
inStream = srcFileinfo.OpenRead();
outStream = disFileinfo.OpenWrite();
// 创建压缩流
GZipStream gstream = new GZipStream(outStream, CompressionMode.Compress);
await inStream.CopyToAsync(gstream);
// 关闭压缩流
gstream.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
// 关闭文件流
if (inStream != null) inStream.Close();
if (outStream != null) outStream.Close();
}
}
/// <summary>
/// 解压文件
/// </summary>
/// <param name="srcFile">源文件</param>
/// <param name="disFile">目标文件</param>
private async Task DecompressFileAsync(string srcFile, string disFile)
{
FileInfo srcFileinfo = new FileInfo(srcFile);
FileInfo disFileinfo = new FileInfo(disFile);

FileStream inStream = null, outStream = null;
try
{
inStream = srcFileinfo.OpenRead();
outStream = disFileinfo.OpenWrite();
// 创建压缩流
GZipStream gstream = new GZipStream(inStream, CompressionMode.Decompress);
await gstream.CopyToAsync(outStream);
// 关闭压缩流
gstream.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
// 关闭文件流
if (inStream != null) inStream.Close();
if (outStream != null) outStream.Close();
}
}
#endregion

private void btnBrowsFile1_Click(object sender, EventArgs e)
{
if (rdbCompress.Checked)
{
openFileDialog1.Filter = "所有文件(*.*)|*.*";
}
else
{
openFileDialog1.Filter = "压缩文件(*.gz)|*.gz";
}
if (openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
txtSrcFile.Text = openFileDialog1.FileName;
}
}

private void btnBrowsFile2_Click(object sender, EventArgs e)
{
if (rdbCompress.Checked)
{
saveFileDialog1.Filter = "压缩文件(*.gz)|*.gz";
}
else
{
saveFileDialog1.Filter = "所有文件(*.*)|*.*";
}
if (saveFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
txtDisFile.Text = saveFileDialog1.FileName;
}
}

private async void btnExe_Click(object sender, EventArgs e)
{
if (File.Exists(txtSrcFile.Text) == false) return;
if (txtDisFile.Text == "") return;
if (rdbCompress.Checked) //压缩
{
// 开始压缩
this.panel1.Enabled = this.panel2.Enabled = this.btnExe.Enabled = false;
await CompressFileAsync(txtSrcFile.Text, txtDisFile.Text);
this.panel1.Enabled = this.panel2.Enabled = this.btnExe.Enabled = true;
}
else //解压缩
{
// 开始解压缩
this.panel1.Enabled = this.panel2.Enabled = this.btnExe.Enabled = false;
await DecompressFileAsync(txtSrcFile.Text, txtDisFile.Text);
this.panel1.Enabled = this.panel2.Enabled = this.btnExe.Enabled = true;
}
}
}
}

2. 图片处理

  1. 从老师提供的 utils 文件夹中将 ImageHelper 类拷贝至当前你的项目中
  2. 创建一个 winform 项目,布局如下
生成验证码

布局:

选择 tabcontrol,然后选择 pictureBox,PlaceholderText 进行输入内嵌文本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 生成的验证码
private string _checkCode;
private void btnGenerateCode_Click(object sender, EventArgs e)
{
//out:表示输出参数,也就是返回的字符串的验证码
var stream = ImageHelper.GenerateCheckCode(out _checkCode);
//将内存流转换为图片
pictureBox1.Image = Image.FromStream(stream);
}
// 验证
private void btnCheck_Click(object sender, EventArgs e)
{
if (_checkCode.Equals(txtCode.Text))
{
MessageBox.Show("正确");
}
else
{
MessageBox.Show("验证码输入错误");
}
}
生成文字水印
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void btnWord_Click(object sender, EventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter ="图片文件|*.png;*.jpg;*;PNG;*.JPG";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
SaveFileDialog save = new SaveFileDialog();
save.Filter = "图片文件|*.png;*.jpg;*;PNG;*.JPG";
save.DefaultExt = "png";
save.FileName = DateTime.Now.ToString("yyyyMMddHHmmss");
if (save.ShowDialog() == DialogResult.OK)
{
//生成文字水印
ImageHelper.GenerateTextWatermark(openFileDialog.FileName,
save.FileName,txtWord.Text,18,"宋体",9, 100);
}
}
}
生成图片水印
1
2
3
4
5
6
7
8
9
10
11
12
// 生成图片水印
private void btnGeneratorImage_Click(object sender, EventArgs e)
{
SaveFileDialog dialog = new SaveFileDialog();
dialog.Filter = "图片文件|*.jpg;*.JPG;*.png;*.PNG;*.jpeg;*.JPEG;*.gif;*.GIF";
dialog.DefaultExt = Path.GetExtension(txtPath.Text); // 拿到后缀名
dialog.FileName = DateTime.Now.ToString("yyyyMMddHHmmss");
if (dialog.ShowDialog() == DialogResult.OK)
{
ImageHelper.GenerateImageWatermark(txtPath.Text, @"C:\Users\Administrator\Desktop\96b5411ff8f028b6c0b809fb0876e3ec.jpeg", dialog.FileName, 9, 10, 100);
}
}
生成缩略图
  1. 定义好枚举,表示若干缩略图大小

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    /// <summary>
    /// 图片大小
    /// </summary>
    public enum ImageSize
    {
    /// <summary>
    /// 50×50
    /// </summary>
    [Description("50×50")]
    Size_50 = 50,
    /// <summary>
    /// 100×100
    /// </summary>
    [Description("100×100")]
    Size_100 = 100,
    /// <summary>
    /// 150×150
    /// </summary>
    [Description("150×150")]
    Size_150 = 150,
    /// <summary>
    /// 220×220
    /// </summary>
    [Description("220×220")]
    Size_220 = 220,
    /// <summary>
    /// 350×350
    /// </summary>
    [Description("350×350")]
    Size_350 = 350
    }
    1. 从老师提供的 utils 文件夹将 EnumHelper 文件拷贝至当前你的项目中

    2. 生成缩略图

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      private void txtGenerate_Click(object sender, EventArgs e)
      {
      //获取文件名称
      var fileInfo = new FileInfo(txtImagePath.Text);
      var fileName = fileInfo.Name.Split('.')[0];
      var newPath = Path.Combine(fileInfo.DirectoryName, fileName);
      if (Directory.Exists(newPath))
      {
      //如果这个要创建的文件夹存在 ,则删了,并且把子目录 也一并删除
      Directory.Delete(newPath,true);
      }
      Directory.CreateDirectory(newPath);
      //获取所有需要生成缩略图的尺寸
      var sizes = EnumHelper.ToDescriptionDictionary<ImageSize>();
      foreach (var size in sizes)
      {
      var thumbnailFilePath = Path.Combine(newPath, $"
      {size.Key}_{size.Key}.png");
      ImageHelper.CreateThumbnail(txtImagePath.Text,
      thumbnailFilePath, size.Key, size.Key);
      }
      }

3. NPOI 操作

​ NPOI,顾名思义,就是 POI 的.NET 版本。 那 POI 又是什么呢? POI 是一套用 Java 写成的库,能够帮助开

发者在没有安装微软 Office 的情况下读写 Office 97-2007 的文件, 支持的文件格式包括 xls,xlsx,

doc,docx, ppt 等。

优缺点

  1. 读写速度快(有个国外的兄弟回复说,他原来用 ExcelPackage 生成用了 4-5 个小时,现在只需要 4-5 分钟)

  2. 稳定性好(相对于用 Office OIA 而言,毕竟那东西是基于 Automation 做的,在 Server 上跑个 Automation 的东西, 想想都觉得可怕),跑过了将近 1000 个测试用例(来自于 POI 的 testcase 目录)

  3. API 简单易用,当然这得感谢 POI 的设计师们 第五,完美支持 Excel 2003 格式(据说 myxls 无法正确读取 xls 模板,但 NPOI 可以),以后也许是所有 Office 2003 格式

缺点

​ 大文件操作占内存,对于数据量较大的文件导入操作可能不太支持。

功能实现

​ Nuget 引用包:NPOI,microsoft.entityframeworkcore,microsoft.entityframeworkcore.sqlserver

Excel**** 导入

​ Html 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<head>
<title>上传文件</title>
</head>
<body>
<div>
<form enctype="multipart/form-data" action="/home/import"
method="post">
<input type="file" name="myexcel"/>
<input type="submit" value="上传" class="btn btn-primary" />
</form>
</div>
</body>
</html>

第一种实现方式:

注意点:id 得是 primary key 主键才行,不然会有很多问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// 原始方式
[HttpPost]
public IActionResult Import(IFormFile myexcel)
{
// 后缀名
var extension = Path.GetExtension(myexcel.FileName);
using var stream = myexcel.OpenReadStream();
// 工作簿
IWorkbook workbook;
if (extension.Equals(".xls"))
{
workbook = new HSSFWorkbook(stream);
}
else // xlsx
{
workbook = new XSSFWorkbook(stream);
}
// sheet 表
var sheet = workbook.GetSheetAt(0);
List<Product> list = new List<Product>();
// 因为第一行是表头,所以i=1,而不是i=0
for (int i = 1; i <= sheet.LastRowNum; i++)
{
var row = sheet.GetRow(i);
Product product = new()
{
PName = row.GetCell(0).ToString(),
Price = Convert.ToDecimal(row.GetCell(1).ToString())
};
list.Add(product);
}
_myDbContext.AddRange(list);
_myDbContext.SaveChanges();
workbook.Close();
return Content("导入完成");
}



// 导入的实现
[HttpPost]
public IActionResult Import(IFormFile myexcel) // myexcel 在跟页面上的Input file="myexcel" 保持一致
{
var ext = Path.GetExtension(myexcel.FileName); // .xls
IWorkbook workbook;

if (ext.Equals(".xls"))
{
// 将上传的excel文件填充到工作簿中
workbook = new HSSFWorkbook(myexcel.OpenReadStream()); // HSSFWorkbook:xls
}
else
{
workbook = new XSSFWorkbook(myexcel.OpenReadStream()); // XSSFWorkbook:xlsx
}

// 读excel
var sheet = workbook.GetSheetAt(0);
// i=1.一定要记得
for (int i = 1; i <= sheet.LastRowNum; i++)
{
var row = sheet.GetRow(i);
Product product = new Product();
product.PName = row.GetCell(0).ToString();
product.Price = Convert.ToDecimal(row.GetCell(1).ToString());

myContext.Products.Add(product);
}

myContext.SaveChanges();

return Content("导入完成");
}

第二种实现方式 (使用工具类):

​ 从 utils 文件夹中拷贝 ExcelHelper 工具类至当前项目中,ExcelHelper 中对 IWorkBook 接口扩展了 Import 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[HttpPost]
public IActionResult Import(IFormFile myexcel)
{
// 工作簿
IWorkbook workbook=null;
// 建立属性名与Excel表头关系
Dictionary<string, string> dict = new()
{
{"PName", "名称"},{"Price", "价格"}
};
_myDbContext.AddRange(workbook.Import<Product>(myexcel,dict));
_myDbContext.SaveChanges();
return Content("导入完成");
}

[HttpPost]
public async Task<IActionResult> Import(IFormFile myexcel) // myexcel 在跟页面上的Input file="myexcel" 保持一致
{

IWorkbook wook = null;
Dictionary<string, string> heads = new() {
{ "PName","商品名称"},
{ "Price","商品价格"}
};

var list = wook.Import<Product>(myexcel, heads);
var task = myContext.AddRangeAsync(list);

myContext.SaveChanges();
return Content("导入完成");
}
Excel 导出

​ ExcelHelper 中对 IWorkBook 接口扩展了 Export 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[HttpGet]
public IActionResult Export()
{
var products = _myDbContext.Product.ToList();
IWorkbook workbook=null;
// 建立属性名与Excel表头关系
Dictionary<string, string> dict = new()
{
{"PName", "名称"},
{"Price", "价格"}
};
var memoryStream = workbook.Export(dict,products);
return File(memoryStream, "application/ostet-stream", "产品列表.xls");
}

[HttpGet]
public IActionResult Export()
{
var list = myContext.Products.ToList();
IWorkbook work = null;
Dictionary<string, string> heads = new()
{
{ "PName", "商品名称" },
{ "Price", "商品价格" }
};
var ms = work.Export(heads, list);

return File(ms.ToArray(), "application/ostet-stream", DateTime.Now.ToString("yyyyMMdd") + ".xls");
}

4. EasyExcel 操作

EasyExcel是一个简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。

​ 官网地址: https://easyexcel.opensource.alibaba.com/(阿里出品) , UP 主也推荐使用 EasyExcel。

​ Nuget 引用包: Rong.EasyExcel

​ up 主试过使用 EasyExcel 对 100 万条数据的 excel 进行操作过,并无性能瓶颈。

** 导入操作 **
  1. 准备实例类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Product
    {
    [IgnoreColumn] // 因为Id是自增的,所以Excel表头中并无此列,必须忽略此字段
    public int Id { get; set; }
    [Display(Name = "名称")]
    public string? PName { get; set; }
    [Display(Name = "价格")]
    public decimal? Price { get; set; }
    }

    IgnoreColumn: 设置忽略某字段

    Display: 与 Excel 表头形成映射关系,Name 属性值必须要与表头的列名保持一致

  2. 在 Program 类中将 EasyExcel 相关服务进行注册

    1
    builder.Services.AddNpoiExcel();
  3. 注入服务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    private IExcelImportManager _excelImportManager; // easyExcel 导入服务
    private MyDbContext _myDbContext; // EF 服务
    public ExcelController(
    IExcelImportManager excelImportManager,
    MyDbContext db
    )
    {
    _excelImportManager = excelImportManager;
    _myDbContext = db;
    }
  4. 完成导入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    [HttpPost]
    public IActionResult Import(IFormFile myexcel)
    {
    using Stream stream = myexcel.OpenReadStream();
    List<ExcelSheetDataOutput<Product>> data = new();
    try
    {
    data = _excelImportManager.Import<Product>(stream,
    p =>
    {
    p.CheckError();
    // 检查错误
    p.SheetIndex =
    0;
    });
    // 获取有效数据
    var allData = data.GetAllData().ToList();
    _myDbContext.Product.AddRange(allData);
    _myDbContext.SaveChanges();
    return Content("导入完成");
    }
    catch (Exception e)
    {
    // 获取错误数据
    var error = data.GetErrorMessage();
    return Content(error);
    }
    }

    注意:需要把多余的 sheet 删掉

导出操作
  1. 注入导出服务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    [HttpPost]
    public IActionResult Import(IFormFile myexcel)
    {
    using Stream stream = myexcel.OpenReadStream();
    List<ExcelSheetDataOutput<Product>> data = new();
    try
    {
    data = _excelImportManager.Import<Product>(stream,
    p =>
    {
    p.CheckError();
    // 检查错误
    p.SheetIndex =
    0;
    });
    // 获取有效数据
    var allData = data.GetAllData().ToList();
    _myDbContext.Product.AddRange(allData);
    _myDbContext.SaveChanges();
    return Content("导入完成");
    }
    catch (Exception e)
    {
    // 获取错误数据
    var error = data.GetErrorMessage();
    return Content(error);
    }
    }

    注意:需要把多余的 sheet 删掉

    导出操作

    1. 注入导出服务

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      private readonly IExcelExportManager _excelExportManager;
      private MyDbContext _myDbContext; // EF 服务
      public ExcelController(
      IExcelExportManager excelExportManager,
      MyDbContext db
      )
      {
      _excelExportManager = excelExportManager;
      _myDbContext = db;
      }
    2. 实现导出

      1
      2
      3
      4
      5
      6
      7
      [HttpGet]
      public IActionResult Export()
      {
      var products = _myDbContext.Product.ToList();
      return File(_excelExportManager.Export(products),
      "application/ostet-stream", "产品列表.xls");
      }

5. 雪花算法

1. 传统的主键策略

主键自增

​ 传统的主键自增在分布式系统中极易出现主键重复情况,特别是在高并发的情况下。

GUID

​ GUID 的主要目的是产生完全唯一的数字。在理想情况下,任何计算机和计算机集群都不会生成两个相

同的 GUID。GUID 的总数也足够大,达到了 2128(3.4×1038)个,所以随机生成两个相同 GUID 的可

能性是非常小的,但并不为 0。所以,用于生成 GUID 的算法通常都加入了非随机的参数(如时间),以

保证这种重复的情况不会发生。

缺点:

1、存储空间大(16 byte),因此它将会占用更多的磁盘大小。 如果你建的索引越多, 影响越严重。

2、很难记忆。join 操作性能比整数要低。

3、没有内置的函数获取最新产生的 guid 主键。

4、GUID 做主键将会添加到表上的其他索引中,因此会降低性能,影响插入速度。

5、GUID 之间比较大小相对数字慢不少, 影响查询速度

2. SnowFlake 算法 - 分布式 ID
SnowFlake算法生成id的结果是一个64bit大小的整数 ,它是一个分布式ID,分布式ID 具有以下特点:

优点:

  1. 毫秒数在高位,自增序列在低位,整个 ID 都是趋势递增的。

  2. 作为 DB 表的主键,索引效率高。

  3. 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成 ID 的性能也是非常高的。

  4. 高性能高可用:生成时不依赖于数据库,完全在内存中生成。

  5. 容量大,每秒中能生成数百万的自增 ID。

  6. 可以根据自身业务特性分配 bit 位,非常灵活。

缺点:

  1. 强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。
  2. 不是严格全局递增的 (有点像莫须有的罪名,也不算是缺点)。
3. 原理结构图
  1. 1bit,不用,因为二进制中最高位是符号位,1 表示负数,0 表示正数。生成的 id 一般都是用整数,所以最高位固定为 0。

  2. 41bit 时间戳,毫秒级。可以表示的数值范围是 (2^41-1),转换成单位年则是 69 年。

  3. 10bit 工作机器 ID,用来表示工作机器的 ID,包括 5 位 datacenterId 和 5 位 workerId。也就是最多可以部署 1024 台雪花机器。

  4. 12bit 序列号,用来记录同毫秒内产生的不同 id,12 位可以表示的最大整数为 4095,来表示同一机器同一时间截(毫秒) 内产生的 4095 个 ID 序号。 如果超过这个数字,则会等下一毫秒再生成

服务一定需要开启才有用,而且端口号得用开启后的端口 6379 才行不然连接不上

redis-server.exe redis.windows.conf 执行该命令

4. .Net 实现

在提供的 utils 文件夹中拷贝 Snowflake 文件夹至当前项目中,里面包含了 IdWorker(雪花语法的具体实现)

1
2
3
4
5
6
7
8
9
10
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var idWorker = new IdWorker(0,0);
for (int i = 0; i < 1000000; i++)
{
var nextId = idWorker.NextId();
Console.WriteLine(nextId);
}
stopwatch.Stop();
Console.WriteLine($"共花费了:{stopwatch.ElapsedMilliseconds/1000.0} s");
5. 雪花算法常见问题

​ 其实我们之前有提到工作机器 Id 的作用,就是用于解决分布式 Id 重复的问题,这个 workerId 是通过构造

方法传入的,如果我们用 10 位来存储这个值,那就是最多支持 1024 个节点

WorkerId 重复问题

​ 如果 workerId 相同,意味着在同一毫秒内,生成的雪花 Id 是有非常大的机率重复。虽然雪花 Id 共 64 位,但是同一毫秒,同一个 workerId ,意味着前 42+10 = 52 位 都是相同的数字,仅靠 剩下的 12 位序列号是很难保证分布式系统中雪花 ID 不重复。所以不同的服务器,它所用的 WorkerId 必须不同,这样才能尽可能的避免并发情况下 Id 重复问题。

解决方案

  1. 在 redis 中存储一个当前 workerId 的最大值

  2. 每次生成 workerId 时,从 redis 中获取到当前 workerId 最大值,并 + 1 作为当前 workerId,并存入 redis

  3. 如果 workerId 为 1023,自增为 1024,则重置 0,作为当前 workerId,并存入 redis

  4. IdWorker 这个类必须设置为单例模式

代码实现

  1. 项目中添加 Nuget 包,搜索 Microsoft.Extensions.Caching.StackExchangeRedis(版本一定要比 redis 的版本低才行)
  2. 优化雪花算法工具包封装类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.StackExchangeRedis;
namespace Snowflake
{
/// <summary>
/// Snowflake辅助类,利用IP生成Idworker
/// </summary>
public static class SnowflakeUtil
{
private static readonly object LockMac = new object();
/// <summary>
/// 过期秒数
/// </summary>
private static long _workId;
private static IdWorker? _idWorker;
/// <summary>
/// 单例模式,创建IdWorker
/// </summary>
/// <returns>IdWorker实例</returns>
public static IdWorker CreateIdWorker()
{
if (_idWorker == null)
{
lock (LockMac)
{
if (_idWorker == null)
{
_workId = GetWorkId();
_idWorker = new IdWorker(_workId,0);
}
}
}
return _idWorker;
}
// redis 中存储当前最大的workerId值,以解决workerId重复问题
private static int GetWorkId()
{
// 因为是在静态类中,所以无法使用注入方式提供对象,只能自己实例化对象
IDistributedCache redis = new RedisCache(new
RedisCacheOptions
{
Configuration = "localhost:6379"
});
int workerId = 0;
string maxWorkerId = redis.GetString("max_worker_id");
if (!string.IsNullOrWhiteSpace(maxWorkerId))
{
workerId = Convert.ToInt32(maxWorkerId)+1;
}
redis.SetString("max_worker_id",workerId.ToString());
return workerId;
}
}
}

Linq 高级查询

延迟执行与强制立即执行

LinQ 查询是延迟执行,运行到 foreach 语句处会跳回 select 语句处。

1
2
3
4
5
6
7
8
9
10
11
12
13
  int[] source = { 1, 2, 3, 4, 5, 6 };
// 查询不会立刻执行
var query = from n in source select n;
// 执行查询
foreach (int x in query)
{
Console.Write(" {0}", x);
}

Console.ReadKey();


//立即执行,Count、Tolist、ToArray

Linq**** 实现的方式

1
2
3
4
5
6
7
8
9
10
11
12
//from i in data where i.sal>=2000 order by descending select i;
List<Emp> data = new List<Emp>;
data.Add(new Emp(){Name=“”,Sal=}); …..
// 使用Linq查询
var list = from i in data where i.Sal>=20000
orderby i.Sal descending
select i;
//输出
foreach(var item in list )
{
Console.Write(item.Name + “:” +item.Sal);
}

联合查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
//var、dynamic是匿名类型
//DefaultEmpty方法返回分组中的所有水元素

using System;
using System.Collections.Generic;
using System.Linq;

namespace MyApp
{

#region 定义类型
/// <summary>
/// 图书分类信息
/// </summary>
public class Category
{
/// <summary>
/// 分别ID
/// </summary>
public int catID { get; set; }
/// <summary>
/// 分类名
/// </summary>
public string catName { get; set; }
}

/// <summary>
/// 图书信息
/// </summary>
public class BookInfo
{
/// <summary>
/// 图书ID
/// </summary>
public int BookID { get; set; }
/// <summary>
/// 书名
/// </summary>
public string BookName { get; set; }
/// <summary>
/// 图书所属分类的ID
/// </summary>
public int CateID { get; set; }
}
#endregion

class Program
{
static void Main(string[] args)
{
// 图书分类示例数据
List<Category> bookCategs = new List<Category>
{
new Category { catID = 201, catName = "文学类" },
new Category { catID = 202, catName = "经济管理类" },
new Category { catID = 203, catName = "机械工程类" },
new Category { catID = 204, catName = "法律基础类" }
};

// 图书信息示例数据
List<BookInfo> books = new List<BookInfo>
{
new BookInfo { BookID = 1, BookName = "图书01", CateID = 202 },
new BookInfo { BookID = 2, BookName = "图书02",CateID = 204 },
new BookInfo { BookID = 3, BookName = "图书03", CateID = 201 },
new BookInfo { BookID = 4, BookName = "图书04", CateID = 202 },
new BookInfo { BookID = 5, BookName = "图书05",CateID = 204 },
new BookInfo { BookID = 6, BookName = "图书06", CateID = 204 },
new BookInfo { BookID = 7, BookName = "图书07", CateID = 203 },
new BookInfo { BookID = 8, BookName = "图书08",CateID = 202 },
new BookInfo { BookID = 9, BookName = "图书09", CateID = 203 },
new BookInfo { BookID = 10, BookName = "图书10", CateID = 202 },
new BookInfo { BookID = 11, BookName = "图书11", CateID = 201 },
new BookInfo { BookID = 12, BookName = "图书12", CateID = 203 },
new BookInfo { BookID = 13, BookName = "图书13", CateID = 201 },
new BookInfo { BookID = 14, BookName = "图书14", CateID = 204 },
new BookInfo { BookID = 15, BookName = "图书15", CateID = 203 },
new BookInfo { BookID = 16, BookName = "图书16", CateID = 202 },
};

// 联合查询,并产生新的类型
var qryres = from b in books
join c in bookCategs on b.CateID equals c.catID
select new
{
b.BookName,
c.catName
};
// 输出结果
foreach (var bitem in qryres)
{
Console.WriteLine("图书名:{0},所属分类:{1}", bitem.BookName, bitem.catName);
}

Console.WriteLine();

// 第二个联合查询
var qryres2 =
from bk in books
join bc in bookCategs on bk.CateID equals bc.catID
select new
{
Book_ID = bk.BookID,
Book_Name = bk.BookName,
Book_Cate = bc.catName
};
// 输出结果
foreach (var bk in qryres2)
{
Console.WriteLine("图书ID:{0},图书名:{1},所属分类:{2}", bk.Book_ID, bk.Book_Name, bk.Book_Cate);
}

Console.Read();
}
}
}





using System;
using System.Collections.Generic;
using System.Linq;

namespace MyApp
{

#region 定义类型
/// <summary>
/// 专辑信息
/// </summary>
public class Album
{
/// <summary>
/// 专辑名称
/// </summary>
public string Title { get; set; }
/// <summary>
/// 发行年份
/// </summary>
public int Year { get; set; }
/// <summary>
/// 专辑描述
/// </summary>
public string Description { get; set; }
}

/// <summary>
/// 曲目信息
/// </summary>
public class Track
{
/// <summary>
/// 曲目名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 艺术家名字
/// </summary>
public string Artist { get; set; }
/// <summary>
/// 所属专辑
/// </summary>
public Album AlbumOf { get; set; }
}
#endregion

class Program
{
static void Main(string[] args)
{
// 专辑列表
Album album1 = new Album { Title = "专辑 1", Year = 2003, Description = "这是第一张专辑。" };
Album album2 = new Album { Title = "专辑 2", Year = 2009, Description = "这是第二张专辑。" };
List<Album> albums = new List<Album> { album1, album2 };

// 曲目列表
Track track1 = new Track { Name = "曲目 1", Artist = "艺术家 1", AlbumOf = album1 };
Track track2 = new Track { Name = "曲目 2", Artist = "艺术家 2", AlbumOf = album2 };
Track track3 = new Track { Name = "曲目 3", Artist = "艺术家 3", AlbumOf = album2 };
Track track4 = new Track { Name = "曲目 4", Artist = "艺术家 4", AlbumOf = album1 };
Track track5 = new Track { Name = "曲目 5", Artist = "艺术家 5", AlbumOf = album2 };
Track track6 = new Track { Name = "曲目 6", Artist = "艺术家 6", AlbumOf = null };
List<Track> tracks = new List<Track> { track1, track2, track3, track4, track5, track6 };

// 开始查询
var res1 = from t in tracks
join a in albums on t.AlbumOf equals a into g1
from a1 in g1.DefaultIfEmpty()
select new
{
TrackName = t.Name,
Artist = t.Artist,
AlbumName = a1 == null ? "未知专辑" : a1.Title
};

// 以下代码会发生异常
//var res1 = from t in tracks
// join a in albums on t.AlbumOf equals a into g1
// from a1 in g1.DefaultIfEmpty()
// select new
// {
// TrackName = t.Name,
// Artist = t.Artist,
// AlbumName = a1.Title
// };

// 输出结果
foreach (var item in res1)
{
Console.WriteLine("曲目:{0},艺术家:{1},专辑:{2}", item.TrackName, item.Artist, item.AlbumName);
}

Console.WriteLine();

var res2 = from t in tracks
join a in albums on t.AlbumOf equals a into g
from a2 in g.DefaultIfEmpty(new Album { Title = "<未知>", Year = 0, Description = "<无>" })
select new
{
TrackName = t.Name,
Year = a2.Year,
Artist = t.Artist,
AlbumName = a2.Title,
AlbumDesc = a2.Description
};
// 输出结果
foreach (var item in res2)
{
Console.WriteLine("曲目:{0},年份:{1},艺术家:{2},专辑:{3},专辑描述:{4}", item.TrackName, item.Year, item.Artist, item.AlbumName, item.AlbumDesc);
}

Console.Read();
}
}
}

嵌套查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
using System;
using System.Linq;

namespace MyApp
{
#region 定义类型
/// <summary>
/// 商品信息
/// </summary>
public class Goods
{
/// <summary>
/// 商品编号
/// </summary>
public string GsNo { get; set; }
/// <summary>
/// 商品名称
/// </summary>
public string GsName { get; set; }
/// <summary>
/// 商品单价
/// </summary>
public double GsPrice { get; set; }
}

/// <summary>
/// 销售单信息
/// </summary>
public class SalesOrder
{
/// <summary>
/// 单据ID
/// </summary>
public int OrderID { get; set; }
/// <summary>
/// 商品编号
/// </summary>
public string GoodsNo { get; set; }
/// <summary>
/// 销售时间
/// </summary>
public DateTime Time { get; set; }
/// <summary>
/// 销售数量
/// </summary>
public int Qty { get; set; }
}
#endregion

class Program
{
static void Main(string[] args)
{
// 商品信息 - 示例数据
Goods[] goodsArr =
{
new Goods { GsNo = "G-1", GsName = "报纸", GsPrice = 1.50d },
new Goods { GsNo = "G-2", GsName = "食盐", GsPrice = 3.65d },
new Goods { GsNo = "G-3", GsName = "火柴", GsPrice = 0.50d },
new Goods { GsNo = "G-4", GsName = "灯泡", GsPrice = 12.30d },
new Goods { GsNo = "G-5", GsName = "剪刀", GsPrice = 4.50d }
};

// 销售单据 - 示例数据
SalesOrder[] orders =
{
new SalesOrder { OrderID = 1, GoodsNo = goodsArr[0].GsNo, Qty = 3, Time = new DateTime(2014, 1, 2) },
new SalesOrder { OrderID = 2, GoodsNo = goodsArr[1].GsNo, Qty = 5, Time = new DateTime(2014, 1, 4) },
new SalesOrder { OrderID = 3, GoodsNo = goodsArr[2].GsNo, Qty = 2, Time = new DateTime(2014, 1, 12) },
new SalesOrder { OrderID = 4, GoodsNo = goodsArr[3].GsNo, Qty = 6, Time = new DateTime(2014, 1, 20) },
new SalesOrder { OrderID = 5, GoodsNo = goodsArr[4].GsNo, Qty = 1, Time = new DateTime(2014, 2, 3) },
new SalesOrder { OrderID = 6, GoodsNo = goodsArr[2].GsNo, Qty = 4, Time = new DateTime(2014, 2, 9) },
new SalesOrder { OrderID = 7, GoodsNo = goodsArr[1].GsNo, Qty = 8, Time = new DateTime(2014, 3, 13) },
new SalesOrder { OrderID = 8, GoodsNo = goodsArr[3].GsNo, Qty = 10, Time = new DateTime(2014, 3, 11) },
new SalesOrder { OrderID = 9, GoodsNo = goodsArr[0].GsNo, Qty = 15, Time = new DateTime(2014, 3, 18) },
new SalesOrder { OrderID = 10, GoodsNo = goodsArr[0].GsNo, Qty = 7, Time = new DateTime(2014, 2, 22) },
new SalesOrder { OrderID = 11, GoodsNo = goodsArr[3].GsNo, Qty = 20, Time = new DateTime(2014, 3, 17) },
new SalesOrder { OrderID = 12, GoodsNo = goodsArr[1].GsNo, Qty = 13, Time = new DateTime(2014, 1, 29) },
new SalesOrder { OrderID = 13, GoodsNo = goodsArr[2].GsNo, Qty = 8, Time = new DateTime(2014, 2, 9) },
new SalesOrder { OrderID = 14, GoodsNo = goodsArr[4].GsNo, Qty = 21, Time = new DateTime(2014, 3, 16) },
new SalesOrder { OrderID = 15, GoodsNo = goodsArr[2].GsNo, Qty = 6, Time = new DateTime(2014, 2, 15) }
};

/*
* 查询并计算出各个商品的
* 总销售额
*/
var res = from g in goodsArr
let totalQty =
/* 以下为嵌套查询 */
(from od in orders
where od.GoodsNo == g.GsNo
select od).Sum(odr => odr.Qty)
select new
{
g.GsNo,
g.GsName,
/* 计算总销售额 */
Total = totalQty * g.GsPrice
};

// 输出查询结果
foreach (var item in res)
{
Console.WriteLine("编号:{0},商品:{1},总额:{2:C2}", item.GsNo, item.GsName, item.Total);
}

Console.Read();
}
}
}

Linq - 标准语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
create table Student
(
Id int primary key identity,
StudentName varchar(30),
Sex char(2),
ClassId int,
Birthday datetime
);
go
insert into Student values('花木兰','女',1001,getdate());
insert into Student values('张三','男',1001,getdate());
insert into Student values('李四','女',1001,getdate());
insert into Student values('王五','男',1002,getdate());
insert into Student values('任我行','男',1002,getdate());
go
create table Score
(
Course varchar(30),
StudentId int,
Degree int
);
go
insert into Score values('C#高级',1,90);
insert into Score values('Java',1,40);
insert into Score values('PHP',1,60);
insert into Score values('C#高级',2,93);
insert into Score values('Java',2,76);
insert into Score values('PHP',2,90);
insert into Score values('C#高级',3,96);
insert into Score values('Java',3,70);
insert into Score values('PHP',3,68);
insert into Score values('C#高级',4,95);
insert into Score values('Java',4,73);
insert into Score values('PHP',4,58);
insert into Score values('C#高级',5,85);
insert into Score values('Java',5,77);
insert into Score values('PHP',5,88);
public class Student
{
public int Id { get; set; }
public string StudentName { get; set; }
public string Sex { get; set; }
public int ClassId { get; set; }
public Nullable<System.DateTime> Birthday { get; set; }
}
public class Score
{
public string Course { get; set; }
public int StudentId { get; set; }
public Nullable<int> Degree { get; set; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
using NUnit.Framework;
namespace Step3.Unit12;
public class LinqTest
{
MyContext context = new MyContext();
//获取所有的学生
[Test]
public void Test1()
{
// select * from student
// IQueryable<Student>: 相当于只是准备好了一条SQL语句,并未执行
IQueryable<Student> query = from s in context.Students select s;
// 如何执行 IQueryable<Student>: ToList(),也可以对它进行循环
var list = query.ToList();
}
//获取指定字段
[Test]
public void Test2()
{
// select id,studentName from Student
var query = from s in context.Students
select new
{
s.Id,
s.StudentName
};
// 匿名对象,要点出来的话,只能在本方法内才可以点得出来
foreach (var s in query)
{
Console.WriteLine($"id={s.Id},name={s.StudentName}");
}
}
// 取别名
[Test]
//select new { xxx = xx.xxx}
public void Test3()
{
//select StudentName as 姓名,Sex as 性别 from Student
var query = from a in context.Students
select new
{
姓名 = a.StudentName,
性别 = a.Sex
};
foreach (var s in query)
{
Console.WriteLine($"姓名={s.姓名},性别={s.性别}");
}
}
//使用投影来解决返回类型问题,将匿名类型转换为具体的强类型(投影)
[Test]
public void Test4()
{
// select studentName as NickName,Sex as Gender from Student
var query = from a in context.Students
select new StudentDto
{
NickName = a.StudentName,
Gender = a.Sex,
Id = a.Id,
ClassId = a.ClassId
};
foreach (var s in query)
{
Console.WriteLine($"姓名={s.NickName},性别={s.Gender}");
}
}
// 排序
[Test]
public void Test5()
{
// 单字段排序
var query = from s in context.Students
orderby s.Id ascending
select s;
// 多字段排序
var query2 = (from s in context.Students orderby s.Id ascending, s.StudentName descending //descending:降序
select s).ToList();
}
// 模糊查询
//like 用contains("xx"),startswith,endswith
[Test]
public void Test6()
{
// like '%任%'
var query = from s in context.Students
where s.StudentName.Contains("任") orderby s.Id ascending select s;
// like '任%'
var query2 = from s in context.Students where s.StudentName.StartsWith("任")
orderby s.Id ascending select s;
// like '%任'
var query3 = from s in context.Students where s.StudentName.EndsWith("任")
orderby s.Id ascending select s;
}
// 多条件查询
[Test]
public void Test7()
{
var query = from s in context.Students
// where s.StudentName.Contains("任") && s.ClassId==1001
// orderby s.Id ascending // 如果是动态拼接SQL条件的话,排序需要放到最后来
select s;
var studentName = "任"; // 假设这个两个变量是从前端的查询框中传过来的查询条

var classId = 1001;
if (!string.IsNullOrWhiteSpace(studentName))
{
query = query.Where(p => p.StudentName.Contains(studentName));
}
if (classId > 0)
{
query = query.Where(p => p.ClassId == classId);
}
// 多字段排序,排序需要放到条件拼接完之后来
query = query.OrderByDescending(p => p.Id).ThenByDescending(p =>
p.StudentName);
}
//分页
[Test]
public void Test8()
{
var query = from s in context.Students
select s;
/**
* 每页10,查询第二页,
* 第二页的数据应该从:11-20
*/
int pageIndex = 2;
int pageSize = 10;
// Skip: 跳过多少条数据,
// Take: 只查询多少条
// 分页之前一条要排序
query = query.OrderByDescending(p => p.Id).Skip((pageIndex - 1) *
pageSize).Take(pageSize);
}
//多表联查(内连接)
//select * from xxx join xxx in score on xxx.id equals xxx.id
[Test]
public void Test9()
{
// select
stu.id,stu.birthday,stu.classid,score.course,score.degree,stu.sex as
gender, stu.studentname as nickname
// from Student stu join Score score on stu.Id= score.StudentId
var query = from stu in context.Students
join score in context.Scores on stu.Id equals score.StudentId
select new StudentDto
{
Id = stu.Id,
Birthday = stu.Birthday,
ClassId = stu.ClassId,
Course = score.Course,
Degree = score.Degree,
Gender = stu.Sex,
NickName = stu.StudentName
};
}
[Test]
//left join---xxx into temp xxx
public void Test10()
{
// select
stu.id,stu.birthday,stu.classid,t.course,t.degree,stu.sex as gender,
stu.studentname as nickname
// from Student stu left join Score t on stu.Id= t.StudentId
// 左连接:会以Student表为主表,没有数据没有匹配上,则会以NULL进行填充
var query = from stu in context.Students
join score in context.Scores on stu.Id equals score.StudentId
into temp
from t in temp.DefaultIfEmpty()
select new StudentDto
{
Id = stu.Id,
Birthday = stu.Birthday,
ClassId = stu.ClassId,
Course = t.Course,
Degree = t.Degree,
Gender = stu.Sex,
NickName = stu.StudentName
};
Console.WriteLine(query.Count()); // select count(*) .....
}
// 分组查询
//group by xx into xxx
[Test]
public void Test11()
{
// 每个班有多少人
// select ClassId,count(Id) from Student group by ClassId
var query = from s in context.Students
group s by s.ClassId
into gs
select new
{
classId = gs.Key, // ClassId: 分组的键,
count = gs.Count()
};
foreach (var g in query)
{
Console.WriteLine($"班级:{g.classId},数量:{g.count}");
}
}
// 筛选出学号大于5的学员
// 并按课程进行分组
// 学号按降序排列
res = from s in students
orderby s.StuID descending
where s.StuID > 5
group s by s.Course into g
select g;
// 输出
Console.WriteLine("\n********************************************");
foreach (var g in res)
{
Console.WriteLine("----- {0} -----", g.Key);
foreach (Student stu in g)
{
Console.WriteLine("学号:{0},姓名:{1},课程:{2}", stu.StuID, stu.StuName, stu.Course);
}
Console.WriteLine();
}



// 每组下的数据
//xxx.Tolist().groupby(g=>g.id)
[Test]
public void Test12()
{
// 方法语法
var query = context.Students.ToList().GroupBy(p=>p.ClassId);
// 查询每组下的数据
foreach (var g in query)
{
Console.WriteLine($"班级:{g.Key}下的数据是:");
foreach (var item in g.ToList())
{
Console.WriteLine($"id={item.Id},name={item.StudentName}");
}
}
}
[Test]
public void GroupHaving()
{
// select ClassId,count(Id) from Student group by ClassId havng count(id)>1
var query = from s in context.Students group s by s.ClassId into gs where gs.Count()>1
select new
{
Key = gs.Key,
Count = gs.Count()
};
foreach (var q in query)
{
Console.WriteLine($"classId={q.Key},数量={q.Count}");
}
}
}

技术篇

数据库

关系数据库

MySQL

windows 身份验证

SqlServer 身份验证

127.0.0.1、localhost、查询 ip 地址是本地

数据库操作:1、使用 SSMS 方式,2、T-SQL 方式

数据库的操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
--创建数据库
create database step2_unit3 on primary(
Name = '第三单元数据库测试',
filename = "E:\sql学习\第三单元数据库测试.mdf",
size = 5mb,
filegrowth = 4mb,
maxsize = 200mb
);
--创建次文件
alter database step2_unit3 add file(
Name = '第三单元数据库测试_次文件',
filename = "E:\sql学习\第三单元数据库测试_次文件.ndf",
size = 5mb,
filegrowth = 4mb,
maxsize = 200mb
);

create database step2_unit4;
use master;
drop database step2_unit4;
-- 查看数据库信息
exec sp_helpdb 'step2_unit4';

--修改数据库名称
exec sp_renamedb 'step2_unit4','unit4';

go
--备份
backup database step2_unit4 to disk ='E:\sql学习\第三单元数据库测试_bak.bak';
go
--还原
--如果数据库存在的情况下加上with replace,否则不用
restore database step2_unit4 from disk = 'E:\sql学习\第三单元数据库测试_bak.bak' with replace;

--分离
exec sp_detach_db 'step2_unit4';
--附加
--附加前需要查看数据库所在路径下的对应mdf
exec sp_attach_db 'step2_unit4','E:\Program Files\Microsoft SQL Server\MSSQL14.MSSQLSERVER\MSSQL\DATA\step2_unit4.mdf';
创建表操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use step2_unit4;
create table Student(
Id int, -- 学生编号
Nickname varchar(20), -- 姓名
Mobile char(11), -- 手机号
Email varchar(30) -- 邮箱
);
alter table Student add Sex bit,Age tinyint;

alter table Student alter column Sex char(4);

alter table Student drop column Email;

--需要加上具体哪张表的字段
exec sp_rename 'MyStudent.NickName' ,'Name' ,'column';

exec sp_rename 'Student','MyStudent';

create table Teacher(
Id int identity, -- 学生编号,identity:默认是从1开始自增,每次增加1
Nickname varchar(20), -- 姓名
Mobile char(11), -- 手机号
Email varchar(30) -- 邮箱
);
表约束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
create table Teacher(
--列级约束
Id int identity primary key, -- 学生编号,identity:默认是从1开始自增,每次增加1
Nickname varchar(20), -- 姓名
Mobile char(11), -- 手机号
Email varchar(30) -- 邮箱
);
create table Score(
Id int identity, -- 学生编号
Nickname varchar(20), -- 姓名
Mobile char(11), -- 手机号
Email varchar(30) -- 邮箱
--表级约束
constraint pk_Score_id primary key(id)
);
create table StudentMate(
Id int identity, -- 学生编号
Nickname varchar(20), -- 姓名
Mobile char(11), -- 手机号
Email varchar(30) -- 邮箱
);
-- 表级约束
alter table StudentMate add constraint pk_StudentMate_id primary key(id);

create table score
(
studentId int,
courseId int,
score int,
addTime datetime,
primary key(studentId,courseId) -- 复合主键
)

create table Category3(
Id int identity, -- 商品分类编号, primary key 是主键约束
CategoryName varchar(20)
)
-- 表级约束
alter table Category3
add constraint pk_category3_id primary key(Id)

-- 商品表
create table Product
(
Id int primary key identity, -- 商品编号
ProductName varchar(20),
Price decimal(8,2), -- 8,2 指的是,加上小数最多可以有8位,和2位小数
-- 商品分类,这里的int 必须与Category商品表中的Id的类型保持一致
-- foreign key :外键
-- references:参照,引用,指向
CategoryId int foreign key references Category3(Id)
);
-- 表级 外键
create table Product2
(
Id int primary key identity, -- 商品编号
ProductName varchar(20),
Price decimal(8,2), -- 8,2 指的是,加上小数最多可以有8位,和2位小数
-- 商品分类,这里的int 必须与Category商品表中的Id的类型保持一致
-- foreign key :外键
-- references:参照,引用,指向
CategoryId int,
constraint fk_CategoryId foreign key(CategoryId) references
Category3(Id)
);
-- 第三种:在创建表的时候,添加了外键,但是没有加外键约束
create table Product3
(
Id int primary key identity, -- 商品编号
ProductName varchar(20),
Price decimal(8,2), -- 8,2 指的是,加上小数最多可以有8位,和2位小数
-- 商品分类,这里的int 必须与Category商品表中的Id的类型保持一致
-- foreign key :外键
-- references:参照,引用,指向
CategoryId int
);
alter table Product3
add constraint fk_CategoryId foreign key(CategoryId) references
Category3(Id)


--唯一约束
create Table UserInfo2
(
Id int primary key identity,
NickName varchar(20) , -- 用户姓名
Email varchar(20),
constraint uq_NickName unique(NickName) -- NickName 添加了一个唯一约束
);
drop table UserInfo2;
create Table UserInfo2
(
Id int primary key identity,
NickName varchar(20) unique(NickName) , -- 用户姓名
Email varchar(20),
);

--检查约束
Create Table UserInfo4
(
Id int primary key identity,
NickName varchar(20),
Email varchar(20),
Sex varchar(10) check(Sex in('男','女')), --性别:检查性别只能输入 ,男或者女
Age tinyint check(Age>=18), -- 年龄:检查年龄是否大于等于18
-- len(Pwd):获取密码的实际长度
Pwd varchar(50) check(len(Pwd)>=4 and len(Pwd)<=16), -- 密码:密码的实际长度必须是4-16位
)

--默认值约束
create Table UserInfo6
(
Id int primary key identity,
NickName varchar(20) default '张三', -- 默认值为张三
Email varchar(20), -- 可以为null
Age tinyint default '10' -- 添加默认值约束
)

--非空约束
Create Table UserInfo5
(
Id int primary key identity,
NickName varchar(20) not null, -- 非空约束
Email varchar(20), -- 可以为null
)

表数据操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
--添加数据
create table Student
(
Id int primary key identity(2,2), -- 每添加一条数据,Id从2开始,每次自增2
NickName nvarchar(15), -- unicode
StudentNo char(11) , -- 学号
Sex nchar(2),
Account varchar(20), -- 账号
[Password] varchar(50) -- 密码
);
insert into Student values
('娜娜','2002','女','user3','123456'),
('娜娜','2002','女','user3','123456'),
('娜娜','2002','女','user3','123456');

insert into UserInfo(UserName,DeptId,Sex,Age,CreateTime) values('刘大
侠','6','未知','18','2020-04-20')


--修改数据
update UserInfo set Pwd='1qaz2wsx',Age=25 where Account like '%admin%' and
Age>30

--删除数据
-- 删除用户表中的数据
delete from Userinfo
select * from UserInfo -- 查询用户表
-- 删除张三,李四两位用户
Delete from UserInfo where UserName='张三' or UserName= '李四'

-- 清空数据
truncate table UserInfo ;

--truncate 与 delete 的区别
--truncate 是真正意义上的清空, 不能加任何查询条件,自增id 会重置
--delete 只是删除数据,如果Id是自增,则自增种子不会从头开始。

--级联删除、级联更新
-- 角色表
create table RoleInfo
(
Id uniqueidentifier primary key,
RoleName varchar(50) not null ,
CreateTime datetime not null default getdate()
);
go
-- 用户表
create table UserInfo
(
Id int primary key identity,
UserName varchar(50) not null,
-- 联级删除:on delete cascade,联级更新
RoleId uniqueidentifier not null --uniqueidentifier 存储全局标识符 ( GUID )。
foreign key references RoleInfo(Id) on delete cascade on update
cascade,
Account varchar(50) not null,
Pwd varchar(50) not null ,
Sex tinyint not null ,
Age tinyint not null
);
go

简单查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
--5.top 的用法?
-- 取前3条数据
-- top :前。。。条
select top 3 * from StudentInfo
--6.top ... percent(百分比)?
-- 查询前30%的学生数据
-- 从这个故事告诉我们,数据没有半条,向上取整
select top 30 percent * from StudentInfo

--15.查询年龄大于20岁前3条学生的姓名,年龄,所有字段取别名
select top 3 stuName as 姓名,stuSex as 性别 from StudentInfo where
(year(getdate())-year(stuBirthday))>20

--11.查询课程包含”SqlServer”的成绩信息
select * from StudentScore where CourseName like '%SqlServer%'
条件查询、分组查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
-- 查询班级编号最大的班级
-- 写法1
select max(classId) from StudentInfo
-- 写法2
select top 1 (skillScore+theoryScore) from StudentScore order by
(skillScore+theoryScore) desc

-- 查询有多少个人参加了考试
select count(distinct stuId) from StudentScore

-- 如果存在大于20岁,则查询结果,
select * from StudentInfo where exists
(
select * from StudentInfo where year(getdate())-year(stuBirthday)>20
)
--如果班级里面有两个以上的老王,则把老王的信息查询出来
select * from StudentInfo where exists
(
select * from
(
select count(stuId) as 数量 from StudentInfo where stuName like
'王%'
) a where a.数量>=1
)


--小技巧: 每什么就以哪个字段进行分组
-- 15.查询不只学了一门课程的学生编号与学习的课程数
-- 翻译成人话:每个学生学习的课程数,并且学习的数量大于1
select stuId,count(CourseName) from StudentScore group by stuId
having count(CourseName)>1

嵌套查询操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
--12.查询不是软件技术1班级的学生信息
-- 关联字段:ClassId
select * from StudentInfo where ClassId not in -- 用in一定不会错,如果子查询的结果只有一条记录时才可以写=
(
select Id from ClassInfo where Name='软件技术1班'
)
select * from StudentInfo where ClassId !=
(
select Id from ClassInfo where Name='软件技术1班'
)

--Row_Number() Over(Order by 字段):按某个字段进行编号排名
-- 以stuId进行升序排名
select Row_Number() Over(Order by stuId) ,* from StudentInfo
-- 按总成绩降序排序并给每一位同学进行编号
select Row_Number() Over(Order by skillScore desc) as 排名, * from
StudentScore
-- 按总成绩降序排序后查询4-8名的学生信息
select * from(
select Row_Number() Over(Order by (skillScore+theoryScore) desc) as
名, * from StudentScore
) aa where aa.排名 between 4 and 8
-- sqlserver 2012以后,offset rows fetch next rows only
-- offset:在。。。位置
select * from StudentScore order by (skillScore+theoryScore) desc offset 3
rows fetch next 5 rows only
-- 获取按Id排序后的第3-5位同学信息
select * from(
select Row_Number() Over(Order by StuId) as 排名, * from StudentScore
) aa where aa.排名 between 3 and 5
--
select * from StudentScore order by Id offset 2 rows fetch next 3 rows
only
连接查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
--5.查询分配了部门的员工信息和相应的部门信息(内连接)
-- Dept:部门表 Emp:员工表
select a.*,b.DName from Emp a join Dept b on a.Did=b.Id -- 为什么取别名,因为
下面可能还会用到这个表

-- 查询学生的姓名,性别,成绩
select stuName,stuSex,skillScore from StudentInfo
inner join StudentScore on StudentInfo.stuId=StudentScore.stuId

1. 左外连接
是以左表为基表,返回左表中所有记录及连接表中符合条件的记录的外连接。
1、以左表为基表
2、返回左表中所有数据
3、连接表不符合条件以NULL填充

use step2;
-- 1.查询所有学生信息以及对应的班级信息(要求显示学生编号,学生姓名,班级编号,班级名称)
-- 左外连接:left outer join
select * from StudentInfo a left outer join ClassInfo b on a.ClassId =b.Id
-- 左连接:以左表主表,不管是否匹配上,都会把左表中的数据都显示出来,未匹配的数据会以NULL进行填充

-- 1.查询所有学生信息以及所有班级信息(要求显示学生编号,学生姓名,班级编号,班级名称)
-- 全连接:full join,不管是否有没有匹配上,都会显示
select * from StudentInfo a full outer join ClassInfo b on a.ClassId=b.Id

Oracle

SqlServer

SQLite

DB2

MariaDB

PostgreSQL

非关系数据库

MongoDb

Redis

NoSql

LiteDB

Apache Cassandra

RavenDB

CouchDB

搜索引擎

ElasticSearch

Solr

Sphinx

云数据库

Azure CosmosDB

Amazon DynamoDB

桌面篇

WPF

WinForm

Linux

RabbitMq

Web 开发

Asp.Net Core

MVC 学习

快速入门学习
Mvc**:约定大于配置 **
  1. 控制器类加 Controller 后缀,而且都放在 Web 项目下的 Controllers 文件夹中,控制器类继承 Controller 基类。
  2. 视图文件必须放在名称为 Views/Pages 的文件夹下的名称为控制器名称的文件夹中。
  3. _ViewStart.cshtml 执行任何 Action (控制器中以 IActionResult 为返回类型的方法叫 Action 方法) 之前,都会先执行它.
  4. 以下划线命名开头的视图一般作为布局 / ViewCompenent 视图,放在 shared 文件夹下面
  5. _ViewImport.cshtm 为全局视图文件公共命名空间的引用
.net5 环境测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using Microsoft.AspNetCore.Mvc;

namespace WebApplication_MVC.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()//该方法是由控制器控制的,但是需要在启动项那边配置一下,不然会有问题
{
return View();
}
//普通方法
public string Index_2()
{
return "我的主页欢迎您!";
}
public string hello()
{
return "hello2,这是一个hello方法";
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace WebApplication_MVC
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
//services.AddControllers();
//这是视图
//services.AddRazorPages();
//控制器带着视图
services.AddControllersWithViews();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseRouting();

app.UseEndpoints(endpoints =>
{
//endpoints.MapGet("/", async context =>
//{
// await context.Response.WriteAsync("Hello World!");
//});
//默认的地址后是以/home/xxx拼接的
endpoints.MapDefaultControllerRoute();
});
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using Microsoft.AspNetCore.Mvc;

namespace WebApplication_MVC.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult Hello()
{
return View();
}
public string Index_2()
{
return "我的主页欢迎您!";
}
public string hello_2()
{
return "hello2,这是一个hello方法";
}
}
}

Home/Hello.cshtml
1
2
3
4
5

@{
StudentViewModel vm = new StudentViewModel();
}
<h1>这是Hello</h1>
Student/StudentList.cshtml
1
2
3
4
5
6
7

@{
Layout = null;//这是去除掉公共的使用共享里的导航栏
StudentViewModel vm = new StudentViewModel();
}

<h1>这是学生列表页</h1>
HomeController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//如果需要使用到控制器,需要引用该包
using Microsoft.AspNetCore.Mvc;

namespace WebApplication_MVC.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult Hello()
{
return View();
}
public string Index_2()
{
return "我的主页欢迎您!";
}
public string hello_2()
{
return "hello2,这是一个hello方法";
}
}
}

StudentController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using Microsoft.AspNetCore.Mvc;

namespace WebApplication_MVC.Controllers
{
public class StudentController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult StudentList()
{
return View();
}
}
}

_ViewStart.cshtml
1
2
3
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
_ViewImports.cshtml
1
2
<!--导入公共的命名空间-->
@using WebApplication_MVC.Models
Shared/_Layout.cshtml

这是共享的文件,类似于导航栏,可用可不用,区分于_ViewStart.cshtml 的公共文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>

<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
</head>
<body>
<h1>我是导航栏</h1>
<hr />
<div>
@RenderBody()
</div>
</body>
</html>

Models/StudentViewModel.cs
1
2
3
4
5
6
7
8
9
namespace WebApplication_MVC.Models
{
public class StudentViewModel
{
public int Id { get; set; }
public string Name { get; set; }
}
}

MVC 项目结构
  • Dependencies:项目所依赖的组件
  • launchSettings.json : 项目发布设置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:4301",
"sslPort": 44345
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"WebApplication_My_Mvc": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

  • Controllers: 存放所有的控制器
  • Models: 存放所有的 ViewModel 文件
  • Views: 存放所有的视图文件
  • Views/Shared : 存放公共的视图文件
  • Views/Shared/_Layout.cshtml : 公共布局文件
  • Views/Shared/Error.cshtml: 错误提示视图
  • Views/_ViewImports.cshtml: 公共导入命名空间,引用公共的标签助手
1
2
3
@using WebApplication_My_Mvc
@using WebApplication_My_Mvc.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
  • Views/_ViewStart.cshtml: 视图起始文件,所有视图在加载时,都会先加载此视图文件
  • wwwroot: 存放所有的静态资源文件(css/js/html)
  • wwwroot/favicon.ico : 应用程序的图标
  • appsetting.json: 当前应用程序的配置文件
  • appsetting.Development.json: 当前环境的配置文件,如果在此文件中未找到想要的配置,则会去 appsetting.json 文件中去寻找。
  • Program:程序的主入口,用于初始化系统的相关配置,注册服务,配置中间件与注册管道。
控制器动作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
      public ViewResult Index2()
{
List<StudentViewModel> list = new List<StudentViewModel>()
{
new(1,"张三"),
new(2,"李四"),
new (3,"王五")
};
return View(list);
}
public JsonResult Index3()
{
List<StudentViewModel> list = new List<StudentViewModel>()
{
new(1,"张三"),
new(2,"李四"),
new (3,"王五")
};
return Json(list);
}
public ContentResult Index4()
{
return Content("hello,任我行码农场");
}
//这个是跳转到对应controller下面的对应的action方法
public RedirectToActionResult Index5()
{
return RedirectToAction("Index2", "Home");
}
//这个是跳转到对应拼接的url
public RedirectResult Index6()
{
return Redirect("/home/index4");
}
public FileResult Download()
{
using MemoryStream ms = new MemoryStream();
var buffer = Encoding.UTF8.GetBytes("文件下载");
ms.Write(buffer);
return File(ms.ToArray(), "application/octet-stream", "测试文件.txt");
}
public FileResult Download()
{
using MemoryStream ms = new MemoryStream();
var buffer = Encoding.UTF8.GetBytes("文件下载");
ms.Write(buffer);
return File(ms.ToArray(), "application/octet-stream", "测试文件.txt");
}
// 告诉浏览器,请求成功了,并且返回状态码200
public IActionResult ShowList3()
{
List<Student> list = new()
{
new(1, "张三"),
new(2, "任我行"),
new Student(3, "李四")
};

return Ok(list);
}
Action**** 方法与普通方法的区别

​ Action 方法是由 Mvc 框架管理,Mvc 框架可以对 Action 方法进行处理与渲染(例如渲染视图,拦截请求等等),而普通则不受 mvc 控制

​ 举个例子,ContentResult 通常的作用也是直接返回一个字符串,当我们执行 Content (「hello, 任我行码农场」) 时,我们只是告诉 Mvc 框架,我们需要返回 “hello, 任我行码农场” ,而并非立即返回,Mvc 框架在此之后可能还会做很多的处理,或许在中间的某个环节,有可能请求被拦截,导致我们可能得到不同的结果(日后要讲的 AOPA 思想)。而 return 「hello, 任我行码农场」 则是立即返回。

Razor**** 视图
注释

ctrl+? 是所有注释,ctrl+/ 是每行都有注释

viewmodel 是跟视图模型有关的数据,其他三个是无关的数据。

既可以使用四种传值方式或者建立 Model 的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@model xxx
@{

}
@:
@foreach(var xxx in Model){

}
@if(){

}
else if(){

}
else{

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
using System.Collections.Generic;
using System.IO;
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace WebApplication_My_Mvc.Controllers
{
public class StudentController : Controller
{
public IActionResult Index()
{
return View();
}
// 视图Action方法
public IActionResult IndexHello()
{
List<Student> list = new()
{
new(1, "张三"),
new(2, "任我行"),
new Student(3, "李四"),
new Student(4,"王五")
};

/*
viewbag["xxx"] = xxx;
viewdata["xxx"] = xxx;
tempdata["xxx"] = xxx;

view(list),@model xxx
@foreach(var xxx in Model)
*/

// TempData
ViewBag.StudentList = list;
TempData["username"] = "万宏伟";
ViewData["Sex"] = "男";
ViewBag.Age = 20;
// ViewData
// 1. 如果你没有指定视图名称,则会将当前的Action名称作为视图名称
// 2. 如果指定了视图名称,则会返回当前指定的视图
return View(list);
}


// 是一个跳转的动作
public IActionResult Index2()
{
var list = ViewData["StudentList"];
// 从Index2 跳转到Index页面

return Redirect("/student/index");
}
// 告诉浏览器,请求成功了,并且返回状态码200
public IActionResult ShowList3()
{
List<Student> list = new()
{
new(1, "张三"),
new(2, "任我行"),
new Student(3, "李四")
};

return Ok(list);
}


// 执行完成之后,立马将结果返回到浏览器中
public List<Student> ShowList()
{
List<Student> list = new()
{
new(1, "张三"),
new(2, "任我行"),
new Student(3, "李四")
};

return list;
}
public IActionResult Add()
{
return View();
}
public IActionResult Test1()
{
// 返回指定名称视图,它会在当前的控制器下面找这个Add.cshtml的视图文件
// 如果你既要指定视图,还要给视图传递一个字符串的数据时,我们应该显示的指定要的传递的数据
return View("Add", // 这个是要要返回的视图名称
"我是传递数据" // 要给视图传递的数据
);
}


public IActionResult Down()
{
using MemoryStream ms = new();
string content = "欢迎来到任我行";
ms.Write(Encoding.UTF8.GetBytes(content));


return File(ms.ToArray(), // 要下载的内容
"application/octet-stream", // 设置它的MediaType(媒体类型)
"hello.txt" // 要下载的文件名
);
}

}
public record Student(int Id, string Name);
}

Student/IndexHello.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
@model List<Student>
@{
List<Student> list = new ()
{
new(1,"任我行"),
new(2,"张三")
};

}
<!DOCTYPE html>

<html>
<head>
<title>学生Index</title>
</head>
<body>
<div>
<h1>我是Index页面</h1>
<span>@TempData["username"]</span>
<span>@ViewData["Sex"]</span>
<span>@ViewBag.Age</span>
@* <span>@Model</span> *@
</div>
@{
var sex = "女";
var students = (List<Student>)ViewBag.StudentList;
}



<table class="table table-hover">
<thead>
<tr>
<th>id</th>
<th>姓名</th>
</tr>
</thead>
<tbody>
@* @foreach (var student in Model) *@
@foreach (var student in students)
{
<tr>
<td>@student.Id</td>
<td>@student.Name</td>
</tr>
}
</tbody>
</table>

@if (sex.Equals("男"))
{
<h1>这里是只有男生才能看的内容</h1>
}
else if (sex.Equals("保密"))
{
<span>这里是保密</span>
}
else
{
@:这里是妹子可以看的内容
}

</body>
</html>

@model List<Student>
@{
List<Student> list = new ()
{
new(1,"任我行"),
new(2,"张三")
};

}
<!DOCTYPE html>

<html>
<head>
<title>学生Index</title>
</head>
<body>
<div>
<h1>我是Index页面</h1>
<span>@TempData["username"]</span>
<span>@ViewData["Sex"]</span>
<span>@ViewBag.Age</span>
@* <span>@Model</span> *@
</div>
@{
var sex = "女";
var students = (List<Student>)ViewBag.StudentList;
}



<table class="table table-hover">
<thead>
<tr>
<th>id</th>
<th>姓名</th>
</tr>
</thead>
<tbody>
@* @foreach (var student in Model) *@
@foreach (var student in students)
{
<tr>
<td>@student.Id</td>
<td>@student.Name</td>
</tr>
}
</tbody>
</table>

@if (sex.Equals("男"))
{
<h1>这里是只有男生才能看的内容</h1>
}
else if (sex.Equals("保密"))
{
<span>这里是保密</span>
}
else
{
@:这里是妹子可以看的内容
}

</body>
</html>


Student/Add.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@model string
@{
ViewBag.Title = "title";
Layout = "_Layout";
}

<h2>title</h2>
@{
Student student = new Student(1, "任我行");ac
Console.WriteLine(student.Id);
}

<form action="/student/Test1">
姓名: <input name="nickanme"/>

<input type="submit" value="保存"/>

</form>

Controllers/StudentController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
using System.Collections.Generic;
using System.IO;
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace WebApplication_My_Mvc.Controllers
{
public class StudentController : Controller
{
public IActionResult Index()
{
return View();
}
// 视图Action方法
public IActionResult IndexHello()
{
List<Student> list = new()
{
new(1, "张三"),
new(2, "任我行"),
new Student(3, "李四"),
new Student(4,"王五")
};

// TempData
ViewBag.StudentList = list;
TempData["username"] = "万宏伟";
ViewData["Sex"] = "男";
ViewBag.Age = 20;
// ViewData
// 1. 如果你没有指定视图名称,则会将当前的Action名称作为视图名称
// 2. 如果指定了视图名称,则会返回当前指定的视图
return View(list);
}


// 是一个跳转的动作
public IActionResult Index2()
{
var list = ViewData["StudentList"];
// 从Index2 跳转到Index页面

return Redirect("/student/index");
}
// 告诉浏览器,请求成功了,并且返回状态码200
public IActionResult ShowList3()
{
List<Student> list = new()
{
new(1, "张三"),
new(2, "任我行"),
new Student(3, "李四")
};

return Ok(list);
}


// 执行完成之后,立马将结果返回到浏览器中
public List<Student> ShowList()
{
List<Student> list = new()
{
new(1, "张三"),
new(2, "任我行"),
new Student(3, "李四")
};

return list;
}
public IActionResult Add()
{
return View();
}
public IActionResult Test1()
{
// 返回指定名称视图,它会在当前的控制器下面找这个Add.cshtml的视图文件
// 如果你既要指定视图,还要给视图传递一个字符串的数据时,我们应该显示的指定要的传递的数据
return View("Add", // 这个是要要返回的视图名称
"我是传递数据" // 要给视图传递的数据
);
}


public IActionResult Down()
{
using MemoryStream ms = new();
string content = "欢迎来到任我行";
ms.Write(Encoding.UTF8.GetBytes(content));


return File(ms.ToArray(), // 要下载的内容
"application/octet-stream", // 设置它的MediaType(媒体类型)
"hello.txt" // 要下载的文件名
);
}

}
public record Student(int Id, string Name);
}

总结
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
 public string Hello()
{
return "Hello,你好啊";
}


//startup里的修改
app.UseEndpoints(endpoints =>
{
//endpoints.MapGet("/", async context =>
//{
// await context.Response.WriteAsync("Hello World!");
//});
endpoints.MapDefaultControllerRoute();//这样在Controller的控制器写的方法就可以运行了
});

//访问方法http://localhost:39710/home/hello

public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddRazorPages();//视图服务

services.AddControllersWithViews();//等同上面两行代码
}

//共享代码和公共代码
@{
Layout = null;
}//这表明可以不适用共享的代码
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}//写在公共代码中,而且路径得写全


<!--导入公共的命名空间-->
@using WebTestCore.Models;


public RedirectToActionResult Index3()
{
//return RedirectToAction("Privacy","home");
return RedirectToAction(nameof(IndexHello));//有利于重构
}

public FileResult Download()
{
using MemoryStream memoryStream = new MemoryStream();
string content = "这是我的生日";
memoryStream.Write(Encoding.UTF8.GetBytes(content));
return File(memoryStream.ToArray(),// 要下载的内容
"application/octet-stream",// 设置它的MediaType(媒体类型)
"birthday.txt");// 要下载的文件名
}

/*这一块比较重要
@model List<Student>
@{
List<Student> list = new ()
{
new(1,"任我行"),
new(2,"张三")
};

}
*/

<!DOCTYPE html>

<html>
<head>
<title>学生Index</title>
</head>
<body>
<div>
<h1>我是Index页面</h1>
</div>
@{
var sex = "女";
// var students = (List<Student>)ViewBag.StudentList;
}



<table class="table table-hover">
<thead>
<tr>
<th>id</th>
<th>姓名</th>
</tr>
</thead>
<tbody>
@foreach (var student in Model)
{
<tr>
<td>@student.Id</td>
<td>@student.Name</td>
</tr>
}
</tbody>
</table>

@if (sex.Equals("男"))
{
<h1>这里是只有男生才能看的内容</h1>
}
else if (sex.Equals("保密"))
{
<span>这里是保密</span>
}
else
{
@:这里是妹子可以看的内容//比较重要的内容
}

</body>
</html>

四种传值方式:
视图传值
1、TempData [“xxx”] = xxx; 需要强制转换
1
2
3
4
5
public IActionResult Index()
{
TempData["username"] = "任我行";
return View();
}
1
2
3
4
5
<body>
<div>
<h1>我的名称:@TempData["username"]</h1>
</div>
</body>

TempData 保存在 Session 中,Controller 每次执行请求的时候,会从 Session 中先获取 TempData,而后清除 Session,获取完 TempData 数据,虽然保存在内部字典对象中,但是其集合中的每个条目访问一次后就从字典表中删 除。具体代码层面,TempData 获取过程是通过 SessionStateTempDataProvider.LoadTempData 方法从 ControllerContext 的 Session 中读取数据,而后清除 Session,故 TempData 只能跨 Controller 传递一次

1
var students = (List<Student>)TempData["data"];
ViewBag ViewData

ViewData:

ViewData 只在当前 Action 中有效,生命周期和 View 相同;

2、ViewData [「xxx」] = xxx; 需要强制转换
1
2
3
4
5
public IActionResult Index()
{
ViewData["username"] = "任我行";
return View();
}
1
2
3
4
5
<body>
<div>
<h1>我的名称:@ViewData["username"]</h1>
</div>
</body>
1
var students = (List<Student>)ViewData["data"];//只能在当前的ACtion使用

ViewBag

ViewBag 其实本质就是 ViewData,只是多了层 Dynamic 控制。所以,使用何种方式完全取决于你的个人爱好。

1
2
3
4
5
public IActionResult Index()
{
ViewBag.UserName = "任我行";
return View();
}
1
2
3
4
5
<body>
<div>
<h1>我的名称:@ViewBag.UserName</h1>
</div>
</body>
3、ViewBag.xxx = xxx; 不需要强制转换
1
2
3
4
5
@foreach (var student in ViewBag.Student)
{
<td>@student.ID</td>
<td>@student.Name</td>
}

两者区别如下:

总结

1、ViewData 和 TempData 是字典类型,赋值方式用字典方式,ViewData [「myName」]

2、ViewBag 是动态类型,使用时直接添加属性赋值即可 ViewBag.myName

3、ViewBag 和 ViewData 只在当前 Action 中有效,等同于 View

4、TempData 可以通过转向继续使用 (Server.Tranfer ()),因为它的值保存在 Session 中。但 TempData 只能经过一次传递,之后会被系统自动清除 (Framework)

5、ViewData 和 ViewBag 中的值可以互相访问,因为 ViewBag 的实现中包含了 ViewData

4、ViewModel
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 public IActionResult Test1()
{
// 返回指定名称视图,它会在当前的控制器下面找这个Add.cshtml的视图文件
// 如果你既要指定视图,还要给视图传递一个字符串的数据时,我们应该显示的指定要的传递的数据
return View("Add", // 这个是要要返回的视图名称
"我是传递数据" // 要给视图传递的数据
);
}//也可以省略视图名称,为当前视图名称

@model List<Student>

@foreach (var student in Model)
{
<td>@student.ID</td>
<td>@student.Name</td>
}

<h1>@Model</h1>

/*
先@model 类型,然后再使用@Model这样
*/

Http

前置知识

客户端与服务端之间的通讯是否也需要某种协议?

答:http 协议. http 协议是一种未进行加密处理,由服务器传输超文本到本地浏览器传输协议。

特点:

  1. 基于 TCP/IP 的高级协议 (Socket)
  2. 默认端口号:80
  3. 基于请求 / 响应模型的: 一次请求对应一次响应
  4. 无状态的:每次请求之间相互独立,不能交互数据
  5. HTTP 协议在应用层
  6. 最初的目的是为了提供一种发布和接收 HTML 页面的方法
  7. 规定了客户端和服务器之间通信格式
Http 的通信流程
  1. 建立 TCP 连接
  2. 客户端向服务端发送命令
  3. 客户端发送请求头信息
  4. 服务器应答
  5. 服务器发送应答头信息
  6. 服务器向客户端发送数据(静态资源,html/css/js)
  7. 服务器关闭 TCP 连接

一般情况下,一旦 Web 服务器向浏览器发送了请求数据,它就要关闭 TCP 连接,然后如果浏览器或者服务器在其头信息加入了这行代码:Connection:keep-alive

TCP 连接在发送后将仍然保持打开状态,于是,浏览器可以继续通过相同的连接发送请求。保持连接节省了为每个请求建立新连接所需的时间,还节约了网络带宽。

URL URI**** 的区别
什么是 ****URI

URI 统一资源标识符(Uniform Resource Identifiers, URI),用来唯一识别一个资源,可以把它理解为你的身份证号。

作用:Web 上可用的资源如 HTML 文档,图像,视频等都是以 URI 来定位的。

什么是 ****URL

URL 统一资源定位符( Uniform Resource Locator ),可以把它理解为你身份证上地址。

是互联网上用来标识某一处资源的地址。

作用:

  1. 可以用来标识一个资源,而且还指明了如果定位这个资源

  2. URL 是 internel 上用来描述信息资源的字符串,主要用在各种 www 程序上。

区别

  1. URI 是一种抽象的,高层次概念定义统一资源标识

  2. 每个 URL 都是一个 URI,但每一个 URI 并不一定是 URL,因为 URI 还包括另外一个子类 URN(统一资源命名),它命名资源但不负责定位资源(姓名 + 你的地址 = 你的身份证号)

  3. URL 就是通过定位的方式来实现 URI 的。

消息数据格式
请求消息格式
  1. 请求行
1
2
//请求方式 请求url 请求协议/版本
//GET /login.html HTTP/1.1
  1. 请求方式:

HTTP 协议有 8 中请求方式:

① GET:请求获取 Request-URI 所标识的资源。

② POST:在 Request-URI 所标识的资源后附加新的数据。

③ HEAD:请求获取由 Request-URI 所标识的资源的响应消息报头。

④ PUT:请求服务器存储一个资源,并用 Request-URI 作为其标识。

⑤ DELETE:请求服务器删除 Request-URI 所标识的资源。

⑥ TRACE:请求服务器回送收到的请求信息,主要用于测试或诊断。

⑦ CONNECT:HTTP 1.1 协议中预留给能够将连接改为管道方式的代理服务器。

⑧ OPTIONS:请求查询服务器的性能,或者查询与资源相关的选项和需求。

常用的有 2 种

GET:

  1. 请求参数在请求行中,在 url 后。
  2. 请求的 url 长度有限制的
  3. 不太安全
  4. 可被收藏到书签,也可被缓存

POST:

  1. 请求参数在请求体中
  2. 请求的 url 长度没有限制的
  3. 相对安全
  4. 请求头:客户端浏览器告诉服务器一些信息
1
//请求头名称: 请求头值

常见的请求头:

  1. User-Agent:浏览器告诉服务器,我访问你使用的浏览器版本信息

作用: 可以在服务器端获取该头的信息,解决浏览器的兼容性问题

  1. Referer:http://localhost/login.html ,告诉服务器,我 (当前请求) 从哪里来。

作用:

  1. 防盗链:
  2. 统计工作:
  3. 请求空行 :就是用于分割 POST 请求的请求头,和请求体的。
  4. 请求体 (正文): 封装 POST 请求消息的请求参数的

字符串格式:

1
2
3
4
5
6
7
8
9
10
11
POST /login.html HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101
Firefox/60.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://localhost/login.html
Connection: keep-alive
Upgrade-Insecure-Requests: 1
username=zhangsan
响应数据格式

响应消息:服务器端发送给客户端的数据

响应行

1
//协议/版本 响应状态码 状态码描述

响应状态码:服务器告诉客户端浏览器本次请求和响应的一个状态,状态码都是 3 位数字。

分类

1xx:服务器接收客户端消息,但没有接受完成,等待一段时间后,发送 1xx 状态码

2xx:成功。代表:200

3xx:重定向。代表:302 (重定向),304 (访问缓存)

4xx:客户端错误。代表:404(请求路径没有对应的资源)405:请求方式没有对应的方法, 401 未授权,403?

5xx:服务器端错误。代表:500 (服务器内部出现异常),503:网关出现问题

响应头

格式:

1
//头名称: 值

常见的响应头:

Content-Type:服务器告诉客户端本次响应体数据格式以及编码格式

Content-disposition:服务器告诉客户端以什么格式打开响应体数据值

attachment;filename=xxx:以附件形式打开响应体。文件下载

响应体

服务器返回的数据

响应字符串格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
HTTP/1.1 200 OK
Content-Type: text/html;charset=UTF-8
Content-Length: 101
Response对象
Date: Wed, 06 Jun 2018 07:08:42 GMT
<html>
<head>
<title></title>
</head>
<body>
hello , response
</body>
</html>
HTTP 各版本简介

HTTP 1.0 : 规定浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个 TCP 连接,服务器完成请求处理后立即断

开 TCP 连接,服务器不跟踪每个客户也不记录过去的请求 。连接无法复用

HTTP1.1 :

复用连接(keep-alive)

缓存处理

身份认证, 状态管理

HTTP 1.1**** 状态代码及其含义

状态代码有三位数字组成,第一个数字定义了响应的类别,且有五种可能取值:

1xx :指示信息–表示请求已接收,继续处理

2xx :成功–表示请求已被成功接收、理解、接受

3xx :重定向–要完成请求必须进行更进一步的操作

4xx :客户端错误–请求有语法错误或请求无法实现

5xx :服务器端错误–服务器未能实现合法的请求

HTTP2.0 :

多路复用 (Multiplexing)

即连接共享,即每一个 request 都是是用作连接共享机制的。一个 request 对应一个 id,这样一个连接上可以有多个 request,每个连接

的 request 可以随机的混杂在一起,接收方可以根据 request 的 id 将 request 再归属到各自不同的服务端请求里面。多路复用原理和 keepalive 区别如下图:

二进制分帧

HTTP1.x 的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认 0 和 1 的组合。基于这种考虑 HTTP2.0 的协议解析决定采用二进制格式,实现方便且健壮。

首部压缩(Header Compression

如上文中所言,对前面提到过 HTTP1.x 的 header 带有大量信息,而且每次都要重复发送,HTTP2.0 使用 encoder 来减少需要传输的 header 大小,通讯双方各自 cache 一份 headerfields 表,既避免了重复 header 的传输,又减小了需要传输的大小。

服务端推送(Server Push

服务端推送是一种在客户端请求之前发送数据的机制。在 HTTP/2 中,服务器可以对客户端的一个请求发送多个响应。Server Push 让 HTTP1.x 时代使用内嵌资源的优化手段变得没有意义;如果一个请求是由你的主页发起的,服务器很可能会响应主页内容、logo 以及样式表,因为它知道客户端会用到这些东西。这相当于在一个 HTML 文档内集合了所有的资源,不过与之相比,服务器推送还有一个很大的优势:可以缓存!也让在遵循同源的情况下,不同页面之间可以共享缓存资源成为可能。

HTTP 3.0

HTTP3.0,也称作 HTTP over QUIC。HTTP3.0 的核心是 QUIC (读音 quick) 协议,由 Google 在 2015 年提出的 SPDY v3 演化而来的新协议,传统的 HTTP 协议是基于传输层 TCP 的协议,而 QUIC 是基于传输层 UDP 上的协议,可以定义成:HTTP3.0 基于 UDP 的安全可靠的 HTTP2.0 协议。

QUIC 协议针对基于 TCP 和 TLS 的 HTTP2.0 协议解决了下面的问题。

1.1 减少了 TCP 三次握手及 TLS 握手时间

不管是 HTTP1.0/1.1 还是 HTTPS,HTTP2.0,都使用了 TCP 进行传输。HTTPS 和 HTTP2 还需要使用 TLS 协议来进行安全传输。这就出现了两个握手延迟,而基于 UDP 协议的 QUIC,因为 UDP 本身没有连接的概念,连接建立时只需要一次交互,半个握手的时间。区别如下图:

1.2 多路复用丢包的线头阻塞问题

QUIC 保留了 HTTP2.0 多路复用的特性,在之前的多路复用过程中,同一个 TCP 连接上有多个 stream,假如其中一个 stream 丢包,在重传前后的 stream 都会受到影响,而 QUIC 中一个连接上的多个 stream 之间没有依赖。所以当发生丢包时,只会影响当前的 stream,也就避免了线头阻塞问题。

1.3 优化重传策略

以往的 TCP 丢包重传策略是:在发送端为每一个封包标记一个编号 (sequence number),接收端在收到封包时,就会回传一个带有对应编号的 ACK 封包给发送端,告知发送端封包已经确实收到。当发送端在超过一定时间之后还没有收到回传的 ACK,就会认为封包已经丢失,启动重新传送的机制,复用与原来相同的编号重新发送一次封包,确保在接收端这边没有任何封包漏接。这样的机制就会带来一些问题,假设发送端总共对同一个封包发送了两次 (初始+重传),使用的都是同一个 sequence number: 编号 N。之后发送端在拿到编号 N 封包的回传 ACK 时,将无法判断这个带有编号 N 的 ACK,是接收端在收到初始封包后回传的 ACK。这就会加大后续的重传计算的耗时。QUIC 为了避免这个问题,发送端在传送封包时,初始与重传的每一个封包都改用一个新的编号,unique packet number,每一个编号都唯一而且严格递增,这样每次在收到 ACK 时,就可以依据编号明确的判断这个 ACK 是来自初始封包或者是重传封包。

1.4 流量控制

通过流量控制可以限制客户端传输资料量的大小,有了流量控制后,接收端就可以只保留相对应大小的接收 buffer , 优化记忆体被占用的空间。但是如果存在一个流量极慢的 stream ,光一个 stream 就有可能估用掉接收端所有的资源。QUIC 为了避免这个潜在的 HOLBlocking,采用了连线层 (connection flow control) 和 Stream 层的 (streamflow control) 流量控制,限制单一 Stream 可以占用的最大 buffer size。

1.5 连接迁移

TCP 连接基于四元组(源 IP、源端口、目的 IP、目的端口),切换网络时至少会有一个因素发生变化,导致连接发生变化。当连接发生变化时,如果还使用原来的 TCP 连接,则会导致连接失败,就得等原来的连接超时后重新建立连接,所以我们有时候发现切换到一个新网络时,即使新网络状况良好,但内容还是需要加载很久。如果实现得好,当检测到网络变化时立刻建立新的 TCP 连接,即使这样,建立新的连接还是需要几百毫秒的时间。QUIC 的连接不受四元组的影响,当这四个元素发生变化时,原连接依然维持。QUIC 连接不以四元组作为标识,而是使用一个 64 位的随机数,这个随机数被称为 Connection lD,对应每个 stream,即使 IP 或者端口发生变化,只要 Connection ID 没有变化,那么连接依然可以维持。

什么是 ****HTTPS

HTTP 协议传输的数据都是未加密的。为了保证这些隐私数据能加密传输,于是网景公司设计了 SSL (Secure Sockets Layer)协议用于对 HTTP 协议传输的数据进行加密,从而就诞生了 HTTPS 。现在的 HTTPS 都是用的 TLS 协议,但是由于 SSL 出现的时间比较早,并且依旧被现在浏览器所支持,因此 SSL 依然是 HTTPS 的代名词。

HTTPS 默认端口号是 443.(http 协议默认端口号是 80)

HTTPS HTTP**** 的一些区别

  1. HTTPS 协议需要到 CA 申请证书,一般免费证书很少,需要交费。
  2. HTTP 是超文本传输协议,信息是明文传输,HTTPS 则是具有安全性的 TLS 加密传输协议。
  3. HTTP 和 HTTPS 使用的是完全不同的连接方式,用的默认端口也不一样,前者是 80,后者是 443.
  4. HTTPS 的连接很简单,HTTPS 协议是由 TLS+HTTP 协议构建的 可进行加密传输、身份认证的网络协议,比 HTTP 协议安全。
HttpContext**** 上下文
1、请求案例

首先需要先跳转到 requesttest/search 下的页面进行搜索提交,然后断点处才会出现结果,否则为 null。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;

namespace WebApplication1_CoreMyTestMvc.Controllers
{
public class RequestController : Controller
{
public IActionResult Search()
{
//获取请求的参数(请求体)
var studentName = Request.Query["StudentName"];

List<Student> list = new List<Student> {
new Student(1,"王海涛"),
new Student(2,"黎利伯"),
new Student(3,"刘俊卿"),
};
if (!string.IsNullOrWhiteSpace(studentName))
{
//模糊查询
list = list.Where(p => p.Name.Contains(studentName)).ToList();
}
//响应体
return View(list);
}
[HttpGet]
public IActionResult MyList()
{
List<Student> list = new List<Student> {
new Student(1,"王海涛"),
new Student(2,"黎利伯"),
new Student(3,"刘俊卿"),
};
//获得请求头
var myToken = HttpUtility.UrlDecode(Request.Headers["myToken"]);
//获取请求的参数(请求体)
//request.query["xxx"]中的这个xx是form表单中的name属性而不是id属性
var studentName = Request.Query["studentName"];

if (!string.IsNullOrWhiteSpace(studentName))
{
//模糊查询
list = list.Where(p => p.Name.Contains(studentName)).ToList();
}
//响应体
return Ok(list);
}
//即使加上了异步的方式也不能解决乱码的问题
public async Task<IActionResult> TestResponse()
{
//以文本的格式输出内容,是为了解决乱码的问题
//Response.Headers["Content-Type"] = "text/plain;charset=utf8";
Response.Headers["Content-Type"] = "text/plain;charset=utf8";
await Response.WriteAsync("在测试响应体",Encoding.UTF8);
return new EmptyResult();
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@model List<Student>
@{
ViewBag.Title = "title";
Layout = "_Layout";
}
<h2>Request请求案例</h2>

<form method="get" action ="/request/search">
姓名: <input name = "studentName" id="studentName"/ >
<input type="submit" value="查询"/>
</form>
<table class="table table-hover">
<thead>
<tr>
<th>id</th>
<th>姓名</th>
</tr>
</thead>
<tbody>
@foreach(var student in Model)
{
<tr>
<td>@student.ID</td>
<td>@student.Name</td>
</tr>
}
</tbody>
</table>

//重要内容
@section Scripts{
<script>
//表单提交
$('form').submit(function(){
//ajax返回的结果一定的是json
$.ajax({
type:'get',
utl:'/request/MyList',
data:{studentName:$('#studentName').val()},//请求体
headers:{//设置请求头
'myToken':encodeURIComponent('中国通')
},
success:function(data){
// location.href='absf/abcs';
}
});
//ajax
return false;//禁止原有的表单提交功能
});
</script>
}

2、HttpContext
1
2
//HttpContext.Request == Request
//HttpContext.Response == Response
错误写法 1

显示不出结果为空

homeController/index
1
2
3
4
5
6
public IActionResult Index()
{
IStudentService studentService = new StudentService();
studentService.Save();
return View();
}
Service/IStudentService.cs
1
2
3
4
5
6
7
namespace WebApplication_My_Mvc.Services
{
public interface IStudentService
{
void Save();
}
}
Service/StudentService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;
using Microsoft.AspNetCore.Http;

namespace WebApplication_My_Mvc.Services
{
public class StudentService:IStudentService
{
public void Save()
{
//这个上下文和Controller下的上下文不是同一个上下文
HttpContext httpContext = new DefaultHttpContext();
var studentName = httpContext.Request.Query["studentName"].ToString();
Console.WriteLine("保存了学生信息");
}
}
}

错误写法 2

可以显示出输入的参数,但是如果多个方法的话需要传参多次,不方便也很麻烦。

homeController/index
1
2
3
4
5
6
7
public IActionResult Index()
{
IStudentService studentService = new StudentService();
//studentService.Save();
studentService.Save(HttpContext);
return View();
}
Service/IStudentService.cs
1
2
3
4
5
6
7
8
9
10
11
using Microsoft.AspNetCore.Http;

namespace WebApplication_My_Mvc.Services
{
public interface IStudentService
{
//void Save();
void Save(HttpContext httpContext);
}
}

Service/StudentService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;
using Microsoft.AspNetCore.Http;

namespace WebApplication_My_Mvc.Services
{
public class StudentService:IStudentService
{
//public void Save()
//{
// HttpContext httpContext = new DefaultHttpContext();
// var studentName = httpContext.Request.Query["studentName"].ToString();
// Console.WriteLine("保存了学生信息");
//}

public void Save(HttpContext httpContext)
{
//HttpContext httpContext = new DefaultHttpContext();
var studentName = httpContext.Request.Query["studentName"].ToString();
Console.WriteLine("保存了学生信息");
}
}
}

正确写法
homeController/index
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using WebApplication_My_Mvc.Models;
using WebApplication_My_Mvc.Services;

namespace WebApplication_My_Mvc.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
//创建服务
private readonly IStudentService _studentService;


public HomeController(ILogger<HomeController> logger, IStudentService studentService)
{
_logger = logger;
_studentService = studentService;
}

public IActionResult Index()
{
//IStudentService studentService = new StudentService();
//studentService.Save();
//studentService.Save(HttpContext);

_studentService.Save();
return View();
}
}
}
Service/IStudentService.cs
1
2
3
4
5
6
7
8
9
10
11
using Microsoft.AspNetCore.Http;

namespace WebApplication_My_Mvc.Services
{
public interface IStudentService
{
void Save();
//void Save(HttpContext httpContext);
}
}

Service/StudentService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
using System;
using Microsoft.AspNetCore.Http;

namespace WebApplication_My_Mvc.Services
{
public class StudentService:IStudentService
{
//public void Save()
//{
// HttpContext httpContext = new DefaultHttpContext();
// var studentName = httpContext.Request.Query["studentName"].ToString();
// Console.WriteLine("保存了学生信息");
//}

//public void Save(HttpContext httpContext)
//{
// //HttpContext httpContext = new DefaultHttpContext();
// var studentName = httpContext.Request.Query["studentName"].ToString();
// Console.WriteLine("保存了学生信息");
//}


//创建上下文环境
private readonly IHttpContextAccessor _contextAccessor;

public StudentService(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}

public void Save()
{
//HttpContext httpContext = new DefaultHttpContext();
var studentName = _contextAccessor.HttpContext.Request.Query["studentName"].ToString();
Console.WriteLine("保存了学生信息");
}
}
}

Startup.cs
1
2
3
4
5
6
7
8
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
// 将当前应用程序上下文放入Mvc的服务容器里面去
//services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddHttpContextAccessor();//上面这个等同于下面这个
services.AddScoped<IStudentService, StudentService>();
}
总结
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class HomeController : Controller
{
//这几个很重要,直接从容器里拿,如果没有构造函数也不需要传参数
private readonly ILogger<HomeController> _logger;
private readonly IStudentService _studentService;

public HomeController(ILogger<HomeController> logger, IStudentService studentService)
{
_logger = logger;
_studentService = studentService;
}

public IActionResult Index()
{
// 大家一定要记住
// 但凡是要创建,优先考虑从容器中拿对象,这样可以不管考虑它的创建的过程到底需要什么构造参数
// 创建Model(视图Model,DataModel) 我们才可以使用这个new 的方式 来创建对象,像创建服务,创建控制器对象,
// 创建Razor视图对象,创建数据访问层Repository对象 等等 这些类型都不应该使用new的方式来创建对象,我们应该从容器中获取对象
//IStudentService studentService = new StudentService();
//studentService.Save(HttpContext);
//studentService.Save();

_studentService.Save();
return View();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//建立Services服务文件夹,这是接口IStudentService
using Microsoft.AspNetCore.Http;

namespace WebApplication1_CoreMyTestMvc.Services
{
public interface IStudentService
{
//void Save(HttpContext httpContext);
void Save();
}
}

//类StudentService
using Microsoft.AspNetCore.Http;
using System;

namespace WebApplication1_CoreMyTestMvc.Services
{
public class StudentService:IStudentService
{
//public void Save(HttpContext httpContext)
private readonly IHttpContextAccessor _contextAccessor;

public StudentService(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}

public void Save()
{
//HttpContext httpContext = new DefaultHttpContext();
//var studentName = httpContext.Request.Query["studentName"];
var studentName = _contextAccessor.HttpContext.Request.Query["studentName"];
Console.WriteLine("保存了学生的信息");
}
}
}


//这是Starup启动项
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();

// 将当前应用程序上下文放入Mvc的服务容器里面去
services.AddSingleton<IHttpContextAccessor,HttpContextAccessor>();
services.AddScoped<IStudentService, StudentService>();
}
3、HttpRequest
RequestController/add
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public IActionResult Add()
{
return View();
}
public IActionResult Submit()
{
//post用form,get用query
var username = Request.Form["username"].ToString(); // 对应着表单的name属性,name 表单可以在Html里面有多份
var studentNo = Request.Form["studentNo"];
var birthday = Convert.ToDateTime(Request.Form["birthday"]);
var hoddy = Request.Form["hobby"].ToList();
foreach (var header in Request.Headers)
{
Console.WriteLine($"头名:{header.Key},值:{header.Value}");
}

Console.WriteLine("/n================================================/n");
var cookies = Request.Cookies;
foreach (var cookie in Request.Cookies)
{
Console.WriteLine($"cookie名:{cookie.Key},cookie值:{cookie.Value}");
}

return Content("添加成功");
}
Request/Add.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@*
For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
*@
@{
ViewBag.Title = "title";
Layout = "_Layout";
}
<h2>Request请求案例</h2>


<form method="post" action="/RequestTest/Submit">
<label for="username">姓名:</label><input name="username" id="username"/>
<label for="studentNo">学号:</label><input name="studentNo" id="studentNo"/>
<label for="birthday">生日:</label>
<input name="birthday" id="birthday" type="date"/>
<label for="birthday">爱好:</label>
<input type="checkbox" value="金钱" name="hobby"/> 金钱
<input type="checkbox" value="编程" name="hobby"/> 编程
<input type="checkbox" value="爬山" name="hobby"/> 爬山

<input type="submit" value="添加"/>
</form>

//没有运行起来,也不知道什么情况
@section Scripts
{
<script src="https://cdn.staticfile.org/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
<script>

$.cookie('mytoken','任我行');
</script>
}
4、HttpResponse
1
2
3
4
5
6
7
8
9
public IActionResult TestResponse()
{
//Response.StatusCode = Convert.ToInt32(HttpStatusCode.Unauthorized);
//return Unauthorized();
//return new EmptyResult();
using var ms = new MemoryStream();
ms.Write(Encoding.Default.GetBytes("欢迎来到任我行码农场"));
return File(ms.ToArray(), "application/oct-stream", "测试.txt");
}
5、Http 响应体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace WebApplication1_CoreMyTestMvc.Controllers
{
public class StudentController : Controller
{
public IActionResult IndexHello()
{
List<Student> list = new List<Student>()
{
new(1,"小妹"),
new(2,"小数"),
new(3,"小鹰"),
};
//TempData["data"] = list;

//ViewData["ListStudent"] = list;

//ViewBag.Student = list;

return View(list);
}
public IActionResult Add()
{

return View();
}

public IActionResult Test1()
{
// 返回指定名称视图,它会在当前的控制器下面找这个Add.cshtml的视图文件
// 如果你既要指定视图,还要给视图传递一个字符串的数据时,我们应该显示的指定要的传递的数据
return View("Add", // 这个是要要返回的视图名称
"我是传递数据" // 要给视图传递的数据
);
}
public RedirectResult Index2()
{
//var list = ViewData["ListStudent"];
return Redirect("/student/Indexhello");
}
public RedirectToActionResult Index3()
{
//return RedirectToAction("Privacy","home");
return RedirectToAction(nameof(IndexHello));//有利于重构
}

public JsonResult ShowResult()
{
List<Student> list = new List<Student>()
{
new(1,"小妹2"),
new(2,"小数3"),
new(3,"小鹰4"),
};
return Json(list);
}

public List<Student> ShowList()
{
List<Student> list = new()
{
new(1, "张三"),
new(2, "任我行"),
new Student(3, "李四")
};

return list;
}

public OkObjectResult ShowList3()
{
List<Student> list = new()
{
new(1, "张三2"),
new(2, "任我行2"),
new Student(3, "李四2")
};

return Ok(list);
}
public ContentResult Test()
{
return Content("这是一段文本内容");
}

public FileResult Download()
{
using MemoryStream memoryStream = new MemoryStream();
string content = "这是我的生日";
memoryStream.Write(Encoding.UTF8.GetBytes(content));
return File(memoryStream.ToArray(),// 要下载的内容
"application/octet-stream",// 设置它的MediaType(媒体类型)
"birthday.txt");// 要下载的文件名
}

}
}
public record Student(int ID, string Name);

中间件

概念:在 ASP.NET Core 中,中间件 (Middleware) 是一个可以处理 HTTP 请求或响应的软件管道ASP.NET Core 中给中间件组件的定位是具有非常特定的用途。例如,我们可能有需要一个中间件组件验证用户,另一个中间件来处理错误,另一个中间件来提供静态文件,如 JavaScript 文件,CSS 文件,图片等等。

中间件就是用于组成应用程序管道来处理请求和响应的组件 。

中间件可以认为有两个基本的职责:

  1. 选择是否将请求传递给管道中的下一个中间件。
  2. 可以在管道中的下一个中间件前后执行一些工作。

我们使用这些中间件组件在 ASP.NET Core 中设置请求处理管道,而正是这管道决定了如何处理请求。 而请求管道是由 Startup.cs 文件中的 Configure() 方法进行配置,它也是应用程序启动的一个重要部分。

误区:面试官说的中间件是指第三方组件,而这边学习的是中间件 (Middleware) 是一个可以处理 HTTP 请求或响应的软件管道。

.net5 以上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
namespace NetCore5_MiddleWare
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();

var app = builder.Build();

//build下面的是管道



// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
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.UseAuthorization();// 使用授权中间件

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

app.Run();
}
}
}

.net5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using WebApplication1_CoreMyTestMvc.Services;

namespace WebApplication1_CoreMyTestMvc
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();

// 将当前应用程序上下文放入Mvc的服务容器里面去
services.AddSingleton<IHttpContextAccessor,HttpContextAccessor>();
services.AddScoped<IStudentService, StudentService>();
}


//configure这个方法是管道


// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
// 配置http 请求管道,由运行时调用
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();// 渲染错误页中间件
}
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.UseAuthorization();

app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=RequestTest}/{action=TestResponse}/{id?}");
});
}
}
}

管道

.Net Core**** 管道(pipeline)是什么?

简单来说,就是从发起请求到返回结果的一个过程在.Net Core 中这里面的处理是由中间件(middleware)来完成。 管道机制解释 用户在发起请求后,系统会自动生成一个请求管道(request pipeline),在这个请求管道中,可以通过 run、map 和 use 方法来配置请求委托 (RequestDelegate),而在单独的请求委托中定义的可重用的类和并行的匿名方法即为中间件,也叫做中间件组件。当发起请求后,系统会创建一个请求管道,在这个管道中,每一个中间件都会按顺序处理(可能会执行,也可能不会被执行,取决于具体的业务逻辑),等最后一个中间件处理完后,又会按照相反的方向返回最终的处理结果

例如,如果您有一个日志记录中间件,它可能只是记录请求的时间,它处理完毕后将请求传递给下一个中间件以进行进一步处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//iis那种方式是没有窗口显示输出的,所以需要在属性那边设置启动对象这样才会有显示窗口输出     
// 这个就是管道(管道是由一个个中间件组成)
public void Configure(IApplicationBuilder app,IWebHostEnvironment env,ILogger<Startup> logger)
{
// 在use 的时候,会把中间件(RequestDelegate)放一个List<RequestDelegate> _components;
app.Use(next =>// 代表是的 下一个要执行的中间件
{
// 代表的是中间件启动的时候,要做的工作内容(只会执行一次)
logger.LogInformation("1.第1个中间件:");
return async context =>// 当前这个请求的上下文
{
// 这里代表的是每一个请求都会执行
logger.LogInformation("1.第1个中间件---before");
//这边的await是等待,执行下一个中间件,所以是await上边的before依次执行,执行到最后发现没有了在回过头来依次执行
await next(context);// 等待执行下一个中间件
logger.LogInformation("1.第1个中间件---after");
};
});
app.Use(next =>// 代表是的 下一个要执行的中间件
{
// 代表的是中间件启动的时候,要做的工作内容(只会执行一次)
logger.LogInformation("2.第2个中间件:");
return async context =>// 当前这个请求的上下文
{
// 这里代表的是每一个请求都会执行
logger.LogInformation("2.第2个中间件---before");
await next(context);// 等待执行下一个中间件
logger.LogInformation("2.第2个中间件---after");
};
});
app.Use(next =>// 代表是的 下一个要执行的中间件
{
// 代表的是中间件启动的时候,要做的工作内容(只会执行一次)
logger.LogInformation("3.第3个中间件:");
return async context =>// 当前这个请求的上下文
{
// 这里代表的是每一个请求都会执行
logger.LogInformation("3.第3个中间件---before");
await next(context);// 等待执行下一个中间件
logger.LogInformation("3.第3个中间件---after");
};
});
}
中间件顺序

下图显示了 ASP.NET Core MVC 和 Razor Pages 应用的完整请求处理管道。 你可以在典型应用中了解现有中间件的顺序,以及在哪里添加自定义中间件。 你可以完全控制如何重新排列现有中间件,或根据场景需要注入新的自定义中间件。

短路方式

​ 中间件组件可以处理请求,并决定不调用管道中的下一个中间件,从而使管道短路,官方微软给了一个英文的名字叫 “terminalmiddleware” , 翻译为 “终端中间件”。短路通常是被允许的,因为它可以避免一些不必要的工作。 例如,如果请求的是像图像或 css 文件这样的静态文件,则 StaticFiles 中间件可以处理和服务该请求并使管道中的其余部分短路。这个意思就是说,在我们的示例中,如果请求是针对静态文件,则 Staticile 中间件不会调用 MVC 中间件,避免一些无谓的操作

1、注释方式

1
2
// 如果不执行next,就会导致短路(这是第一种方式)
await next(context);// 等待执行下一个中间件

2、app.run 方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
 public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
{
// 在use 的时候,会把中间件(RequestDelegate)放一个List<RequestDelegate> _components;
app.Use(next =>// 代表是的 下一个要执行的中间件
{
// 代表的是中间件启动的时候,要做的工作内容(只会执行一次)
logger.LogInformation("1.第1个中间件:");
return async context =>// 当前这个请求的上下文
{
// 这里代表的是每一个请求都会执行
logger.LogInformation("1.第1个中间件---before");
await next(context);// 等待执行下一个中间件
logger.LogInformation("1.第1个中间件---after");
};
});
app.Use(next =>// 代表是的 下一个要执行的中间件
{
// 代表的是中间件启动的时候,要做的工作内容(只会执行一次)
logger.LogInformation("2.第2个中间件:");
return async context =>// 当前这个请求的上下文
{
// 这里代表的是每一个请求都会执行
logger.LogInformation("2.第2个中间件---before");
await next(context);// 等待执行下一个中间件
logger.LogInformation("2.第2个中间件---after");
};
});
// 第2种短路的方式
app.Run(async context =>
{
logger.LogInformation("短路啦");
});
app.Use(next =>// 代表是的 下一个要执行的中间件
{
// 代表的是中间件启动的时候,要做的工作内容(只会执行一次)
logger.LogInformation("3.第3个中间件:");
return async context =>// 当前这个请求的上下文
{
// 这里代表的是每一个请求都会执行
logger.LogInformation("3.第3个中间件---before");
await next(context);// 等待执行下一个中间件
logger.LogInformation("3.第3个中间件---after");
};
});
}

// 第2种短路的方式(.net5方式带参数)
app.Run(async context =>
{
logger.LogInformation("短路啦");
});

//.net5以上没有参数
app.run();
app.Use app.Run 的区别

它俩都可以添加一个中间件至请求管道中。

  1. Use 有权决定是否执行下一个中间件,如果不执行,则出现短路情况
  2. Run 是直接短路,不会执行后面的中间件。
终端节点
1
2
3
4
5
6
7
8
// 终端节点(为了让控制器与Action匹配到路由)
app.UseEndpoints(endpoints =>
{
//endpoints.MapDefaultControllerRoute();
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=RequestTest}/{action=Search}/{id?}");
});
路由中间件
路由模板:
  • 在启动时 Program.cs 或在属性中定义。
  • 描述 URL 路径如何与操作相匹配。
  • 用于生成链接的 URL。 生成的链接通常在响应中返回。

操作既支持传统路由,也支持属性路由。 通过在控制器或操作上放置路由可实现属性路由。 有关详细信息,请参阅混合路由。

1
2
3
4
5
6
7
8
9
// 匹配到合适的路由(合适的EndPoint)
app.UseRouting();

// 路由终端节点: 它可以让系统去执行终端节点上的控制器以及对应的Action
app.MapControllerRoute(
name:"default",
pattern:"{controller=Home}/{action=Index}/{id?}"
);

设置传统路由
1
2
3
4
5
app.UseRouting();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run(); // 必须添加一个终节点

路由模板 「{controller=Home}/{action=Index}/{id?}」 :

  • 匹配 URL 路径,例如 /Products/Details/5
  • 通过标记路径来提取路由值 {controller = Products, action = Details, id = 5} 。 如果应用有一个名为 ProductsController 的控制器和一个 Details 操作,则提取路由值会导致匹配:
1
2
3
4
5
6
7
8
public class ProductsController : Controller
{
public IActionResult Details(int id)
{
//MyDisplayRouteInfo 由 Rick.Docs.Samples.RouteInfo NuGet 包提供,会显示路由信息。
return ControllerContext.MyDisplayRouteInfo(id);
}
}
  • /Products/Details/5 模型绑定 id = 5 的值,以将 id 参数设置为 5 。 有关更多详细信息,请参阅模型绑定。
  • {controller=Home} 将 Home 定义为默认 controller 。
  • {action=Index} 将 Index 定义为默认 action 。
  • {id?} 中的?字符将 id 定义为可选。
  • 默认路由参数和可选路由参数不必包含在 URL 路径中进行匹配。 有关路由模板语法的详细说明,请参阅路由模板参考。
  • 匹配 URL 路径 / 。
  • 生成路由值 {controller = Home, action = Index} 。

controller 和 action 的值使用默认值。 id 不会生成值,因为 URL 路径中没有相应的段。 / 仅在存在 HomeController 和 Index 操作时匹配:

1
2
3
4
public class HomeController : Controller
{
public IActionResult Index() { ... }
}

使用前面的控制器定义和路由模板,为以下 URL 路径运行 HomeController.Index 操作:

  • /Home/Index/17
  • /Home/Index
  • /Home
  • /

URL 路径 / 使用路由模板默认 Home 控制器和 Index 操作。 URL 路径 /Home 使用路由模板默认 Index 操作。

简便方法 MapDefaultControllerRoute:

1
app.MapDefaultControllerRoute();

替代:

1
2
3
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.ResponseCompression;

namespace NetCore8_MiddleWare.Controllers
{
public class ProductController : Controller
{
public IActionResult Detail(int id=0)
{
List<ProductInfo> list = new List<ProductInfo>()
{
new(1,"钻石"),
new(2,"黄金"),
new(3,"青铜")
};
if (id > 0)
{
var product = list.FirstOrDefault(p => p.Id == id);
return Ok(product??new ProductInfo(0,"未找到数据"));
}
//return View();
return Ok("没有找到该数据");
}

public record ProductInfo(int Id,string productName);
}
}

属性路由
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
app.MapControllers();// 使用属性路由,必须得在.net5环境中使用,以上的使用不管用

//[Route("product")]
[Route("[controller]/[action]")]//更简便
public class ProductController : Controller
{
//[Route("product/detail/{id}")]
//[Route("detail/{id}")]
[Route("{id}")]
public IActionResult Detail(int id=0)
{
List<ProductInfo> list = new List<ProductInfo>()
{
new(1,"钻石"),
new(2,"黄金"),
new(3,"青铜")
};
if (id > 0)
{
var product = list.FirstOrDefault(p => p.Id == id);
return Ok(product??new ProductInfo(0,"未找到数据"));
}
//return View();
return Ok("没有找到该数据");
}
//[Route("product/index")]
//[Route("index")]
public IActionResult Index()
{
return Ok("产品列表");
}
}
public record ProductInfo(int Id, string productName);

路由约束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
app.MapControllerRoute(
name: "default",
//pattern: "{controller=Home}/{action=Index}/{id?}");
pattern: "{controller:minlength(3)}/{action=Index}/{id?}");


[Route("[controller]/[action]")]
public class ProductController : Controller
{
[HttpGet("{id:required:min(1):int:max(100)}")]
public IActionResult Detail(int id=0)
{
List<ProductInfo> list = new List<ProductInfo>()
{
new(1,"钻石"),
new(2,"黄金"),
new(3,"青铜")
};
if (id > 0)
{
var product = list.FirstOrDefault(p => p.Id == id);
return Ok(product??new ProductInfo(0,"未找到数据"));
}
//return View();
return Ok("没有找到该数据");
}
}

疑惑解答:

1. 当访问一个 ****Web ** 应用地址时,**Asp.Net Core 是怎么执行到 Controller Action 的呢?

答:程序启动的时候会把所有的 Controller 中的 Action 映射存储到 routeOptions 的集合中,Action 映射成 Endpoint 终结者 的 RequestDelegate 委托属性,最后通过 UseEndPoints 添加 EndpointMiddleware 中间件进行执行,同时这个中间件中的 Endpoint 终结者路由已经是通过 Rouing 匹配后的路由。

2. EndPoint 跟普通路由又存在着什么样的关系?

答: Ednpoint 终结者路由是普通路由 map 转换后的委托路由,里面包含了路由方法的所有元素信息 EndpointMetadataCollection 和 RequestDelegate 委托。

3. UseRouing() UseAuthorization() UseEndpoints() 这三个中间件的关系是什么呢?

答: UseRouing 中间件主要是路由匹配,找到匹配的终结者路由 Endpoint ; UseEndpoints 中间件主要针对 UseRouing 中间件匹配到的路由进行 委托方法的执行等操作。 UseAuthorization 中间件主要针对 UseRouing 中间件中匹配到的路由进行拦截 做授权验证操作等,通过则执行下一个中间件 UseEndpoints () , 具体的关系可以看下面的流程图:

上面流程图中省略了一些部分,主要是把 UseRouing 、UseAuthorization 、UseEndpoint 这三个中间件的关系突显出来。

异常中间件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
namespace WebApplication_ASP_NET_Core_MVC
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.
//if (!app.Environment.IsDevelopment())
//{
// 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.MapControllers();
//app.UseAuthorization();

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

app.Run();
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 public IActionResult Index()
{
ProductInfo info = null;
_logger.LogInformation(info.Id.ToString());//null异常
return View();
}
//Program文件下
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage(); // 开发者的异常页面
}
else
{
// 生产环境下
app.UseExceptionHandler("/Home/Error");
}

//Properties/launchSettings下面
"ASPNETCORE_ENVIRONMENT": "Production"//"profiles"下面的修改环境配置

这个页面的出现是选择了选择调试才出现的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using WebApplication_ASP_NET_Core_MVC.Models;

namespace WebApplication_ASP_NET_Core_MVC.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;

public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}

public IActionResult Index()
{
ProductInfo productInfo = null;
_logger.LogInformation(productInfo.Id.ToString());
return View();
}

//public IActionResult Privacy()
//{
// List<StudentViewModel> list = new List<StudentViewModel>()
// {
// new StudentViewModel(1,"西瓜"),
// new StudentViewModel(2,"桃子"),
// new StudentViewModel(3,"梨子")
// };
// return View(list);
//}
//public record StudentViewModel(int ID,string Name);

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}

launchSettings.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:64098",
"sslPort": 44364
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5292",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7270;http://localhost:5292",
"environmentVariables": {
//"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_ENVIRONMENT": "Production"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
//"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_ENVIRONMENT": "Production"//修改环境为生产环境
}
}
}
}
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
namespace WebApplication_ASP_NET_Core_MVC
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.
//if (!app.Environment.IsDevelopment())
//{
// 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();
//}
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();// 开发者的异常页面
}
else
{
// 生产环境下
app.UseExceptionHandler("/Home/Error");
}

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

app.UseRouting();
//app.MapControllers();
//app.UseAuthorization();

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

app.Run();
}
}
}
静态资源中间件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 有时候环境的不同,需要加载的内容也不相同
app.UseStaticFiles();// 会把wwwroot这个文件夹当作静态资源文件夹

public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
// 项目启动的时候,系统会帮忙把这个接口加入到Service容器中
private readonly IHostEnvironment _hostEnvironment;

public HomeController(ILogger<HomeController> logger,IHostEnvironment hostEnvironment)
{
_logger = logger;
_hostEnvironment = hostEnvironment;
}
//[Route("Index")]
public IActionResult Index()
{
//ProductInfo info = null;
//_logger.LogInformation(info.Id.ToString());
_logger.LogInformation("WebRootPath:"+_hostEnvironment.WebRootPath);
_logger.LogInformation("ContentRootPath:" + _hostEnvironment.ContentRootPath);
return View();
}
}

"ASPNETCORE_ENVIRONMENT": "UAT"//除了IIS Express没变之外,除此之外在选择运行的时候也要看下是否是对应的http还是https或者是iis

if (app.Environment.IsEnvironment("UAT"))
{
app.Logger.LogInformation("这里uat环境");
}
Session

这里的问题是没有加入 httpcontext… 上下文服务所导致的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
//这两个非常重要,注释掉就不能运行
app.UseSession();// 可以放到Session中
builder.Services.AddSession();// 将Session服务添加到容器中


builder.Services.AddHttpContextAccessor();

//HomeController

private readonly ISession _session;
//private readonly IHttpContextAccessor _contextAccessor;//session得有上下文

//public HomeController(ILogger<HomeController> logger,IHostEnvironment hostEnvironment,ISession session)
//{
// _logger = logger;
// _hostEnvironment = hostEnvironment;
// _session = session;
//}

public HomeController(ILogger<HomeController> logger, IHostEnvironment hostEnvironment, IHttpContextAccessor contextAccessor)//这才是真正的注入
{
_logger = logger;
_hostEnvironment = hostEnvironment;
_session = contextAccessor.HttpContext!.Session;
}
public IActionResult Index()
{
//ProductInfo info = null;
//_logger.LogInformation(info.Id.ToString());
//_logger.LogInformation("ContentRootPath"+_hostEnvironment.ContentRootPath);

//ISession session;
//每个用户都拥有自己的一个Session内存块,不会和其他用户把数据搞混
_session.SetString("username","张三");
return View();
}

//ProductController
private readonly ISession _session;
private readonly IHttpContextAccessor _contextAccessor;

public ProductController(IHttpContextAccessor contextAccessor)
{
_session = contextAccessor.HttpContext!.Session;//!是为了消除波浪线
_contextAccessor = contextAccessor;
}


//[Route("product/detail/{id}")]
//[Route("detail/{id}")]
//[Route("{id}")]
[HttpGet("{id:required:min(1):int:max(100)}")]
public IActionResult Detail(int id=0)
{
//HttpContext.Session;//如果是控制器的就不需要上下文注入的方式了,但是在其他类里就需要例如Service
var username = _session.GetString("username");
List<ProductInfo> list = new List<ProductInfo>()
{
new(1,"钻石"),
new(2,"黄金"),
new(3,"青铜")
};
if (id > 0)
{
var product = list.FirstOrDefault(p => p.Id == id);
return Ok(product??new ProductInfo(0,"未找到数据"));
}
//return View();

return Ok("没有找到该数据");
}
Session 相关属性与配置
1
2
3
4
5
6
7
8
9
10
11
12
//httponly为真是只能在客户端里去读取
//cookie value表示当前处于哪个会话

var sessionId = _session.Id; // 标识一个会话,同一个会话中,这个Id是唯一的

builder.Services.AddSession(p =>
{
p.Cookie.Name = "wanhongwei.session";
//p.IOTimeout = TimeSpan.FromMinutes(30);
p.IOTimeout = TimeSpan.FromSeconds(5);//先是能获取到username之后5秒钟后获取到null
p.Cookie.HttpOnly = true;
});// 将Session服务添加到容器中
Session 存储序列化对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//homecontroller
//用到json的时候需要导入对应的包才行
public IActionResult SetUser()
{
//DateTime? dateTime = new DateTime();
//var value = dateTime.Value;//这样可以点出来value用DateTime?
UserInfo userInfo = new UserInfo(1,"万宏伟",Convert.ToDateTime("1997-06-15"));
// 对象序列化,,, info ------> json
var userJson = JsonConvert.SerializeObject(userInfo);
_session.SetString("userInfo", userJson);
return Ok("添加成功");
}
public IActionResult GetUser()
{
var userJson = _session.GetString("userInfo");
var userInfo = JsonConvert.DeserializeObject<UserInfo>(userJson);
return Ok(userInfo);
}

public record UserInfo(int id, string nickName, DateTime birthday);
Session 扩展方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using NetCore8_MiddleWare.Utils;//引用这个扩展方法所在文件
using Newtonsoft.Json;//安装这个包

public IActionResult SetUser()
{
//DateTime? dateTime = new DateTime();
//var value = dateTime.Value;
UserInfo userInfo = new UserInfo(1,"万宏伟",Convert.ToDateTime("1997-06-15"));
// 对象序列化,,, info ------> json
//var userJson = JsonConvert.SerializeObject(userInfo);
//_session.SetString("userInfo", userJson);
_session.Set("userInfo",userInfo);
return Ok("添加成功");
}
public IActionResult GetUser()
{
//var userJson = _session.GetString("userInfo");
//var userInfo = JsonConvert.DeserializeObject<UserInfo>(userJson);
//return Ok(userInfo);
return Ok(_session.Get<UserInfo>("userInfo"));
}
扩展方法需要放在 Utils 文件下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//SessionUtil
using Newtonsoft.Json;

namespace NetCore8_MiddleWare.Utils
{
public static class SessionUtil
{
public static void Set<T>(this ISession session,string key,T obj)
{
//var userInfo = JsonConvert.SerializeObject(obj);
//session.SetString(key, userInfo);
session.SetString(key, JsonConvert.SerializeObject(obj));
}
public static T Get<T>(this ISession session, string key)
{
return JsonConvert.DeserializeObject<T>(session.GetString(key));
}
}
}

对象序列化 - ProtoBuf-Net

nuget 安装 protobuf-net

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[ProtoContract]
public class UserInfo
{
public UserInfo()
{

}
public UserInfo(int id, string nickName, DateTime birthday)
{
this.Id = id;
this.NickName = nickName;
this.Birthday = birthday;
}
[ProtoMember(1)]
public int Id { get; set; }
[ProtoMember(2)]
public string NickName { get; set; }
[ProtoMember(3)]
public DateTime Birthday { get; set; }
}

//SessionUtil
public static void Set<T>(this ISession session, string key, T obj)
{
if (null == obj)
return;
using var ms = new MemoryStream();// 流的本质就是二进制数组
// 将obj对象序列至ms内存流中
Serializer.Serialize(ms, obj);// memoryStream 里面就有东西
session.Set(key,ms.ToArray());
}
public static T? Get<T>(this ISession session, string key)//?是用来去波浪线的
{
var butter = session.Get(key);
if(butter != null)
{
using var ms = new MemoryStream(butter);
return Serializer.Deserialize<T>(ms);
}
return default;
}

视图与模型

背景

有一个基于数据库的原始数据实体,但是后面再创建一个基于这个数据实体进行按需所给。

使用视图模型的好处:

面对那些业务场景不需要的字段我们不应该返回给前端,

  1. 方便此业务场景的维护,就算将来当前业务场景发生变化,也不至于影响到其他同学的调用。

  2. 字段太多,对于其他调用者来说不太友好,别人不知道这个字段是干嘛用的,特别是现在流行微服务开发,当我们给别人提供接口时,切记要记得 “按需所给” ,否则别人可能会为了你这些 “没用的字段” 而 去大费周章的去东挪西凑。

  3. 更符合当前的 DDD 开发模式。

Ado.net 实现数据的显示与添加功能
SQLServer 创建数据库 MvcUnit4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
create database MvcUnit4;
go
use MvcUnit4;
go
create table Product
(
Id bigint primary key,
ProductName varchar(30),
CategoryName varchar(30),
Price decimal(10,2),
Remark varchar(200),
CreatedUserId bigint,
UpdatedUserId bigint,
CreatedTime datetime,
UpdatedTime datetime,
Deleted bit
);
insert into Product values(1,'C# 入门','.Net 开发',25,'',1,1,getdate(),getdate(),0);
insert into Product values(2,'Sql基础','数据库',25,'',1,1,getdate(),getdate(),0);
insert into Product values(3,'SQL高级','数据库',120,'',1,1,getdate(),getdate(),0);
insert into Product values(4,'Ado.Net','数据库访问技术',25,'',1,1,getdate(),getdate(),0);
insert into Product values(5,'EntityFramework','数据库访问技术',120,'',1,1,getdate(),getdate(),0);
insert into Product values(6,'C#高级','.Net 开发',140,'',1,1,getdate(),getdate(),0);
insert into Product values(7,'Asp.net Mvc Core','.Net 开发',25,'',2,2,getdate(),getdate(),0);
insert into Product values(8,'MySql基础','数据库',25,'',2,2,getdate(),getdate(),0);
go
create table UserInfo
(
Id bigint primary key,
UserName varchar(30),
NickName varchar(30),
Pwd varchar(50),
CreatedUserId bigint,
UpdatedUserId bigint,
CreatedTime datetime,
UpdatedTime datetime,
Deleted bit
);
insert into UserInfo values(1,'renwoxing','任我行','123456',0,0,getdate(),getdate(),0);
insert into UserInfo values(2,'xiaoming','小明','123456',0,0,getdate(),getdate(),0);
创建 DataModel 文件夹
BaseBo.cs
1
2
3
4
5
6
7
8
9
10
11
namespace WebApplication1_Step4.DataModel;

public class BaseBo
{
public long Id{ get;set;}
public long CreatedUserId { get; set;}
public long UpdatedUserId { get; set;}
public DateTime CreatedTime => DateTime.Now;
public DateTime UpdatedTime => DateTime.Now;
public bool Deleted => false;
}
BaseEntity.cs
1
2
3
4
5
6
7
8
9
10
11
namespace WebApplication1_Step4.DataModel;

public class BaseEntity
{
public long Id{ get;set;}
public long CreatedUserId { get; set;}
public long UpdatedUserId { get; set;}
public DateTime CreatedTime { get; set; }= DateTime.Now;
public DateTime UpdatedTime { get; set; }= DateTime.Now;
public bool Deleted { get; set; } = false;
}
Product.cs
1
2
3
4
5
6
7
8
9
namespace WebApplication1_Step4.DataModel;

public class Product:BaseEntity
{
public String? ProductName {get; set;}
public decimal Price { get; set; }
public String? CategoryName { get; set; }
public String? Remark { get; set; }
}
UserInfo.cs
1
2
3
4
5
6
7
8
namespace WebApplication1_Step4.DataModel;

public class UserInfo:BaseEntity
{
public String? UserName { get;set;}
public String? NickName { get;set;}
public String? Pwd { get;set;}
}
创建 Model 文件夹
ProductCreateBo.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using WebApplication1_Step4.DataModel;

namespace WebApplication1_Step4.Models;

/// <summary>
/// 添加商品的视图模型
/// </summary>
public class ProductCreateBo:BaseBo
{
public String? ProductName {get; set;}
public decimal Price { get; set; }
public String? CategoryName { get; set; }
public String? Remark { get; set; }
// 添加人以及最后维护可以从Session获取到,所以CreatedUserId,UpdatedUserId,可以不需要
// CreatedTime,UpdatedTime也不需要,因为我们可以业务代码中直接获取时间:DateTime.Now
}
ProductViewModel.cs
1
2
3
4
5
6
7
8
9
10
11
12
namespace WebApplication1_Step4.Models;

public class ProductViewModel
{
public long Id { get; set; }
public String? ProductName {get; set;}
public decimal Price { get; set; }
public String? CategoryName { get; set; }
public String? CreatedNickName { get; set; }
public String? UpdatedNickName { get; set; }
public DateTime UpdatedTime { get; set; }
}
修改 appsettings.json
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {//添加数据库
"Sql": "server=.;uid=sa;pwd=123456;database=MvcUnit4;"
}
}

创建 Services 服务
IProductService.cs 接口
1
2
3
4
5
6
7
8
9
10
11
12
using WebApplication1_Step4.Models;

namespace WebApplication1_Step4.Services
{
public interface IProductService
{
List<ProductViewModel> Search();

void Add(ProductCreateBo bo);
}
}

ProductService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
using System.Data.SqlClient;
using AutoMapper;
using Snowflake;
using WebApplication1_Step4.DataModel;
using WebApplication1_Step4.utils;
using WebApplication1_Step4.utils.Snowflake;
using WebApplication1_Step4.Models;
namespace WebApplication1_Step4.Services
{
public class ProductService : IProductService
{
//private readonly IMapper _mapper;
private readonly IdWorker _idWorker;
//public ProductService(IMapper mapper)
//{
// _mapper = mapper;
// _idWorker = SnowflakeUtil.CreateIdWorker();
//}
public ProductService()
{
_idWorker = SnowflakeUtil.CreateIdWorker();
}
public List<ProductViewModel> Search()
{
string sql = @"select a.Id,ProductName,CategoryName,Price,a.UpdatedTime,
b.NickName as CreatedNickName,
c.NickName as UpdatedNickName
from Product a
left join UserInfo b on a.CreatedUserId = b.Id
left join UserInfo c on a.UpdatedUserId = c.Id
where a.Deleted=0";
return DbHelper.GetList<ProductViewModel>(sql);
}
public void Add(ProductCreateBo bo)
{
// 先将Bo对象 转换为DataModel对象(因为Bo对象不能出现在Repository数据访问层)
var product = new Product()
{
Id = _idWorker.NextId(),
CategoryName = bo.CategoryName,
CreatedUserId = bo.CreatedUserId,
Price = bo.Price,
UpdatedUserId = bo.UpdatedUserId,
ProductName = bo.ProductName,
Remark = bo.Remark
};
//var product = _mapper.Map<ProductCreateBo,Product>(bo);
//product.Id = _idWorker.NextId();
string sql = "insert into Product values(@id,@productName,@categoryName,@price,@remark,@createdUserId,@updatedUserId,@createdTime,@updatedTime,@deleted)";
SqlParameter[] sqlParameters =
{
new("@id", product.Id),
new("@productName", product.ProductName),
new("@categoryName", product.CategoryName),
new("@price", product.Price),
new("@remark", product.Remark),
new("@createdUserId", product.CreatedUserId),
new("@updatedUserId", product.UpdatedUserId),
new("@createdTime", product.CreatedTime),
new("@updatedTime", product.UpdatedTime),
new("@deleted", product.Deleted)
};
DbHelper.ExecuteNonQuery(sql, sqlParameters);
}
}
}

修改 views 文件夹、添加 Product 文件夹
Add.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@model ProductCreateBo

@{
ViewBag.Title = "title";
Layout = "_Layout";
}

<h2>商品添加</h2>

<form action="/product/submit" method="post">
商品名称: @Html.TextBoxFor(p=>p.ProductName) <br/>
@* 与下面是等效的 *@
@* <input type="text" name="ProductName" id="ProductName"/> *@
商品类型: @Html.TextBoxFor(p=>p.CategoryName) <br/>
商品价格: @Html.TextBoxFor(p=>p.Price) <br/>

备注: @Html.TextAreaFor(p=>p.Remark) <br/>


<input type="submit" value="添加"/>


</form>
Search.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@model List<ProductViewModel>

@{
ViewBag.Title = "商品列表";
Layout = "_Layout";
}

<h2>商品列表</h2>
@* <a href="/product/add?id=1" class="btn btn-primary" style="color:red">添加商品</a> *@
@Html.ActionLink("添加商品","Add","Product",
htmlAttributes:new{style="color:white;" ,@class="btn btn-primary"})

<table class="table table-hover">
<tr>
<td>id</td>
<td>商品名称</td>
<td>商品类型</td>
<td>价格</td>
<td>添加人</td>
<td>最后维护人</td>
<td>最后更新时间</td>
</tr>
@foreach (var p in Model)
{
<tr>
<td>@p.Id</td>
<td>@p.ProductName</td>
<td>@p.CategoryName</td>
<td>@p.Price</td>
<td>@p.CreatedNickName</td>
<td>@p.UpdatedNickName</td>
<td>@p.UpdatedTime</td>
</tr>
}

</table>

添加 Utils 文件夹
DbHelper.cs

需要导入对应的包 system.data.sqlclient (得是 4.8.3) 如果是 4.9 的化就会被弃用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
using System.Data;
using System.Data.SqlClient;
using System.Reflection;

namespace WebApplication1_Step4.utils;

public class DbHelper
{
private static string? _connString;

// 静态构造方法,
// 第一次访问 这个的类的时候,会执行一次静态构造


static DbHelper()
{
ConfigurationBuilder configuration = new ConfigurationBuilder(); //读取配置文件
var config = configuration.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(file =>
{
file.Path = "/appsettings.json";
file.Optional = false;
file.ReloadOnChange = true;
}).Build();
_connString = config.GetConnectionString("Sql");
}

private static SqlCommand PrepareCommand(SqlConnection conn, string sql, CommandType cmdType,
params SqlParameter[]? parameters)
{
using SqlCommand cmd = new SqlCommand(sql, conn);
cmd.CommandType = cmdType;
if (parameters != null && parameters.Length > 0)
{
cmd.Parameters.AddRange(parameters);
}

return cmd;
}


/**
* 执行增删改的操作
*/

public static int ExecuteNonQuery(string sql,params SqlParameter[]? parameters)
{
using SqlConnection conn = new(_connString);
conn.Open();
return PrepareCommand(conn, sql, CommandType.Text, parameters).ExecuteNonQuery();
}

public static int ExecuteNonQuery(string sql, CommandType cmdType = CommandType.Text,
params SqlParameter[]? parameters)
{
using SqlConnection conn = new(_connString);
conn.Open();
return PrepareCommand(conn, sql, cmdType, parameters).ExecuteNonQuery();
}

public static int ExecuteScalar(string sql,params SqlParameter[]? parameters)
{
using SqlConnection conn = new(_connString);
conn.Open();
return Convert.ToInt32(PrepareCommand(conn, sql, CommandType.Text, parameters).ExecuteScalar());
}

public static int ExecuteScalar(string sql, CommandType cmdType = CommandType.Text,
params SqlParameter[]? parameters)
{
using SqlConnection conn = new(_connString);
conn.Open();
return Convert.ToInt32(PrepareCommand(conn, sql, cmdType, parameters).ExecuteScalar());
}

public static List<T> GetList<T>(string sql, CommandType cmdType = CommandType.Text,
params SqlParameter[]? parameters) where T : class, new()
{
using SqlDataAdapter adapter = new(PrepareCommand(
new (_connString),sql, cmdType, parameters));

DataTable dataTable = new DataTable();
adapter.Fill(dataTable);
return ToList<T>(dataTable);
}

public static List<T> GetList<T>(string sql, params SqlParameter[]? parameters) where T : class, new()
{
using SqlDataAdapter adapter = new(PrepareCommand(
new (_connString),sql, CommandType.Text, parameters));

DataTable dataTable = new DataTable();
adapter.Fill(dataTable);
return ToList<T>(dataTable);
}

private static List<T> ToList<T>(DataTable dt) where T : class, new()
{
Type t = typeof(T);
PropertyInfo[] propertys = t.GetProperties();
List<T> lst = new List<T>();
string typeName = string.Empty;

foreach (DataRow dr in dt.Rows)
{
T entity = new T();
foreach (PropertyInfo pi in propertys)
{
typeName = pi.Name;
if (dt.Columns.Contains(typeName))
{
if (!pi.CanWrite) continue;
object value = dr[typeName];
if (value == DBNull.Value) continue;
if (pi.PropertyType == typeof(string))
{
pi.SetValue(entity, value.ToString(), null);
}
else if (pi.PropertyType == typeof(int) || pi.PropertyType == typeof(int?))
{
pi.SetValue(entity, int.Parse(value.ToString()), null);
}
else if (pi.PropertyType == typeof(DateTime?) || pi.PropertyType == typeof(DateTime))
{
pi.SetValue(entity, DateTime.Parse(value.ToString()), null);
}
else if (pi.PropertyType == typeof(float))
{
pi.SetValue(entity, float.Parse(value.ToString()), null);
}
else if (pi.PropertyType == typeof(double))
{
pi.SetValue(entity, double.Parse(value.ToString()), null);
}
else
{
pi.SetValue(entity, value, null);
}
}
}

lst.Add(entity);
}

return lst;
}
}
添加 SnowFalke 文件夹
IdWorker.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
using System.Net.NetworkInformation;


namespace Snowflake
{
public class IdWorker
{
/// <summary>
/// 当前时间戳,你可以替换成你系统上线时的时间戳
/// </summary>
public const long Twepoch = 1655824116834L;

/// <summary>
/// 机器id所占的位数
/// </summary>
const int WorkerIdBits = 10;
/// <summary>
/// 序列在id中占的位数
/// </summary>
const int SequenceBits = 12;
/// <summary>
/// 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
/// </summary>
public const long MaxWorkerId = -1L ^ (-1L << WorkerIdBits);
/// <summary>
/// 机器ID向左移12位
/// </summary>
private const int WorkerIdShift = SequenceBits;
/// <summary>
/// 时间截向左移22位(10+12)
/// </summary>
public const int TimestampLeftShift = WorkerIdShift + WorkerIdBits;
/// <summary>
/// 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
/// </summary>
private const long SequenceMask = -1L ^ (-1L << SequenceBits);

private long _sequence = 0L;
private long _lastTimestamp = -1L;

/// <summary>
/// 同一库表由不同的IdWorker对象生成主键时,需要区分参数
/// </summary>
/// <param name="workerId"></param>
/// <param name="sequence"></param>
public IdWorker(long workerId, long sequence)
{
WorkerId = workerId;
_sequence = sequence;
// sanity check for workerId
if (workerId > MaxWorkerId || workerId < 0)
{
throw new ArgumentException(
$"workerId 不能比最大值 {MaxWorkerId} 大 或者比 0 小");
}
}

public long WorkerId { get; protected set; }

public long Sequence
{
get { return _sequence; }
internal set { _sequence = value; }
}

// def get_timestamp() = System.currentTimeMillis

readonly object _lock = new();

public virtual long NextId()
{
lock (_lock)
{
var timestamp = TimeGen();
// 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
if (timestamp < _lastTimestamp)
{
//exceptionCounter.incr(1);
//log.Error("clock is moving backwards. Rejecting requests until %d.", _lastTimestamp);
throw new Exception(
$"系统发生了时钟回拨. {_lastTimestamp - timestamp} 毫秒后可继续生产ID");
}
// 如果是同一时间生成的,则进行毫秒内序列
if (_lastTimestamp == timestamp)
{
_sequence = (_sequence + 1) & SequenceMask;
// 毫秒内序列溢出
if (_sequence == 0)
{
//阻塞到下一个毫秒,获得新的时间戳
timestamp = TilNextMillis(_lastTimestamp);
// 你在这里可以换成记录日志的方式
Console.WriteLine(
$"{nameof(IdWorker)}:{GetLocalMacAddress()}序列号超过限制,重新取时间戳:lastTimestamp=[{_lastTimestamp}] AND timestamp=[{timestamp}]");
}
}
// 时间戳改变,毫秒内序列重置
else
{
_sequence = 0;
}
// 上次生成ID的时间截
_lastTimestamp = timestamp;
// 移位并通过或运算拼到一起组成64位的ID
var id = ((timestamp - Twepoch) << TimestampLeftShift) |
(WorkerId << WorkerIdShift) |
_sequence;

return id;
}
}

/// <summary>
/// 阻塞到下一个毫秒,直到获得新的时间戳
/// </summary>
/// <param name="lastTimestamp">上次生成ID的时间截</param>
/// <returns></returns>
protected virtual long TilNextMillis(long lastTimestamp)
{
var timestamp = TimeGen();
while (timestamp <= lastTimestamp)
{
timestamp = TimeGen();
}

return timestamp;
}
/// <summary>
/// 返回以毫秒为单位的当前时间
/// </summary>
/// <returns></returns>
protected virtual long TimeGen()
{
return (long) (DateTime.UtcNow - new DateTime
(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds;
}

/// <summary>
/// 获取本机mac(跨平台的方法)
/// </summary>
/// <returns></returns>
private static string GetLocalMacAddress()
{
IPGlobalProperties computerProperties = IPGlobalProperties.GetIPGlobalProperties();
NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();

if (nics == null || nics.Length < 1)
{
return string.Empty;
}


System.Text.StringBuilder sb = new System.Text.StringBuilder();
foreach (NetworkInterface adapter in nics)
{
if (adapter.NetworkInterfaceType != NetworkInterfaceType.Ethernet)
{
continue;
}

PhysicalAddress address = adapter.GetPhysicalAddress();
byte[] bytes = address.GetAddressBytes();
for (int i = 0; i < bytes.Length; i++)
{
sb.Append(bytes[i].ToString("X2"));

if (i != bytes.Length - 1)
{
sb.Append("-");
}
}

return sb.ToString();
}

return string.Empty;
}
}
}
SnowflakeUtil.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
using Snowflake;

// using Microsoft.Extensions.Caching.StackExchangeRedis;

namespace WebApplication1_Step4.utils.Snowflake;

/// <summary>
/// Snowflake辅助类,利用IP生成Idworker
/// </summary>
public static class SnowflakeUtil
{
private static readonly object LockMac = new object();
/// <summary>
/// 过期秒数
/// </summary>
private static long _workId;
private static IdWorker? _idWorker;


/// <summary>
/// 单例模式,创建IdWorker
/// </summary>
/// <returns>IdWorker实例</returns>
public static IdWorker CreateIdWorker()
{
if (_idWorker == null)
{
lock (LockMac)
{
if (_idWorker == null)
{
_workId = GetWorkId();
_idWorker = new IdWorker(_workId,0);

}
}
}
return _idWorker;
}

// redis 中存储当前最大的workerId值,以解决workerId重复问题
private static int GetWorkId()
{
// // 因为是在静态类中,所以无法使用注入方式提供对象,只能自己实例化对象
// IDistributedCache redis = new RedisCache(new RedisCacheOptions
// {
// Configuration = "localhost:6379"
// });
// int workerId = 0;
// string maxWorkerId = redis.GetString("max_worker_id");
// if (!string.IsNullOrWhiteSpace(maxWorkerId))
// {
// workerId = Convert.ToInt32(maxWorkerId)+1;
// }
//
// redis.SetString("max_worker_id",workerId.ToString());
//
// return workerId;
return 1;
}

}
创建 Profiles 文件夹
ProductProfile.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using AutoMapper;
using WebApplication1_Step4.Models;
using WebApplication1_Step4.DataModel;
using WebApplication1_Step4.AutomapModels;
namespace WebApplication1_Step4.Profiles
{
// 一个数据库,一个profile, 一个数据库最好命名风格要一致
public class ProductProfile:Profile
{
public ProductProfile()
{
// 创建映射关系
CreateMap<ProductCreateBo, Product>();

//目前用不上
//CreateMap<Product, ProductViewModel>();

}
}
}

添加 ProductController 文件
ProductController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using Microsoft.AspNetCore.Mvc;
using WebApplication1_Step4.Models;
using WebApplication1_Step4.Services;

namespace WebApplication1_Step4.Controllers
{
public class ProductController : Controller
{
private readonly IProductService _productService;
// new : 只适用于dataModel,dto,bo,viewModel
public ProductController(IProductService productService)
{
_productService = productService;
}
public IActionResult Search()
{
var list = _productService.Search();
return View(list);
}
public IActionResult Add()
{
return View();
}
[HttpPost]
public IActionResult Submit(ProductCreateBo bo)
{
//var productName = Request.Form["ProductName"];
//var categoryName = Request.Form["CategoryName"];
_productService.Add(bo);

return RedirectToAction("Search");
}
}
}

修改 Program.cs
Program.cs
1
builder.Services.AddScoped<IProductService, ProductService>();
AutoMap 组件 **-** 自动映射 (下划线的还有点问题后续解决问题)
添加包

AutoMapper.Extensions.Microsoft.DependencyInjection (11.0 版本还在,12 的版本已经弃用)

创建 Profile 文件夹
CustomerProfile.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using AutoMapper;
using WebApplication_ASP_NET_Core_MVC.DataModel;
using WebApplication_ASP_NET_Core_MVC.Models;
namespace WebApplication_ASP_NET_Core_MVC.Profiles
{
// 一个数据库,一个profile, 一个数据库最好命名风格要一致
public class CustomerProfile:Profile
{
public CustomerProfile()
{
//创建映射关系
CreateMap<ProductCreateBo, Product>();
CreateMap<Product_1_2, Product_to_1_2>();

//因为目前暂时没有用上,是因为用到了反射
//CreateMap<Product, ProductViewModel>();
}
}
}

修改 Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//快速入门的设置
//builder.Services.AddAutoMapper(cfg =>
//{
// cfg.AddProfile<CustomerProfile>();
//});
//这两个都是等价的,只不过上面这个可以多写点东西
builder.Services.AddAutoMapper(typeof(CustomerProfile));


builder.Services.AddAutoMapper(cfg =>
{
//这样的话就可以扫描整个解决方案里的所有的映射
cfg.AddMaps(new []{ "WebApplication_ASP_NET_Core_MVC"});
// propertyName ------> PropertyName 默认是支持的
cfg.SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
cfg.DestinationMemberNamingConvention = new PascalCaseNamingConvention();

});
ProductService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
using System.Data.SqlClient;
using AutoMapper;
using Snowflake;
using WebApplication_ASP_NET_Core_MVC.DataModel;
using WebApplication_ASP_NET_Core_MVC.Models;
using WebApplication_ASP_NET_Core_MVC.utils;
using WebApplication_ASP_NET_Core_MVC.utils.Snowflake;

namespace WebApplication_ASP_NET_Core_MVC.Services
{
public class ProductService : IProductService
{
private readonly IMapper _mapper;
private readonly IdWorker _idWorker;
//public ProductService()
//{
// //_mapper = mapper;
// _idWorker = SnowflakeUtil.CreateIdWorker();
//}
public ProductService(IMapper mapper)
{
_mapper = mapper;
_idWorker = SnowflakeUtil.CreateIdWorker();
}

public List<ProductViewModel> Search()
{
string sql = @"select a.Id,ProductName,CategoryName,Price,a.UpdatedTime,
b.NickName as CreatedNickName,
c.NickName as UpdatedNickName
from Product a
left join UserInfo b on a.CreatedUserId = b.Id
left join UserInfo c on a.UpdatedUserId = c.Id
where a.Deleted=0";
return DbHelper.GetList<ProductViewModel>(sql);
}

public void Add(ProductCreateBo bo)
{
// 先将Bo对象 转换为DataModel对象(因为Bo对象不能出现在Repository数据访问层)
//var product = new Product()
//{
// Id = _idWorker.NextId(),
// CategoryName = bo.CategoryName,
// CreatedUserId = bo.CreatedUserId,
// Price = bo.Price,
// UpdatedUserId = bo.UpdatedUserId,
// ProductName = bo.ProductName,
// Remark = bo.Remark
//};

var product = _mapper.Map<ProductCreateBo, Product>(bo);
product.Id = _idWorker.NextId();//得使用这个不然id会有问题

string sql = "insert into Product values(@id,@productName,@categoryName,@price,@remark,@createdUserId,@updatedUserId,@createdTime,@updatedTime,@deleted)";

SqlParameter[] parameters =
{
new("@id", product.Id),
new("@productName", product.ProductName),
new("@categoryName", product.CategoryName),
new("@price", product.Price),
new("@remark", product.Remark),
new("@createdUserId", product.CreatedUserId),
new("@updatedUserId", product.UpdatedUserId),
new("@createdTime", product.CreatedTime),
new("@updatedTime", product.UpdatedTime),
new("@deleted", product.Deleted)
};

DbHelper.ExecuteNonQuery(sql, parameters);
}
}
}

创建 AutomapModels 文件夹
命名方式 camelCase/PascalCase

作用:驼峰命名与 Pascal 命名的兼容。

以下全局配置会映射 property_name 到 PropertyName

Product_1_2.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace WebApplication_ASP_NET_Core_MVC.AutomapModels
{
public class Product_1_2
{
//下划线的会出现问题
product_name = "1",
category_name = "2"
//productName = "1",
//categoryName = "2"
}

public class Product_to_1_2
{
public string? ProductName { get; set; }
public string? CategoryName { get; set; }
}
}

映射时匹配前缀或后缀
CustomerProfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using AutoMapper;
using WebApplication_ASP_NET_Core_MVC.AutomapModels;
using WebApplication_ASP_NET_Core_MVC.DataModel;
using WebApplication_ASP_NET_Core_MVC.Models;
namespace WebApplication_ASP_NET_Core_MVC.Profiles
{
// 一个数据库,一个profile, 一个数据库最好命名风格要一致
public class CustomerProfile:Profile
{
public CustomerProfile()
{
//创建映射关系
CreateMap<ProductCreateBo, Product>();

CreateMap<Product_1_2, Product_to_1_2>();

RecognizePrefixes("Step4");
CreateMap<Product_1_4, Product_to_1_4>();

//要么全局配置,要么当前映射表里面配置
//因为目前暂时没有用上
//CreateMap<Product, ProductViewModel>();
}
}
}
program.cs
1
2
3
4
5
6
7
8
9
10
builder.Services.AddAutoMapper(cfg =>
{
//这样的话就可以扫描整个解决方案里的所有的映射
cfg.AddMaps(new []{ "WebApplication_ASP_NET_Core_MVC"});
cfg.RecognizePrefixes("Step4");
// propertyName ------> PropertyName 默认是支持的
//cfg.SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
//cfg.DestinationMemberNamingConvention = new PascalCaseNamingConvention();

});
Product_1_4.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
namespace WebApplication_ASP_NET_Core_MVC.AutomapModels
{
public class Product_1_4
{
public string? Step4ProductName { get; set; }
public string? Step4CategoryName { get; set; }
}
public class Product_to_1_4
{
public string? ProductName { get; set; }
public string? CategoryName { get; set; }
}
}
TestAutoMapController
1
2
3
4
5
6
7
8
9
10
public void Test1_4()
{
var product = new Product_1_4()
{
Step4ProductName = "1",
Step4CategoryName = "2"
};
var productTo14 = _mapper.Map<Product_1_4, Product_to_1_4>(product);
Console.WriteLine(productTo14);
}
测试集合映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

// 测试集合映射
public void Test2_0()
{
List<Product_1_2> list = new List<Product_1_2>()
{
new Product_1_2()
{
productName = "1",
categoryName = "1"
},
new Product_1_2()
{
productName = "2",
categoryName = "2"
}
};

var toList = _mapper.Map<List<Product_1_2>,List<Product_to_1_2>>(list);
Console.WriteLine(toList);
}
手动控制某些成员的映射
testautomapcontroller
1
2
3
4
5
6
7
8
9
10
11
12
13
// 测试属性无规律的映射
public void Test2_1()
{
var user = new UserInfo2_1()
{
Id = 1,
NickName = "万宏伟",
Sex = '男'
};

var userTo = _mapper.Map<UserInfo2_1, UserInfoTo2_1>(user);
Console.WriteLine(userTo);
}
userinfo2_1.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace WebApplication_ASP_NET_Core_MVC.AutomapModels
{
public class UserInfo2_1
{
public long Id { get; set; }
public string NickName { get; set; }
public char Sex { get; set; }
}


public class UserInfoTo2_1
{
public long UserId { get; set; }
public string UserName { get; set; }
public char Gender { get; set; }
}
}

userprofile.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using AutoMapper;
using WebApplication_ASP_NET_Core_MVC.AutomapModels;

namespace WebApplication_ASP_NET_Core_MVC.Profiles
{
public class UserProfile:Profile
{
public UserProfile()
{
// 专门配置没有规律可言,而且名字又不一样的属性配置,如果说属性名有规律可言或者说属性名一样,则不需要这样
CreateMap<UserInfo2_1, UserInfoTo2_1>()
.ForMember(dest => dest.UserId,
opt => opt.MapFrom(soure => soure.Id))
.ForMember(dest => dest.UserName,
opt => opt.MapFrom(soure => soure.NickName))
.ForMember(dest => dest.Gender,
opt => opt.MapFrom(soure => soure.Sex));
}
}
}

嵌套(Nested)类和继承类映射
嵌套类
Product2_2.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace WebApplication_ASP_NET_Core_MVC.AutomapModels
{
public class Product2_2
{
public int Id { get; set; }
public UserInfo2_1 User { get; set; }
}

public class ProductTo2_2
{
public int Id { get; set; }
public UserInfoTo2_1 User { get; set; }
}

}

CustomerProfile.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using AutoMapper;
using WebApplication_ASP_NET_Core_MVC.AutomapModels;
using WebApplication_ASP_NET_Core_MVC.DataModel;
using WebApplication_ASP_NET_Core_MVC.Models;
namespace WebApplication_ASP_NET_Core_MVC.Profiles
{
// 一个数据库,一个profile, 一个数据库最好命名风格要一致
public class CustomerProfile:Profile
{
public CustomerProfile()
{
//创建映射关系
CreateMap<ProductCreateBo, Product>();

CreateMap<Product_1_2, Product_to_1_2>();

RecognizePrefixes("Step4");
CreateMap<Product_1_4, Product_to_1_4>();

CreateMap<Product2_2, ProductTo2_2>();

//因为目前暂时没有用上
//CreateMap<Product, ProductViewModel>();
}
}
}

TestAutoMapController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void Test2_2()
{
var product = new Product2_2()
{
Id = 1,
User = new UserInfo2_1()
{
Id = 1,
NickName = "万宏伟",
Sex = '男'
}
};

var to = _mapper.Map<Product2_2, ProductTo2_2>(product);
Console.WriteLine(to);
}
继承类
ParentSource.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
namespace WebApplication_ASP_NET_Core_MVC.AutomapModels
{
public class ParentSource
{
public int Id { get; set; }
public string? Name { get; set; }
}

public class ChildSource : ParentSource
{
public int Money { get; set; }
}


public class ParentTo
{
public int Id { get; set; }
public string? Name { get; set; }
}
public class ChildTo : ParentTo
{
public int Money { get; set; }
}
}

TestAutoMapController
1
2
3
4
5
6
7
8
9
10
11
12
public void Test2_3()
{
ParentSource source = new ChildSource()
{
Id = 1,
Money = 200,
Name = "任我行"
};

var parentTo = _mapper.Map<ParentSource,ParentTo>(source);
Console.WriteLine(parentTo);
}
UserProfile.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using AutoMapper;
using Step4.Unit4.AutomapModels;

namespace Step4.Unit4.Profiles;

public class UserProfile:Profile
{
public UserProfile()
{
// 专门配置没有规律可言,而且名字又不一样的属性配置,如果说属性名有规律可言或者说属性名一样,则不需要这样
CreateMap<UserInfo2_1, UserInfoTo2_1>()
.ForMember(dest=>dest.UserId,
opt=>opt.MapFrom(source=>source.Id))
.ForMember(dest=>dest.UserName,
opt=>opt.MapFrom(source=>source.NickName))
.ForMember(dest=>dest.Gender,
opt=>opt.MapFrom(source=>source.Sex));

// CreateMap<ParentSource, ParentTo>()
// .Include<ChildSource, ChildTo>();
// CreateMap<ChildSource, ChildTo>();
// 可以简写成
CreateMap<ParentSource, ParentTo>()
.IncludeAllDerived();
CreateMap<ChildSource, ChildTo>();
}
}
Html 辅助标签

作用:可以用于模型校验,虽然前端有一套,但是后端也有一套的话会更好。

StudentController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace WebApplication_ASP_NET_Core_MVC.Controllers
{
public class StudentController : Controller
{
public IActionResult Add()
{
// SelectList === List<SelectListItem>
List<ClassInfo> list = new()
{
new(){Id=1,ClassName="C#班级"},
new(){Id=2,ClassName="数据库班级"},
new(){Id=3,ClassName="Asp.Net Core班级"}
};
//绑定下拉框
ViewBag.ClassList = new SelectList(list,"Id","ClassName");
return View();
}
[HttpPost]
public IActionResult Submit()
{
return Ok("添加成功");
}
}
}
public class ClassInfo
{
public int Id { get; set; }
public string? ClassName { get; set; }
}

Add.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
@{
ViewBag.Title = "title";
Layout = "_Layout";
}
<h2>Html辅助标签</h2>
@* <form action="/student/submit" method="post" class="></form> *@
@using(Html.BeginForm("Submit","Student",method:FormMethod.Post,htmlAttributes:new { @class=""}))
{
@Html.Hidden("Id")
@* <input type="hidden" name="Id" id="Id"></input> *@
<table class="table" style="width: 700px;">
<tr>
<td>
@Html.Label("NickName","姓名: ")
@* <label for="NickName">姓名:</label> *@
</td>
<td>
@* 需要有value值才行 *@
@Html.TextBox("NickName", value: "123", htmlAttributes: new { @class = "form-control", placeholder = "请输入姓名", autocomplete = "false" })
@* <input type="text" name="NickName" id="NickName" class="form-control" placeholder="请输入姓名"/> *@
</td>
<td></td>
</tr>
<tr>
<td>
@Html.Label("Account","账号: ")
</td>
<td>
@Html.TextBox("Account", "",new { autocomplete = "false" })
</td>
</tr>
<tr>
<td>
@Html.Label("Pwd","密码: ")
</td>
<td>
@Html.Password("Pwd", "", new { autocomplete = "false" })
</td>
</tr>
<tr>
<td>
@Html.Label("Sex", "性别: ")
</td>
<td>
@Html.RadioButton("Sex","",true)男
@Html.RadioButton("Sex", "", false)女
</td>
</tr>
<tr>
<td>
@Html.Label("ClassId", "班级: ")
</td>
<td>

@* @Html.DropDownList("ClassId", selectList: null, optionLabel: "请选择班级", new { @class = "form-control" }) *@
@Html.DropDownList("ClassId",(SelectList)ViewBag.ClassList,optionLabel:"请选择班级",new{@class="form-control"})
@* <select>
<option value="1">c#班级</option>
<option value="2">数据库班级</option>
</select> *@
</td>
</tr>
@* <tr> *@
@* <td>
@Html.Label("Course", "课程: ")
</td> *@
@* <td> *@

@* @Html.DropDownList("ClassId", selectList: null, optionLabel: "请选择班级", new { @class = "form-control" }) *@
@* @Html.ListBox("Course", (SelectList)ViewBag., new { @class = "form-control" }) *@
@* <select>
<option value="1">c#班级</option>
<option value="2">数据库班级</option>
</select> *@
@* </td> *@
@* </tr> *@
<tr>
<td>
@Html.Label("Hobby", "爱好: ")
</td>
<td>
<input type="checkbox" name="Hobby" value="编程">编程
<input type="checkbox" name="Hobby" value="爬山">爬山
<input type="checkbox" name="Hobby" value="">钱
@* 因为下面这个会有异常,明明3个数据会显示5个数据,多出来2个false *@
@* @Html.CheckBox("Hobby",false,new{value="编程"})编程
@Html.CheckBox("Hobby", false, new { value = "爬山" })爬山
@Html.CheckBox("Hobby", false, new { value = "" })钱 *@
</td>
</tr>
<tr>
<td>
@Html.Label("Description", "简介: ")
</td>
<td>
@Html.TextArea("Description")
</td>
</tr>
<tr>
<td>
@Html.Label("Birthday", "生日:")
</td>
<td>
@Html.TextBox("Birthday", "", new { type = "date" })
</td>
</tr>
<tr>
<td>
<input type="submit" class="btn btn-primary" value="保存" />
</td>
<td>
</td>
</tr>
</table>
}
Search.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@model List<ProductViewModel>

@{
ViewBag.Title = "商品列表";
Layout = "_Layout";
}

<h2>商品列表</h2>
@* <a href="/product/add?id=1" class="btn btn-primary" style="color:red">添加商品</a> *@
@Html.ActionLink("添加商品", "Add", "Product", new{id=1},
htmlAttributes: new { style = "color:white;", @class = "btn btn-primary" })

<table class="table table-hover">
<tr>
<td>id</td>
<td>商品名称</td>
<td>商品类型</td>
<td>价格</td>
<td>添加人</td>
<td>最后维护人</td>
<td>最后更新时间</td>
</tr>
@foreach (var p in Model)
{
<tr>
<td>@p.Id</td>
<td>@p.ProductName</td>
<td>@p.CategoryName</td>
<td>@p.Price</td>
<td>@p.CreatedNickName</td>
<td>@p.UpdatedNickName</td>
<td>@p.UpdatedTime</td>
</tr>
}

</table>

标签补充
Add.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
@model StudentBo
@{
ViewBag.Title = "title";
Layout = "_Layout";
}
<style>
.table span {
color: red;
}
</style>
<h2>Html辅助标签</h2>
@* <form action="/student/submit" method="post" class="></form> *@
@using(Html.BeginForm("Submit","Student",method:FormMethod.Post,htmlAttributes:new { @class=""}))
{
@Html.ValidationSummary()//默认是true,是false的话下面就会显示错误
@* @Html.Hidden("Id") *@
@* <input type="hidden" name="Id" id="Id"></input> *@
<table class="table" style="width: 700px;">
<tr>
<td>
@* @Html.Label("NickName","姓名: ") *@
@Html.Label("NickName", "姓名: ")
@* <label for="NickName">姓名:</label> *@
</td>
<td>
@Html.TextBoxFor(p => p.NickName)
@* 需要有value值才行 *@
@* @Html.TextBox("NickName", value: "123", htmlAttributes: new { @class = "form-control", placeholder = "请输入姓名", autocomplete = "false" }) *@
@* <input type="text" name="NickName" id="NickName" class="form-control" placeholder="请输入姓名"/> *@
</td>
<td>
@Html.ValidationMessageFor(p => p.NickName)
</td>
</tr>
<tr>
<td>
@Html.Label("Account","账号: ")
</td>
<td>
@Html.TextBoxFor(p => p.Account)
@* @Html.TextBox("Account", "",new { autocomplete = "false" }) *@
</td>
<td>
@Html.ValidationMessageFor(p=>p.Account)
</td>
</tr>
<tr>
<td>
@* @Html.Label("Pwd","密码: ") *@
@Html.DisplayName("Pwd")//实现标签的显示
</td>
<td>
@Html.Password("Pwd", "", new { autocomplete = "false" })
</td>
<td>
@Html.ValidationMessageFor(p => p.Pwd)
</td>
</tr>
<tr>
<td>
@Html.Label("ConfirmPwd", "确认密码:")
</td>
<td>
@Html.Password("ConfirmPwd", "", new { autocomplete = "false" })
</td>
<td>
@Html.ValidationMessageFor(p => p.ConfirmPwd)
</td>
</tr>
<tr>
<td>
@Html.Label("Sex", "性别: ")
</td>
<td>
@Html.RadioButton("Sex","",true)男
@Html.RadioButton("Sex", "", false)女
</td>
</tr>
<tr>
<td>
@Html.Label("ClassId", "班级: ")
</td>
<td>

@* @Html.DropDownList("ClassId", selectList: null, optionLabel: "请选择班级", new { @class = "form-control" }) *@
@Html.DropDownList("ClassId",(SelectList)ViewBag.ClassList,optionLabel:"请选择班级",new{@class="form-control"})
@* <select>
<option value="1">c#班级</option>
<option value="2">数据库班级</option>
</select> *@
</td>
<td>
@Html.ValidationMessageFor(p => p.ClassId)
</td>
</tr>
@* <tr> *@
@* <td>
@Html.Label("Course", "课程: ")
</td> *@
@* <td> *@

@* @Html.DropDownList("ClassId", selectList: null, optionLabel: "请选择班级", new { @class = "form-control" }) *@
@* @Html.ListBox("Course", (SelectList)ViewBag., new { @class = "form-control" }) *@
@* <select>
<option value="1">c#班级</option>
<option value="2">数据库班级</option>
</select> *@
@* </td> *@
@* </tr> *@
<tr>
<td>
@Html.Label("Hobby", "爱好: ")
</td>
<td>
<input type="checkbox" name="Hobby" value="编程">编程
<input type="checkbox" name="Hobby" value="爬山">爬山
<input type="checkbox" name="Hobby" value="">钱
@* 因为下面这个会有异常,明明3个数据会显示5个数据,多出来2个false *@
@* @Html.CheckBox("Hobby",false,new{value="编程"})编程
@Html.CheckBox("Hobby", false, new { value = "爬山" })爬山
@Html.CheckBox("Hobby", false, new { value = "" })钱 *@
</td>
</tr>
<tr>
<td>
@Html.Label("Description", "简介: ")
</td>
<td>
@Html.TextArea("Description")
</td>
</tr>
<tr>
<td>
@Html.Label("Birthday", "生日:")
</td>
<td>
@Html.TextBox("Birthday", "", new { type = "date" })
</td>
<td>
@Html.ValidationMessageFor(p => p.Birthday)
</td>
</tr>
<tr>
<td>
@Html.Label("Email", "Email:")
</td>
<td>
@Html.TextBox("Email")
</td>
<td>
@Html.ValidationMessageFor(p => p.Email)
</td>
</tr>
<tr>
<td>
<input type="submit" class="btn btn-primary" value="保存" />
</td>
<td>
</td>
</tr>
</table>
}

@section Scripts
{
@* <script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script> *@
@* <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script> *@
@(await Html.PartialAsync("_ValidationScriptsPartial"))
}
Shared/_StudentView.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@using X.PagedList.Mvc.Core
@model X.PagedList.StaticPagedList<StudentViewModel>


<table class="table table-hover">
<tr>
<td>账号</td>
<td>姓名</td>
<td>性别</td>
<td>班级名称</td>
<td>爱好</td>
<td>生日</td>
<td>简介</td>
<td>操作</td>
</tr>
@foreach (var s in Model)
{
<tr>
<td>@s.Account</td>
<td>@s.NickName</td>
<td>@s.Sex</td>
<td>@s.ClassName</td>
<td>@s.Hobby</td>
<td>@s.Birthday.ToString("yyyy-MM-dd")</td>
<td>@Html.Raw(s.Description)</td>//用于Html标签的实现
<td>
<a class="btn btn-info" href="/student/edit/@s.Id">编辑</a>
<a onclick="return confirm('您是否删除吗?')" class="btn btn-danger" href="/student/delete/@s.Id">删除</a>
</td>
</tr>
}
</table>
@Html.PagedListPager(Model, page => $"/student/search?CurrentPage={page}&PageSize=3")
Models/StudentBo.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using System.ComponentModel.DataAnnotations;

namespace WebApplication_ASP_NET_Core_MVC.Models
{
public class StudentBo
{
/**
* 模型校验的分类:
* 1. 必须 Required
* 2. 长度范围校验:6-16 StringLength
* 3. 正则校验:手机号,身份证,邮箱 RegularExpression
* 4. 数字范围 Range
* 5. 属性对比:Compare
*/

public long Id { get; set; }
[Required(ErrorMessage = "请输入账号")]
[StringLength(16, MinimumLength = 2, ErrorMessage = "账号是2-16位")]
[Display(Name = "账号")]
public string? Account { get; set; }


[Required(ErrorMessage = "请输入密码"), StringLength(16, MinimumLength = 2, ErrorMessage = "密码是2-16位")]
// [DataType(DataType.Password)] // 等我们讲到TagHelper的时候,再给大家演示这个DataType
[Display(Name = "密码")]//标签的补充
public string? Pwd { get; set; }

[Required(ErrorMessage = "请输入确认密码"), Compare("Pwd", ErrorMessage = "确认密码不一样")]
public string? ConfirmPwd { get; set; }

[Required(ErrorMessage = "请输入姓名")]
[Display(Name = "姓名")]
public string? NickName { get; set; }


public string? Sex { get; set; }


[Range(0, int.MaxValue)]
[Required(ErrorMessage = "请选择班级")]
public int ClassId { get; set; }
public string[]? Hobby { get; set; }
public string? Description { get; set; }

[Required(ErrorMessage = "请选择生日")]
public DateTime Birthday { get; set; }


[Required(ErrorMessage = "请输入邮箱")]
// 1310734881@qq.com
[RegularExpression(@"\w+@\w+\.\w+", ErrorMessage = "邮箱格式不正确")]
// [EmailAddress]
public string? Email { get; set; }
}
}

Dapper 实现增删改查
SQL Server 创建表并添加数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
create table StudentInfo
(
Id bigint primary key,
NickName varchar(30),
Account varchar(30),
Pwd varchar(50),
Sex char(2),
Birthday datetime,
[Description] varchar(200),
Hobby varchar(50),
ClassId int
);

insert into StudentInfo(Id,
NickName,
Account,
Pwd,
Sex,
Birthday,
[Description],
Hobby,
ClassId
) values(1,'小明','x123456','123456','男','1998-09-09','个人up','唱歌,跳舞',1)
添加包

Dapper

Profile 文件夹
StudentProfile.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using AutoMapper;
using Microsoft.AspNetCore.Http.HttpResults;
using WebApplication_ASP_NET_Core_MVC.DataModel;
using WebApplication_ASP_NET_Core_MVC.Models;

namespace WebApplication_ASP_NET_Core_MVC.Profiles
{
public class StudentProfile:Profile
{
public StudentProfile()
{
//实体类转换为bo类,暂时还用不上
CreateMap<StudentInfo,StudentBo>();
CreateMap<StudentBo, StudentInfo>();
CreateMap<StudentInfo, StudentViewModel>();
}
}
}

DataModel 文件夹
StudentInfo.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
namespace WebApplication_ASP_NET_Core_MVC.DataModel;
/**
* create table StudentInfo
(
Id bigint primary key,
NickName varchar(30),
Account varchar(30),
Pwd varchar(50),
Sex char(2),
Birthday datetime,
[Description] varchar(200),
Hobby varchar(50),
ClassId int
);

insert into StudentInfo(Id,
NickName,
Account,
Pwd,
Sex,
Birthday,
[Description],
Hobby,
ClassId
) values(1,'小明','x123456','123456','男','1998-09-09','个人up','唱歌,跳舞',1)
*/
// 数据实体类
//跟数据库打交道的类
public class StudentInfo
{
public long Id { get; set; }
public string? Account { get; set; }
public string? Pwd { get; set; }
public string? NickName { get; set; }
public string? Sex { get; set; }
public int ClassId { get; set; }
public string? Hobby { get; set; }
public string? Description { get; set; }
public DateTime Birthday { get; set; }
}
StudentController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using WebApplication_ASP_NET_Core_MVC.Models;
using WebApplication_ASP_NET_Core_MVC.Services;

namespace WebApplication_ASP_NET_Core_MVC.Controllers
{
public class StudentController : Controller
{
private readonly IStudentService _studentService;

public StudentController(IStudentService studentService)
{
_studentService = studentService;
}
public IActionResult Search()
{
//var list = _studentService.Search();
//return View(list);
return View(_studentService.Search());
}

public IActionResult Add()
{
// SelectList === List<SelectListItem>
//这个写在list里面的话是无法左连接的
//List<ClassInfo> list = new()
//{
// new(){Id=1,ClassName="C#班级"},
// new(){Id=2,ClassName="数据库班级"},
// new(){Id=3,ClassName="Asp.Net Core班级"}
//};
//绑定下拉框
//ViewBag.ClassList = new SelectList(list,"Id","ClassName");
ViewBag.ClassList = new SelectList(_studentService.GetClassList(), "Id", "ClassName");
return View();
}
// Dapper 实现增删改查(非常轻量一款ORM框架) ,它是介于Ado.net 与 EntityFramework 之间一款数据库访问技术框架
[HttpPost]
public IActionResult Submit(StudentBo bo)
{
_studentService.Save(bo);
return RedirectToAction("Search");
//return Ok("添加成功");
}
public IActionResult Edit(long id)
{
//return View(_studentService.GetModel(id));
var studentViewModel = _studentService.GetModel(id);
ViewBag.ClassList = new SelectList(_studentService.GetClassList(),
"Id", // 下拉框选中的值
"ClassName", //下拉框显示的值
studentViewModel.ClassId); // 下拉框当前选中值
return View(studentViewModel);
}
public IActionResult Delete(long id)
{
_studentService.Delete(id);
return RedirectToAction("Search");
}
}
}
public class ClassInfo
{
public int Id { get; set; }
public string? ClassName { get; set; }
}

Models 文件夹

一个表维护三个类,一个跟数据库打交道的类 studentinfo.cs,一个添加修改的类 studentbo.cs,一个数据显示的类 studentviewmodel.cs

StudentBo.cs

这不是跟数据库打交道的类,也就是充当传输的作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace WebApplication_ASP_NET_Core_MVC.Models
{
public class StudentBo
{
//用于添加修改的一个类,修改需要id
public long Id { get; set; }
public string? Account { get; set; }
public string? Pwd { get; set; }
public string? NickName { get; set; }
public string? Sex { get; set; }
public int ClassId { get; set; }
public string? Hobby { get; set; }
public string? Description { get; set; }
public DateTime Birthday { get; set; }
}
}

StudentViewModel.cs

好处是这边发生更改不影响 bo 也不影响实体类也就是 studentinfo,这是用于给用户展示的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace WebApplication_ASP_NET_Core_MVC.Models
{
public class StudentViewModel
{
public long Id { get; set; }
public string? Account { get; set; }
//public string? Pwd { get; set; }
public string? NickName { get; set; }
public string? Sex { get; set; }
//拿班级id换班级名称
public int ClassId { get; set; }
public string? ClassName { get; set; }
public string? Hobby { get; set; }
public DateTime Birthday { get; set; }
public string? Description { get; set; }
}
}

Services 文件夹
IStudentService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using WebApplication_ASP_NET_Core_MVC.DataModel;
using WebApplication_ASP_NET_Core_MVC.Models;

namespace WebApplication_ASP_NET_Core_MVC.Services
{
public interface IStudentService
{
List<StudentViewModel> Search();
/// <summary>
/// 添加或者修改(Id>0表示修改)
/// </summary>
/// <param name="bo"></param>
void Save(StudentBo bo);

/// <summary>
/// 数据的回显(反填/编辑)
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
StudentBo GetModel(long id);
/// <summary>
/// 根据主键进行删除
/// </summary>
/// <param name="id"></param>
void Delete(long id);

//这边classinfo是将controller里面的放到datamodel里面的classinfo中
List<ClassInfo> GetClassList();
}
}

StudentService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using AutoMapper;
using Dapper;
using Snowflake;
using WebApplication_ASP_NET_Core_MVC.DataModel;
using WebApplication_ASP_NET_Core_MVC.Models;
using WebApplication_ASP_NET_Core_MVC.utils.Snowflake;

namespace WebApplication_ASP_NET_Core_MVC.Services
{
public class StudentService:IStudentService
{

//三个内置服务,已经注入到ioc容器里面了,ILogger,IWebHostEnvironment,IConfiguration
//IWebHostEnvironment这个只能在服务器内点出来,其他地方是IHostEnvironment
//private readonly IConfiguration _configuration;
private readonly string _connString;
private readonly IMapper _mapper;
private readonly IdWorker _idWorker;
public StudentService(IConfiguration configuration, IMapper mapper)
{
_connString = configuration.GetConnectionString("sql");
_mapper = mapper;
_idWorker = SnowflakeUtil.CreateIdWorker();
}
public List<StudentViewModel> Search()
{
string sql = "select * from StudentInfo";
using IDbConnection connection = new SqlConnection(_connString);
//query需要引入using Dapper这个包才行
//return connection.Query<StudentViewModel>(sql).ToList();
var list = connection.Query<StudentViewModel>(sql).ToList();
if (list.Count == 0)
{
return null;
}
// 拿班级的id换取班级名称
var classIds = list.Select(p => p.ClassId);
var classInfos = GetClassList().Where(p => classIds.Contains(p.Id));
foreach (var studentViewModel in list)
{
//select top 1 * from classInfo where Id=studentViewModel.Id
studentViewModel.ClassName = classInfos.FirstOrDefault(p => p.Id == studentViewModel.ClassId).ClassName;
}
return list;
}
public void Save(StudentBo bo)
{
//这个@id参数名字必须跟属性名字一样不然会出问题
//string sql = "update StudentInfo set NickName=@NickName,Sex=@Sex,Birthday=@Birthday,Description=@Description,Hobby=@Hobby,ClassId=@ClassId where Id=@Id";
string sql = string.Empty;
//automap底层原理也是反射,反射就是用于封装组件用的
var entity = _mapper.Map<StudentBo, StudentInfo>(bo);
entity.Hobby = bo.Hobby.ToString();
//entity.Hobby = string.Join(",", bo.Hobby); // 将数组以逗号隔开
if (bo.Id== 0) // 添加
{
sql = "insert into StudentInfo values(@Id, @NickName, @Account, @Pwd, @Sex, @Birthday, @Description, @Hobby, @ClassId )";
entity.Id = _idWorker.NextId();
}
else
{
sql = "update StudentInfo set NickName=@NickName,Sex=@Sex,Birthday=@Birthday,Description=@Description,Hobby=@Hobby,ClassId=@ClassId where Id=@Id";
}
//关键字之前会加入[]才行
//string sql = "insert into StudentInfo values(@Id,@NickName,@Account,@Pwd,@Sex,@Birthday,@Description,@Hobby,@ClassId)";
using IDbConnection connection = new SqlConnection(_connString);
//automap底层原理也是反射,反射就是用于封装组件用的
//var entity = _mapper.Map<StudentBo, StudentInfo>(bo);
//entity.Id = _idWorker.NextId();
//反射
//因为entity参数的名字和属性名字一样,所以不需要匿名对象,不然需要匿名对象new {id=entity.id...}
connection.Execute(sql, entity);
}
public StudentBo GetModel(long id)
{
//参数化查询
string sql = "select * from StudentInfo where Id=@id";
using IDbConnection connection = new SqlConnection(_connString);
//orm思想的话就是写实体类
//id=id如果左边和右边一样可以省略,不一样就得写上去id1=id,id跟上面@id要一样
var entity = connection.QueryFirst<StudentInfo>(sql, new {id });// id1 匿名对象的属性名 == sql参数对象名
//实体类映射到bo对象
return _mapper.Map<StudentInfo,StudentBo>(entity);
//return _mapper.Map<StudentInfo, StudentViewModel>(entity);
}
public void Delete(long id)
{
string sql = "delete from StudentInfo where Id=@id";
using IDbConnection connection = new SqlConnection(_connString);
connection.Execute(sql, new { id });
}

public List<ClassInfo> GetClassList()
{
List<ClassInfo> list = new()
{
new(){Id = 1,ClassName = "C#初级班"},
new(){Id = 2,ClassName = "数据库初级班"},
new(){Id = 3,ClassName = "Asp.Net Core 班"}
};

return list;
}
}
}
Program.cs
1
builder.Services.AddScoped<IStudentService,StudentService>();
Views 文件夹
/Student/Search.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@model List<StudentViewModel>

@{
ViewBag.Title = "title";
Layout = "_Layout";
}

<h2>学生查询</h2>
@Html.ActionLink("添加", "Add")
<table class="table table-hover">
<tr>
<td>账号</td>
<td>姓名</td>
<td>性别</td>
<td>班级名称</td>
<td>爱好</td>
<td>生日</td>
<td>操作</td>
</tr>
@foreach(var s in Model)
{
<tr>
<td>@s.Account</td>
<td>@s.NickName</td>
<td>@s.Sex</td>
<td>@s.ClassName</td>
<td>@s.Hobby</td>
@* 用于显示时间 *@
<td>@s.Birthday.ToString("yyyy-MM-dd")</td>
<td>
@* <a class="btn btn-default" href="/student/edit/@s.Id">编辑</a> *@
<a class="btn btn-info" href="/student/edit/@s.Id">编辑</a>
<a onclick="return confirm('您是否删除吗?')" class="btn btn-danger" href="/student/delete/@s.Id">删除</a>
</td>
</tr>
}
</table>
/Student/Edit.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
@model StudentViewModel
@{
ViewBag.Title = "title";
Layout = "_Layout";
}
<h2>编辑学生</h2>
@* <form action="/student/submit" method="post" class="></form> *@
@using (Html.BeginForm("Submit", "Student", method: FormMethod.Post, htmlAttributes: new { @class = "" }))
{
@Html.Hidden("Id")
@* <input type="hidden" name="Id" id="Id"></input> *@
<table class="table" style="width: 700px;">
<tr>
<td>
@Html.Label("NickName", "姓名: ")
@* <label for="NickName">姓名:</label> *@
</td>
<td>
@* 需要有value值才行 *@
@* @Html.TextBox("NickName", value: "123", htmlAttributes: new { @class = "form-control", placeholder = "请输入姓名", autocomplete = "false" }) *@
@Html.TextBox("NickName")
@* <input type="text" name="NickName" id="NickName" class="form-control" placeholder="请输入姓名"/> *@
</td>
<td></td>
</tr>
<tr>
<td>
@Html.Label("Account", "账号: ")
</td>
<td>
@* 因为readonly是关键字所以需要加上@ *@
@* @Html.TextBox("Account", "", new { autocomplete = "false",@readonly="readonly" }) *@
@* 如果value值为空无法反填数据 *@
@* @Html.TextBox("Account") *@
@Html.TextBox("Account", Model.Account, new{@readonly = "readonly"})
</td>
<tr>
<td>
@Html.Label("Sex", "性别: ")
</td>
<td>
@* @Html.RadioButton("Sex", "", true)男
@Html.RadioButton("Sex", "", false)女 *@
@* 这边数据库sex.char(2)得是2如果是4,那么后面会有多出来的空格需要匹配 *@
@Html.RadioButton("Sex", "",Model.Sex == "") 男
@Html.RadioButton("Sex", "", Model.Sex == "") 女
</td>
</tr>
<tr>
<td>
@Html.Label("ClassId", "班级: ")
</td>
<td>

@* @Html.DropDownList("ClassId", selectList: null, optionLabel: "请选择班级", new { @class = "form-control" }) *@
@Html.DropDownList("ClassId", (SelectList)ViewBag.ClassList, optionLabel: "请选择班级", new { @class = "form-control" })
@* <select>
<option value="1">c#班级</option>
<option value="2">数据库班级</option>
</select> *@
</td>
</tr>
@* <tr> *@
@* <td>
@Html.Label("Course", "课程: ")
</td> *@
@* <td> *@

@* @Html.DropDownList("ClassId", selectList: null, optionLabel: "请选择班级", new { @class = "form-control" }) *@
@* @Html.ListBox("Course", (SelectList)ViewBag., new { @class = "form-control" }) *@
@* <select>
<option value="1">c#班级</option>
<option value="2">数据库班级</option>
</select> *@
@* </td> *@
@* </tr> *@
<tr>
<td>
@Html.Label("Hobby", "爱好: ")
</td>
<td>
<input type="checkbox" name="Hobby" value="编程" checked="@(Model.Hobby.Contains("编程") ? "checked" : "")" /> 编程
<input type="checkbox" name="Hobby" value="爬山" checked="@(Model.Hobby.Contains("爬山") ? "checked" : "")" /> 爬山
<input type="checkbox" name="Hobby" value="" checked="@(Model.Hobby.Contains("钱") ? "checked" : "")" /> 钱
@* <input type="checkbox" name="Hobby" value="编程">编程
<input type="checkbox" name="Hobby" value="爬山">爬山
<input type="checkbox" name="Hobby" value="">钱 *@
@* 因为下面这个会有异常,明明3个数据会显示5个数据,多出来2个false *@
@* @Html.CheckBox("Hobby",false,new{value="编程"})编程
@Html.CheckBox("Hobby", false, new { value = "爬山" })爬山
@Html.CheckBox("Hobby", false, new { value = "" })钱 *@
</td>
</tr>
<tr>
<td>
@Html.Label("Description", "简介: ")
</td>
<td>
@Html.TextArea("Description")
</td>
</tr>
<tr>
<td>
@Html.Label("Birthday", "生日:")
</td>
<td>
@* @Html.TextBox("Birthday", "", new { type = "date" }) *@
@* 因为必须得用到所以必须得赋一个值 *@
@* Model.Birthday.Date.ToString()这样还是不能赋上值,所以还是得取消掉才行 *@
@* @Html.TextBox("Birthday", Model.Birthday.Date.ToString(), new { type = "date" }) *@
@Html.TextBox("Birthday")
</td>
</tr>
<tr>
<td>
<input type="submit" class="btn btn-primary" value="保存" />
</td>
<td>
</td>
</tr>
</table>
}
Mvc Core 分页查询
添加包

PagedList.Mvc–framwork

X.PagedList.Mvc.Core–.net6.0

SQL Server 页面
/Student/Search.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@* @model List<StudentViewModel> *@
@using X.PagedList.Mvc.Core
@model X.PagedList.StaticPagedList<StudentViewModel>
@{
ViewBag.Title = "title";
Layout = "_Layout";
}

<h2>学生查询</h2>
<form action="/student/search">
账号:@Html.TextBox("Account") 姓名:@Html.TextBox("NickName")
班级:@Html.DropDownList("ClassId", ViewBag.ClassList, "请选择班级", null)
<input type="submit" class="btn btn-primary" value="查询" />
</form>
@Html.ActionLink("添加", "Add")
<table class="table table-hover">
<tr>
<td>账号</td>
<td>姓名</td>
<td>性别</td>
<td>班级名称</td>
<td>爱好</td>
<td>生日</td>
<td>操作</td>
</tr>
@foreach(var s in Model)
{
<tr>
<td>@s.Account</td>
<td>@s.NickName</td>
<td>@s.Sex</td>
<td>@s.ClassName</td>
<td>@s.Hobby</td>
@* 用于显示时间 *@
<td>@s.Birthday.ToString("yyyy-MM-dd")</td>
<td>
@* <a class="btn btn-default" href="/student/edit/@s.Id">编辑</a> *@
<a class="btn btn-info" href="/student/edit/@s.Id">编辑</a>
<a onclick="return confirm('您是否删除吗?')" class="btn btn-danger" href="/student/delete/@s.Id">删除</a>
</td>
</tr>
}
</table>
@Html.PagedListPager(Model,page=>Url.Action("Search",new{CurrentPage=page,PageSize=3}))
IStudentService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using WebApplication_ASP_NET_Core_MVC.DataModel;
using WebApplication_ASP_NET_Core_MVC.Models;

namespace WebApplication_ASP_NET_Core_MVC.Services
{
public interface IStudentService
{
//List<StudentViewModel> Search();
//分页查询用的
List<StudentViewModel> Search(StudentQuery query, out int totalCount);
/// <summary>
/// 添加或者修改(Id>0表示修改)
/// </summary>
/// <param name="bo"></param>
void Save(StudentBo bo);

/// <summary>
/// 数据的回显(反填/编辑)
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
//StudentBo GetModel(long id);
///// <summary>
///// 根据主键进行删除
///// </summary>
///// <param name="id"></param>
StudentViewModel GetModel(long id);
/// <summary>
/// 根据主键进行删除
/// </summary>
/// <param name="id"></param>
void Delete(long id);

//这边classinfo是将controller里面的放到datamodel里面的classinfo中
List<ClassInfo> GetClassList();
}
}

StudentService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using AutoMapper;
using Dapper;
using Microsoft.AspNetCore.Mvc;
using Snowflake;
using WebApplication_ASP_NET_Core_MVC.DataModel;
using WebApplication_ASP_NET_Core_MVC.Models;
using WebApplication_ASP_NET_Core_MVC.utils.Snowflake;

namespace WebApplication_ASP_NET_Core_MVC.Services
{
public class StudentService:IStudentService
{

//三个内置服务,已经注入到ioc容器里面了,ILogger,IWebHostEnvironment,IConfiguration
//IWebHostEnvironment这个只能在服务器内点出来,其他地方是IHostEnvironment
//private readonly IConfiguration _configuration;
private readonly string _connString;
private readonly IMapper _mapper;
private readonly IdWorker _idWorker;
public StudentService(IConfiguration configuration, IMapper mapper)
{
_connString = configuration.GetConnectionString("sql");
_mapper = mapper;
_idWorker = SnowflakeUtil.CreateIdWorker();
}
//Dapper用的
//public List<StudentViewModel> Search()
//{
// string sql = "select * from StudentInfo";
// using IDbConnection connection = new SqlConnection(_connString);
// //query需要引入using Dapper这个包才行
// //return connection.Query<StudentViewModel>(sql).ToList();
// var list = connection.Query<StudentViewModel>(sql).ToList();
// if (list.Count == 0)
// {
// return null;
// }
// // 拿班级的id换取班级名称
// var classIds = list.Select(p => p.ClassId);
// var classInfos = GetClassList().Where(p => classIds.Contains(p.Id));
// foreach (var studentViewModel in list)
// {
// //select top 1 * from classInfo where Id=studentViewModel.Id
// studentViewModel.ClassName = classInfos.FirstOrDefault(p => p.Id == studentViewModel.ClassId).ClassName;
// }
// return list;
//}
//分页查询用的
public List<StudentViewModel>? Search(StudentQuery query, out int totalCount)
{
//string sql = "select * from StudentInfo";
//using IDbConnection connection = new SqlConnection(_connString);
StringBuilder sql = new("select * from StudentInfo where 1=1 ");
StringBuilder countSql = new("select count(*) from studentInfo where 1=1 ");
DynamicParameters parameters = new DynamicParameters();
if (!string.IsNullOrWhiteSpace(query.Account))
{
sql.Append(" and Account like @account");
countSql.Append(" and Account like @account");
parameters.Add("account", "%" + query.Account + "%");
}
if (!string.IsNullOrWhiteSpace(query.NickName))
{
sql.Append(" and NickName like @nickName");
countSql.Append(" and NickName like @nickName");
//模糊查询
parameters.Add("nickName", "%" + query.NickName + "%");
}
if (query.ClassId > 0)
{
sql.Append(" and classId=@classId");
countSql.Append(" and classId=@classId");
parameters.Add("classId", query.ClassId);
}
//总条数不能拼接下面这个
sql.Append(" order by Id desc offset @start rows fetch next @pageSize rows only");
//这个using得在上面,放在parameters下面是有问题的,因为countsql是不需要这两个参数的,没有这两个参数所以无法执行
using IDbConnection connection = new SqlConnection(_connString);
totalCount = connection.ExecuteScalar<int>(countSql.ToString(), parameters);
parameters.Add("start", (query.CurrentPage - 1) * query.PageSize);
parameters.Add("pageSize", query.PageSize);
//query需要引入using Dapper这个包才行
//return connection.Query<StudentViewModel>(sql).ToList();
//var list = connection.Query<StudentViewModel>(sql).ToList();
var list = connection.Query<StudentViewModel>(sql.ToString(), parameters).ToList();


if (list.Count == 0)
{
//return null;
return new List<StudentViewModel>();
}
// 拿班级的id换取班级名称
var classIds = list.Select(p => p.ClassId);
var classInfos = GetClassList().Where(p => classIds.Contains(p.Id));
foreach (var studentViewModel in list)
{
//select top 1 * from classInfo where Id=studentViewModel.Id
studentViewModel.ClassName = classInfos.FirstOrDefault(p => p.Id == studentViewModel.ClassId).ClassName;
}
return list;
}
public void Save(StudentBo bo)
{
//这个@id参数名字必须跟属性名字一样不然会出问题
//string sql = "update StudentInfo set NickName=@NickName,Sex=@Sex,Birthday=@Birthday,Description=@Description,Hobby=@Hobby,ClassId=@ClassId where Id=@Id";
string sql = string.Empty;
//automap底层原理也是反射,反射就是用于封装组件用的
var entity = _mapper.Map<StudentBo, StudentInfo>(bo);
//entity.Hobby = bo.Hobby.ToString();
entity.Hobby = string.Join(",", bo.Hobby); // 将数组以逗号隔开
if (bo.Id== 0) // 添加
{
sql = "insert into StudentInfo values(@Id, @NickName, @Account, @Pwd, @Sex, @Birthday, @Description, @Hobby, @ClassId )";
entity.Id = _idWorker.NextId();
}
else
{
sql = "update StudentInfo set NickName=@NickName,Sex=@Sex,Birthday=@Birthday,Description=@Description,Hobby=@Hobby,ClassId=@ClassId where Id=@Id";
}
//关键字之前会加入[]才行
//string sql = "insert into StudentInfo values(@Id,@NickName,@Account,@Pwd,@Sex,@Birthday,@Description,@Hobby,@ClassId)";
using IDbConnection connection = new SqlConnection(_connString);
//automap底层原理也是反射,反射就是用于封装组件用的
//var entity = _mapper.Map<StudentBo, StudentInfo>(bo);
//entity.Id = _idWorker.NextId();
//反射
//因为entity参数的名字和属性名字一样,所以不需要匿名对象,不然需要匿名对象new {id=entity.id...}
connection.Execute(sql, entity);
}
//不太合适为studentbo
//public StudentBo GetModel(long id)
//{
// //参数化查询
// string sql = "select * from StudentInfo where Id=@id";
// using IDbConnection connection = new SqlConnection(_connString);
// //orm思想的话就是写实体类
// //id=id如果左边和右边一样可以省略,不一样就得写上去id1=id,id跟上面@id要一样
// var entity = connection.QueryFirst<StudentInfo>(sql, new {id });// id1 匿名对象的属性名 == sql参数对象名
// //实体类映射到bo对象
// return _mapper.Map<StudentInfo,StudentBo>(entity);
// //return _mapper.Map<StudentInfo, StudentViewModel>(entity);
//}
public StudentViewModel GetModel(long id)
{
//参数化查询
string sql = "select * from StudentInfo where Id=@id";
using IDbConnection connection = new SqlConnection(_connString);
//orm思想的话就是写实体类
//id=id如果左边和右边一样可以省略,不一样就得写上去id1=id,id跟上面@id要一样
var entity = connection.QueryFirst<StudentInfo>(sql, new { id });// id1 匿名对象的属性名 == sql参数对象名
//实体类映射到bo对象
return _mapper.Map<StudentInfo, StudentViewModel>(entity);
//return _mapper.Map<StudentInfo, StudentViewModel>(entity);
}
public void Delete(long id)
{
string sql = "delete from StudentInfo where Id=@id";
using IDbConnection connection = new SqlConnection(_connString);
connection.Execute(sql, new { id });
}

public List<ClassInfo> GetClassList()
{
List<ClassInfo> list = new()
{
new(){Id = 1,ClassName = "C#初级班"},
new(){Id = 2,ClassName = "数据库初级班"},
new(){Id = 3,ClassName = "Asp.Net Core 班"}
};

return list;
}
}
}

StudentController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using WebApplication_ASP_NET_Core_MVC.Models;
using WebApplication_ASP_NET_Core_MVC.Services;
using X.PagedList;

namespace WebApplication_ASP_NET_Core_MVC.Controllers
{
public class StudentController : Controller
{
private readonly IStudentService _studentService;

public StudentController(IStudentService studentService)
{
_studentService = studentService;
}
//public IActionResult Search()
//{
// //var list = _studentService.Search();
// //return View(list);
// return View(_studentService.Search());
//}
//分页查询用的
public IActionResult Search(StudentQuery query)
{
//var list = _studentService.Search();
//return View(list);
ViewBag.ClassList = new SelectList(_studentService.GetClassList(), "Id", "ClassName");
var result = _studentService.Search(query, out int totalCount);
// 构造分页对象
var page = new StaticPagedList<StudentViewModel>(result, query.CurrentPage, query.PageSize, totalCount);
//PageList.Mvc 分页组件
// X.PageList.Mvc 分页组件
//return View();
return View(page);
}

public IActionResult Add()
{
// SelectList === List<SelectListItem>
//这个写在list里面的话是无法左连接的
//List<ClassInfo> list = new()
//{
// new(){Id=1,ClassName="C#班级"},
// new(){Id=2,ClassName="数据库班级"},
// new(){Id=3,ClassName="Asp.Net Core班级"}
//};
//绑定下拉框
//ViewBag.ClassList = new SelectList(list,"Id","ClassName");
ViewBag.ClassList = new SelectList(_studentService.GetClassList(), "Id", "ClassName");
return View();
}
// Dapper 实现增删改查(非常轻量一款ORM框架) ,它是介于Ado.net 与 EntityFramework 之间一款数据库访问技术框架
[HttpPost]
public IActionResult Submit(StudentBo bo)
{
_studentService.Save(bo);
return RedirectToAction("Search");
//return Ok("添加成功");
}
public IActionResult Edit(long id)
{
//return View(_studentService.GetModel(id));
var studentViewModel = _studentService.GetModel(id);
ViewBag.ClassList = new SelectList(_studentService.GetClassList(),
"Id", // 下拉框选中的值
"ClassName", //下拉框显示的值
studentViewModel.ClassId); // 下拉框当前选中值
return View(studentViewModel);
}
public IActionResult Delete(long id)
{
_studentService.Delete(id);
return RedirectToAction("Search");
}
}
}
public class ClassInfo
{
public int Id { get; set; }
public string? ClassName { get; set; }
}

强类型辅助方法及分布视图
/Student/Add.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
@model StudentBo
@{
ViewBag.Title = "title";
Layout = "_Layout";
}
<h2>Html辅助标签</h2>
@* <form action="/student/submit" method="post" class="></form> *@
@using(Html.BeginForm("Submit","Student",method:FormMethod.Post,htmlAttributes:new { @class=""}))
{
@Html.Hidden("Id")
@* <input type="hidden" name="Id" id="Id"></input> *@
<table class="table" style="width: 700px;">
<tr>
<td>
@Html.Label("NickName","姓名: ")
@* <label for="NickName">姓名:</label> *@
</td>
<td>
@Html.TextBoxFor(p => p.NickName)
@* 需要有value值才行 *@
@* @Html.TextBox("NickName", value: "123", htmlAttributes: new { @class = "form-control", placeholder = "请输入姓名", autocomplete = "false" }) *@
@* <input type="text" name="NickName" id="NickName" class="form-control" placeholder="请输入姓名"/> *@
</td>
<td></td>
</tr>
<tr>
<td>
@Html.Label("Account","账号: ")
</td>
<td>
@Html.TextBoxFor(p => p.Account)
@* @Html.TextBox("Account", "",new { autocomplete = "false" }) *@
</td>
</tr>
<tr>
<td>
@Html.Label("Pwd","密码: ")
</td>
<td>
@Html.Password("Pwd", "", new { autocomplete = "false" })
</td>
</tr>
<tr>
<td>
@Html.Label("Sex", "性别: ")
</td>
<td>
@Html.RadioButton("Sex","",true)男
@Html.RadioButton("Sex", "", false)女
</td>
</tr>
<tr>
<td>
@Html.Label("ClassId", "班级: ")
</td>
<td>

@* @Html.DropDownList("ClassId", selectList: null, optionLabel: "请选择班级", new { @class = "form-control" }) *@
@Html.DropDownList("ClassId",(SelectList)ViewBag.ClassList,optionLabel:"请选择班级",new{@class="form-control"})
@* <select>
<option value="1">c#班级</option>
<option value="2">数据库班级</option>
</select> *@
</td>
</tr>
@* <tr> *@
@* <td>
@Html.Label("Course", "课程: ")
</td> *@
@* <td> *@

@* @Html.DropDownList("ClassId", selectList: null, optionLabel: "请选择班级", new { @class = "form-control" }) *@
@* @Html.ListBox("Course", (SelectList)ViewBag., new { @class = "form-control" }) *@
@* <select>
<option value="1">c#班级</option>
<option value="2">数据库班级</option>
</select> *@
@* </td> *@
@* </tr> *@
<tr>
<td>
@Html.Label("Hobby", "爱好: ")
</td>
<td>
<input type="checkbox" name="Hobby" value="编程">编程
<input type="checkbox" name="Hobby" value="爬山">爬山
<input type="checkbox" name="Hobby" value="">钱
@* 因为下面这个会有异常,明明3个数据会显示5个数据,多出来2个false *@
@* @Html.CheckBox("Hobby",false,new{value="编程"})编程
@Html.CheckBox("Hobby", false, new { value = "爬山" })爬山
@Html.CheckBox("Hobby", false, new { value = "" })钱 *@
</td>
</tr>
<tr>
<td>
@Html.Label("Description", "简介: ")
</td>
<td>
@Html.TextArea("Description")
</td>
</tr>
<tr>
<td>
@Html.Label("Birthday", "生日:")
</td>
<td>
@Html.TextBox("Birthday", "", new { type = "date" })
</td>
</tr>
<tr>
<td>
<input type="submit" class="btn btn-primary" value="保存" />
</td>
<td>
</td>
</tr>
</table>
}
/Shared/_StudentView.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@using X.PagedList.Mvc.Core
@model X.PagedList.StaticPagedList<StudentViewModel>


<table class="table table-hover">
<tr>
<td>账号</td>
<td>姓名</td>
<td>性别</td>
<td>班级名称</td>
<td>爱好</td>
<td>生日</td>
<td>简介</td>
<td>操作</td>
</tr>
@foreach (var s in Model)
{
<tr>
<td>@s.Account</td>
<td>@s.NickName</td>
<td>@s.Sex</td>
<td>@s.ClassName</td>
<td>@s.Hobby</td>
<td>@s.Birthday.ToString("yyyy-MM-dd")</td>
<td>@Html.Raw(s.Description)</td>
<td>
<a class="btn btn-info" href="/student/edit/@s.Id">编辑</a>
<a onclick="return confirm('您是否删除吗?')" class="btn btn-danger" href="/student/delete/@s.Id">删除</a>
</td>
</tr>
}
</table>
@Html.PagedListPager(Model, page => $"/student/search?CurrentPage={page}&PageSize=3")
/Student/Search.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@* @model List<StudentViewModel> *@
@using X.PagedList.Mvc.Core
@model X.PagedList.StaticPagedList<StudentViewModel>
@{
ViewBag.Title = "title";
Layout = "_Layout";
}

<h2>学生查询</h2>
<form action="/student/search">
账号:@Html.TextBox("Account") 姓名:@Html.TextBox("NickName")
班级:@Html.DropDownList("ClassId", ViewBag.ClassList, "请选择班级", null)
<input type="submit" class="btn btn-primary" value="查询" />
</form>
@Html.ActionLink("添加", "Add")
@* 需要将数据传递给分部视图 *@
@* @Html.Partial("_StudentView",Model) *@
@* @(await Html.PartialAsync("_StudentView", Model)) *@
Html.PartialAsync("_StudentView", Model)
@* <table class="table table-hover"> *@
@* <tr> *@
@* <td>账号</td> *@
@* <td>姓名</td> *@
@* <td>性别</td> *@
@* <td>班级名称</td> *@
@* <td>爱好</td> *@
@* <td>生日</td> *@
@* <td>操作</td> *@
@* </tr> *@
@* @foreach(var s in Model) *@
@* { *@
@* <tr> *@
@* <td>@s.Account</td> *@
@* <td>@s.NickName</td> *@
@* <td>@s.Sex</td> *@
@* <td>@s.ClassName</td> *@
@* <td>@s.Hobby</td> *@
@* @* 用于显示时间 *@
@* <td>@s.Birthday.ToString("yyyy-MM-dd")</td> *@
@* <td> *@
@* @* <a class="btn btn-default" href="/student/edit/@s.Id">编辑</a> *@
@* <a class="btn btn-info" href="/student/edit/@s.Id">编辑</a> *@
@* <a onclick="return confirm('您是否删除吗?')" class="btn btn-danger" href="/student/delete/@s.Id">删除</a> *@
@* </td> *@
@* </tr> *@
@* } *@
@* </table> *@
@* @Html.PagedListPager(Model,page=>Url.Action("Search",new{CurrentPage=page,PageSize=3})) *@
使用 HTML 辅助方法载入分部视图

使用 Html.Partial 载入分布视图

@Html.Partial(「Page」)

@Html.Partial(「Page」,Model)

@Html.Partial(「Page」,ViewData[「Model」])

@Html.Partial (「Page」,Model,ViewData [「Model」])// 两个参数使用

RenderPartial 辅助方法与 Partial 非常相似,但 RenderPartial 不是返回字符串,而是直接写入响应输出流。出于这个原因,必须把 RenderPartial 放入代码块中,而不能放在代码表达式中,为了说明这一点,下面两行代码向输出流写入相同的内容:

@

@Html.Partial(「Page」)

一般情况下,因为 Partial 相对于 RenderPartial 来说更方便,所以应该选择 Partial。然而,RenderPartial 拥有较好的性能,因为它是直接写入响应流的,但这种性能优势需要大量的使用才能看出来。

使用 Html.Action 辅助方法,从控制器载入分布视图

Action 和 RenderAction 类似于 Partial 和 RenderPartial 辅助方法。Partial 辅助方法通常在单独的文件中应用视图标记来帮助视图渲染视图模型的一部分。另一方面,Action 执行单独的控制器操作,并显示结果。Action 提供了更多的灵活性和重用性,因为控制器操作可以建立不同的模型,可以利用单独的控制器上下文。

1
2
3
4
public ActionResult GetPartialView()
{
return PartialView();
}

利用 Controller 类型中的 PartialView 辅助方法来载入分布视图,而这种载入方式与用 View 辅助方法唯一的差别,仅在于它不会套用母版页面,其它则完全相同。

@Html.Action(「GetPartialView」);

通过 Html.Action 与 Html.Partial 载入分部视图结果是一样的,但载入的过程却差别很大。若使用 Html.Partial 载入分部视图是通过 HtmlHelper 直接读取 *.cshtml 文件,直接执行该视图并取得结果。若使用 Html.Action 的话,则会通过 HtmlHelper 对 IIS 再进行一次处理要求(通过 Server.Execute 方法),因此,使用 Html.Action 会重新执行一遍 Controller 的生命周期

模型校验
Models/StudentBo.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using System.ComponentModel.DataAnnotations;

namespace WebApplication_ASP_NET_Core_MVC.Models
{
public class StudentBo
{
/**
* 模型校验的分类:
* 1. 必须 Required
* 2. 长度范围校验:6-16 StringLength
* 3. 正则校验:手机号,身份证,邮箱 RegularExpression
* 4. 数字范围 Range
* 5. 属性对比:Compare
*/

public long Id { get; set; }
[Required(ErrorMessage = "请输入账号")]
[StringLength(16, MinimumLength = 2, ErrorMessage = "账号是2-16位")]
[Display(Name = "账号")]
public string? Account { get; set; }


[Required(ErrorMessage = "请输入密码"), StringLength(16, MinimumLength = 2, ErrorMessage = "密码是2-16位")]
// [DataType(DataType.Password)] // 等我们讲到TagHelper的时候,再给大家演示这个DataType
[Display(Name = "密码")]
public string? Pwd { get; set; }

[Required(ErrorMessage = "请输入确认密码"), Compare("Pwd", ErrorMessage = "确认密码不一样")]
public string? ConfirmPwd { get; set; }

[Required(ErrorMessage = "请输入姓名")]
[Display(Name = "姓名")]
public string? NickName { get; set; }


public string? Sex { get; set; }


[Range(0, int.MaxValue)]
[Required(ErrorMessage = "请选择班级")]
public int ClassId { get; set; }
public string[]? Hobby { get; set; }
public string? Description { get; set; }

[Required(ErrorMessage = "请选择生日")]
public DateTime Birthday { get; set; }


[Required(ErrorMessage = "请输入邮箱")]
// 1310734881@qq.com
[RegularExpression(@"\w+@\w+\.\w+", ErrorMessage = "邮箱格式不正确")]
// [EmailAddress]
public string? Email { get; set; }
}
}

Models/StudentEditBo.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using System.ComponentModel.DataAnnotations;

namespace WebApplication_ASP_NET_Core_MVC.Models;

public class StudentEditBo
{
public long Id { get; set; }
[Required(ErrorMessage = "请输入账号")]
[StringLength(16, MinimumLength = 2, ErrorMessage = "账号是2-16位")]
public string? Account { get; set; }




[Required(ErrorMessage = "请输入姓名")]
public string? NickName { get; set; }


public string? Sex { get; set; }


[Range(0, int.MaxValue)]
[Required(ErrorMessage = "请选择班级")]
public int ClassId { get; set; }
public string[]? Hobby { get; set; }
public string? Description { get; set; }

[Required(ErrorMessage = "请选择生日")]
public DateTime Birthday { get; set; }

}
Student/Add.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
@model StudentBo
@{
ViewBag.Title = "title";
Layout = "_Layout";
}
<style>
.table span {
color: red;
}
</style>
<h2>Html辅助标签</h2>
@* <form action="/student/submit" method="post" class="></form> *@
@using(Html.BeginForm("Submit","Student",method:FormMethod.Post,htmlAttributes:new { @class=""}))
{
@Html.ValidationSummary()//默认是true,是false的话下面就会显示错误
@* @Html.Hidden("Id") *@
@* <input type="hidden" name="Id" id="Id"></input> *@
<table class="table" style="width: 700px;">
<tr>
<td>
@Html.Label("NickName","姓名: ")
@* <label for="NickName">姓名:</label> *@
</td>
<td>
@Html.TextBoxFor(p => p.NickName)
@* 需要有value值才行 *@
@* @Html.TextBox("NickName", value: "123", htmlAttributes: new { @class = "form-control", placeholder = "请输入姓名", autocomplete = "false" }) *@
@* <input type="text" name="NickName" id="NickName" class="form-control" placeholder="请输入姓名"/> *@
</td>
<td>
@Html.ValidationMessageFor(p => p.NickName)
</td>
</tr>
<tr>
<td>
@Html.Label("Account","账号: ")
</td>
<td>
@Html.TextBoxFor(p => p.Account)
@* @Html.TextBox("Account", "",new { autocomplete = "false" }) *@
</td>
<td>
@Html.ValidationMessageFor(p=>p.Account)
</td>
</tr>
<tr>
<td>
@Html.Label("Pwd","密码: ")
</td>
<td>
@Html.Password("Pwd", "", new { autocomplete = "false" })
</td>
<td>
@Html.ValidationMessageFor(p => p.Pwd)
</td>
</tr>
<tr>
<td>
@Html.Label("ConfirmPwd", "确认密码:")
</td>
<td>
@Html.Password("ConfirmPwd", "", new { autocomplete = "false" })
</td>
<td>
@Html.ValidationMessageFor(p => p.ConfirmPwd)
</td>
</tr>
<tr>
<td>
@Html.Label("Sex", "性别: ")
</td>
<td>
@Html.RadioButton("Sex","",true)男
@Html.RadioButton("Sex", "", false)女
</td>
</tr>
<tr>
<td>
@Html.Label("ClassId", "班级: ")
</td>
<td>

@* @Html.DropDownList("ClassId", selectList: null, optionLabel: "请选择班级", new { @class = "form-control" }) *@
@Html.DropDownList("ClassId",(SelectList)ViewBag.ClassList,optionLabel:"请选择班级",new{@class="form-control"})
@* <select>
<option value="1">c#班级</option>
<option value="2">数据库班级</option>
</select> *@
</td>
<td>
@Html.ValidationMessageFor(p => p.ClassId)
</td>
</tr>
@* <tr> *@
@* <td>
@Html.Label("Course", "课程: ")
</td> *@
@* <td> *@

@* @Html.DropDownList("ClassId", selectList: null, optionLabel: "请选择班级", new { @class = "form-control" }) *@
@* @Html.ListBox("Course", (SelectList)ViewBag., new { @class = "form-control" }) *@
@* <select>
<option value="1">c#班级</option>
<option value="2">数据库班级</option>
</select> *@
@* </td> *@
@* </tr> *@
<tr>
<td>
@Html.Label("Hobby", "爱好: ")
</td>
<td>
<input type="checkbox" name="Hobby" value="编程">编程
<input type="checkbox" name="Hobby" value="爬山">爬山
<input type="checkbox" name="Hobby" value="">钱
@* 因为下面这个会有异常,明明3个数据会显示5个数据,多出来2个false *@
@* @Html.CheckBox("Hobby",false,new{value="编程"})编程
@Html.CheckBox("Hobby", false, new { value = "爬山" })爬山
@Html.CheckBox("Hobby", false, new { value = "" })钱 *@
</td>
</tr>
<tr>
<td>
@Html.Label("Description", "简介: ")
</td>
<td>
@Html.TextArea("Description")
</td>
</tr>
<tr>
<td>
@Html.Label("Birthday", "生日:")
</td>
<td>
@Html.TextBox("Birthday", "", new { type = "date" })
</td>
<td>
@Html.ValidationMessageFor(p => p.Birthday)
</td>
</tr>
<tr>
<td>
@Html.Label("Email", "Email:")
</td>
<td>
@Html.TextBox("Email")
</td>
<td>
@Html.ValidationMessageFor(p => p.Email)
</td>
</tr>
<tr>
<td>
<input type="submit" class="btn btn-primary" value="保存" />
</td>
<td>
</td>
</tr>
</table>
}

@section Scripts
{
@* <script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script> *@
@* <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script> *@
@(await Html.PartialAsync("_ValidationScriptsPartial"))
}
Student/Edit.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
@model StudentViewModel
@{
ViewBag.Title = "title";
Layout = "_Layout";
}
<h2>编辑学生</h2>
@* <form action="/student/submit" method="post" class="></form> *@
@* @using (Html.BeginForm("Submit", "Student", method: FormMethod.Post, htmlAttributes: new { @class = "" })) *@
@using (Html.BeginForm("SubmitEdit", "Student", method: FormMethod.Post, htmlAttributes: new { @class = "" }))
{
@Html.Hidden("Id")
@* <input type="hidden" name="Id" id="Id"></input> *@
<table class="table" style="width: 700px;">
<tr>
<td>
@Html.Label("NickName", "姓名: ")
@* <label for="NickName">姓名:</label> *@
</td>
<td>
@* 需要有value值才行 *@
@* @Html.TextBox("NickName", value: "123", htmlAttributes: new { @class = "form-control", placeholder = "请输入姓名", autocomplete = "false" }) *@
@Html.TextBox("NickName")
@* <input type="text" name="NickName" id="NickName" class="form-control" placeholder="请输入姓名"/> *@
</td>
<td></td>
</tr>
<tr>
<td>
@Html.Label("Account", "账号: ")
</td>
<td>
@* 因为readonly是关键字所以需要加上@ *@
@* @Html.TextBox("Account", "", new { autocomplete = "false",@readonly="readonly" }) *@
@* 如果value值为空无法反填数据 *@
@* @Html.TextBox("Account") *@
@Html.TextBox("Account", Model.Account, new{@readonly = "readonly"})
</td>
<tr>
<td>
@Html.Label("Sex", "性别: ")
</td>
<td>
@* @Html.RadioButton("Sex", "", true)男
@Html.RadioButton("Sex", "", false)女 *@
@* 这边数据库sex.char(2)得是2如果是4,那么后面会有多出来的空格需要匹配 *@
@Html.RadioButton("Sex", "",Model.Sex == "") 男
@Html.RadioButton("Sex", "", Model.Sex == "") 女
</td>
</tr>
<tr>
<td>
@Html.Label("ClassId", "班级: ")
</td>
<td>

@* @Html.DropDownList("ClassId", selectList: null, optionLabel: "请选择班级", new { @class = "form-control" }) *@
@Html.DropDownList("ClassId", (SelectList)ViewBag.ClassList, optionLabel: "请选择班级", new { @class = "form-control" })
@* <select>
<option value="1">c#班级</option>
<option value="2">数据库班级</option>
</select> *@
</td>
</tr>
@* <tr> *@
@* <td>
@Html.Label("Course", "课程: ")
</td> *@
@* <td> *@

@* @Html.DropDownList("ClassId", selectList: null, optionLabel: "请选择班级", new { @class = "form-control" }) *@
@* @Html.ListBox("Course", (SelectList)ViewBag., new { @class = "form-control" }) *@
@* <select>
<option value="1">c#班级</option>
<option value="2">数据库班级</option>
</select> *@
@* </td> *@
@* </tr> *@
<tr>
<td>
@Html.Label("Hobby", "爱好: ")
</td>
<td>
<input type="checkbox" name="Hobby" value="编程" checked="@(Model.Hobby.Contains("编程") ? "checked" : "")" /> 编程
<input type="checkbox" name="Hobby" value="爬山" checked="@(Model.Hobby.Contains("爬山") ? "checked" : "")" /> 爬山
<input type="checkbox" name="Hobby" value="" checked="@(Model.Hobby.Contains("钱") ? "checked" : "")" /> 钱
@* <input type="checkbox" name="Hobby" value="编程">编程
<input type="checkbox" name="Hobby" value="爬山">爬山
<input type="checkbox" name="Hobby" value="">钱 *@
@* 因为下面这个会有异常,明明3个数据会显示5个数据,多出来2个false *@
@* @Html.CheckBox("Hobby",false,new{value="编程"})编程
@Html.CheckBox("Hobby", false, new { value = "爬山" })爬山
@Html.CheckBox("Hobby", false, new { value = "" })钱 *@
</td>
</tr>
<tr>
<td>
@Html.Label("Description", "简介: ")
</td>
<td>
@Html.TextArea("Description")
</td>
</tr>
<tr>
<td>
@Html.Label("Birthday", "生日:")
</td>
<td>
@* @Html.TextBox("Birthday", "", new { type = "date" }) *@
@* 因为必须得用到所以必须得赋一个值 *@
@* Model.Birthday.Date.ToString()这样还是不能赋上值,所以还是得取消掉才行 *@
@* @Html.TextBox("Birthday", Model.Birthday.Date.ToString(), new { type = "date" }) *@
@Html.TextBox("Birthday")
</td>
</tr>
<tr>
<td>
<input type="submit" class="btn btn-primary" value="保存" />
</td>
<td>
</td>
</tr>
</table>
}
Profiles/StudentProfile.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using AutoMapper;
using Microsoft.AspNetCore.Http.HttpResults;
using WebApplication_ASP_NET_Core_MVC.DataModel;
using WebApplication_ASP_NET_Core_MVC.Models;

namespace WebApplication_ASP_NET_Core_MVC.Profiles
{
public class StudentProfile:Profile
{
public StudentProfile()
{
//实体类转换为bo类,暂时还用不上
CreateMap<StudentInfo,StudentBo>();
CreateMap<StudentBo, StudentInfo>();
CreateMap<StudentInfo, StudentViewModel>();

CreateMap<StudentEditBo, StudentBo>();
}
}
}

StudentController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
using System.Collections.Generic;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using WebApplication_ASP_NET_Core_MVC.Models;
using WebApplication_ASP_NET_Core_MVC.Services;
using X.PagedList;

namespace WebApplication_ASP_NET_Core_MVC.Controllers
{
public class StudentController : Controller
{
private readonly IStudentService _studentService;
private readonly IMapper _mapper;
public StudentController(IStudentService studentService, IMapper mapper)
{
_studentService = studentService;
_mapper = mapper;
}
//public IActionResult Search()
//{
// //var list = _studentService.Search();
// //return View(list);
// return View(_studentService.Search());
//}
//分页查询用的
public IActionResult Search(StudentQuery query)
{
//var list = _studentService.Search();
//return View(list);
ViewBag.ClassList = new SelectList(_studentService.GetClassList(), "Id", "ClassName");
var result = _studentService.Search(query, out int totalCount);
// 构造分页对象
var page = new StaticPagedList<StudentViewModel>(result, query.CurrentPage, query.PageSize, totalCount);
//PageList.Mvc 分页组件
// X.PageList.Mvc 分页组件
//return View();
return View(page);
}

public IActionResult Add()
{
// SelectList === List<SelectListItem>
//这个写在list里面的话是无法左连接的
//List<ClassInfo> list = new()
//{
// new(){Id=1,ClassName="C#班级"},
// new(){Id=2,ClassName="数据库班级"},
// new(){Id=3,ClassName="Asp.Net Core班级"}
//};
//绑定下拉框
//ViewBag.ClassList = new SelectList(list,"Id","ClassName");
ViewBag.ClassList = new SelectList(_studentService.GetClassList(), "Id", "ClassName");
return View();
}
// Dapper 实现增删改查(非常轻量一款ORM框架) ,它是介于Ado.net 与 EntityFramework 之间一款数据库访问技术框架
//[HttpPost]
//public IActionResult Submit(StudentBo bo)
//{
// _studentService.Save(bo);
// return RedirectToAction("Search");
// //return Ok("添加成功");
//}


[HttpPost]
public IActionResult Submit(StudentBo bo)
{
// 只有当StudentBo模型中的校验全部通过之后,IsValid 才为真
if (ModelState.IsValid)
{
_studentService.Save(bo);
return RedirectToAction("Search");
}

ViewBag.ClassList = new SelectList(_studentService.GetClassList(), "Id", "ClassName");
ModelState.AddModelError("", "服务端模型校验不通过");
return View("Add", bo);
}
// 修改的功能
[HttpPost]
public IActionResult SubmitEdit(StudentEditBo bo)
{
// 只有当StudentBo模型中的校验全部通过之后,IsValid 才为真
if (ModelState.IsValid)
{
var studentBo = _mapper.Map<StudentEditBo, StudentBo>(bo);
_studentService.Save(studentBo);
return RedirectToAction("Search");
}

ViewBag.ClassList = new SelectList(_studentService.GetClassList(), "Id", "ClassName");
ModelState.AddModelError("", "服务端模型校验不通过");
return View("Edit", _studentService.GetModel(bo.Id));
}
public IActionResult Edit(long id)
{
//return View(_studentService.GetModel(id));
var studentViewModel = _studentService.GetModel(id);
ViewBag.ClassList = new SelectList(_studentService.GetClassList(),
"Id", // 下拉框选中的值
"ClassName", //下拉框显示的值
studentViewModel.ClassId); // 下拉框当前选中值
return View(studentViewModel);
}
public IActionResult Delete(long id)
{
_studentService.Delete(id);
return RedirectToAction("Search");
}
}
}
public class ClassInfo
{
public int Id { get; set; }
public string? ClassName { get; set; }
}

模型绑定
Apifox 软件
1
2
3
4
5
6
7
8
9
10
11
// [FromHeader(Name = "id")] int id,[FromHeader] string name
[HttpPost]
//public IActionResult BindHeadTest([FromHeader] BindTest test)
//{

// return Ok(test);
//}
public IActionResult BindHeadTest([FromHeader(Name = "id")] int id, [FromHeader] string name)
{
return Ok(new{id,name});
}
1
2
3
4
5
6
7
8
9
10
11
using Microsoft.AspNetCore.Mvc;

namespace WebApplication_ModelBinding.Models;

public class BindTest
{
[FromHeader]
public string? Name { get; set; }
[FromHeader]
public int Id { get; set; }
}
1
2
3
4
5
6
7
8
9
10
11
// [FromHeader(Name = "id")] int id,[FromHeader] string name
[HttpPost]
public IActionResult BindHeadTest([FromHeader] BindTest test)
{

return Ok(test);
}
//public IActionResult BindHeadTest([FromHeader(Name = "id")] int id, [FromHeader] string name)
//{
// return Ok(new{id,name});
//}
Models/BindTest.cs
1
2
3
4
5
6
7
8
9
10
11
using Microsoft.AspNetCore.Mvc;

namespace WebApplication_ModelBinding.Models;

public class BindTest
{
//[FromHeader]
public string? Name { get; set; }
//[FromHeader]
public int Id { get; set; }
}
Models/ProductInput.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace WebApplication_ModelBinding.Models;

public class Category
{
public int Id { get; set;}
public String? CategoryName { get;set;}
}
public class ProductInput
{
public int Id { get; set;}
[Display(Name = "商品名称")]
public String? ProductName { get;set;}
[BindNever]
public decimal Price { get;set;}
[BindRequired]
public Category? ProductCategory { get; set; }

public IFormFile? ProductImage { get; set; }
}
Controllers/ProductController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using Microsoft.AspNetCore.Mvc;
using Snowflake;
using WebApplication_ModelBinding.Models;
using WebApplication_ModelBinding.Snowflake;

namespace WebApplication_ModelBinding.Controllers;

public class ProductController : Controller
{

private readonly IWebHostEnvironment _environment;
private readonly IdWorker _idWorker;

public ProductController(IWebHostEnvironment environment)
{
_environment = environment;
_idWorker = SnowflakeUtil.CreateIdWorker();
}

public IActionResult Create()
{
return View();
}

// [Bind("Price","ProductCategory")] 指定某些属性的绑定,默认是绑定所有的属性
[HttpPost]
public IActionResult Submit(ProductInput input)
{

//var fileName = _idWorker.NextId() + "." + Path.GetExtension(input.ProductImage!.FileName);

//// 获取存储路径
//var filePath = Path.Combine(_environment.ContentRootPath, "upload", fileName);
//using FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write);

//input.ProductImage.CopyTo(fileStream); // 每次从文件流中拿80K,直到所有的字节流都被拷贝完毕
return Ok("添加成功");
}

}
Controllers/BindController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using Microsoft.AspNetCore.Mvc;
using WebApplication_ModelBinding.Models;

namespace WebApplication_ModelBinding.Controllers;

public class BindController:Controller
{

public IActionResult BindQueryTest([FromQuery] BindTest test)
{

return Ok(test);
}

// application/json
[HttpPost]
public IActionResult BindBodyTest([FromBody] BindTest test)
{

return Ok(test);
}

[HttpPost]
public IActionResult BindFormTest([FromForm] BindTest test)
{

return Ok(test);
}

[HttpGet]
public IActionResult BindRouteTest([FromRoute] BindTest test)
{

return Ok(test);
}

// [FromHeader(Name = "id")] int id,[FromHeader] string name
[HttpPost]
public IActionResult BindHeadTest([FromHeader] BindTest test)
{

return Ok(test);
}
}
Views/Product/Create.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@using Microsoft.AspNetCore.Mvc.RazorPages
@model ProductInput

@{
ViewBag.Title = "title";
Layout = "_Layout";
}

<h2>模型绑定</h2>
@Html.ActionLink("测试FromRoute", "BindRouteTest", "Bind", new {id = 666, name = "小明"})

<form action="/product/submit" method="post" enctype="multipart/form-data">
<table class="table">
<tr>
<td>
@Html.LabelFor(p => p.ProductName)
</td>
<td>
@Html.TextBoxFor(p => p.ProductName)
</td>
</tr>
<tr>
<td>
@Html.LabelFor(p => p.Price)
</td>
<td>
@Html.TextBoxFor(p => p.Price, htmlAttributes: new {type = "number"})
@* <input type="number" name="price"/> *@
</td>
</tr>
<tr>
<td>
@Html.LabelFor(p => p.ProductCategory!.CategoryName)
</td>
<td>
@Html.TextBoxFor(p => p.ProductCategory!.CategoryName)
</td>
</tr>
<tr>
<td>
@Html.LabelFor(p => p.ProductImage)
</td>
<td>
@Html.TextBoxFor(p => p.ProductImage,new{type="file"})
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="提交" class="btn btn-primary"/>
</td>
</tr>
</table>
</form>
文件上传
UploadController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Net.Http.Headers;
using Snowflake;
using WebApplication_ModelBinding.Snowflake;

namespace WebApplication_ModelBinding.Controllers;

public class UploadController:Controller
{
private readonly IWebHostEnvironment _environment;
private readonly IdWorker _idWorker;

// 上传页面
public UploadController(IWebHostEnvironment environment)
{
_environment = environment;
_idWorker = SnowflakeUtil.CreateIdWorker();
}

public IActionResult Index()
{
return View();
}

public IActionResult MoreUpload()
{
return View();
}

[HttpPost]
public IActionResult MoreUpload(IFormFile[] photo)
{
foreach (var formFile in photo)
{
var fileName = _idWorker.NextId()+"."+Path.GetExtension(formFile.FileName);

// 获取存储路径
var filePath = Path.Combine(_environment.ContentRootPath, "upload",fileName);
using FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write);

formFile.CopyTo(fileStream);
}

return Ok("添加成功");
}

// 文件默认大小不能超过30M
[HttpPost]
public IActionResult Single(IFormFile photo)
{
var extension = Path.GetExtension(photo.FileName);
// 允许的后缀名
//var extArrs = new [] {"png","jpg","mp4","mp3" };
//if (!extArrs.Contains(extension))
//{
// return Ok("不支持这种后缀名");
//}


// OSS,MinIO,FastDFS
var fileName = _idWorker.NextId()+"."+extension;

// 获取存储路径
var filePath = Path.Combine(_environment.ContentRootPath, "upload",fileName);
using FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write);

photo.CopyTo(fileStream); // 每次从文件流中拿80K,直到所有的字节流都被拷贝完毕
return Ok("上传成功");
}


public IActionResult LargeFile()
{
return View();
}


//不能用IFormFile, 那就只能从请求中获取上传的文件内容
[HttpPost]
// 200M
[RequestFormLimits(MultipartBodyLengthLimit = 609715200)]
[RequestSizeLimit(609715200)]
public async Task<IActionResult> UploadLargeFile()
{
// Request.Body
// 流式上传
var request = HttpContext.Request;

if (!request.HasFormContentType ||
!MediaTypeHeaderValue.TryParse(request.ContentType, out var mediaTypeHeader) ||
string.IsNullOrEmpty(mediaTypeHeader.Boundary.Value))
{
return new UnsupportedMediaTypeResult();
}

// 从请求正文中读取数据
var reader = new MultipartReader(mediaTypeHeader.Boundary.Value, request.Body);
var section = await reader.ReadNextSectionAsync();

while (section != null)
{
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition,
out var contentDisposition);

if (hasContentDispositionHeader && contentDisposition.DispositionType.Equals("form-data") &&
!string.IsNullOrEmpty(contentDisposition.FileName.Value))
{
// 获取文件后缀名
var extension = Path.GetExtension(contentDisposition.FileName.Value);
// 为文件重命名,防止文件重名
var fileName = _idWorker.NextId() + "." + extension;
var saveToPath = Path.Combine(_environment.ContentRootPath, "upload", fileName);


using var targetStream = System.IO.File.Create(saveToPath);
await section.Body.CopyToAsync(targetStream);

}

section = await reader.ReadNextSectionAsync();
}

return Ok("上传成功");

}

}
Views/upload/index.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

@{
ViewBag.Title = "title";
Layout = "_Layout";
}

<h2>单文件上传</h2>

<form action="/upload/single" method="post" enctype="multipart/form-data">
<input type="file" name="photo"/>
<input type="submit" value="提交" class="btn btn-primary" />


<!---姓名,性别。。。。我们数据库中只保存文件的地址: /upload/3243243324234.png--->
</form>

Views/upload/LargeFile.cshtml
1
2
3
4
5
6
7
8
9
10
11
12

@{
ViewBag.Title = "title";
Layout = "_Layout";
}

<h2>大文件上传</h2>
<form action="/upload/uploadLargeFile" method="post" enctype="multipart/form-data">
<input type="file" name="photo"/>
<input type="submit" value="提交" class="btn btn-primary" />
</form>

Views/upload/MoreUpload.cshtml
1
2
3
4
5
6
7
8
9
10
11
12

@{
ViewBag.Title = "title";
Layout = "_Layout";
}

<h2>多文件上传</h2>

<form action="/upload/moreupload" method="post" enctype="multipart/form-data">
<input type="file" name="photo" multiple="multiple"/>
<input type="submit" value="提交" class="btn btn-primary" />
</form>
结合表单上传文件
ProductController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
using Microsoft.AspNetCore.Mvc;
using Snowflake;
using WebApplication_ModelBinding.Models;
using WebApplication_ModelBinding.Snowflake;

namespace WebApplication_ModelBinding.Controllers;

public class ProductController : Controller
{

private readonly IWebHostEnvironment _environment;
private readonly IdWorker _idWorker;

public ProductController(IWebHostEnvironment environment)
{
_environment = environment;
_idWorker = SnowflakeUtil.CreateIdWorker();
}

public IActionResult Create()
{
return View();
}

// [Bind("Price","ProductCategory")] 指定某些属性的绑定,默认是绑定所有的属性
[HttpPost]
public IActionResult Submit(ProductInput input)
{

var fileName = _idWorker.NextId() + "." + Path.GetExtension(input.ProductImage!.FileName);

// 获取存储路径
var filePath = Path.Combine(_environment.ContentRootPath, "upload", fileName);
using FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write);

input.ProductImage.CopyTo(fileStream); // 每次从文件流中拿80K,直到所有的字节流都被拷贝完毕
return Ok("添加成功");
}
}
Views/product/create.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
@using Microsoft.AspNetCore.Mvc.RazorPages
@model ProductInput

@{
ViewBag.Title = "title";
Layout = "_Layout";
}

<h2>模型绑定</h2>
@Html.ActionLink("测试FromRoute", "BindRouteTest", "Bind", new {id = 666, name = "小明"})

@* <form action="/product/submit" method="post" enctype="application/x-www-form-urlencoded"> *@
<form action="/product/submit" method="post" enctype="multipart/form-data">//上传文件的加密方式
<table class="table">
<tr>
<td>
@Html.LabelFor(p => p.ProductName)
</td>
<td>
@Html.TextBoxFor(p => p.ProductName)
</td>
</tr>
<tr>
<td>
@Html.LabelFor(p => p.Price)
</td>
<td>
@Html.TextBoxFor(p => p.Price, htmlAttributes: new {type = "number"})
@* <input type="number" name="price"/> *@
</td>
</tr>
<tr>
<td>
@Html.LabelFor(p => p.ProductCategory!.CategoryName)
</td>
<td>
@Html.TextBoxFor(p => p.ProductCategory!.CategoryName)
</td>
</tr>
<tr>
<td>
@Html.LabelFor(p => p.ProductImage)
</td>
<td>
@Html.TextBoxFor(p => p.ProductImage,new{type="file"})
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="提交" class="btn btn-primary"/>
</td>
</tr>
</table>
</form>
productinfo.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace WebApplication_ModelBinding.Models;

public class Category
{
public int Id { get; set;}
public String? CategoryName { get;set;}
}
public class ProductInput
{
public int Id { get; set;}
[Display(Name = "商品名称")]
public String? ProductName { get;set;}
[BindNever]
public decimal Price { get;set;}
[BindRequired]
public Category? ProductCategory { get; set; }

public IFormFile? ProductImage { get; set; }
}

特殊视图

_Layout.cshtml 布局页
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - WebApplication_ModelBinding</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/WebApplication_ModelBinding.styles.css" asp-append-version="true" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container-fluid">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">WebApplication_ModelBinding</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()<!--@RenderBody()是注入视图特定内容的位置。例如,如果使用此布局视图呈现 index.chtml 视图,则会在我们 调用@RenderBody()方法 的位置注入index.cshtml 视图内容 。-->
</main>
</div>

<footer class="border-top footer text-muted">
<div class="container">
&copy; 2025 - WebApplication_ModelBinding - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)//对应@section Scripts
</body>
</html>

** _ViewStart.cshtml** 视图
1
2
3
4
5
6
7
8
9
10
11
@{
Layout = "_Layout"; // 如果子视图不指定布局视图,则默认为"_Layout.cshtml"
}
@if (ViewData["Layout"]!=null && ViewData["Layout"].Equals("Admin"))
{
Layout = "_AdminLayout";
}
else
{
Layout = "_Layout";
}
_ViewImport.cshtml 命名导入视图
1
2
3
4
5
6
7
8
9
10
11
@using StudentManagement.Models;
@using StudentManagement.ViewModels;
@*还支持以下指令*@
@*
@addTagHelper
@removeTagHelper
@tagHelperPrefix
@model
@inherits
@inject
*@
标签助手

标签助手是服务端代码能够参与在 Razor 文件中创建和呈现 HTML 元素。例如,内置的 ImageTagHelper 可以将版本号追加到图像名称。无论何时更改图像,服务器都会为图像生成新的唯一版本,因此可以保证客户端获取当前图像(而不是过时的缓存图像)。内置的标签助手多用于常见任务,例如创建表单,链接和加载资源等。标签助手是在 C# 中定义的,它们基于元素名称,属性名称或父标签来定位 HTML 元素。例如,当应用 LabelTagHelper 特性时,内置的 LabelTagHelper 可以减少 Razor 视图中 HTML**** 和 C# 之间的 **** 显示转换

1
<select asp-for="OrderState" asp-items="@Html.GetEnumSelectList<OrderState>()"></select>
Student/Add.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
@model Student

@{
ViewBag.Title = "title";
Layout = "_Layout";
}
<style>
.table span,.validation-summary-errors{
color:red;
}
</style>


<h2>表单标签助手</h2>

<form asp-action="Submit" asp-controller="Student" method="post">
<!--只显示服务端返回的错误--->
<div asp-validation-summary="ModelOnly"></div>

<table class="table">
<tr>
<td>
<label asp-for="Account"></label>
</td>
<td>
<input asp-for="Account" class="form-control"/>
</td>
<td>
<span asp-validation-for="Account"></span>
</td>
</tr>
<tr>
<td>
<label asp-for="Password"></label>
</td>
<td>
<input asp-for="Password" class="form-control"/>
</td>
<td>
<span asp-validation-for="Password"></span>
</td>
</tr>
<tr>
<td>
<label asp-for="ConfirmPassword"></label>
</td>
<td>
<input asp-for="ConfirmPassword" class="form-control"/>
</td>
<td>
<span asp-validation-for="ConfirmPassword"></span>
</td>
</tr>
<tr>
<td>
<label asp-for="Hobby"></label>
</td>
<td>
//得用原生态的不然会出错
@* <input type="checkbox" asp-for="Hobby" value="编程" /> 编程 *@
<input type="checkbox" name="Hobby" value="编程"/> 编程
<input type="checkbox" name="Hobby" value="学习"/> 学习
<input type="checkbox" name="Hobby" value="爬山"/> 爬山
</td>
<td>

</td>
</tr>
<tr>
<td>
<label asp-for="Gender"></label>
</td>
<td>
<input type="radio" name="Gender" value="男"/> 男
<input type="radio" name="Gender" value="女"/> 女
</td>
<td>

</td>
</tr>
<tr>
<td>
<label asp-for="Province"></label>
</td>
<td>
<select asp-for="Province" asp-items="ViewBag.ProvinceList" class="form-control">
<option value="0">请选择</option>
</select>
</td>
<td>
<span asp-validation-for="Province"></span>
</td>
</tr>
<tr>
<td>
<label asp-for="Birthday"></label>
</td>
<td>
<input asp-for="Birthday" class="form-control"/>
</td>
<td>
<span asp-validation-for="Birthday"></span>
</td>
</tr>
<tr>
<td>
<label asp-for="Description"></label>
</td>
<td>
<textarea asp-for="Description" class="form-control"></textarea>
</td>
<td>
</td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="提交" class="btn btn-primary"/>
</td>
</tr>
</table>
</form>
@section Scripts
{
@* <script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script> *@
@* <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script> *@
<partial name="_ValidationScriptsPartial"/>
}

Student/index.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@* @{
ViewData["Title"] = "Home Page";
}

<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div> *@

@using WebApplication_ModelBinding.ViewComponents

@* @tagHelperPrefix "renwoxing:" *@
@{
ViewData["Title"] = "Home Page";
}

<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>


<email mail-to="1271646107@qq.com"></email>

@* <renwoxing:email mail-to="1271646107@qq.com"></renwoxing:email> *@


<!--调用视图组件:第一种调用方式--->
@* @await Component.InvokeAsync(typeof(StudentViewComponent), new { name = "小明" }) *@


<!--调用视图组件第二种写法,通过标签助手的形式调用-->
@* <vc:student name="张三"></vc:student> *@
Models/Student.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
using System.ComponentModel.DataAnnotations;

namespace Step4.Unit5.Models;

public class Student
{
[Display(Name = "账号")]
[Required(ErrorMessage = "请输入账号")]
public String? Account { get; set; }

[Display(Name = "密码")]
[DataType(DataType.Password)]
[Required(ErrorMessage = "请输入密码")]
public String? Password { get; set; }

[Display(Name = "确认密码")]
[DataType(DataType.Password)]
[Compare("Password",ErrorMessage = "两次输入的密码不一致")]
public String? ConfirmPassword { get; set; }

[Display(Name = "爱好")]
public String[]? Hobby { get; set; }

[Display(Name = "性别")]
public String? Gender { get; set; }

[Display(Name = "祖籍")]
[Required(ErrorMessage = "请选择祖籍")]
[Range(1,int.MaxValue,ErrorMessage = "请选择祖籍")]
public int Province { get; set; }

[Display(Name = "生日")]
[DataType(DataType.Date)]
[Required(ErrorMessage = "请选择你的生日")]
public DateTime Birthday { get; set; }

[Display(Name = "简介")]
public string? Description { get; set; }
}
StudentController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using WebApplication_ModelBinding.Models;

namespace WebApplication_ModelBinding.Controllers;

public class StudentController:Controller
{
private static readonly List<Student> StudentList = new List<Student>();

public IActionResult Add()
{

ViewBag.ProvinceList = new SelectList(new List<ProvinceInfo>()
{
new(1,"北京"),
new(2,"江西")
},"Id","Name");

return View();
}

public IActionResult Submit(Student student)
{
if (ModelState.IsValid)
{
var s = StudentList.FirstOrDefault(p=>p.Account!.Equals(student.Account));
if (s!=null)
{
ModelState.AddModelError("","账号已注册");
ViewBag.ProvinceList = new SelectList(new List<ProvinceInfo>()
{
new(1,"北京"),
new(2,"江西")
},"Id","Name");
return View("Add", student);
}

StudentList.Add(student);
return Redirect("/home/index");
}
return Ok(student);
}
}

public record ProvinceInfo(int Id,String? Name);
Shared/_Layout.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>@ViewData["Title"] - Step4.Unit5</title>
<link rel="stylesheet"
href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.1/css/bootstrap.min.css"
asp-fallback-href-include="~/lib/bootstrap/dist/css/bootstrap.css"
asp-fallback-test-class="alert-warning"
asp-fallback-test-property="color"
asp-fallback-test-value="#664d03"
/>
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true"/>
<link rel="stylesheet" href="~/Step4.Unit5.styles.css" asp-append-version="true"/>
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container-fluid">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Step4.Unit5</a>

<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>

<footer class="border-top footer text-muted">
<div class="container">
&copy; 2022 - Step4.Unit5 - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</div>
</footer>


<environment names="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
</environment>
<environment names="Production">
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
</environment>
<script src="~/js/site.js" asp-append-version="true"></script>



@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
tags/EmailTagHelper.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using Microsoft.AspNetCore.Razor.TagHelpers;

namespace Step4.Unit5.tags;

[HtmlTargetElement("email")]
public class EmailTagHelper:TagHelper
{
public string? MailTo { get; set; }

// <a href="mailto:1310734881@qq.com">1310734881@qq.com</a>
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "a";
output.Attributes.SetAttribute("href","mailto:"+MailTo);
output.Content.SetContent(MailTo);
}
}
Views/_ViewImports.cshtml
1
2
3
4
5
@using WebApplication_ModelBinding
@using WebApplication_ModelBinding.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, WebApplication_ModelBinding

ViewComponents/StudentViewComponent.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using Microsoft.AspNetCore.Mvc;

namespace WebApplication_ModelBinding.ViewComponents;

public class StudentViewComponent:ViewComponent
{


// 执行调用视图组件
// public IViewComponentResult Invoke(string? name="")
// {
// List<StudentInfo> result = new()
// {
// new(1, "张三"),
// new(2, "小明")
// };
// if (!string.IsNullOrWhiteSpace(name))
// {
// result = result.Where(p => p.Name.Contains(name)).ToList();
// }
//
// return View(result);// 视图名称名称默认叫Default.cshtml
// }

public async Task<IViewComponentResult> InvokeAsync(string? name="")
{
List<StudentInfo> result = new()
{
new(1, "张三"),
new(2, "小明")
};
if (!string.IsNullOrWhiteSpace(name))
{
result = result.Where(p => p.Name.Contains(name)).ToList();
}

return View(result);// 视图名称名称默认叫Default.cshtml
}


}

public record StudentInfo(int Id,String? Name);
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
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.UseAuthorization();

// 设置区域路由
app.MapControllerRoute("MyArea",
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}"
);

// app.MapAreaControllerRoute()


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

app.Run();

服务与配置

以前
1
2
3
4
5
6
7
8
9
public class UserController:Controller
{
public async Task<IActionResult> Index()
{
UserService _userService = new UserService();
var users = await _userService.Users.ToListAsync();
return View(users);
}
}
现在
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class UserController:Controller
{
// 热插拔
private readonly IUserService _userService; // 接口注入
public UserController(IUserService userService)
{
_userService = userService;
}
public async Task<IActionResult> Index()
{
var users = await _userService.Users.ToListAsync();
return View(users);
}
}
正转
1
2
3
4
5
private ILoginService _loginService;
public AccountController()
{
_loginService = new EFLoginService()
}
反转

把依赖的创建丢给其它人,自己只负责使用,其它人丢给你依赖的这个过程理解为注入。

为了在业务变化的时候尽少改动代码可能造成的问题。

1
2
3
4
5
6
7
8
9
10
public AccountController(ILoginService loginService)
{
_loginService = loginService;
}

public AccountController(ILoginService<ApplicationUser> loginService)
{
_loginService = loginService;
}
builder.Services.AddTransient<ILoginService,RedisLoginService>();
何为容器
容器负责两件事情:
  • 绑定服务与实例之间的关系
  • 获取实例,并对实例进行管理(创建与销毁)
ASP.NET CORE DI

使用时通过 Nuget 包 Microsoft.Extensions.DependencyInjection 来使用 。

相对与其他 DI 框架,它比较轻量,且只支持构造器注入,不支持属性注入、以及方法注入

在.NET Core 中 DI 的核心分为两个组件:IServiceCollection 和 IServiceProvider

  • IServiceCollection 负责注册
  • IServiceProvider 负责提供实例
服务的注册方式
1
2
3
builder.Services.Add(ServiceDescriptor item)
builder.Services.AddXXX<TService, TImplementation>()
builder.Services.AddXXX(Func<IServiceProvider, TService> implementationFactory)
服务提供者 ServiceProvider

服务实例的创建、销毁均由 ServiceProvider 负责维护,生成的服务实例一般不要执行对象的 Dispose 方法(如果有的话),因为服务实例有可能被其他地方使用。

当释放 ServiceProvider 时,该 Provider 将会释放由它生成的对象。

在生成实例时,如果对象实现了 Dispose 接口,它会把该对象保存在 disposables 列表中,当 Provider 释放时,当即会自动调用 disposables 元素对象的 Dispose 方法而 ServiceProvider 本身又可以创建新的 ServiceProvider,这样就相当生成了一个作用域 Scope。

获取的服务的三种方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//获取的服务的三种方式
//第一种方式
public IActionResult Index([FromServices] IFoodService foodService1){

}
//第二种方式
var foodService2 = Request.HttpContext.RequestServices.GetService<IFoodService>();
//第三种方式
private readonly ILogger<HomeController> _logger;

public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
服务的生命周期

生命周期分为三种:

  • 瞬态 Transient: 每一次从 ServiceProvider 取得,均返回一个新的实例。

    请求获取 -(GC 回收 - 主动释放) 每一次获取的对象都不是同一个

  • 作用域 Scope:在同一作用域下生成的实例相同,即同一个 ServiceProvider 下取得的实例相同

    请求开始 - 请求结束 在这次请求中获取的对象都是同一个

  • 单例 Singleton: 任意一个 ServiceProvider 下取得的实例都相同

    项目启动 - 项目关闭 相当于静态类 只会有一个

    作用域主要针对特定 ServiceProvider 的,即在同一个 ServiceProvider 下,注册为 Scope 类型的对象为单例

program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using NLog.Web;
using Serilog;
using Serilog.Events;
using Step4.Unit6;
using Step4.Unit6.Options;
using Step4.Unit6.Services;
using Step4.Unit6.Services.Impl;

var builder = WebApplication.CreateBuilder(args);
// // 配置log4net
// builder.WebHost.ConfigureLogging(p =>
// {
// p.AddFilter("Microsoft.AspNetCore.Mvc.Razor.Internal", LogLevel.Warning);
// p.AddFilter("Microsoft.AspNetCore.Mvc.Razor.Razor", LogLevel.Debug);
// p.AddFilter("Microsoft.AspNetCore.Mvc.Razor", LogLevel.Error);
//
// // 也可以指定配置文件的路径以及名称
// // var path = Directory.GetCurrentDirectory() + "log4net.config";
//
// p.AddLog4Net();// 指定使用log4net
// });

// 配置nlog
// builder.WebHost.UseNLog();


// // 配置Serilog
// builder.Host.UseSerilog((context, config) =>
// {
// // 配置文件指定为appsettings.json
// config.ReadFrom.Configuration(context.Configuration);
// });



builder.Host.UseSerilog((context, config) =>
config
.WriteTo.Console() // 输入到控制台
// 同时输出到文件中,并且按天输出(每天都会创建一个新的文件)
.WriteTo.File($"{builder.Environment.ContentRootPath}/logs/renwoxing.log",
rollingInterval: RollingInterval.Day
// fff:毫秒 zzz:时区
,outputTemplate:"{Timestamp:HH:mm:ss fff zzz} " +
"|| {Level} " + // Level:日志级别
"|| {SourceContext:1} " + //SourceContext:日志上下文
"|| {Message} " + // Message:日志内容
"|| {Exception} " + // Exception:异常信息
"||end {NewLine}" //end:结束标志 NewLine:换行
)
.MinimumLevel.Information() // 设置最小级别
.MinimumLevel.Override("Default", LogEventLevel.Information) // 默认设置
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning) // 只输出微软的错误日志
// 生命周期日志
.MinimumLevel.Override("Default.Hosting.Lifetime", LogEventLevel.Information)
.Enrich.FromLogContext() // 将日志上下文也记录到日志中
);


builder.Services.Configure<RedisOptions>(builder.Configuration.GetSection("NoSQL:Redis"));


builder.Services.AddControllersWithViews();



builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()).ConfigureContainer<ContainerBuilder>(p =>
{
p.RegisterModule<AutofacModuleRegister>();
});


// 注册服务的第一种方式
// builder.Services.Add(new ServiceDescriptor(typeof(IFoodService),
// typeof(FoodService), ServiceLifetime.Scoped));

// 注册服务的第二种方式
// builder.Services.AddScoped(typeof(IFoodService), typeof(FoodService));
// builder.Services.AddSingleton(typeof(IFoodService), typeof(FoodService));


// 注册服务的第三种方式
// builder.Services.AddSingleton<IFoodService, FoodService>();
// builder.Services.AddTransient<IFoodService, FoodService>();
// builder.Services.AddSingleton<IFoodService, FoodService>();








var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
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();
}

var foodService = app.Services.GetService<IFoodService>();


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

app.UseRouting();

app.UseAuthorization();

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

app.Run();
HomeController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
using System.Diagnostics;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Step4.Unit6.Models;
using Step4.Unit6.Options;
using Step4.Unit6.Services;

namespace Step4.Unit6.Controllers;

public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly IConfiguration _configuration;
// 获取配置类
private readonly IOptions<RedisOptions> _options;

public HomeController(ILogger<HomeController> logger, IConfiguration configuration, IOptions<RedisOptions> options)
{
_logger = logger;
_configuration = configuration;
_options = options;
}

public IActionResult Index()
{
// _logger.LogTrace("trance日志测试");
// _logger.LogDebug("debug日志测试");
// _logger.LogInformation("information日志测试");
// _logger.LogWarning("waring日志测试");
// _logger.LogError("error日志测试");


// var host = _configuration["NoSQL:Redis:Host"];
//var port = _configuration["NoSQL:Redis:port"];
// _logger.LogInformation(host);


// var redisSection = _configuration.GetSection("NoSQL:Redis");
// var redisOptions = redisSection.Get<RedisOptions>();
// _logger.LogInformation(redisOptions.UserName);


var redis = _options.Value;


return View();
}


builder.Services.AddScoped<IFoodService, FoodService>();//相同
//builder.Services.AddTransient<IFoodService, FoodService>();//不同
//builder.Services.AddSingleton<IFoodService, FoodService>();//相同



// public IActionResult Index([FromServices] IFoodService foodService1)
// {
// var foodService2 = Request.HttpContext.RequestServices.GetService<IFoodService>();
//
// if (object.ReferenceEquals(foodService1,foodService2))
// {
// _logger.LogInformation("相同");
// }
// else
// {
// _logger.LogInformation("不同");
// }
//
// foodService1.Print();
//
//
// return View();
// }

public IActionResult Privacy()
{
return View();
}

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel {RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier});
}
}
Options/RedisOptions.cs
1
2
3
4
5
6
7
8
9
namespace Step4.Unit6.Options;

public class RedisOptions
{
public string? Host { get; set; }
public string? Port { get; set; }
public string? UserName { get; set; }
public string? Password { get; set; }
}
Services/IFoodService.cs
1
2
3
4
5
6
namespace Step4.Unit6.Services;

public interface IFoodService
{
void Print();
}
Services/Impl/FoodService.cs
1
2
3
4
5
6
7
8
9
namespace Step4.Unit6.Services.Impl;

public class FoodService:IFoodService
{
public void Print()
{
Console.WriteLine("hello ,food");
}
}
Views/Home/Index.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@using Step4.Unit6.Services
@inject IFoodService _foodService

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

<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
@{
_foodService.Print();
}
</div>
Autofac
Nuget 安装相关包
1
2
Autofac 5.2.0
Autofac.Extensions.DependencyInjection 6.0.0
AutofacModuleRegister.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using Autofac;
using Step4.Unit6.Services;
using Step4.Unit6.Services.Impl;

namespace Step4.Unit6;

public class AutofacModuleRegister:Module
{
// 可以注入服务
protected override void Load(ContainerBuilder builder)
{
// builder.RegisterType<FoodService>().As<IFoodService>().InstancePerDependency();
// builder.RegisterType<FoodService>().As<IFoodService>().InstancePerLifetimeScope();
builder.RegisterType<FoodService>().As<IFoodService>().SingleInstance();
}
}
program.cs
1
2
3
4
5
6
7
8
// 将ServiceProvider 更换为Autofac,autofac可以写拦截器是比原生的要强
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureContainer<ContainerBuilder>(containerBuilder =>
{
// 注册刚刚自定义的Module类
containerBuilder.RegisterModule<AutofacModuleRegister>();
// 如果有多个Module,还可以继续添加
});
生命周期
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
var builder = new ContainerBuilder();
// 当您创建一个嵌套的生命周期Scope时,您可以“标记”或“命名”该范围。
// 具有每个匹配生命周期作用域的组件最多只有一个与给定名称匹配的嵌套生命周期作用域实例。
// 这允许您创建一种“作用域单例”,在这种单例中,其他嵌套的生命周期作用域可以共享组件的实例,而无需声明全局共享实例。
builder.RegisterType<Worker>().InstancePerMatchingLifetimeScope("my-request");
// 创建标记的作用域Scope
using(var scope1 = container.BeginLifetimeScope("my-request"))
{
for(var i = 0; i < 100; i++)
{
var w1 = scope1.Resolve<Worker>();
using(var scope2 = scope1.BeginLifetimeScope())
{
var w2 = scope2.Resolve<Worker>();
// w1和w2的实例总是相同的,因为使用Instance Per Matching Lifetime Scope且为指定标记的Scope,
// 嵌套的生命周期作用域可以共享实例,它实际上在标记作用域中是一个单例
}
}
}
// 创建另一个标记的作用域Scope
using(var scope3 = container.BeginLifetimeScope("my-request"))
{
for(var i = 0; i < 100; i++)
{
// w3和w1/w2是不同的实例,因为他们是两个不同的生命周期,虽然它们的标记相同
// Instance Per Matching Lifetime Scope依然是在不同的生命周期作用域创建新的实例
var w3 = scope3.Resolve<Worker>();
using(var scope4 = scope3.BeginLifetimeScope())
{
var w4 = scope4.Resolve<Worker>();
// w3和w4是相同的实例,因为使用Instance Per Matching Lifetime Scope且为指定标记的Scope
// 嵌套的生命周期作用域可以共享实例
// w3和w4是同样的实例,w1和w2是同样的实例,但是w1/w2和w3/w4是不同的实例,因为他们是两个不同的作用域Scope
}
}
}
// 不能在标记不匹配的生命周期Scope中获取Instance Per Matching Lifetime Scope中标记的实例
// 会抛出异常
using(var noTagScope = container.BeginLifetimeScope())
{
// 在没有正确命名(标记)的生命周期Scope的情况下试图解析每个匹配生命周期Scope的组件,会抛出异常
var fail = noTagScope.Resolve<Worker>();
}
系统内置服务
内置的日志服务
program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
builder.ConfigureLogging((hostingContext, logging) =>
{
bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
if (isWindows)
{
// Default the EventLogLoggerProvider to warning or above
logging.AddFilter<EventLogLoggerProvider>
(level => level >= LogLevel.Warning);
}
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
logging.AddEventSourceLogger();
if (isWindows)
{
// Add the EventLogLoggerProvider on windows machines
logging.AddEventLog();
}
logging.Configure(options =>
{
options.ActivityTrackingOptions =
ActivityTrackingOptions.SpanId
| ActivityTrackingOptions.TraceId
| ActivityTrackingOptions.ParentId;
});
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"Logging": {
"Debug": {
"LogLevel": {
"Default": "Information"
}
},
"Console": {
"LogLevel": {
"Microsoft.AspNetCore.Mvc.Razor.Internal": "Warning",
"Microsoft.AspNetCore.Mvc.Razor.Razor": "Debug",
"Microsoft.AspNetCore.Mvc.Razor": "Error",
"Default": "Trace"
}
},
"LogLevel": {
"Default": "Debug"
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
_logger.LogTrace("Trace日志测试");
_logger.LogDebug("Debug日志测试");
_logger.LogInformation("Information日志测试");
_logger.LogWarning("Warning日志测试");
_logger.LogError("Error日志测试");

return View();
}
Log4Net 使用
安装 2 个包
1
2
Microsoft.Extensions.Logging 6.0
Microsoft.Extensions.Logging.Log4Net.AspNetCore 6.1
log4net.config
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
<?xml version="1.0" encoding="utf-8"?>

<log4net>
<!-- Debug 将日志以回滚文件的形式写到文件中 -->
<appender name="RollingFileDebug" type="log4net.Appender.RollingFileAppender">
<!-- 日志文件存放位置,可以为绝对路径也可以为相对路径 -->
<file value="log/" />
<!-- 日志文件的命名规则 -->
<datePattern value="yyyy-MM-dd/'Debug.log'" />
<!-- 将日志信息追加到已有的日志文件中-->
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="100" />
<maximumFileSize value="1MB" />
<!-- 指定按日期切分日志文件 -->
<rollingStyle value="Date" />
<!-- 当将日期作为日志文件的名字时,必须将staticLogFileName的值设置为false -->
<staticLogFileName value="false" />
<!-- 最小锁定模式,以允许多个进程可以写入同一个文件 -->
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern
value=" &#13;&#10; &#13;&#10;【日志时间】: %d 【线程ID】: %thread &#13;&#10;【日志级别】: %-5p &#13;&#10;【日志对象】:%logger &#13;&#10;【日志内容】:%m &#13;&#10; " />
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="Debug" />
<param name="LevelMax" value="Debug" />
</filter>
</appender>

<!-- Info 将日志以回滚文件的形式写到文件中 -->
<appender name="RollingFileInfo" type="log4net.Appender.RollingFileAppender">
<!-- 日志文件存放位置,可以为绝对路径也可以为相对路径 -->
<file value="log/" />
<!-- 日志文件的命名规则 -->
<datePattern value="yyyy-MM-dd/'Info.log'" />
<!-- 将日志信息追加到已有的日志文件中-->
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="100" />
<maximumFileSize value="1MB" />
<!-- 指定按日期切分日志文件 -->
<rollingStyle value="Date" />
<!-- 当将日期作为日志文件的名字时,必须将staticLogFileName的值设置为false -->
<staticLogFileName value="false" />
<!-- 最小锁定模式,以允许多个进程可以写入同一个文件 -->
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern
value=" &#13;&#10; &#13;&#10;【日志时间】:%d 【线程ID】:%thread &#13;&#10;【日志级别】:%-5p &#13;&#10;【日志对象】:%logger &#13;&#10;【日志内容】:%m &#13;&#10; " />
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="Info" />
<param name="LevelMax" value="Info" />
</filter>
</appender>

<!-- WARN 将日志以回滚文件的形式写到文件中 -->
<appender name="RollingFileWarn" type="log4net.Appender.RollingFileAppender">
<!-- 日志文件存放位置,可以为绝对路径也可以为相对路径 -->
<file value="log/" />
<!-- 日志文件的命名规则 -->
<datePattern value="yyyy-MM-dd/'Warn.log'" />
<!-- 将日志信息追加到已有的日志文件中-->
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="100" />
<maximumFileSize value="1MB" />
<!-- 指定按日期切分日志文件 -->
<rollingStyle value="Date" />
<!-- 当将日期作为日志文件的名字时,必须将staticLogFileName的值设置为false -->
<staticLogFileName value="false" />
<!-- 最小锁定模式,以允许多个进程可以写入同一个文件 -->
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern
value=" &#13;&#10; &#13;&#10;【日志时间】:%d 【线程ID】:%thread &#13;&#10;【日志级别】:%-5p &#13;&#10;【日志对象】:%logger &#13;&#10;【日志内容】:%m &#13;&#10; " />
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="Warn" />
<param name="LevelMax" value="Warn" />
</filter>
</appender>

<!-- Error 将日志以回滚文件的形式写到文件中 -->
<appender name="RollingFileError" type="log4net.Appender.RollingFileAppender">
<!-- 日志文件存放位置,可以为绝对路径也可以为相对路径 -->
<file value="log/" />
<!-- 日志文件的命名规则 -->
<datePattern value="yyyy-MM-dd/'Error.log'" />
<!-- 将日志信息追加到已有的日志文件中-->
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="100" />
<maximumFileSize value="1MB" />
<!-- 指定按日期切分日志文件 -->
<rollingStyle value="Date" />
<!-- 当将日期作为日志文件的名字时,必须将staticLogFileName的值设置为false -->
<staticLogFileName value="false" />
<!-- 最小锁定模式,以允许多个进程可以写入同一个文件 -->
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern
value=" &#13;&#10; &#13;&#10;【日志时间】:%d 【线程ID】:%thread &#13;&#10;【日志级别】:%-5p &#13;&#10;【日志对象】:%logger &#13;&#10;【日志内容】:%m &#13;&#10; " />
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="Error" />
<param name="LevelMax" value="Error" />
</filter>
</appender>

<!-- Fatal 将日志以回滚文件的形式写到文件中 -->
<appender name="RollingFileFatal" type="log4net.Appender.RollingFileAppender">
<!-- 日志文件存放位置,可以为绝对路径也可以为相对路径 -->
<file value="log/" />
<!-- 日志文件的命名规则 -->
<datePattern value="yyyy-MM-dd/'Fatal.log'" />
<!-- 将日志信息追加到已有的日志文件中-->
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="100" />
<maximumFileSize value="1MB" />
<!-- 指定按日期切分日志文件 -->
<rollingStyle value="Date" />
<!-- 当将日期作为日志文件的名字时,必须将staticLogFileName的值设置为false -->
<staticLogFileName value="false" />
<!-- 最小锁定模式,以允许多个进程可以写入同一个文件 -->
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern
value=" &#13;&#10; &#13;&#10;【日志时间】:%d 【线程ID】:%thread &#13;&#10;【日志级别】:%-5p &#13;&#10;【日志对象】:%logger &#13;&#10;【日志内容】:%m &#13;&#10; " />
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="Fatal" />
<param name="LevelMax" value="Fatal" />
</filter>
</appender>

<root>
<!--控制级别,由低到高:ALL|DEBUG|INFO|WARN|ERROR|FATAL|OFF
比如定义级别为INFO,则INFO级别向下的级别,比如DEBUG日志将不会被记录
如果没有定义LEVEL的值,则缺省为DEBUG-->
<appender-ref ref="RollingFileDebug" />
<appender-ref ref="RollingFileInfo" />
<appender-ref ref="RollingFileWarn" />
<appender-ref ref="RollingFileError" />
<appender-ref ref="RollingFileFatal" />
<level value="DEBUG" />
</root>

</log4net>
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
// 配置log4net
builder.WebHost.ConfigureLogging(p =>
{
p.AddFilter("Microsoft.AspNetCore.Mvc.Razor.Internal", LogLevel.Warning);
p.AddFilter("Microsoft.AspNetCore.Mvc.Razor.Razor", LogLevel.Debug);
p.AddFilter("Microsoft.AspNetCore.Mvc.Razor", LogLevel.Error);

// 也可以指定配置文件的路径以及名称
//var path = "c://log/log4.net";
// var path = Directory.GetCurrentDirectory() + "log4net.config";

p.AddLog4Net();// 指定使用log4net
});
NLog 日志

属性变为始终复制以及内容便于发布的时候

安装包
1
NLog.Web.AspNetCore 5.1.4
Program.cs
1
2
// 配置nlog
builder.WebHost.UseNLog();
nlog.config
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

<targets>
<!--此部分中的所有目标将自动异步-->
<target name="asyncFile" xsi:type="AsyncWrapper">
<!--项目日志保存文件路径说明fileName="${basedir}/保存目录,以年月日的格式创建/${shortdate}/${记录器名称}-${单级记录}-${shortdate}.txt"-->
<target name="log_file" xsi:type="File"
fileName="${basedir}/ProjectLogs/${shortdate}/${logger}-${level}-${shortdate}.txt"
layout="${longdate} | ${message} ${onexception:${exception:format=message} ${newline} ${stacktrace} ${newline}"
archiveFileName="${basedir}/archives/${logger}-${level}-${shortdate}-{#####}.txt"
archiveAboveSize="102400"
archiveNumbering="Sequence"
concurrentWrites="true"
keepFileOpen="false" />
</target>
<!--使用可自定义的着色将日志消息写入控制台-->
<target name="colorConsole" xsi:type="ColoredConsole" layout="[${date:format=HH\:mm\:ss}]:${message} ${exception:format=message}" />
</targets>

<!--规则配置,final - 最终规则匹配后不处理任何规则-->
<rules>
<logger name="Microsoft.*" minlevel="Info" writeTo="log_file" final="true" />
<logger name="*" minlevel="Info" writeTo="asyncFile" />
<logger name="*" minlevel="Warn" writeTo="colorConsole" />
</rules>
</nlog>
SeriLog(推荐使用)

默认级别 - 如果没有指定 MinimumLevel ,则将处理 Information 级别事件和更高级别的事件。

安装包
1
2
3
Serilog.AspNetCore 6.0.1
Serilog.Settings.Configuration 3.4.0
Serilog.Sinks.Console 4.0.1
appsettings.json
1
2
3
4
5
6
7
8
9
10
{
"Serilog": {
"MinimumLevel": {
"Default": "Debug"
},
"WriteTo": [
{ "Name": "Console" }
]
}
}
Program.cs
1
2
3
4
5
6
// 配置Serilog
builder.Host.UseSerilog((context, config) =>
{
// 配置文件指定为appsettings.json
config.ReadFrom.Configuration(context.Configuration);
});
日志扩展
输出到文件需要引用一个包
1
Serilog.Sinks.File 5.0.0
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
builder.Host.UseSerilog((context, config) =>
config
.WriteTo.Console() // 输入到控制台
// 同时输出到文件中,并且按天输出(每天都会创建一个新的文件)
//这个会输出到wwwroot下面的Logs文件夹下
//.WriteTo.File($"{builder.Environment.WebRootPath}/logs/renwoxing.log",
//是输出到单独一个文件夹叫logs下面
.WriteTo.File($"{builder.Environment.ContentRootPath}/logs/renwoxing.log",
rollingInterval: RollingInterval.Day
// fff:毫秒 zzz:时区
, outputTemplate: "{Timestamp:HH:mm:ss fff zzz} " +
"|| {Level} " + // Level:日志级别
"|| {SourceContext:1} " + //SourceContext:日志上下文
"|| {Message} " + // Message:日志内容
"|| {Exception} " + // Exception:异常信息
"||end {NewLine}" //end:结束标志 NewLine:换行
)
.MinimumLevel.Information() // 设置最小级别
.MinimumLevel.Override("Default", LogEventLevel.Information) // 默认设置
.MinimumLevel.Override("Microsoft", LogEventLevel.Error) // 只输出微软的错误日志
// 生命周期日志
.MinimumLevel.Override("Default.Hosting.Lifetime", LogEventLevel.Information)
.Enrich.FromLogContext() // 将日志上下文也记录到日志中
);
appsettings.json
1
2
3
4
5
6
7
8
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
Configuration 配置
HomeController/Index
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 获取配置类
private readonly IOptions<RedisOptions> _options;

public HomeController(IOptions<RedisOptions> options)
{

_options = options;
}


public IActionResult Index()
{

//var siteName = _configuration["siteName"];
//var host = _configuration["NoSQL:Redis:Host"];

//虽然面向对象思想有了,但是每次都需要还是很麻烦
//var redisSection = _configuration.GetSection("NoSQL:Redis");
//var redisOptions = redisSection.Get<RedisOptions>();
//_logger.LogInformation(redisOptions.UserName);

//通过注入的方式
var redis = _options.Value;

return View();
}
Program.cs
1
builder.Services.Configure<RedisOptions>(builder.Configuration.GetSection("NoSQL:Redis"));
appsettings.Development.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"SiteName": "任我行码农场-appsetting.Development.json",
"NoSQL": {
"Redis": {
"Host": "127.0.0.1",
"Port": "65536",
"UserName": "root",
"Password": "123456"
},
"MongoDb": {
"UserName": "test",
"Password": "123456"
}
}
}
Options\RedisOptions.cs
1
2
3
4
5
6
7
8
9
namespace WebApplication_ASP_NET_Core_MVC;

public class RedisOptions
{
public string? Host { get; set; }
public string? Port { get; set; }
public string? UserName { get; set; }
public string? Password { get; set; }
}

结合 EntityFramework

环境搭建
创建类库
1
2
WebApplication_MVC_EFCore_Service
WebApplication_MVC_EFCore_Model
创建数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
create database MvcUnit7;
go
use MvcUnit7;
go
-- 债务人
create table Debtor
(
Id bigint primary key,
NickName varchar(30) not null, -- 姓名
IdCard char(18) not null, -- 证件号
HomeAddress varchar(200), -- 家庭住址
Phone char(11) , -- 手机号
TotalDebtorMoney int, -- 欠款总金额
Gender char(2), -- 性别
CreatedTime datetime,
UpdatedTime datetime,
CreatedUserId bigint,
UpdatedUserId bigint,
Deleted int
);
go
-- 银行案件
create table Account
(
Id bigint primary key,
DebtorId bigint,
BankName varchar(50), -- 银行
AccountState int , -- 案件状态
SaleMan varchar(30), -- 负责的业务员
DebtorMoney int, -- 欠款金额
CreatedTime datetime,
UpdatedTime datetime,
CreatedUserId bigint,
UpdatedUserId bigint,
Deleted int
);
go
insert into Debtor values(1,'张三','362025199806240510','石家庄','13823889999',100,'男',getdate(),getdate(),1,1,0);
insert into Debtor values(2,'李四','110025199806240510','重庆市','13823888888',200,'男',getdate(),getdate(),1,1,0);
insert into Debtor values(3,'王五','130025199806240510','深圳市','13845618888',300,'男',getdate(),getdate(),1,1,0);
insert into Debtor values(4,'小明','130025199806240510','深圳市','13845618888',300,'男',getdate(),getdate(),1,1,0);
insert into Debtor values(5,'小红','230025199806240510','广州市','13845618888',300,'女',getdate(),getdate(),1,1,0);
insert into Account values(1,1,'中信',1,'阿诚',50,getdate(),getdate(),1,1,0);
insert into Account values(2,1,'中国银行',1,'阿诚',50,getdate(),getdate(),1,1,0);
insert into Account values(3,2,'光大',2,'孙诗雨',100,getdate(),getdate(),1,1,0);
insert into Account values(4,2,'招行',2,'孙诗雨',100,getdate(),getdate(),1,1,0);
insert into Account values(5,3,'光大',3,'赵琴琴',150,getdate(),getdate(),1,1,0);
insert into Account values(6,3,'招行',3,'赵琴琴',150,getdate(),getdate(),1,1,0);
insert into Account values(7,4,'农业',4,'徐小丽',150,getdate(),getdate(),1,1,0);
insert into Account values(8,4,'浦发',4,'张志强',150,getdate(),getdate(),1,1,0);
insert into Account values(9,5,'农业',4,'徐小丽',150,getdate(),getdate(),1,1,0);
insert into Account values(10,5,'浦发',4,'张志强',150,getdate(),getdate(),1,1,0);
创建类
WebApplication_MVC_EFCore_Model/BaseEntity.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace WebApplication_MVC_EFCore_Model;

public class BaseEntity
{
[Key]
public long Id { get; set; }

public DateTime UpdatedTime { get; set; } = DateTime.Now;
public DateTime CreatedTime { get; set; }= DateTime.Now;
public long UpdatedUserId { get; set; }
public long CreatedUserId { get; set; }
public int Deleted { get; set; } = 0;
}
WebApplication_MVC_EFCore_Model/Debtor.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using System.ComponentModel.DataAnnotations.Schema;

namespace WebApplication_MVC_EFCore_Model;

/// <summary>
/// 债务人
/// </summary>
[Table("Debtor")]
public class Debtor:BaseEntity
{
/// <summary>
/// 姓名
/// </summary>
public String? NickName { get; set; }
/// <summary>
/// 身份证
/// </summary>
public string? IdCard { get; set; }
/// <summary>
/// 家庭地址
/// </summary>
public String? HomeAddress { get; set; }
/// <summary>
/// 手机号
/// </summary>
public string? Phone { get; set; }
/// <summary>
/// 欠款总金额
/// </summary>
public int? TotalDebtorMoney { get; set; }
/// <summary>
/// 性别
/// </summary>
public string? Gender { get; set; }
}

WebApplication_MVC_EFCore_Model/Account.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;

namespace WebApplication_MVC_EFCore_Model;

/// <summary>
/// 银行案件
/// </summary>
[Table("Account")]
public class Account:BaseEntity
{
/// <summary>
/// 债务人
/// </summary>
public long DebtorId { get; set; }
/// <summary>
/// 银行名称
/// </summary>
public string? BankName { get; set; }
/// <summary>
/// 当前案件状态
/// </summary>
public AccountStateEnum AccountState { get; set; }
/// <summary>
/// 业务员
/// </summary>
public string? SaleMan { get; set; }
/// <summary>
/// 欠款金额
/// </summary>
public int DebtorMoney { get; set; }
}

/// <summary>
/// 案件状态
/// </summary>
public enum AccountStateEnum
{
[Description("等待还款")]
Wait,
[Description("已还部分")]
Partial,
[Description("禁止催收")]
Forbid,
[Description("已完结")]
Finish
}
安装包
1
2
3
4
5
6
7
8
9
10
11
Microsoft.EntityFrameworkCore 6.0.1

//WebApplication_MVC_EFCore和WebApplication_MVC_EFCore_Service都需要
Microsoft.EntityFrameworkCore.SqlServer(SQLServer 数据库) 6.0.1

//用于数据迁移用的
Microsoft.EntityFrameworkCore.Tools(可选)
Microsoft.EntityFrameworkCore.Design(可选)

//mysql数据库
MySql.Data.EntityFrameworkCore
appsettings.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"SqlConnection": "server=.;uid=sa;pwd=123456;database=MvcUnit7",
"MySqlConnection": "server=.;uid=root;pwd=123456;database=MvcUnit7"
}
}

WebApplication_MVC_EFCore_Service/StepDbContext.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//添加引用model层
//WebApplication_MVC_EFCore添加Service层

using Microsoft.EntityFrameworkCore;
using WebApplication_MVC_EFCore_Model;

namespace WebApplication_MVC_EFCore_Service;

public class StepDbContext:DbContext
{
public StepDbContext(DbContextOptions<StepDbContext> options) : base(options)
{

}
public virtual DbSet<Account> Accounts { set; get; }
public virtual DbSet<Debtor> Debtors { set; get; }
}
Program.cs
1
2
3
4
5
builder.Services.AddDbContext<StepDbContext>(p =>
{
//p.UseSqlServer(builder.Configuration["ConnectionStrings:SqlConnection"]);
p.UseSqlServer(builder.Configuration.GetConnectionString("SqlConnection"));
});
分页显示
Service/Dto/AccountViewModel.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
namespace Step4.Unit7.Service.Dto;

public class AccountViewModel
{
public long Id { get; set; }


/// <summary>
/// 债务人
/// </summary>
public long DebtorId { get; set; }
public string? DebtorName { get; set; }
public string? IdCard { get; set; }
public string? Phone { get; set; }

/// <summary>
/// 银行名称
/// </summary>
public string? BankName { get; set; }

/// <summary>
/// 当前案件状态(值)
/// </summary>
public int State { get; set; }

/// <summary>
/// 当前案件状态(要显示的中文)
/// </summary>
public String? AccountState { get; set; }
/// <summary>
/// 业务员
/// </summary>
public string? SaleMan { get; set; }
/// <summary>
/// 欠款金额
/// </summary>
public int DebtorMoney { get; set; }

public DateTime UpdatedTime { get; set; }
public int Deleted { get; set; }
}
Service/Dto/condition/AccountRequest.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using Step4.Unit7.Model;

namespace Step4.Unit7.Service.Dto.condition;

/// <summary>
/// 案件搜索条件
/// </summary>
public class AccountRequest:PageRequest
{
/// <summary>
/// 债务人名称
/// </summary>
public string? DebtorName { get; set; }
/// <summary>
/// 银行名称
/// </summary>
public string? BankName { get; set; }
/// <summary>
/// 当前案件状态
/// </summary>
public AccountStateEnum? AccountState { get; set; }
}
Service/Dto/condition/PageRequest.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace Step4.Unit7.Service.Dto.condition;

public class PageRequest
{
/// <summary>
/// 页大小
/// </summary>
public int PageSize { get; set; } = 3;
/// <summary>
/// 当前页
/// </summary>
public int PageIndex { get; set; } = 1;


public int Total { get; set; }
}
Service/IAccountService.cs
1
2
3
4
5
6
7
8
9
using Step4.Unit7.Service.Dto;
using Step4.Unit7.Service.Dto.condition;

namespace Step4.Unit7.Service;

public interface IAccountService
{
List<AccountViewModel> Search(AccountRequest? request);
}
Service/AccountService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using AutoMapper;
using Snowflake;
using Step4.Unit7.Model;
using Step4.Unit7.Service.Dto;
using Step4.Unit7.Service.Dto.condition;
using Step4.Unit7.Service.utils;
using Step4.Unit7.Service.utils.Snowflake;

namespace Step4.Unit7.Service;

public class AccountService:IAccountService
{
private readonly StepDbContext _context;

public AccountService(StepDbContext context)
{
_context = context;
}

public List<AccountViewModel> Search(AccountRequest? request)
{
var accounts = from a in _context.Accounts
join b in _context.Debtors on a.DebtorId equals b.Id into abtemp
from abJoin in abtemp.DefaultIfEmpty()

select new AccountViewModel
{
Id = a.Id,
AccountState = a.AccountState.ToDescription(),
State = (int)a.AccountState,
BankName = a.BankName,
DebtorId = a.DebtorId,
SaleMan = a.SaleMan,
UpdatedTime = a.UpdatedTime,
DebtorMoney = a.DebtorMoney,
DebtorName = abJoin.NickName,
IdCard = abJoin.IdCard,
Phone = abJoin.Phone,
Deleted = a.Deleted
};

request.Total = accounts.Count();

return accounts.OrderByDescending(p=>p.Id).Skip((request.PageIndex-1)*request.PageSize).Take(request.PageSize).ToList();
}
}
向 Service 导入 util 包
AccountController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Step4.Unit7.Model;
using Step4.Unit7.Service;
using Step4.Unit7.Service.Dto;
using Step4.Unit7.Service.Dto.condition;
using Step4.Unit7.Service.utils;
using X.PagedList;

namespace Step4.Unit7.Controllers;

public class AccountController:Controller
{
private readonly IAccountService _accountService;

public AccountController(IAccountService accountService)
{
_accountService = accountService;
}


public IActionResult Index(AccountRequest? request = null)
{

var list = _accountService.Search(request);
var pageModel = new StaticPagedList<AccountViewModel>(list, request.PageIndex, request.PageSize,request.Total);
return View(pageModel);
}
}
Views/Account/Index.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@using X.PagedList.Mvc.Core
@using Microsoft.EntityFrameworkCore.Metadata.Internal
@model X.PagedList.StaticPagedList<Step4.Unit7.Service.Dto.AccountViewModel>

@{
ViewBag.Title = "title";
Layout = "_Layout";
}

<h2>案件查询</h2>

<table class="table table-hover">
<tr>
<td>姓名</td>
<td>身份证</td>
<td>银行</td>
<td>状态</td>
<td>手机号</td>
<td>业务员</td>
<td>欠款金额</td>
<td>操作时间</td>
<td>操作</td>
</tr>
@foreach (var account in Model)
{
<tr>
<td>@account.DebtorName</td>
<td>@account.IdCard</td>
<td>@account.BankName</td>
<td>@account.AccountState</td>
<td>@account.Phone</td>
<td>@account.SaleMan</td>
<td>@account.DebtorMoney</td>
<td>@account.UpdatedTime</td>
</tr>
}
</table>

@Html.PagedListPager(Model, pageIndex => $"/Account/Index?pageIndex={pageIndex}&pageSize=3")
查询显示
AccountController
1
2
3
4
5
6
7
8
9
10
11
12
13
public IActionResult Index(AccountRequest? request = null)
{

ViewBag.StateList = EnumHelper.ToDescriptionDictionary<AccountStateEnum>().Select(p => new SelectListItem
{
Text = p.Value,
Value = p.Key.ToString()
});

var list = _accountService.Search(request);
var pageModel = new StaticPagedList<AccountViewModel>(list, request.PageIndex, request.PageSize,request.Total);
return View(pageModel);
}
Views/Account/Index.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
@using X.PagedList.Mvc.Core
@using Microsoft.EntityFrameworkCore.Metadata.Internal
@model X.PagedList.StaticPagedList<Step4.Unit7.Service.Dto.AccountViewModel>

@{
ViewBag.Title = "title";
Layout = "_Layout";
}

<h2>案件查询</h2>

//添加这个
<form>
//原生的会还原
姓名: @Html.TextBox("DebtorName")
银行:@Html.TextBox("BankName")
状态:@Html.DropDownList("AccountState", ViewBag.StateList, "请选择", null)
<input type="submit" value="提交"/>

<a asp-action="Add" class="btn btn-info">添加</a>
</form>


<table class="table table-hover">
<tr>
<td>姓名</td>
<td>身份证</td>
<td>银行</td>
<td>状态</td>
<td>手机号</td>
<td>业务员</td>
<td>欠款金额</td>
<td>操作时间</td>
<td>操作</td>
</tr>
@foreach (var account in Model)
{
<tr>
<td>@account.DebtorName</td>
<td>@account.IdCard</td>
<td>@account.BankName</td>
<td>@account.AccountState</td>
<td>@account.Phone</td>
<td>@account.SaleMan</td>
<td>@account.DebtorMoney</td>
<td>@account.UpdatedTime</td>
<td>
<a asp-action="Edit" asp-route-id="@account.Id">编辑</a>
&nbsp;&nbsp;
<a onclick="return confirm('是否真的删除?')" asp-action="Delete" asp-route-id="@account.Id">删除</a>
</td>

</tr>
}
</table>

@Html.PagedListPager(Model, pageIndex => $"/Account/Index?pageIndex={pageIndex}&pageSize=3")
Step4.Unit7.Service\AccountService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public List<AccountViewModel> Search(AccountRequest? request)
{
var accounts = from a in _context.Accounts
join b in _context.Debtors on a.DebtorId equals b.Id into abtemp
from abJoin in abtemp.DefaultIfEmpty()

select new AccountViewModel
{
Id = a.Id,
AccountState = a.AccountState.ToDescription(),
State = (int)a.AccountState,
BankName = a.BankName,
DebtorId = a.DebtorId,
SaleMan = a.SaleMan,
UpdatedTime = a.UpdatedTime,
DebtorMoney = a.DebtorMoney,
DebtorName = abJoin.NickName,
IdCard = abJoin.IdCard,
Phone = abJoin.Phone,
Deleted = a.Deleted
};

if (!string.IsNullOrWhiteSpace(request.BankName))
{
accounts = accounts.Where(p => p.BankName.Contains(request.BankName));
}
if (!string.IsNullOrWhiteSpace(request.DebtorName))
{
accounts = accounts.Where(p => p.DebtorName.Contains(request.DebtorName));
}

if (request.AccountState!=null)
{
accounts = accounts.Where(p => p.State == (int)request.AccountState);
}

request.Total = accounts.Count();

return accounts.OrderByDescending(p=>p.Id).Skip((request.PageIndex-1)*request.PageSize).Take(request.PageSize).ToList();
}
增删改查
AccountController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Step4.Unit7.Model;
using Step4.Unit7.Service;
using Step4.Unit7.Service.Dto;
using Step4.Unit7.Service.Dto.condition;
using Step4.Unit7.Service.utils;
using X.PagedList;

namespace Step4.Unit7.Controllers;

public class AccountController:Controller
{
private readonly IAccountService _accountService;

public AccountController(IAccountService accountService)
{
_accountService = accountService;
}


public IActionResult Index(AccountRequest? request = null)
{

ViewBag.StateList = EnumHelper.ToDescriptionDictionary<AccountStateEnum>().Select(p => new SelectListItem
{
Text = p.Value,
Value = p.Key.ToString()
});

var list = _accountService.Search(request);
var pageModel = new StaticPagedList<AccountViewModel>(list, request.PageIndex, request.PageSize,request.Total);
return View(pageModel);
}

public IActionResult Add()
{
ViewBag.DebtorList = new SelectList(_accountService.GetDebtor(), "Id", "NickName");
return View();
}

[HttpPost]
public IActionResult SubmitAdd(AccountBo bo)
{
if (ModelState.IsValid)
{
_accountService.Add(bo);
return RedirectToAction("Index");
}

ViewBag.DebtorList = new SelectList(_accountService.GetDebtor(), "Id", "NickName");
return View("Add", bo);
}


public IActionResult Edit(long id)
{
ViewBag.StateList = EnumHelper.ToDescriptionDictionary<AccountStateEnum>().Select(p => new SelectListItem
{
Text = p.Value,
Value = p.Key.ToString()
});
return View(_accountService.GetModel(id));
}

[HttpPost]
public IActionResult SubmitUpdate(AccountUpdateBo bo)
{
if (ModelState.IsValid)
{
_accountService.Update(bo);
return RedirectToAction("Index");
}

ViewBag.DebtorList = new SelectList(_accountService.GetDebtor(), "Id", "NickName");
return View("Edit", bo);
}

public IActionResult Delete(long id)
{
_accountService.Delete(id);
return RedirectToAction("Index");
}

}
Step4.Unit7.Service\Profiles\AccountProfile.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//automapper.extensions.microsoft.dependencyinjection  11.0.0

using AutoMapper;
using Step4.Unit7.Model;
using Step4.Unit7.Service.Dto;

namespace Step4.Unit7.Service.Profiles;

public class AccountProfile:Profile
{
public AccountProfile()
{
CreateMap<AccountBo, Account>();

CreateMap<Account,AccountUpdateBo>();

CreateMap<Debtor, DebtorViewModel>();

}
}
Step4.Unit7.Service\Dto\AccountBo.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
using System.ComponentModel.DataAnnotations;
using Step4.Unit7.Model;

namespace Step4.Unit7.Service.Dto;

public class AccountBo
{

/// <summary>
/// 债务人
/// </summary>
[Required(ErrorMessage = "请选择债务人")]
[Display(Name = "债务人")]
public long? DebtorId { get; set; }

/// <summary>
/// 银行名称
/// </summary>
[Required(ErrorMessage = "请输入银行名称")]
[Display(Name = "银行名称")]
public string? BankName { get; set; }

/// <summary>
/// 业务员
/// </summary>
[Display(Name = "业务员")]
[Required(ErrorMessage = "请输入业务员姓名")]
public string? SaleMan { get; set; }

/// <summary>
/// 欠款金额
/// </summary>
[Display(Name = "欠款金额")]
[Required(ErrorMessage = "请输入欠款金额")]
[Range(1,int.MaxValue,ErrorMessage = "必须输入整数")]
public int DebtorMoney { get; set; }
}
Step4.Unit7.Service\Dto\DebtorViewModel.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
namespace Step4.Unit7.Service.Dto;

public class DebtorViewModel
{
public long Id { get; set; }
/// <summary>
/// 姓名
/// </summary>
public String? NickName { get; set; }
/// <summary>
/// 身份证
/// </summary>
public string? IdCard { get; set; }
/// <summary>
/// 家庭地址
/// </summary>
public String? HomeAddress { get; set; }
/// <summary>
/// 手机号
/// </summary>
public string? Phone { get; set; }
/// <summary>
/// 欠款总金额
/// </summary>
public int? TotalDebtorMoney { get; set; }
/// <summary>
/// 性别
/// </summary>
public string? Gender { get; set; }
}
Step4.Unit7.Service\Dto\AccountUpdateBo.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System.ComponentModel.DataAnnotations;
using Step4.Unit7.Model;

namespace Step4.Unit7.Service.Dto;

public class AccountUpdateBo
{
public long Id { get; set; }
[Display(Name = "状态")]
[Required(ErrorMessage = "请选择案件状态")]
public AccountStateEnum AccountState { get; set; }

[Display(Name = "业务员")]
[Required(ErrorMessage = "请输入业务员姓名")]
public string? SaleMan { get; set; }
}
Step4.Unit7\Step4.Unit7.Model\Debtor.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using System.ComponentModel.DataAnnotations.Schema;

namespace Step4.Unit7.Model;

/// <summary>
/// 债务人
/// </summary>
[Table("Debtor")]
public class Debtor:BaseEntity
{
/// <summary>
/// 姓名
/// </summary>
public String? NickName { get; set; }
/// <summary>
/// 身份证
/// </summary>
public string? IdCard { get; set; }
/// <summary>
/// 家庭地址
/// </summary>
public String? HomeAddress { get; set; }
/// <summary>
/// 手机号
/// </summary>
public string? Phone { get; set; }
/// <summary>
/// 欠款总金额
/// </summary>
public int? TotalDebtorMoney { get; set; }
/// <summary>
/// 性别
/// </summary>
public string? Gender { get; set; }
}
Step4.Unit7.Model\Account.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;

namespace Step4.Unit7.Model;

/// <summary>
/// 银行案件
/// </summary>
[Table("Account")]
public class Account:BaseEntity
{
/// <summary>
/// 债务人
/// </summary>
public long DebtorId { get; set; }
/// <summary>
/// 银行名称
/// </summary>
public string? BankName { get; set; }
/// <summary>
/// 当前案件状态
/// </summary>
public AccountStateEnum AccountState { get; set; }
/// <summary>
/// 业务员
/// </summary>
public string? SaleMan { get; set; }
/// <summary>
/// 欠款金额
/// </summary>
public int DebtorMoney { get; set; }
}

/// <summary>
/// 案件状态
/// </summary>
public enum AccountStateEnum
{
[Description("等待还款")]
Wait,
[Description("已还部分")]
Partial,
[Description("禁止催收")]
Forbid,
[Description("已完结")]
Finish
}
Step4.Unit7.Model\BaseEntity.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Step4.Unit7.Model;

public class BaseEntity
{
[Key]
public long Id { get; set; }

public DateTime UpdatedTime { get; set; } = DateTime.Now;
public DateTime CreatedTime { get; set; }= DateTime.Now;
public long UpdatedUserId { get; set; }
public long CreatedUserId { get; set; }
public int Deleted { get; set; } = 0;
}
Step4.Unit7\Views\Account\Index.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@using X.PagedList.Mvc.Core
@using Microsoft.EntityFrameworkCore.Metadata.Internal
@model X.PagedList.StaticPagedList<Step4.Unit7.Service.Dto.AccountViewModel>

@{
ViewBag.Title = "title";
Layout = "_Layout";
}

<h2>案件查询</h2>

<form>
姓名: @Html.TextBox("DebtorName")
银行:@Html.TextBox("BankName")
状态:@Html.DropDownList("AccountState", ViewBag.StateList, "请选择", null)
<input type="submit" value="提交"/>

<a asp-action="Add" class="btn btn-info">添加</a>
</form>


<table class="table table-hover">
<tr>
<td>姓名</td>
<td>身份证</td>
<td>银行</td>
<td>状态</td>
<td>手机号</td>
<td>业务员</td>
<td>欠款金额</td>
<td>操作时间</td>
<td>操作</td>
</tr>
@foreach (var account in Model)
{
<tr>
<td>@account.DebtorName</td>
<td>@account.IdCard</td>
<td>@account.BankName</td>
<td>@account.AccountState</td>
<td>@account.Phone</td>
<td>@account.SaleMan</td>
<td>@account.DebtorMoney</td>
<td>@account.UpdatedTime</td>
<td>
<a asp-action="Edit" asp-route-id="@account.Id">编辑</a>
&nbsp;&nbsp;
<a onclick="return confirm('是否真的删除?')" asp-action="Delete" asp-route-id="@account.Id">删除</a>
</td>

</tr>
}
</table>

@Html.PagedListPager(Model, pageIndex => $"/Account/Index?pageIndex={pageIndex}&pageSize=3")
Step4.Unit7\Views\Account\Add.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@model Step4.Unit7.Service.Dto.AccountBo

@{
ViewBag.Title = "title";
Layout = "_Layout";
}

<h2>添加</h2>

<form asp-action="SubmitAdd" method="post">
<table class="table">
<tr>
<td>
<label asp-for="DebtorId"></label>
</td>
<td>
<select asp-items="ViewBag.DebtorList" asp-for="DebtorId">
<option value="">请选择</option>
</select>
</td>
<td>
<span asp-validation-for="DebtorId"></span>
</td>
</tr>
<tr>
<td>
<label asp-for="BankName"></label>
</td>
<td>
<input asp-for="BankName"/>
</td>
<td>
<span asp-validation-for="BankName"></span>
</td>
</tr>
<tr>
<td>
<label asp-for="SaleMan"></label>
</td>
<td>
<input asp-for="SaleMan"/>
</td>
<td>
<span asp-validation-for="SaleMan"></span>
</td>
</tr>
<tr>
<td>
<label asp-for="DebtorMoney"></label>
</td>
<td>
<input type="number" asp-for="DebtorMoney"/>
</td>
<td>
<span asp-validation-for="DebtorMoney"></span>
</td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="提交" class="btn btn-primary"/>
</td>

</tr>
</table>
</form>
Step4.Unit7\Views\Account\Edit.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@model Step4.Unit7.Service.Dto.AccountUpdateBo
@{
ViewBag.Title = "title";
Layout = "_Layout";
}

<h2>编辑</h2>
<form asp-action="SubmitUpdate" method="post">
<input type="hidden" asp-for="Id"/>
<table class="table">
<tr>
<td>
<label asp-for="AccountState"></label>
</td>
<td>
<select asp-items="ViewBag.StateList" asp-for="AccountState">
<option value="">请选择</option>
</select>
</td>
<td>
<span asp-validation-for="AccountState"></span>
</td>
</tr>

<tr>
<td>
<label asp-for="SaleMan"></label>
</td>
<td>
<input asp-for="SaleMan"/>
</td>
<td>
<span asp-validation-for="SaleMan"></span>
</td>
</tr>

<tr>
<td colspan="3">
<input type="submit" value="提交" class="btn btn-primary"/>
</td>

</tr>
</table>
</form>

Program.cs
1
2
3
builder.Services.AddScoped<IAccountService, AccountService>();

builder.Services.AddAutoMapper(typeof(AccountProfile));
Step4.Unit7.Service\IAccountService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using Step4.Unit7.Service.Dto;
using Step4.Unit7.Service.Dto.condition;

namespace Step4.Unit7.Service;

public interface IAccountService
{
List<AccountViewModel> Search(AccountRequest? request);

void Add(AccountBo bo);

void Update(AccountUpdateBo bo);

AccountUpdateBo GetModel(long id);

void Delete(long id);


List<DebtorViewModel> GetDebtor();
}
Step4.Unit7.Service\AccountService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
using AutoMapper;
using Snowflake;
using Step4.Unit7.Model;
using Step4.Unit7.Service.Dto;
using Step4.Unit7.Service.Dto.condition;
using Step4.Unit7.Service.utils;
using Step4.Unit7.Service.utils.Snowflake;

namespace Step4.Unit7.Service;

public class AccountService:IAccountService
{
private readonly StepDbContext _context;
private readonly IMapper _mapper;
private readonly IdWorker _idWorker;

public AccountService(StepDbContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
_idWorker = SnowflakeUtil.CreateIdWorker();
}

public List<AccountViewModel> Search(AccountRequest? request)
{
var accounts = from a in _context.Accounts
join b in _context.Debtors on a.DebtorId equals b.Id into abtemp
from abJoin in abtemp.DefaultIfEmpty()

select new AccountViewModel
{
Id = a.Id,
AccountState = a.AccountState.ToDescription(),
State = (int)a.AccountState,
BankName = a.BankName,
DebtorId = a.DebtorId,
SaleMan = a.SaleMan,
UpdatedTime = a.UpdatedTime,
DebtorMoney = a.DebtorMoney,
DebtorName = abJoin.NickName,
IdCard = abJoin.IdCard,
Phone = abJoin.Phone,
Deleted = a.Deleted
};
// 过滤掉已删除的数据
accounts = accounts.Where(p => p.Deleted == 0);

if (!string.IsNullOrWhiteSpace(request.BankName))
{
accounts = accounts.Where(p => p.BankName.Contains(request.BankName));
}
if (!string.IsNullOrWhiteSpace(request.DebtorName))
{
accounts = accounts.Where(p => p.DebtorName.Contains(request.DebtorName));
}

if (request.AccountState!=null)
{
accounts = accounts.Where(p => p.State == (int)request.AccountState);
}

request.Total = accounts.Count();

return accounts.OrderByDescending(p=>p.Id).Skip((request.PageIndex-1)*request.PageSize).Take(request.PageSize).ToList();
}

public void Add(AccountBo bo)
{
var account = _mapper.Map<Account>(bo);
account.Id = _idWorker.NextId();
account.AccountState = AccountStateEnum.Wait;

_context.Accounts.Add(account);
_context.SaveChanges();
}

public void Update(AccountUpdateBo bo)
{
var entity = _context.Accounts.FirstOrDefault(p=>p.Id == bo.Id);
entity!.AccountState = bo.AccountState;
entity.SaleMan = bo.SaleMan;
entity.UpdatedTime = DateTime.Now;

_context.SaveChanges();
}

public AccountUpdateBo GetModel(long id)
{
var entity = _context.Accounts.FirstOrDefault(p=>p.Id == id);
return _mapper.Map<AccountUpdateBo>(entity);
}

public void Delete(long id)
{
// 真删除
// var entity = _context.Accounts.FirstOrDefault(p=>p.Id == id);
// _context.Accounts.Remove(entity);
// _context.SaveChanges();

// 假删除
var entity = _context.Accounts.FirstOrDefault(p=>p.Id == id);
entity.Deleted = 1;// 假删除
entity.UpdatedTime = DateTime.Now;
_context.SaveChanges();
}

public List<DebtorViewModel> GetDebtor()
{
return _mapper.Map<List<DebtorViewModel>>(_context.Debtors.ToList());
}
}

角色与授权

Identity 完成登录
  • 登录与注册的 View
  • 用于登录的控制器 AccountController
  • Model 校验
appsettings.json
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultDbConnection": "server=localhost;uid=sa;pwd=123456;database=MvcUnit;"
}
}

安装包
1
2
3
4
5
6
7
8
// IdentityDbContext 需要安装包:
1. Microsoft.AspNetCore.Identity.EntityFrameworkCore 6.0.1
2. Microsoft.EntityFrameworkCore 6.0.1
3. Microsoft.EntityFrameworkCore.SqlServer 6.0.1
// 命令在 Visual Studio 中使用[包管理器控制台]运行
4. Microsoft.EntityFrameworkCore.Tools 6.0.1
// 将一组通用标识服务添加到应用程序,包括默认 UI、令牌提供程序,以及配置身份验证以使用标识 Cookie。
5. Microsoft.AspNetCore.Identity.UI 6.0.1
Program.cs
1
2
3
4
5
6
builder.Services.AddDbContext<IdentityDbContext>(options =>
{
options.UseSqlServer(
builder.Configuration.GetConnectionString("DefaultDbConnection"),
b => b.MigrationsAssembly("MvcDemo")); // 因为IdentityDbContext不在当前程序集中
});
数据迁移
Visual Studio 2022
1
2
3
//程序包管理器控制台
Add-Migration InitIdentity -Context IdentityDbContext
Update-Database
CLI 命令行
1
2
3
1. dotnet tool install --global dotnet-ef
2. dotnet-ef migrations add InitIdentity -c IdentityDbContext
3. dotnet-ef database update
登录与注册功能
Models\LoginViewModel.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
using System.ComponentModel.DataAnnotations;

namespace WebApplication_MVC_Identity.Models;

public class LoginViewModel
{
[Display(Name = "用户名")]
[Required(ErrorMessage = "请输入用户名")]
public String? UserName { get; set; }

[Display(Name = "密码")]
[Required(ErrorMessage = "请输入密码")]
public string? Pwd { get; set; }
}
Models\RegisterViewModel.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System.ComponentModel.DataAnnotations;

namespace WebApplication_MVC_Identity.Models;

public class RegisterViewModel
{
[Display(Name = "用户名")]
[Required(ErrorMessage = "请输入用户名")]
public String? UserName { get; set; }

[Display(Name = "密码")]
[Required(ErrorMessage = "请输入密码")]
public string? Pwd { get; set; }

[Display(Name = "邮箱")]
[Required(ErrorMessage = "请输入邮箱")]
[EmailAddress(ErrorMessage = "邮箱格式不正确")]
public String? Email { get; set; }
}
Views\Account\Login.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@model LoginViewModel

<h5>请您登录或者<a asp-action="Register">注册</a> </h5>

<h1>用户登录</h1>
<form asp-action="SubmitLogin" method="post">
<div asp-validation-summary="ModelOnly"></div>
<div>
<label asp-for="UserName"></label>
<input asp-for="UserName"/>
<span asp-validation-for="UserName"></span>
</div>
<div>
<label asp-for="Pwd"></label>
<input asp-for="Pwd"/>
<span asp-validation-for="Pwd"></span>
</div>
<div>
<input type="submit" class="btn btn-primary" value="登录"/>
</div>
</form>

@section Scripts
{
<partial name="_ValidationScriptsPartial"/>
}
Views\Account\Register.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@model RegisterViewModel
<style>

</style>

<h1>用户注册</h1>
<form asp-action="SubmitRegister" method="post">
<div asp-validation-summary="ModelOnly"></div>
<div>
<label asp-for="UserName"></label>
<input asp-for="UserName"/>
<span asp-validation-for="UserName"></span>
</div>
<div>
<label asp-for="Pwd"></label>
<input asp-for="Pwd"/>
<span asp-validation-for="Pwd"></span>
</div>
<div>
<label asp-for="Email"></label>
<input asp-for="Email"/>
<span asp-validation-for="Email"></span>
</div>
<div>
<input type="submit" class="btn btn-primary" value="注册"/>
</div>
</form>

@section Scripts
{
<partial name="_ValidationScriptsPartial"/>
}
Program.cs
1
2
builder.Services.AddDefaultIdentity<IdentityUser>()// 添加默认的身份验证方式
.AddEntityFrameworkStores<IdentityDbContext>(); // 默认是存储在内存中,我们设置为使用EF进行存储
Controllers\AccountController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using WebApplication_MVC_Identity.Models;
using System.Security.Claims;

namespace WebApplication_MVC_Identity.Controllers;

public class AccountController : Controller
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;

public AccountController(SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager)
{
_signInManager = signInManager;
_userManager = userManager;
}

[AllowAnonymous]
public IActionResult Login()
{
return View();
}

[AllowAnonymous]
[HttpPost]
public async Task<IActionResult> SubmitLogin(LoginViewModel viewModel)
{
if (ModelState.IsValid)
{
var user = await _signInManager.UserManager.FindByNameAsync(viewModel.UserName);
if (user != null)
{
//await _signInManager.SignInAsync(user, false, viewModel.Pwd);//下次进入系统时,需要再次的输入用户名和密码,也就是不保存用户名和密码的信息
var result = await _signInManager.PasswordSignInAsync(user, viewModel.Pwd, false, false);
if (result.Succeeded)
{
return RedirectToAction("Index","Home");
}
}

ModelState.AddModelError("", "用户名或者密码输入不正确");
}

return View("Login", viewModel);
}

[AllowAnonymous]
public IActionResult Register()
{
return View();
}

[AllowAnonymous]
[HttpPost]
public async Task<IActionResult> SubmitRegister(RegisterViewModel viewModel)
{

if (ModelState.IsValid)
{
var user = new IdentityUser
{
UserName = viewModel.UserName,
Email = viewModel.Email
};
var result = await _userManager.CreateAsync(user, viewModel.Pwd);

if (result.Succeeded)
{
await _userManager.AddClaimsAsync(user,new List<Claim> {
new Claim("用户管理","添加用户")
});
return RedirectToAction("Index", "Home");
}




foreach (var error in result.Errors)
{
ModelState.AddModelError("",error.Description);
}
}
return View("Register",viewModel);
}

[Authorize]
public async Task<IActionResult> Logout()
{
await _signInManager.SignOutAsync();
return RedirectToAction("Login");
}
}
相关配置
Controllers\AccountController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
public class AccountController : Controller
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;

public AccountController(SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager)
{
_signInManager = signInManager;
_userManager = userManager;
}

[AllowAnonymous]
public IActionResult Login()
{
return View();
}

[AllowAnonymous]
[HttpPost]
public async Task<IActionResult> SubmitLogin(LoginViewModel viewModel)
{
if (ModelState.IsValid)
{
var user = await _signInManager.UserManager.FindByNameAsync(viewModel.UserName);
if (user != null)
{
//await _signInManager.SignInAsync(user, false, viewModel.Pwd);//下次进入系统时,需要再次的输入用户名和密码,也就是不保存用户名和密码的信息
var result = await _signInManager.PasswordSignInAsync(user, viewModel.Pwd, false, false);
if (result.Succeeded)
{
return RedirectToAction("Index","Home");
}
}

ModelState.AddModelError("", "用户名或者密码输入不正确");
}

return View("Login", viewModel);
}

[AllowAnonymous]
public IActionResult Register()
{
return View();
}

[AllowAnonymous]
[HttpPost]
public async Task<IActionResult> SubmitRegister(RegisterViewModel viewModel)
{

if (ModelState.IsValid)
{
var user = new IdentityUser
{
UserName = viewModel.UserName,
Email = viewModel.Email
};
var result = await _userManager.CreateAsync(user, viewModel.Pwd);

if (result.Succeeded)
{
await _userManager.AddClaimsAsync(user,new List<Claim> {
new Claim("用户管理","添加用户")
});
return RedirectToAction("Index", "Home");
}




foreach (var error in result.Errors)
{
ModelState.AddModelError("",error.Description);
}
}
return View("Register",viewModel);
}

[Authorize]
public async Task<IActionResult> Logout()
{
await _signInManager.SignOutAsync();
return RedirectToAction("Login");
}
}
Controllers\HomeController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[Authorize] // 必须登录之后才可访问
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;

public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}

public IActionResult Index()
{
return View();
}

public IActionResult Privacy()
{
return View();
}

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
Views\Shared_Layout.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
@using Microsoft.AspNetCore.Identity

@添加了这些
@inject SignInManager<IdentityUser> _signInManager
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Step4.Unit8</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/Step4.Unit8.styles.css" asp-append-version="true" />
</head>
<body>
<header>

@添加了这些
@if (this.User.Identity.IsAuthenticated)
{
@:@User.Identity.Name,
<a asp-controller="Account" asp-action="Logout">退出登录</a>
}
else
{
<a asp-controller="Account" asp-action="Login">您好,请登录</a>
}

@* @if (_signInManager.IsSignedIn(this.User)) *@
@* { *@
@* <a asp-controller="Account" asp-action="Logout">退出登录</a> *@
@* } *@
@* else *@
@* { *@
@* <a asp-controller="Account" asp-action="Login">您好,请登录</a> *@
@* } *@


<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container-fluid">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Step4.Unit8</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="User" asp-action="Index">用户管理</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Role" asp-action="Index">角色管理</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>

<footer class="border-top footer text-muted">
<div class="container">
&copy; 2022 - Step4.Unit8 - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// Identity 的一些默认配置选项
builder.Services.Configure<IdentityOptions>(option =>
{
// 该值指示密码是否必须包含数字。 默认为 true。
option.Password.RequireDigit = false;
// 设置密码必须包含的最小长度。 默认值为 6。
option.Password.RequiredLength = 3;
// 设置密码必须包含的唯一字符的最小数目。 默认值为 1。
option.Password.RequiredUniqueChars = 1;
// 该值指示密码是否必须包含小写 ASCII 字符。 默认为 true。
option.Password.RequireLowercase = false;
// 该值指示密码是否必须包含大写 ASCII 字符。 默认为 true。
option.Password.RequireUppercase = false;
// 该值指示密码是否必须包含非字母数字字符。 默认为 true。
option.Password.RequireNonAlphanumeric = false;


// 设置 TimeSpan 用户在锁定时被锁定。 默认为 5 分钟。
option.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
// 该值指示是否可以锁定新用户。默认值为 true。
option.Lockout.AllowedForNewUsers = true;
// 设置在用户锁定之前允许的失败访问尝试次数,前提是已启用锁定。 默认值为 5。
option.Lockout.MaxFailedAccessAttempts = 5;

// 用户是否需要唯一的电子邮件。 默认为 false。
option.User.RequireUniqueEmail = false;
// 设置用户名中用于验证用户名的允许字符列表,默认为 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+
option.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";


// 该值指示是否需要确认 IUserConfirmation<TUser> 的帐户登录。 默认为 false。
option.SignIn.RequireConfirmedAccount = false;
// 该值指示是否需要确认的电子邮件地址登录。 默认为 false。
option.SignIn.RequireConfirmedEmail = false;
// 该值指示是否需要确认的电话号码登录。 默认为 false。
option.SignIn.RequireConfirmedPhoneNumber = false;
});

builder.Services.AddAuthentication(); // 认证,再授权
builder.Services.AddAuthorization(); // 授权


app.UseAuthentication();
app.UseAuthorization();
用户配置
Controllers\UserController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using WebApplication_MVC_Identity.Models;

namespace WebApplication_MVC_Identity.Controllers;

[Authorize]
public class UserController : Controller
{

private readonly UserManager<IdentityUser> _userManager;

public UserController(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}

public IActionResult Index()
{
return View( _userManager.Users.ToList());
}

public async Task<IActionResult> Edit(string id)
{
var user = await _userManager.FindByIdAsync(id);
//可以实用autofac
UserEditViewModel viewModel = new UserEditViewModel
{
Id = user.Id,
UserName = user.UserName,
Email = user.Email
};
return View(viewModel);
}


public async Task<IActionResult> Delete(string id)
{
var user = await _userManager.FindByIdAsync(id);
await _userManager.DeleteAsync(user);
return RedirectToAction("Index");
}

// 用户的修改功能
[HttpPost]
public async Task<IActionResult> Submit(UserEditViewModel viewModel)
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByIdAsync(viewModel.Id);
user.Email = viewModel.Email;
user.UserName = viewModel.UserName;


var result = await _userManager.UpdateAsync(user);
if (result.Succeeded)
{
return RedirectToAction("Index");
}

foreach (var error in result.Errors)
{
ModelState.AddModelError("",error.Description);
}
}

return RedirectToAction("Edit", viewModel);

}
}
Views\User\Index.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@using Microsoft.AspNetCore.Identity
@model IEnumerable<Microsoft.AspNetCore.Identity.IdentityUser>
@inject UserManager<IdentityUser> _userManager

@{
ViewBag.Title = "title";
Layout = "_Layout";
}

<h2>用户管理</h2>
<a class="btn btn-info" asp-action="Register" asp-controller="Account">添加用户</a>
<table class="table table-hover">
<thead>
<tr>
<th>id</th>
<th>用户名</th>
<th>Email</th>
<th>角色</th>
<th>Claims</th>
<th>操作</th>
</tr>
</thead>
<tbody>
@foreach (var u in Model)
{
<tr>
<td>@u.Id</td>
<td>@u.UserName</td>
<td>@u.Email</td>
@* <td>@(string.Join(",", await _userManager.GetRolesAsync(u))) </td> *@
@* <td>@(string.Join(",", await _userManager.GetClaimsAsync(u))) </td> *@
<td>
<a asp-action="Edit" asp-route-id="@u.Id">编辑</a>
&nbsp;&nbsp;&nbsp;
<a onclick="return confirm('确认删除吗?')" href="/user/delete/@u.Id">删除</a>
</td>
</tr>
}
</tbody>
</table>

Views\Shared_Layout.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> _signInManager
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>@ViewData["Title"] - Step4.Unit8</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css"/>
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true"/>
<link rel="stylesheet" href="~/Step4.Unit8.styles.css" asp-append-version="true"/>
</head>
<body>
<header>
@if (this.User.Identity.IsAuthenticated)
{
@:@User.Identity.Name,
<a asp-controller="Account" asp-action="Logout">退出登录</a>
}
else
{
<a asp-controller="Account" asp-action="Login">您好,请登录</a>
}

@* @if (_signInManager.IsSignedIn(this.User)) *@
@* { *@
@* <a asp-controller="Account" asp-action="Logout">退出登录</a> *@
@* } *@
@* else *@
@* { *@
@* <a asp-controller="Account" asp-action="Login">您好,请登录</a> *@
@* } *@


<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container-fluid">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Step4.Unit8</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</li>
//加了这个
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="User" asp-action="Index">用户管理</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Role" asp-action="Index">角色管理</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>

<footer class="border-top footer text-muted">
<div class="container">
&copy; 2022 - Step4.Unit8 - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
Models\UserEditViewModel.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System.ComponentModel.DataAnnotations;

namespace WebApplication_MVC_Identity.Models;

public class UserEditViewModel
{
public string? Id { get; set; }

[Display(Name = "用户名")]
[Required(ErrorMessage = "请输入用户名")]
public String? UserName { get; set; }

[Display(Name = "邮箱")]
[Required(ErrorMessage = "请输入邮箱")]
[EmailAddress(ErrorMessage = "邮箱格式不正确")]
public String? Email { get; set; }

[Display(Name = "角色")]
[Required(ErrorMessage = "请选择角色")]
public string? RoleName { get; set; }
}
角色管理
Program.cs
1
2
// 添加了用户与角色相关的服务
builder.Services.AddIdentity<IdentityUser,IdentityRole().AddEntityFrameworkStores<IdentityDbContext>();
Controllers\RoleController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using WebApplication_MVC_Identity.Models;

namespace WebApplication_MVC_Identity.Controllers;

// 如果需要设置多个角色,中间以逗号隔开
[Authorize(Policy ="角色")] // 通过策略绑定的角色
//[Authorize(Roles ="管理员")] // 直接指定了角色
public class RoleController : Controller
{
private readonly RoleManager<IdentityRole> _roleManager;

// GET
public RoleController(RoleManager<IdentityRole> roleManager)
{
_roleManager = roleManager;
}

public IActionResult Index()
{
return View(_roleManager.Roles.ToList());
}

public IActionResult Create()
{
return View();
}

[HttpPost]
public async Task<IActionResult> SubmitCreate(RoleCreateViewModel viewModel)
{
if (ModelState.IsValid)
{
var role = new IdentityRole()
{
Name = viewModel.RoleName
};
var result = await _roleManager.CreateAsync(role);
if (result.Succeeded)
{
return RedirectToAction("Index");
}
foreach (var err in result.Errors)
{
ModelState.AddModelError("",err.Description);
}
}
return View("Create",viewModel);
}


public async Task<IActionResult> Edit(string id)
{
var role = await _roleManager.FindByIdAsync(id);
var viewModel = new RoleEditViewModel()
{
Id = role.Id,
RoleName = role.Name
};
return View(viewModel);
}

[HttpPost]
public async Task<IActionResult> SubmitEdit(RoleEditViewModel viewModel)
{
if (ModelState.IsValid)
{
var role = await _roleManager.FindByIdAsync(viewModel.Id);
role.Name = viewModel.RoleName;
var result = await _roleManager.UpdateAsync(role);
if (result.Succeeded)
{
return RedirectToAction("Index");
}
foreach (var err in result.Errors)
{
ModelState.AddModelError("",err.Description);
}
}

return View("Edit", viewModel);
}


public async Task<IActionResult> Delete(string id)
{
var role = await _roleManager.FindByIdAsync(id);
await _roleManager.DeleteAsync(role);

return RedirectToAction("Index");
}
}
Models\RoleCreateViewModel.cs
1
2
3
4
5
6
7
8
9
10
11
12
using System.ComponentModel.DataAnnotations;

namespace WebApplication_MVC_Identity.Models;

/// <summary>
/// 添加角色视图模型
/// </summary>
public class RoleCreateViewModel
{
[Required(ErrorMessage = "请输入角色名称")]
public string? RoleName { get; set; }
}
Models\RoleEditViewModel.cs
1
2
3
4
5
6
7
8
9
10
namespace WebApplication_MVC_Identity.Models;

/// <summary>
/// 角色编辑视图
/// </summary>
public class RoleEditViewModel
{
public string? Id { get; set; }
public string? RoleName { get; set; }
}
Views\Role\Create.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@model RoleCreateViewModel


<h2>添加角色</h2>

<form asp-action="SubmitCreate" method="post">
<div class="text-danger" asp-validation-summary="ModelOnly"></div>
<div class="form-group row">
<label asp-for="RoleName"></label>
<div class="col-sm-10">
<input class="form-control" asp-for="RoleName"/>
<span asp-validation-for="RoleName" class="text-danger"></span>
</div>
</div>

<div class="form-group">
<input type="submit" class="btn btn-primary" value="提交"/>
<a asp-action="Index" class="btn btn-secondary">返回列表</a>
</div>
</form>
Views\Role\Edit.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@model RoleEditViewModel

<h2>编辑用户</h2>
<form asp-action="SubmitEdit" method="post">
<input asp-for="Id" type="hidden"/>

<div class="text-danger" asp-validation-summary="ModelOnly"></div>
<div class="form-group row">
<label asp-for="RoleName"></label>
<div class="col-sm-10">
<input class="form-control" asp-for="RoleName"/>
<span asp-validation-for="RoleName" class="text-danger"></span>
</div>
</div>

<div class="form-group">
<input type="submit" class="btn btn-primary" value="提交"/>
<a asp-action="Index" class="btn btn-secondary">返回列表</a>
</div>
</form>
Views\Role\Index.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@model IEnumerable<Microsoft.AspNetCore.Identity.IdentityRole>

<h2>角色管理</h2>
<a asp-action="Create" class="btn btn-primary">添加角色</a>
<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>角色名称</th>
<th>操作</th>
</tr>
</thead>
<tbody>
@foreach (var r in Model)
{
<tr>
<td>@r.Id</td>
<td>@r.Name</td>
<td>
<a href="/role/edit/@r.Id" class="btn btn-info">编辑</a>
<a onclick="return confirm('确认需要删除吗?')" href="/role/delete/@r.Id" class="btn btn-danger">删除</a>
</td>
</tr>
}

</tbody>
</table>
Views\Shared_Layout.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> _signInManager
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - WebApplication_MVC_Identity</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/Step4.Unit8.styles.css" asp-append-version="true" />
</head>
<body>
<header>
@if (this.User.Identity.IsAuthenticated)
{
@:@User.Identity.Name,
<a asp-controller="Account" asp-action="Logout">退出登录</a>
}
else
{
<a asp-controller="Account" asp-action="Login">您好,请登录</a>
}

@* @if (_signInManager.IsSignedIn(this.User)) *@
@* { *@
@* <a asp-controller="Account" asp-action="Logout">退出登录</a> *@
@* } *@
@* else *@
@* { *@
@* <a asp-controller="Account" asp-action="Login">您好,请登录</a> *@
@* } *@


<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container-fluid">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">WebApplication_MVC_Identity</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="User" asp-action="Index">用户管理</a>
</li>
//添加这个
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Role" asp-action="Index">角色管理</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>

<footer class="border-top footer text-muted">
<div class="container">
&copy; 2022 - WebApplication_MVC_Identity - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
用户角色管理
Controllers\UserController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using WebApplication_MVC_Identity.Models;

namespace WebApplication_MVC_Identity.Controllers;

[Authorize]
public class UserController : Controller
{

private readonly UserManager<IdentityUser> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;

public UserController(UserManager<IdentityUser> userManager, RoleManager<IdentityRole> roleManager)
{
_userManager = userManager;
_roleManager = roleManager;
}

public IActionResult Index()
{
return View(_userManager.Users.ToList());
}

public async Task<IActionResult> Edit(string id)
{
var user = await _userManager.FindByIdAsync(id);
UserEditViewModel viewModel = new UserEditViewModel
{
Id = user.Id,
UserName = user.UserName,
Email = user.Email
};
// 查询用户所属的角色
viewModel.RoleName = (await _userManager.GetRolesAsync(user)).FirstOrDefault();

ViewBag.RoleList = new SelectList(_roleManager.Roles.ToList(), "Name", "Name");

return View(viewModel);
}


public async Task<IActionResult> Delete(string id)
{
var user = await _userManager.FindByIdAsync(id);
await _userManager.DeleteAsync(user);
return RedirectToAction("Index");
}

// 用户的修改功能
[HttpPost]
public async Task<IActionResult> Submit(UserEditViewModel viewModel)
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByIdAsync(viewModel.Id);
user.Email = viewModel.Email;
user.UserName = viewModel.UserName;


var result = await _userManager.UpdateAsync(user);
if (result.Succeeded)
{
// 如果当前用户不存在这个角色,则将角色信息添加到用户角色表中
if (!await _userManager.IsInRoleAsync(user, viewModel.RoleName))
{
await _userManager.AddToRoleAsync(user, viewModel.RoleName);
}



return RedirectToAction("Index");
}

foreach (var error in result.Errors)
{
ModelState.AddModelError("", error.Description);
}
}

return RedirectToAction("Edit", viewModel);
}
}
Views\User\Index.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@using Microsoft.AspNetCore.Identity
@model IEnumerable<Microsoft.AspNetCore.Identity.IdentityUser>
@inject UserManager<IdentityUser> _userManager

@{
ViewBag.Title = "title";
Layout = "_Layout";
}

<h2>用户管理</h2>
<a class="btn btn-info" asp-action="Register" asp-controller="Account">添加用户</a>
<table class="table table-hover">
<thead>
<tr>
<th>id</th>
<th>用户名</th>
<th>Email</th>
<th>角色</th>
@* <th>Claims</th> *@
<th>操作</th>
</tr>
</thead>
<tbody>
@foreach (var u in Model)
{
<tr>
<td>@u.Id</td>
<td>@u.UserName</td>
<td>@u.Email</td>
<td>@(string.Join(",", await _userManager.GetRolesAsync(u))) </td>
@* <td>@(string.Join(",", await _userManager.GetClaimsAsync(u))) </td> *@
<td>
<a asp-action="Edit" asp-route-id="@u.Id">编辑</a>
&nbsp;&nbsp;&nbsp;
<a onclick="return confirm('确认删除吗?')" href="/user/delete/@u.Id">删除</a>
</td>
</tr>
}
</tbody>
</table>

授权策略
基于角色授权
Program.cs
1
2
3
4
builder.Services.AddAuthorization(p => {
p.AddPolicy("角色", a => a.RequireRole("管理员"));
//p.AddPolicy("权限", a => a.RequireClaim("用户管理", new string[] { "添加用户", "编辑用户", "删除用户", "用户管理" }));
}); // 授权
Controllers\RoleController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using WebApplication_MVC_Identity.Models;

namespace WebApplication_MVC_Identity.Controllers;

// 如果需要设置多个角色,中间以逗号隔开
//[Authorize(Policy = "角色")] // 通过策略绑定的角色
[Authorize(Roles ="管理员")] // 直接指定了角色
//[Authorize]
public class RoleController : Controller
{
private readonly RoleManager<IdentityRole> _roleManager;

// GET
public RoleController(RoleManager<IdentityRole> roleManager)
{
_roleManager = roleManager;
}

public IActionResult Index()
{
return View(_roleManager.Roles.ToList());
}

public IActionResult Create()
{
return View();
}

[HttpPost]
public async Task<IActionResult> SubmitCreate(RoleCreateViewModel viewModel)
{
if (ModelState.IsValid)
{
var role = new IdentityRole()
{
Name = viewModel.RoleName
};
var result = await _roleManager.CreateAsync(role);
if (result.Succeeded)
{
return RedirectToAction("Index");
}
foreach (var err in result.Errors)
{
ModelState.AddModelError("",err.Description);
}
}
return View("Create",viewModel);
}


public async Task<IActionResult> Edit(string id)
{
var role = await _roleManager.FindByIdAsync(id);
var viewModel = new RoleEditViewModel()
{
Id = role.Id,
RoleName = role.Name
};
return View(viewModel);
}

[HttpPost]
public async Task<IActionResult> SubmitEdit(RoleEditViewModel viewModel)
{
if (ModelState.IsValid)
{
var role = await _roleManager.FindByIdAsync(viewModel.Id);
role.Name = viewModel.RoleName;
var result = await _roleManager.UpdateAsync(role);
if (result.Succeeded)
{
return RedirectToAction("Index");
}
foreach (var err in result.Errors)
{
ModelState.AddModelError("",err.Description);
}
}

return View("Edit", viewModel);
}


public async Task<IActionResult> Delete(string id)
{
var role = await _roleManager.FindByIdAsync(id);
await _roleManager.DeleteAsync(role);

return RedirectToAction("Index");
}
}
基于 Claim 授权(部分授权)
Program.cs
1
2
3
4
builder.Services.AddAuthorization(p => {
p.AddPolicy("角色", a => a.RequireRole("管理员"));
p.AddPolicy("权限", a => a.RequireClaim("用户管理", new string[] { "添加用户", "编辑用户", "删除用户", "用户管理" }));
}); // 授权
Controllers\AccountController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using WebApplication_MVC_Identity.Models;
using System.Security.Claims;
using WebApplication_MVC_Identity.Models;

namespace Step4.Unit8.Controllers;

public class AccountController : Controller
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;

public AccountController(SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager)
{
_signInManager = signInManager;
_userManager = userManager;
}

[AllowAnonymous]
public IActionResult Login()
{
return View();
}

[AllowAnonymous]
[HttpPost]
public async Task<IActionResult> SubmitLogin(LoginViewModel viewModel)
{
if (ModelState.IsValid)
{
var user = await _signInManager.UserManager.FindByNameAsync(viewModel.UserName);
if (user != null)
{
var result = await _signInManager.PasswordSignInAsync(user, viewModel.Pwd, false, false);
if (result.Succeeded)
{
return RedirectToAction("Index", "Home");
}
}

ModelState.AddModelError("", "用户名或者密码输入不正确");
}

return View("Login", viewModel);
}

[AllowAnonymous]
public IActionResult Register()
{
return View();
}

[AllowAnonymous]
[HttpPost]
public async Task<IActionResult> SubmitRegister(RegisterViewModel viewModel)
{

if (ModelState.IsValid)
{
var user = new IdentityUser
{
UserName = viewModel.UserName,
Email = viewModel.Email
};
var result = await _userManager.CreateAsync(user, viewModel.Pwd);

if (result.Succeeded)
{
await _userManager.AddClaimsAsync(user, new List<Claim> {
new Claim("用户管理","添加用户")
});
return RedirectToAction("Index", "Home");
}




foreach (var error in result.Errors)
{
ModelState.AddModelError("", error.Description);
}
}
return View("Register", viewModel);
}

[Authorize]
public async Task<IActionResult> Logout()
{
await _signInManager.SignOutAsync();
return RedirectToAction("Login");
}
}
Views\User\Index.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@using Microsoft.AspNetCore.Identity
@model IEnumerable<Microsoft.AspNetCore.Identity.IdentityUser>
@inject UserManager<IdentityUser> _userManager

@{
ViewBag.Title = "title";
Layout = "_Layout";
}

<h2>用户管理</h2>
<a class="btn btn-info" asp-action="Register" asp-controller="Account">添加用户</a>
<table class="table table-hover">
<thead>
<tr>
<th>id</th>
<th>用户名</th>
<th>Email</th>
<th>角色</th>
<th>Claims</th>
<th>操作</th>
</tr>
</thead>
<tbody>
@foreach (var u in Model)
{
<tr>
<td>@u.Id</td>
<td>@u.UserName</td>
<td>@u.Email</td>
<td>@(string.Join(",", await _userManager.GetRolesAsync(u))) </td>
<td>@(string.Join(",", await _userManager.GetClaimsAsync(u))) </td>
<td>
<a asp-action="Edit" asp-route-id="@u.Id">编辑</a>
&nbsp;&nbsp;&nbsp;
<a onclick="return confirm('确认删除吗?')" href="/user/delete/@u.Id">删除</a>
</td>
</tr>
}
</tbody>
</table>

面向切面编程

定义

无感增强功能,不影响原先的业务

Controllers\UserController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
using Microsoft.AspNetCore.Mvc;

namespace WebApplication_MVC_AOP.Controllers
{
public class UserController : Controller
{
List<UserInfo> list = new List<UserInfo>
{
new(1,"张三"),
new(2,"李四")
};


// 一般用于查询
[HttpGet]
public IActionResult GetList()
{
return Ok(list);
}

// 一般用于添加
[HttpPost]
public IActionResult AddUser([FromBody] UserInfo user)
{
list.Add(user);
return Ok(list);
}




// 一般用于修改
[HttpPut]
public IActionResult UpdateUser([FromBody] UserInfo user)
{
var first = list.FirstOrDefault(p=>p.Id == user.Id);
first!.Name = user.Name;
return Ok(list);
}

// 一般用于删除
[HttpDelete]
public IActionResult DeleteUser(int id)
{
var first = list.FirstOrDefault(p => p.Id == id);
list.Remove(first!);

return Ok(list);
}
}
}


public record UserInfo
{
public UserInfo()
{

}
public UserInfo(int id,string? name)
{
this.Id = id;
this.Name = name;
}
public int Id { get; set; }
public string? Name { get; set; }
};
XSS 攻击
Controllers\XssController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using Microsoft.AspNetCore.Mvc;
using System.Web;

namespace WebApplication_MVC_AOP.Controllers
{
public class XssController : Controller
{
private readonly ILogger<XssController> _logger;

public XssController(ILogger<XssController> logger)
{
_logger = logger;
}

public IActionResult Index()
{
return View();
}


public IActionResult Submit(string input)
{
var result = HttpUtility.HtmlEncode(input);
_logger.LogInformation(result);
return Ok(result);
}
}
}

Views\Xss\Index.cshtml
1
2
3
4
5
<form asp-action="Submit" asp-controller="Xss">
内容: <input name="input" />
<br />
<input type="submit" value="提交" />
</form>
CSRF 攻击
Controllers\CsrfController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using Microsoft.AspNetCore.Mvc;
using WebApplication_MVC_AOP.Models;

namespace WebApplication_MVC_AOP.Controllers
{

public class CsrfController : Controller
{
public IActionResult Index()
{
return View();
}

// 要求你传一个防伪标签过来(如果没有传防伪标签过来,程序就会认为是一个不安全或者说是不信任的提交)
//[ValidateAntiForgeryToken] // 防止跨站远程攻击
[HttpPost]
public IActionResult Submit(StudentViewModel viewModel)
{

return Ok(viewModel);
}


[HttpDelete]
[IgnoreAntiforgeryToken] // 忽略CSRF攻击
public IActionResult Delete()
{
return Ok("");
}
}
}
Models\StudentViewModel.cs
1
2
3
4
5
6
7
8
9
namespace WebApplication_MVC_AOP.Models
{
public class StudentViewModel
{
public string? UserName { get; set; }
public string? Email { get; set; }
public string? Desc { get; set; }
}
}
Views\Csrf\Index.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
@model StudentViewModel


<!--使用标签助手会自动生成一个防伪标签-->
<form asp-action="Submit" asp-controller="Csrf">
<table class="table">
<tr>
<td>
<label asp-for="UserName"></label>
</td>
<td>
<input asp-for="UserName" />
</td>
</tr>
<tr>
<td>
<label asp-for="Email"></label>
</td>
<td>
<input asp-for="Email" />
</td>
</tr>
<tr>
<td>
<label asp-for="Desc"></label>
</td>
<td>
<input asp-for="Desc" />
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="提交" />
</td>
</tr>
</table>
</form>




@*<form action="/CSRF/Submit" method="post">
@Html.AntiForgeryToken()
<table class="table">
<tr>
<td>
@Html.LabelFor(p=>p.UserName)
</td>
<td>
@Html.TextBoxFor(p=>p.UserName)
</td>
</tr>
<tr>
<td>
@Html.LabelFor(p=>p.Email)
</td>
<td>
@Html.TextBoxFor(p=>p.Email)
</td>
</tr>
<tr>
<td>
@Html.LabelFor(p=>p.Desc)
</td>
<td>
@Html.TextAreaFor(p=>p.Desc)
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="提交" />
</td>
</tr>
</table>


</form>*@
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 自定义防伪配置
builder.Services.AddAntiforgery(options =>
{
// Set Cookie properties using CookieBuilder properties†.
options.FormFieldName = "AntiforgeryFieldname";
options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
options.SuppressXFrameOptionsHeader = false;
});
builder.Services.AddControllersWithViews(option =>
{
// 全局校验伪造令牌的过滤器
option.Filters.Add<AutoValidateAntiforgeryTokenAttribute>();
//option.Filters.Add<CustomerExceptionFilterAttribute>();
});
其他系统过滤器
Controllers\UserController.cs
1
2
3
4
5
6
7
8
// 一般用于查询
[HttpGet]
//[NonAction] // 无法被Mvc识别
[ActionName("List")]
public IActionResult GetList()
{
return Ok(list);
}
授权过滤器
Filters\CustomerAuthorizeAttribute.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Web;

namespace WebApplication_MVC_AOP.Filters
{
public class CustomerAuthorizeAttribute : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{

// 判断是否登录
if (!context.HttpContext.User.Identity!.IsAuthenticated)
{
// 获取目标地址
var controller = context.RouteData.Values["controller"];
var action = context.RouteData.Values["action"];
context.Result = new RedirectToActionResult("Login", "Account", new { redirectURL=HttpUtility.UrlEncode($"/{controller}/{action}") });
}
}
}
}
Controllers\AccountController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using WebApplication_MVC_AOP.Models;
using System.Security.Claims;
using System.Web;

namespace WebApplication_MVC_AOP.Controllers;

public class AccountController : Controller
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;

public AccountController(SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager)
{
_signInManager = signInManager;
_userManager = userManager;
}

[AllowAnonymous]
public IActionResult Login(string? redirectURL = null)
{
LoginViewModel viewModel = new LoginViewModel { RedirectURL= redirectURL};
return View(viewModel);
}

[AllowAnonymous]
[HttpPost]
public async Task<IActionResult> SubmitLogin(LoginViewModel viewModel)
{
if (ModelState.IsValid)
{
var user = await _signInManager.UserManager.FindByNameAsync(viewModel.UserName);
if (user != null)
{
var result = await _signInManager.PasswordSignInAsync(user, viewModel.Pwd, false, false);
if (result.Succeeded)
{
// 登录成功之后跳转至目标地址
if (!string.IsNullOrWhiteSpace(viewModel.RedirectURL))
{
return Redirect(HttpUtility.UrlDecode(viewModel.RedirectURL));
}
return RedirectToAction("Index","Home");
}
}

ModelState.AddModelError("", "用户名或者密码输入不正确");
}

return View("Login", viewModel);
}



[Authorize]
public async Task<IActionResult> Logout()
{
await _signInManager.SignOutAsync();
return RedirectToAction("Login");
}
}
Models\LoginViewModel.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System.ComponentModel.DataAnnotations;
using System.Xml.Linq;

namespace WebApplication_MVC_AOP.Models
{
public class LoginViewModel
{
[Display(Name = "用户名")]
[Required(ErrorMessage = "请输入用户名")]
public String? UserName { get; set; }

[Display(Name = "密码")]
[Required(ErrorMessage = "请输入密码")]
public string? Pwd { get; set; }

public string? RedirectURL { get; set; }
}
}
Views\Shared_Layout.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - WebApplication_MVC_AOP</title>
<script type="importmap"></script>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/WebApplication_MVC_AOP.styles.css" asp-append-version="true" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container-fluid">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">WebApplication_MVC_AOP</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Account" asp-action="Logout">退出</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>

<footer class="border-top footer text-muted">
<div class="container">
&copy; 2025 - WebApplication_MVC_AOP - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
Views\Account\Login.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@model LoginViewModel

<h5>请您登录或者<a asp-action="Register">注册</a> </h5>

<h1>用户登录</h1>
<form asp-action="SubmitLogin" method="post">

<input asp-for="RedirectURL" type="hidden" />

<div asp-validation-summary="ModelOnly"></div>
<div>
<label asp-for="UserName"></label>
<input asp-for="UserName"/>
<span asp-validation-for="UserName"></span>
</div>
<div>
<label asp-for="Pwd"></label>
<input asp-for="Pwd"/>
<span asp-validation-for="Pwd"></span>
</div>
<div>
<input type="submit" class="btn btn-primary" value="登录"/>
</div>
</form>

@section Scripts
{
<partial name="_ValidationScriptsPartial"/>
}
appsettings.json
1
2
3
4
5
6
7
8
9
10
11
12
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultDbConnection": "server=.;uid=sa;pwd=123456;database=MvcUnit;"
}
}
安装包
1
2
3
4
// IdentityDbContext 需要安装包:
1. Microsoft.AspNetCore.Identity.EntityFrameworkCore
2. Microsoft.EntityFrameworkCore
3. Microsoft.EntityFrameworkCore.SqlServer
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
      builder.Services.AddDbContext<IdentityDbContext>(p =>
{
p.UseSqlServer(builder.Configuration.GetConnectionString("DefaultDbConnection"),
b => b.MigrationsAssembly("WebApplication_MVC_AOP"));
});
builder.Services.AddIdentity<IdentityUser, IdentityRole>(p =>
{

})
.AddEntityFrameworkStores<IdentityDbContext>();
builder.Services.AddAuthentication();
builder.Services.AddAuthorization();

app.UseAuthentication(); // 认证
app.UseAuthorization(); // 授权
Controllers\UserController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
//需要先访问user/list再登录就会跳转了
using Microsoft.AspNetCore.Mvc;
using WebApplication_MVC_AOP.Filters;

namespace WebApplication_MVC_AOP.Controllers
{
[CustomerAuthorize]
public class UserController : Controller
{
List<UserInfo> list = new List<UserInfo>
{
new(1,"张三"),
new(2,"李四")
};


// 一般用于查询
[HttpGet]
//[NonAction] // 无法被Mvc识别
[ActionName("List")]
public IActionResult GetList()
{
return Ok(list);
}

// 一般用于添加
[HttpPost]
public IActionResult AddUser([FromBody] UserInfo user)
{
list.Add(user);
return Ok(list);
}




// 一般用于修改
[HttpPut]
public IActionResult UpdateUser([FromBody] UserInfo user)
{
var first = list.FirstOrDefault(p => p.Id == user.Id);
first!.Name = user.Name;
return Ok(list);
}

// 一般用于删除
[HttpDelete]
public IActionResult DeleteUser(int id)
{
var first = list.FirstOrDefault(p => p.Id == id);
list.Remove(first!);

return Ok(list);
}
}
}


public record UserInfo
{
public UserInfo()
{

}
public UserInfo(int id, string? name)
{
this.Id = id;
this.Name = name;
}
public int Id { get; set; }
public string? Name { get; set; }
};
异常过滤器
Controllers\HomeController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using Microsoft.AspNetCore.Mvc;
using WebApplication_MVC_AOP.Filters;
using WebApplication_MVC_AOP.Models;
using System.Diagnostics;
using WebApplication_MVC_AOP.Models;

namespace WebApplication_MVC_AOP.Controllers
{
[CustomerExceptionFilter]
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly IConfiguration _configuration;

public HomeController(ILogger<HomeController> logger, IConfiguration configuration)
{
_logger = logger;
_configuration = configuration;
}
public IActionResult Index()
{
var test = _configuration["test"];
_logger.LogInformation(test.ToString()); // 故意让其报错
return View();
}

public IActionResult Privacy()
{
return View();
}

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}
Filters\CustomerExceptionFilterAttribute.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using System.Text.Json.Serialization;

//报错后需要再次点击继续按钮就可以出来页面
namespace WebApplication_MVC_AOP.Filters
{
public class CustomerExceptionFilterAttribute : Attribute, IExceptionFilter
{

// 当发生异常时,执行此方法
public void OnException(ExceptionContext context)
{

context.Result = new JsonResult(new
{
Success = false,
Msg = context.Exception.Message,
Data = context.RouteData.Values
});

//if (!context.ExceptionHandled)
//{
// var obj = new
// {
// Success = false,
// Msg = context.Exception.Message,
// Data = context.RouteData.Values
// };
// context.Result = new JsonResult(obj);
// //logger.LogInformation(context.Exception.Message);
//}
}
}
}
过滤器的应用方式
Filters\CustomerExceptionFilterAttribute.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using System.Text.Json.Serialization;

namespace WebApplication_MVC_AOP.Filters
{
public class CustomerExceptionFilterAttribute : Attribute, IExceptionFilter
{
private readonly ILogger<CustomerExceptionFilterAttribute> logger;

public CustomerExceptionFilterAttribute(ILogger<CustomerExceptionFilterAttribute> logger)
{
this.logger = logger;

}
// 当发生异常时,执行此方法
public void OnException(ExceptionContext context)
{

//context.Result = new JsonResult(new
//{
// Success = false,
// Msg = context.Exception.Message,
// Data = context.RouteData.Values
//});

if (!context.ExceptionHandled)
{
var obj = new
{
Success = false,
Msg = context.Exception.Message,
Data = context.RouteData.Values
};
context.Result = new JsonResult(obj);
logger.LogInformation(context.Exception.Message);
}
}

}
}

Controllers\HomeController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
using Microsoft.AspNetCore.Mvc;
using WebApplication_MVC_AOP.Filters;
using WebApplication_MVC_AOP.Models;
using System.Diagnostics;
using WebApplication_MVC_AOP.Models;

namespace WebApplication_MVC_AOP.Controllers
{
//[CustomerExceptionFilter]
//第一种方式
[TypeFilter(typeof(CustomerExceptionFilterAttribute))]
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly IConfiguration _configuration;

public HomeController(ILogger<HomeController> logger, IConfiguration configuration)
{
_logger = logger;
_configuration = configuration;
}
// 一般要将这个过滤器,添加至容器中
//第二种方式

//builder.Services.AddSingleton<CustomerExceptionFilterAttribute>();
[ServiceFilter(typeof(CustomerExceptionFilterAttribute))]
public IActionResult Index()
{
var test = _configuration["test"];
_logger.LogInformation(test.ToString()); // 故意让其报错
return View();
}

public IActionResult Privacy()
{
return View();
}

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}

//第三种方式
builder.Services.AddControllersWithViews(option =>
{
// 全局校验伪造令牌的过滤器
option.Filters.Add<AutoValidateAntiforgeryTokenAttribute>();
option.Filters.Add<CustomerExceptionFilterAttribute>();
});
资源过滤器
Controllers\ResourceController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using Microsoft.AspNetCore.Mvc;
using WebApplication_MVC_AOP.Filters;
using WebApplication_MVC_AOP.Models;

namespace WebApplication_MVC_AOP.Controllers
{
public class ResourceController : Controller
{
[CustomerResourceFilter]
public IActionResult Index()
{
return View();
}

[HttpPost]
public IActionResult Submit(StudentViewModel viewModel)
{

return Ok(viewModel);
}
}
}
Filters\CustomerResourceFilterAttribute.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace WebApplication_MVC_AOP.Filters
{
public class CustomerResourceFilterAttribute : Attribute, IResourceFilter
{
private readonly static Dictionary<string, IActionResult> dict = new();
public void OnResourceExecuting(ResourceExecutingContext context)
{
var path = context.HttpContext.Request.Path;
if (dict.ContainsKey(path))
{
context.Result = dict[path];
}
}

public void OnResourceExecuted(ResourceExecutedContext context)
{
var path = context.HttpContext.Request.Path;
dict[path] = context.Result;
}


}
}
Views\Resource\Index.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@model StudentViewModel


<form asp-action="Submit" asp-controller="Resource">
<table class="table">
<tr>
<td>
<label asp-for="UserName"></label>
</td>
<td>
<input asp-for="UserName" />
</td>
</tr>
<tr>
<td>
<label asp-for="Email"></label>
</td>
<td>
<input asp-for="Email" />
</td>
</tr>
<tr>
<td>
<label asp-for="Desc"></label>
</td>
<td>
<input asp-for="Desc" />
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="提交" />
</td>
</tr>
</table>
</form>
行为过滤器
Filters\CustomerActionFilterAttribute.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace WebApplication_MVC_AOP.Filters
{
public class CustomerActionFilterAttribute : Attribute, IActionFilter
{
//这是第三步
public void OnActionExecuted(ActionExecutedContext context)
{

}
//这是第一步
public void OnActionExecuting(ActionExecutingContext context)
{
var controller = context.HttpContext.Request.RouteValues["controller"].ToString().ToLower();
var action = context.HttpContext.Request.RouteValues["action"].ToString().ToLower();


// 识别当前环境是手机号,还是电脑端
var userAgent = context.HttpContext.Request.Headers["user-agent"].ToString();

if (userAgent.Contains("Android")|| userAgent.Contains("iPhone") )
{
if (context.HttpContext.Request.Path.ToString().ToLower().Contains($"/mobile/{controller}/{action}"))
{
return;
}
context.Result = new RedirectResult($"/mobile/{controller}/{action}");
}
else
{
if (context.HttpContext.Request.Path.ToString().ToLower().Contains($"/pc/{controller}/{action}"))
{
return;
}
context.Result = new RedirectResult($"/pc/{controller}/{action}");
}
}
}
}

Areas\Mobile\Controllers\ProductController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using Microsoft.AspNetCore.Mvc;
using WebApplication_MVC_AOP.Filters;

namespace WebApplication_MVC_AOP.Areas.Mobile.Controllers
{
[Area("Mobile")]
public class ProductController : Controller
{
[CustomerActionFilter]
//这是第二步
public IActionResult Index()
{
return View();
}
}
}
Areas\Mobile\Views\Product\Index.cshtml
1
2
3
4
5
6

@{
ViewData["Title"] = "Index";
}

<h1>这是手机端</h1>
Areas\Pc\Controllers\ProductController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using Microsoft.AspNetCore.Mvc;
using WebApplication_MVC_AOP.Filters;

namespace WebApplication_MVC_AOP.Areas.Pc.Controllers
{
[Area("Pc")]
public class ProductController : Controller
{
[CustomerActionFilter]
public IActionResult Index()
{
return View();
}
}
}
Areas\Pc\Views\Product\Index.cshtml
1
2
3
4
5
6

@{
ViewData["Title"] = "Index";
}

<h1>这是PC 端</h1>
Program.cs
1
2
3
app.MapControllerRoute(
name: "area",
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
结果过滤器
Filters\CustomerActionResultFilterAttribute.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using Microsoft.AspNetCore.Mvc.Filters;

namespace Step4.Unit9.Filters
{
public class CustomerActionResultFilterAttribute : Attribute, IResultFilter
{
public void OnResultExecuted(ResultExecutedContext context)
{

}

public void OnResultExecuting(ResultExecutingContext context)
{

}
}
}
拦截器的使用
安装包
1
2
Autofac.Extensions.DependencyInjection 5.0.1
Autofac.Extras.DynamicProxy 4.5.0
Program.cs
1
2
3
4
5
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureContainer<ContainerBuilder>(p =>
{
p.RegisterModule<AutofacRegisterModule>();
});
AutofacRegisterModule.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//用于非控制层的
using Autofac;
using Autofac.Extras.DynamicProxy;
using WebApplication_MVC_AOP.Interceptors;
using WebApplication_MVC_AOP.Services;

namespace WebApplication_MVC_AOP
{
public class AutofacRegisterModule:Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<LogAop>();


// 注入服务,并且添加拦截器
builder.RegisterType<StudentService>().As<IStudentService>()
.EnableInterfaceInterceptors().InterceptedBy(typeof(LogAop));

}
}
}
Interceptors\LogAop.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using Castle.DynamicProxy;
using Microsoft.Extensions.Logging;
using System.Reflection;

namespace WebApplication_MVC_AOP.Interceptors
{
/// <summary>
/// 写日志的拦截器
/// </summary>
public class LogAop : IInterceptor
{
private readonly ILogger<LogAop> logger;

public LogAop(ILogger<LogAop> logger)
{
this.logger = logger;
}

// 获取当前正在调用的目标方法
// var method = invocation.Method;
// 获取方法返回值类型,也可以invocation.TargetType获取
// Type t = method.ReturnType
// 方法放行。如果不调用此方法,此目标方法则被拦截,即目标方法不执行
// invocation.Proceed();
// 目标方法返回值,一定要放在invocation.Proceed() 后面才会有值
// invocation.ReturnValue = 0;
// 目标方法的参数
// invocation.Arguments


public void Intercept(IInvocation invocation)
{



// 拦截器的作用:
/**
可以在方法执行前或者执行后,都可以做一些业务逻辑的处理, 而不影响原先的代码
*/
logger.LogInformation("您添加了一行日志");

invocation.Proceed();

}
}
}
Services\IStudentService.cs
1
2
3
4
5
6
7
8
9
using WebApplication_MVC_AOP.Models;

namespace WebApplication_MVC_AOP.Services
{
public interface IStudentService
{
List<StudentViewModel> GetList();
}
}
Services\StudentService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using WebApplication_MVC_AOP.Models;

namespace WebApplication_MVC_AOP.Services
{
public class StudentService : IStudentService
{
public List<StudentViewModel> GetList()
{
List<StudentViewModel> list = new() {
new(){ UserName = "张三", Email="234@qq.com",Desc="测试数据" },
new(){ UserName = "张三", Email="234@qq.com",Desc="测试数据" },
new(){ UserName = "张三", Email="234@qq.com",Desc="测试数据" }
};

return list;
}
}
}
Controllers\HomeController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using Microsoft.AspNetCore.Mvc;
using WebApplication_MVC_AOP.Filters;
using WebApplication_MVC_AOP.Models;
using WebApplication_MVC_AOP.Services;
using System.Diagnostics;
using WebApplication_MVC_AOP.Models;
using WebApplication_MVC_AOP.Services;

namespace WebApplication_MVC_AOP.Controllers
{
//[CustomerExceptionFilter]
//[TypeFilter(typeof(CustomerExceptionFilterAttribute))]
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly IConfiguration _configuration;
private readonly IStudentService studentService;

public HomeController(ILogger<HomeController> logger, IConfiguration configuration
, IStudentService studentService)
{
_logger = logger;
_configuration = configuration;
this.studentService = studentService;
}

// 一般要将这个过滤器,添加至容器中
//[ServiceFilter(typeof(CustomerExceptionFilterAttribute))]
public IActionResult Index()
{
//var test = _configuration["test"];
//_logger.LogInformation(test.ToString()); // 故意让其报错
studentService.GetList();
return View();
}

public IActionResult Privacy()
{
return View();
}

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}
项目发布
IIS 部署
Linux 部署
Linux-docker
Step4.Unit8\Dockerfile
1
2
3
4
5
6
7
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
COPY . /unit8
WORKDIR /unit8
EXPOSE 8888
ENTRYPOINT ["dotnet", "Step4.Unit8.dll","--urls","http://0.0.0.0:8888"]
1
docker run -d -p 8888:8888 --name=unit8 unit8

RESTful API

Ado.net

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
//命名空间: System.Data.SqlClient 
//程序集: System.Data.SqlClient.dll
//System.Configuration.ConfigurationManager

SqlConnection connection = new SqlConnection();
connection.ConnectionString =
"server=.;uid=sa;pwd=123456;database=unit21";
connection.Open();// 打开连接
Console.WriteLine($"State={connection.State}");
Console.WriteLine($"DataBase={connection.Database}");
Console.WriteLine($"ServerVersion={connection.ServerVersion}");
Console.WriteLine($"DataSource={connection.DataSource}");
Console.WriteLine($"ConnectionTimeout={connection.ConnectionTimeout}");
connection.Close();// 一定要记得关闭连接

SqlConnection connection =
new SqlConnection("server=.;uid=sa;pwd=123456;database=unit21");
/*
tate=Open
DataBase=unit21
ServerVersion=14.00.1000
DataSource=.
ConnectionTimeout=15
*/

//修改超时时间
SqlConnection connection =
new SqlConnection("server=.;uid=sa;pwd=123456;database=unit21;timeout=30;");
connection.Open();
Console.WriteLine($"ConnectionTimeout={connection.ConnectionTimeout}");
connection.Close();


//自动释放,实现了IDispose 接口的类,都可以使用using 来实现自动释放资源功能。
/* *
* using : 引用命名空间
* using: 释放资源
*
* 1. 为什么以前我们定义的变量不需要释放资源呢?
* GC: 垃圾回收器,这里面封装了一些算法规定了什么时候去回收资源。我们自己定义一些变量,不
需要关心它有没有释放掉,因为GC会帮我们自动回收资源
*
* 2. 既然已经有了GC,为什么还需要手动的释放资源呢?
* 答:GC 只能回收托管资源(由.Net CLR管理的资源),而SqlConnection是属于非托管资源
*
* 3. 我怎么知道它是非托管?
* 答:如果它实现了IDisposable 接口,那么我们就可以使用using 来手动的释放资源了
*/
using (SqlConnection connection = new
SqlConnection("server=.;uid=sa;pwd=123456;database=unit21;timeout=30"))
{
connection.Open();// 打开连接
Console.WriteLine($"State={connection.State}");
Console.WriteLine($"DataBase={connection.Database}");
Console.WriteLine($"ServerVersion={connection.ServerVersion}");
Console.WriteLine($"DataSource={connection.DataSource}");
Console.WriteLine($"ConnectionTimeout={connection.ConnectionTimeout}");
} // 并未调用close()方法,但会自动关闭

using SqlConnection connection = new
SqlConnection("server=.;uid=sa;pwd=123456;database=unit21;timeout=30");

//Command 是数据库命令对象,它是数据库执行的一个 Transact-SQL 语句或存储过程 。

using SqlCommand cmd = new SqlCommand();
cmd.Connection = connection;
cmd.CommandText = $"insert into product values('枸杞',18)";
cmd.ExecuteNonQuery();// 执行

using SqlCommand cmd = new SqlCommand("select count(id) from product");
cmd.Connection = connection;
object count = cmd.ExecuteScalar();// 获取首行首列

using SqlCommand cmd = new SqlCommand("select * from product",connection);
using SqlDataReader dataReader = cmd.ExecuteReader(); // 返回一个读取对象
while (dataReader.Read())
{
object id = dataReader.GetValue("id");
object pname = dataReader.GetValue("pname");
object price = dataReader.GetValue("price");

// 索引下标顺序必须与数据库列顺序一致
object id = dataReader[0];
object pname = dataReader[1];
object price = dataReader[2];

// 索引下标顺序必须与数据库列顺序一致
Product product = new Product(
dataReader.GetInt32(0),
dataReader.GetString(1),
dataReader.GetInt32(2)
);

Console.WriteLine($"id: {id}\t pname: {pname}\t price: {price}\t \n");
}

record Product(int Id,string Name,int Price);


using SqlDataAdapter adapter = new SqlDataAdapter();
using SqlCommand cmd = new SqlCommand("select * from product");
cmd.Connection = connection;
adapter.SelectCommand = cmd;
DataSet ds = new DataSet();
adapter.Fill(ds); // 填充数据
Console.WriteLine(ds.Tables[0].Rows.Count);


/**
* 为什么要使用List
* 1. List 更符合面向对象编程思想
* 2. row 在操作列名的时候,有可能会将列名写错
* 3. List集合操作速度要比DataTable要快,并且属于类型安全(C#高级中会有专门一个单元,
给大家讲解泛型)
*/
using SqlDataAdapter adapter2 = new SqlDataAdapter("select * from product",
"server=.;uid=sa;pwd=123456;database=unit21");
DataTable dataTable = new DataTable();
adapter2.Fill(dataTable);
List<Product> list = new ();
foreach (DataRow row in dataTable.Rows)
{
Product product = new();
product.Id = Convert.ToInt32(row["id"]);
if (row["pname"] is not DBNull) // 判断非空
{
product.ProductName = row["pname"].ToString();
}
// 数字类型的一定要记得做判断
if (row["price"] is not DBNull)
{
product.Price = Convert.ToDecimal(row["price"]);
}
list.Add(product);
}
Console.WriteLine(list.Count);
public class Product
{
public int Id { get; set; }
public string? ProductName { get; set; }
public decimal Price { get; set; }
}



构建 DataTable

1
2
3
4
5
6
7
8
9
10
11
12
13
DataTable dataTable = new DataTable("商品表");
// 创建表头
dataTable.Columns.Add("id");
dataTable.Columns.Add("productname");
DataColumn column = new DataColumn("price");
dataTable.Columns.Add(column);
//创建行
var dataRow = dataTable.NewRow();
dataRow["id"] = 1;
dataRow["productname"]="口红";
dataRow["price"] = 269;
// 添加到DataTable中
dataTable.Rows.Add(dataRow);

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//添加引用:system.Configuration.ConfigurationManager
//配置文件名称必须叫 App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<add name="connString"
connectionString="server=.;uid=sa;pwd=123456;database=unit21"
providerName="System.Data.SqlClient"></add>
</connectionStrings>
</configuration>

// 读取配置文件
var connectionString =
ConfigurationManager.ConnectionStrings["connString"].ConnectionString;
using SqlDataAdapter adapter = new SqlDataAdapter("select * from
product",connectionString);
DataTable dataTable = new DataTable();
adapter.Fill(dataTable);
Console.WriteLine(dataTable.Rows.Count);

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="siteName" value="任我行码农场"/>
</appSettings>
</configuration>

var siteName = ConfigurationManager.AppSettings["siteName"].ToString();
Console.WriteLine(siteName);

数据连接池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<add name="connString"
connectionString="server=.;uid=sa;pwd=123456;database=unit21;pooling=false;">
</add>
</connectionStrings>
</configuration>

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
// 读取配置文件
var connectionString =
ConfigurationManager.ConnectionStrings["connString"].ConnectionString;
for (int i = 0; i < 10000; i++)
{
using SqlConnection connection = new SqlConnection(connectionString);
connection.Open();
}
stopwatch.Stop();
Console.WriteLine("总耗时(s):"+stopwatch.ElapsedMilliseconds/1000.0);
// 输出结果:总耗时(s):11.897

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<add name="connString"
connectionString="server=.;uid=sa;pwd=123456;database=unit21;pooling=true;">
</add>
</connectionStrings>
</configuration>
// 输出结果:总耗时(s):0.28

参数化查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 读取配置文件
var connectionString =
ConfigurationManager.ConnectionStrings["localString"].ConnectionString;
using SqlConnection conn = new(connectionString);
conn.Open();
// 一定要记得加@符号
using SqlCommand cmd = new SqlCommand("select count(*) from Users where
Account=@account and Pwd = @pwd",conn);
SqlParameter parameter1 = new SqlParameter();
parameter1.ParameterName = "account"; // 参数名,要与 上面的sql语句中的参数名要对应上,@符号可省略
parameter1.Size = 20; // 参数大小
parameter1.SqlDbType = SqlDbType.VarChar; // 类型
parameter1.Value = "zhangsan"; // 参数值
SqlParameter parameter2 = new SqlParameter();
parameter2.ParameterName = "pwd"; // 参数名,要与 上面的sql语句中的参数名要对应上,@符号可省略
parameter2.Size = 20; // 参数大小
parameter2.SqlDbType = SqlDbType.VarChar; // 类型
parameter2.Value = "123"; // 参数值
// 将参数添加至命令对象中
cmd.Parameters.Add(parameter1);
cmd.Parameters.Add(parameter2);
// 执行sql命令
var count = Convert.ToInt32(cmd.ExecuteScalar());
if (count > 0)
{
Console.WriteLine("登录成功");
}
else
{
Console.WriteLine("登录失败,用户名或者密码输入不正确");
}

// 创建参数数组对象
SqlParameter[] parameters =
{
new SqlParameter("nickName","李重文"),
new SqlParameter("account","lichongwen"),
new SqlParameter("pwd","123")
};

//所以当此值为零时,必须将整数值转换为 Object 类型,
Parameter = new SqlParameter("@pname", (object)0);


// 创建参数数组对象
SqlParameter[] parameters =
{
new SqlParameter("nickName",SqlDbType.VarChar,20),
new SqlParameter("account",SqlDbType.VarChar,20),
new SqlParameter("pwd",SqlDbType.VarChar,20),
new SqlParameter("id",SqlDbType.Int,4)
};
parameters[0].Value = "张三丰";
parameters[1].Value = "zhangsanfeng";
parameters[2].Value = "123456";
parameters[3].Value = 1;

存储过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
create proc p_users_insert
(
@nickName varchar(20),
@account varchar(20),
@pwd varchar(20)
) as
begin
insert into Users values(@nickName,@account,@pwd)
end


var connectionString =
ConfigurationManager.ConnectionStrings["connString"].ConnectionString;
using SqlConnection conn = new SqlConnection(connectionString);
conn.Open();
// 这里要写存储过程的名称
using SqlCommand cmd = new SqlCommand("p_users_insert", conn);
cmd.CommandType = CommandType.StoredProcedure;// 指定为存储过程类型
SqlParameter[] parameters =
{
new SqlParameter("nickName","李重文"),
new SqlParameter("account","lichongwen"),
new SqlParameter("pwd","123")
};
// 批量添加 参数对象数组 至 命令对象中
cmd.Parameters.AddRange(parameters);
cmd.ExecuteNonQuery();

输出参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
alter proc p_users_insert
(
@nickName varchar(20),
@account varchar(20),
@pwd varchar(20),
@code int output,
@msg varchar(20) output -- 输出参数
) as
begin
insert into Users values(@nickName,@account,@pwd);
set @msg = '添加成功';
set @code=1;
end


var connectionString =
ConfigurationManager.ConnectionStrings["connString"].ConnectionString;
using SqlConnection conn = new SqlConnection(connectionString);
conn.Open();
// 这里要写存储过程的名称
using SqlCommand cmd = new SqlCommand("p_users_insert", conn);
cmd.CommandType = CommandType.StoredProcedure;// 指定为存储过程类型
SqlParameter[] parameters =
{
new SqlParameter("nickName","孙婷婷"),
new SqlParameter("account","suntingting"),
new SqlParameter("pwd","123"),
new SqlParameter("code",SqlDbType.Int,4), // 不给输出参数设置值
new SqlParameter("msg",SqlDbType.VarChar,20)
};
parameters[3].Direction = ParameterDirection.Output; // 第4个参数设置为输出参数
parameters[4].Direction = ParameterDirection.Output; // 第5个参数设置为输出参数
// 批量添加 参数对象数组 至 命令对象中
cmd.Parameters.AddRange(parameters);
cmd.ExecuteNonQuery();
Console.WriteLine("code的值为:"+ parameters[3].Value); // 获取输出参数返回的值
Console.WriteLine("msg的值为:"+ parameters[4].Value);

Web 项目操作 Ado (下面三个以及这个都需要学习)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//appsetting.json 配置文件:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
// 连接字符串
"SqlServer": "server=.;uid=sa;pwd=123456;database=unit21;pooling=true;"
}
}

//读取连接字符串
public class HomeController : Controller
{
// 注入配置对象(系统在启动时,已经在IOC容器中注册好了)
private readonly IConfiguration _configuration;
public HomeController(IConfiguration configuration)
{
_configuration = configuration;
}
public IActionResult Index()
{
// 读取连接字符串
string connString =
_configuration.GetConnectionString("SqlServer");
return View();
}
}


//操作Ado.net
//读取数据
public class HomeController : Controller
{
// 注入配置对象(系统在启动时,已经在IOC容器中注册好了)
private readonly IConfiguration _configuration;
public HomeController(IConfiguration configuration)
{
_configuration = configuration;
}
public IActionResult Index()
{
// 读取连接字符串
string connString =
_configuration.GetConnectionString("SqlServer");
// 创建参数数组对象
SqlParameter[] parameters =
{
new SqlParameter("nickName",SqlDbType.VarChar),
new SqlParameter("pwd",SqlDbType.VarChar)
};
Console.WriteLine("请输入需要查询的姓名:");
string? nickName = Console.ReadLine();
parameters[0].Value = $"%{nickName}%"; // 模糊查询要加%
parameters[1].Value = "123456";
SqlDataAdapter adapter = new("select * from Users where
NickName like @nickName and Pwd=@pwd",connString);
// 批量添加参数对象
adapter.SelectCommand.Parameters.AddRange(parameters);
DataTable dataTable = new DataTable();
adapter.Fill(dataTable);
Console.WriteLine("\nId\t姓名\t账号 \t \t密码");
Console.WriteLine("--------------------------------------------
----------");
foreach (DataRow row in dataTable.Rows)
{
Console.WriteLine($"{row["id"]}\t{row["nickname"]}\t{row["account"]}\t{row["pwd"]}");
}
return View();
}
}

视频操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;

// 注入配置对象(系统在启动时,已经在IOC容器中注册好了)
private readonly IConfiguration _configuration;

private readonly string _connectionString;

public HomeController(ILogger<HomeController> logger, IConfiguration configuration)
{
_logger = logger;
_configuration = configuration;
_connectionString =_configuration.GetConnectionString("SqlServer");
}

public IActionResult Index()
{
using SqlConnection connection = new(_connectionString);
SqlDataAdapter adapter = new ("select * from Student",connection);
DataTable dataTable = new DataTable();
adapter.Fill(dataTable);
return View(dataTable);
}

public IActionResult Privacy()
{
return View();
}

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}

index.html 操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@model System.Data.DataTable
@using System.Data
@{
ViewData["Title"] = "Home Page";
}

<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

<table class="table table-hover">
<thead>
<tr>
<th>id</th>
<th>名称</th>
<th>学号</th>
<th>性别</th>
<th>账户</th>
<th>密码</th>
</tr>
</thead>
<tbody>
@foreach(DataRow row in Model.Rows)
{
<tr>
<td>@row["id"]</td>
<td>@row["NickName"]</td>
<td>@row["StudentNo"]</td>
<td>@row["Sex"]</td>
<td>@row["Account"]</td>
<td>@row["Password"]</td>
</tr>
}
</tbody>
</table>

封装 DbHelper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/*
1. 读取
2. 写入
3. Count计数
*/

using System.Data;
using System.Data.SqlClient;
namespace WebApplication1;
public class DbHelper
{
private static string? _connString;
// 静态构造方法,
// 第一次访问 这个的类的时候,会执行一次静态构造
static DbHelper()
{
ConfigurationBuilder configuration = new ConfigurationBuilder(); //读取配置文件
var config =
configuration.SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile(file=>
{
file.Path = "/appsettings.json";
file.Optional = false;
file.ReloadOnChange = true;
}).Build();
_connString = config.GetConnectionString("SqlServer");
}
private static SqlCommand PrepareCommand(SqlConnection conn,string
sql,CommandType cmdType, params SqlParameter[]? parameters)
{
using SqlCommand cmd = new SqlCommand(sql, conn);
cmd.CommandType = cmdType;
if (parameters!=null && parameters.Length>0)
{
cmd.Parameters.AddRange(parameters);
}
return cmd;
}
/**
* 执行增删改的操作
*/
public static int ExecuteNonQuery(string sql,CommandType cmdType =
CommandType.Text, params SqlParameter[]? parameters)
{
using SqlConnection conn = new(_connString);
conn.Open();
return PrepareCommand(conn,sql,cmdType,parameters).ExecuteNonQuery();
}
public static int ExecuteScalar(string sql,CommandType cmdType =
CommandType.Text, params SqlParameter[]? parameters)
{
using SqlConnection conn = new(_connString);
conn.Open();
return
Convert.ToInt32(PrepareCommand(conn,sql,cmdType,parameters).ExecuteScalar()
);
}
public static DataTable GetDataTable(string sql,CommandType cmdType =
CommandType.Text, params SqlParameter[]? parameters)
{
using SqlDataAdapter adapter = new(sql, new
SqlConnection(_connString));
adapter.SelectCommand.CommandType = cmdType;
if (parameters!=null && parameters.Length>0)
{
adapter.SelectCommand.Parameters.AddRange(parameters);
}
DataTable dataTable = new DataTable();
adapter.Fill(dataTable);
return dataTable;
}
}

数据访问技术 (ORM)

FreeSQL/SQLSugger

EntityFrameworkCore

ORM 介绍

安装.Net CLI

EFCore 入门

QuackStart\BloggingContext.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace QuackStart
{
/// <summary>
/// 数据库上下文,
/// </summary>
public class BloggingContext:DbContext
{

#region 声明表
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
#endregion


// 连接字符串:.db 文件的完整路径
// 配置连接字符串,日志的输出方式 等等
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite("Data Source=D://blogging.db");
}
}
}

QuackStart\Blog.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace QuackStart
{
public class Blog
{
public int BlogId { get; set; }
public string? Url { get; set; }

public List<Post> Posts { get; } = new();
}
}
QuackStart\Post.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace QuackStart
{
public class Post
{
public int PostId { get; set; }
public string? Title { get; set; }
public string? Content { get; set; }

public int BlogId { get; set; }
public Blog? Blog { get; set; }
}
}
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
namespace QuackStart
{
internal class Program
{
static void Main(string[] args)
{
//Console.WriteLine("Hello, World!");

BloggingContext context = new BloggingContext();
/// 增加一条数据
Blog blog = new() { Url="http://baidu.com"};


context.Blogs.Add(blog); // insert into
// Blog blog2 = blog;
context.SaveChanges(); // 执行,提交

// 查询:select * from Blog limit 1
var first = context.Blogs.First();//select * from Blog
Console.WriteLine(first);

// 更新
//first.Url = "http://renwoxing.com"; // update blog set url="http://renwoxing.com" where id=0
//context.SaveChanges();

// 查询:select * from Blog

//不要跨线程,异步操作

// 删除
//context.Blogs.Remove(first); // delete from Blog where BlogId=0;
//await context.SaveChangesAsync();

//var list = context.Blogs.ToList();
//Console.WriteLine(list.Count);

//await context.DisposeAsync();// 释放上下文 1073
}
}
}
安装包
1
2
microsoft.entityframeworkcore.tools 9.0.1
microsoft.entityframeworkcore.design 9.0.1
DbContext 在 mvc 运行
安装包
1
2
microsoft.entityframeworkcore.design 9.0.1
microsoft.entityframeworkcore.sqlserver 9.0.1
appsettings.json
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"SQL": "server=.;uid=sa;pwd=123456;database=blogging;Encrypt=False",
"MySQL": "server=.;uid=sa;pwd=123456;database=blogging;Encrypt=False"
}
}
Program.cs
1
2
3
4
builder.Services.AddDbContext<BlogContext>(p =>
{
p.UseSqlServer(builder.Configuration.GetConnectionString("SQL"));
});
BlogContext.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using Microsoft.EntityFrameworkCore;
using EFCore_mvc.Data;

namespace EFCore_mvc
{
public class BlogContext:DbContext
{
public BlogContext(DbContextOptions<BlogContext> options) : base(options)
{

}

public DbSet<Blog> Blogs { get; set; }

public DbSet<Post> Posts { get; set; }
}
}
Data\Blog.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFCore_mvc.Data
{
public class Blog
{
public int BlogId { get; set; }
public string? Url { get; set; }

public List<Post> Posts { get; } = new();
}
}
Data\Post.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFCore_mvc.Data
{
public class Post
{
public int PostId { get; set; }
public string? Title { get; set; }
public string? Content { get; set; }

public int BlogId { get; set; }
public Blog? Blog { get; set; }
}
}
Controllers\HomeController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
using System.Diagnostics;
using EFCore_mvc.Data;
using EFCore_mvc.Models;
using Microsoft.AspNetCore.Mvc;

namespace EFCore_mvc.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly BlogContext _context;
public HomeController(ILogger<HomeController> logger, BlogContext context)
{
_logger = logger;
_context = context;
}

public IActionResult Index()
{
//推荐用异步方法
_context.Blogs.Add(new Blog() { Url = "http://baidu.com" });
_context.SaveChanges();
return View();
}

public IActionResult Privacy()
{
return View();
}

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}
DbContext 几种创建方式(总共三种,还有之前那一种方式)
第一种方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using System.Diagnostics;
using EFCore_mvc.Data;
using EFCore_mvc.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace EFCore_mvc.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly IConfiguration configuration;
public HomeController(ILogger<HomeController> logger, IConfiguration configuration)
{
_logger = logger;
this.configuration = configuration;
}

public IActionResult Index()
{
DbContextOptionsBuilder<BlogContext> builder = new DbContextOptionsBuilder<BlogContext>();
builder.UseSqlServer(configuration.GetConnectionString("SQL"));
using BlogContext context = new BlogContext(builder.Options);


context.Blogs.Add(new Blog() { Url = "http://sina.com" });
context.SaveChanges();
return View();
}

public IActionResult Privacy()
{
return View();
}

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}

//builder.Services.AddDbContextFactory<BlogContext>(p =>
//{
// p.UseSqlServer(builder.Configuration.GetConnectionString("SQL"));
//});

第二种方式
HomeController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using System.Diagnostics;
using EFCore_mvc.Data;
using EFCore_mvc.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace EFCore_mvc.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
//private readonly BlogContext _context;
private readonly IConfiguration configuration;
//public HomeController(ILogger<HomeController> logger, BlogContext context)
//{
public HomeController(ILogger<HomeController> logger, IConfiguration configuration)
{
_logger = logger;
this.configuration = configuration;
//_context = context;
}

public IActionResult Index()
{
BlogContext blogContext = new BlogContext();
blogContext.Blogs.Add(new Blog() { Url = "http://jd.com" });
blogContext.SaveChanges();
return View();
}

public IActionResult Privacy()
{
return View();
}

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}

BlogContext.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using Microsoft.EntityFrameworkCore;
using EFCore_mvc.Data;

namespace EFCore_mvc
{
public class BlogContext:DbContext
{
//public BlogContext(DbContextOptions<BlogContext> options) : base(options)
//{

//}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("server=.;uid=sa;pwd=123456;database=blogging;Encrypt=False");
}
public DbSet<Blog> Blogs { get; set; }

public DbSet<Post> Posts { get; set; }
}
}
DbContext 工厂
Program.cs
1
2
3
4
5
// 工厂方式创建数据库上下文
builder.Services.AddDbContextFactory<BlogContext>(p =>
{
p.UseSqlServer(builder.Configuration.GetConnectionString("SQL"));
});
Views\FactoryContext\Index.cshtml
1
2
3
4
5
6
7
@*
For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
*@
@{
}
<h1>Factory 工厂</h1>

Controllers\FactoryContextController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;

namespace EFCore_mvc.Controllers
{



public class FactoryContextController : Controller
{
// 注入DbContext 工厂
private readonly IDbContextFactory<BlogContext> blogContextFactory;

public FactoryContextController(IDbContextFactory<BlogContext> blogContext)
{
this.blogContextFactory = blogContext;
}

public IActionResult Index()
{
// 创建上下文
using var context = blogContextFactory.CreateDbContext();

context.Blogs.Add(new Data.Blog() { Url="http://tmaill.com"});
context.SaveChanges();

return View();
}
}
}
一个项目中操作多种数据库 (有点问题)
安装包
1
dotnet add package Pomelo.EntityFrameworkCore.MySql --version 8.0.2
Program.cs
1
2
3
4
5
6
7
8
9
builder.Services.AddDbContext<BlogContext>(p =>
{
p.UseSqlServer(builder.Configuration.GetConnectionString("SQL"));
});
// 再注入一个MySql的数据库上下文
builder.Services.AddDbContext<MySqlBlogContext>(p =>
{
p.UseMySql(builder.Configuration.GetConnectionString("MySQL"), new MySqlServerVersion("5.7");
});
MySqlBlogContext.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using Microsoft.EntityFrameworkCore;
using EFCore_mvc.Data;

namespace EFCore_mvc
{
public sealed class MySqlBlogContext:DbContext
{
public MySqlBlogContext(DbContextOptions<MySqlBlogContext> options) : base(options)
{

}

public DbSet<Blog> Blogs { get; set; }

public DbSet<Post> Posts { get; set; }

public DbSet<Author> Author { get; set; }


// 优先级最高(如果使用了连接池,则不可重复修改连接字符串)
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySql("server=.;uid=root;pwd=123456;database=blog2", new MySqlServerVersion("5.7"));
}
}
}
Data\Author.cs
1
2
3
4
5
6
7
8
namespace EFCore_mvc.Data
{
public class Author
{
public int AuthorId { get; set; }
public string Name { get; set; } = null!;
}
}
DbContext 连接池
Program.cs
1
2
3
4
5
// 使用连接池操作上下文,默认为 128。
builder.Services.AddDbContextPool<MySqlBlogContext>(p =>
{
p.UseMySql(builder.Configuration.GetConnectionString("MySQL"), new MySqlServerVersion("5.7"));
});
Controllers\ContextPoolController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore;

namespace EFCore_mvc.Controllers
{
public class ContextPoolController : Controller
{
private readonly MySqlBlogContext _context;
public ContextPoolController(MySqlBlogContext context)
{
_context = context;
}

public async Task<IActionResult> Index()
{
await _context.Blogs.AddAsync(new Data.Blog()
{
Url = "http://renwoxing.com"
});
await _context.SaveChangesAsync();


// var options = new DbContextOptionsBuilder<MySqlBlogContext>()
//.UseMySql(@"server=127.0.0.1;uid=root;pwd=123456;database=blogging",new MySqlServerVersion("5.7"))
//.Options;

// var factory = new PooledDbContextFactory<MySqlBlogContext>(options);

// using (var context = factory.CreateDbContext())
// {
// var list = context.Blogs.ToList();
// Console.WriteLine(list.Count);
// }


return View();
}
}
}

FluentApi 更新表名

Data\Post.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFCore_FluentApi.Data
{
public class Post
{
public int PostId { get; set; }
public string Title { get; set; } = null!;
public string Content { get; set; } = null!;

public int BlogId { get; set; } // 默认生效的外键名:BlogEntityBlogId,BlogEntityId,BlogBlogId,BlogId

public Blog? Blog { get; set; }

}
}
Data\Blog.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFCore_FluentApi.Data
{
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; } = null!;

public List<Post> Posts { get; } = new();
}
}

MySqlContext.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using EFCore_FluentApi.Data;

namespace EFCore_FluentApi
{
public class MySqlContext:DbContext
{
// 这里不要写构造函数

public DbSet<Blog> Blogs { get; set; }

public DbSet<Post> Posts { get; set; }

public DbSet<Author> Author { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySql("server=127.0.0.1;uid=root;pwd=123456;database=unit3.blog",new MySqlServerVersion("5.7"));
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().ToTable("unit3_blog");
modelBuilder.Entity<Post>().ToTable("unit3_post");
modelBuilder.Entity<Author>().ToTable("unit3_author");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().ToTable("unit3_blog");
modelBuilder.Entity<Post>().ToTable("unit3_post");
modelBuilder.Entity<Author>().ToTable("unit3_author");
}
}
}
1
dotnet -ef migrations add updatetable -c mysqlcontext
SqlContext.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using EFCore_FluentApi.Data;

namespace EFCore_FluentApi
{
public class SqlContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }

public DbSet<Post> Posts { get; set; }

public DbSet<Author> Author { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("server=127.0.0.1;uid=sa;pwd=123456;database=unit3.blog");
}
}
}

FluentApi 更新 Schema

1
2
3
4
5
6
7
    protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().ToTable("unit3_blog", "renwoxing");
modelBuilder.Entity<Post>().ToTable("unit3_post", "renwoxing");
modelBuilder.Entity<Author>().ToTable("unit3_author", "renwoxing");
}
dotnet -ef migrations add schema -c mysqlcontext

FluentApi 表注释

1
modelBuilder.Entity<Blog>().ToTable("unit3_blog").HasComment("博客")

FluentApi 排除表迁移

1
2
// 排除迁移
modelBuilder.Ignore<Post>();

FluentApi 属性配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
modelBuilder.Entity<Blog>().ToTable("unit3_blog").HasComment("博客")
.Property(p => p.Url)
.HasColumnName("blog_url")
.HasComment("博客地址")
;

// mysql,字符串默认的类型是varchar(max)
modelBuilder.Entity<Post>().ToTable("unit3_post").HasComment("文章")
.Property(p => p.Content)
.HasColumnName("post_content")
.HasComment("文章内容")
;
modelBuilder.Entity<Post>().Property(p => p.Title)
.HasColumnName("post_title")
.HasComment("文章标题")
;


modelBuilder.Entity<Blog>().Property(p => p.Url)
//.IsRequired()// 设置为非空
.HasColumnType("varchar(50)")
.HasComment("博客地址");// 字符串默认的类型是nvarchar(max)


modelBuilder.Entity<Post>().ToTable("Post").HasComment("文章");
modelBuilder.Entity<Post>()
.Property(p => p.Content)
.HasColumnName("post_content")
.HasColumnType("varchar(3000)")
.HasComment("文章内容")
//.IsRequired()
;
modelBuilder.Entity<Post>().Property(p => p.Title)
.HasColumnName("post_title")
.HasColumnType("varchar(50)")
//.IsRequired()
.HasComment("文章标题");

public string Url { get; set; } = null!;

FluentApi 分组配置

1
2
3
4
5
6
7
8
9
10
11
12
13
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
#region 一张一张表的添加方式
// 一张一张表的添加方式
new BlogEntityConfig().Configure(modelBuilder.Entity<Blog>());
new PostEntityConfig().Configure(modelBuilder.Entity<Post>());
#endregion


// 批量添加实体配置
modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
modelBuilder.ApplyConfigurationsFromAssembly(typeof(BlogEntityConfig).Assembly);
}
EntityConfig\BlogEntityConfig.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Unit3.ModelCreating.Data;

namespace Unit3.ModelCreating.EntityConfig
{
public class BlogEntityConfig : IEntityTypeConfiguration<Blog>
{
public void Configure(EntityTypeBuilder<Blog> builder)
{
// 设置表名
builder.ToTable("unit3_blog");


//配置属性
builder.Property(p => p.Url) // 默认的类型就是varchar
//.IsRequired()
.HasComment("文章地址")
.HasColumnName("blog_url")
.HasMaxLength(100);



builder.Property(p => p.CreatedTime)
.HasComment("添加时间")
.HasColumnName("blog_created_time")
.HasMaxLength(100)
.HasDefaultValue(DateTime.Now)
//.HasDefaultValueSql("getdate()")
;



builder.Property(p => p.UpdatedTime)
//.HasDefaultValue(DateTime.Now) // sqlserver 下需要加的
.ValueGeneratedOnAddOrUpdate();// 也会给一个默认值datetime.now
}
}
}

FluentApi 主键设置

EntityConfig\AuthorEntityConfig.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Unit3.ModelCreating.Data;

namespace Unit3.ModelCreating.EntityConfig
{
// 1. 主键约束,2.外键约束,3.非空约束,4 默认值约束 5. 唯一约束

public class AuthorEntityConfig : IEntityTypeConfiguration<Author>
{

public void Configure(EntityTypeBuilder<Author> builder)
{
builder.ToTable("unit_author").HasComment("作者");

builder.HasKey(p => p.AuthorIdKey).HasName("PrimaryKey_AuthorIdKey"); // 默认名:pk_属性名


builder.Property(p => p.Name)
.HasMaxLength(30)
.HasComment("作者名称");
}
}
}

FluentApi 默认值操作 / 值自动更新

EntityConfig\TagEntityConfig.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Unit3.ModelCreating.Data;

namespace Unit3.ModelCreating.EntityConfig
{
public class TagEntityConfig : IEntityTypeConfiguration<Tag>
{
public void Configure(EntityTypeBuilder<Tag> builder)
{
builder.ToTable("tag").HasComment("标签");
// 取消自增
builder.Property(p => p.TagId).ValueGeneratedNever();
}
}
}

builder.Property(p => p.CreatedTime)
.HasComment("添加时间")
.HasColumnName("blog_created_time")
.HasMaxLength(100)
.HasDefaultValue(DateTime.Now)
//.HasDefaultValueSql("getdate()")
;



builder.Property(p => p.UpdatedTime)
//.HasDefaultValue(DateTime.Now) // sqlserver 下需要加的
.ValueGeneratedOnAddOrUpdate();// 也会给一个默认值datetime.now

FluentApi 值自动更新

1

保存数据

增删改查
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
[Test]
public void Test()
{
//单个添加
Role role = new Role(){
RoleId = 1,
RoleName = "cat"
};
using var context = new EfCoreDemoContext();
context.Add(role);

//批量添加
List<Role> roles = new List<Role>(){
new Role(){
RoleId = 1,
RoleName = "cat"
},
new Role(){
RoleId = 2,
RoleName = "dog"
}
};
context.AddRange(roles);

//推荐修改
using var context = new EfCoreDemoContext();
//跟踪跟新,可以只修改部分字段
var role = context.Roles.FirstOrDefault(p => p.RoleId == 1);
role.RoleName = "金蝉1";
role.PassportId = 10;
//批量修改
var roles = context.Roles.Where(p => p.RoleId>0).toList();
foreach(var role in roles){
role.RoleName = "金蝉1";
role.PassportId = 10;
}

//不推荐修改
using var context = new EfCoreDemoContext();
var role = new Role()
{
RoleId = 1,
RoleName = "亚瑟",
PassportId = 1,
Grade = 1,
CreateTime = DateTime.Now
};
//强制更新,需要全部的字段,如果没有赋值,则会为空
context.Entry(role).State = EntityState.Modified;

//删除
using var context = new EfCoreDemoContext();
// 从数据库中查询出需要的删除的对象
var role = context.Roles.FirstOrDefault(p => p.RoleId == 1);
//context.Remove(role);
// 如果传的是一个集合,那就是批量删除,
// 如果是一个实体对象,那就是单删
context.RemoveRange(role);

context.SaveChanges();
}
SaveChanges 用法

多个操作的时候可以写一个,因为是事务,但是如果其中有一个有错误,所有的都会出现回滚都不执行.

保存相关数据(包含其他属性)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
//保存带有导航属性
[Test]
public void TestIncludeNavigate()
{
//可以不需要外键,自寻查找
var role = new Role()
{
//RoleId = 1,
RoleName = "八戒",
PassportId = 3,
Grade = 3,
CreateTime = DateTime.Now,
Bills = new List<Bill>()
{
new Bill()
{
CashType="银行转账",
Fee=400,
PassportId=3,
PayTime = DateTime.Now,
//RoleId=1 // 没有外键,且可以不用赋值,会自动寻找主体主键的值
}
}
};

using var context = new EfCoreDemoContext();
context.Roles.Add(role);

context.SaveChanges();
}

//更改外键的值
[Test]
public void Test()
{
// 如果这个实体找不到,则会被添加进来,如果存在,则会更新现有的实体
var role = new Role()
{
RoleId = 2,
RoleName = "悟空-2",
PassportId = 3,
Grade = 3,
CreateTime = DateTime.Now
};

using var context = new EfCoreDemoContext();

var bill = context.Bills.Include(p => p.Role).FirstOrDefault();
bill.Role = role; // 没有外键也可以更新



//bill.RoleId = 999999;

context.SaveChanges();
}

//删除关系
/// <summary>
/// 删除关系
/// </summary>
[Test]
public void TestRemoveNavigate()
{
using var context = new EfCoreDemoContext();
var bill = context.Bills.Include(p => p.Role).FirstOrDefault();

context.Roles.Remove(bill.Role);

context.SaveChanges();
}
并发问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
//乐观并发,是指额外加上一个字段byte[] version,并且加上特性[Timestamp],通过值1,2来发现是否有并发情况。
//悲观并发是指加上锁来实现的


// 并发问题:
// 1. 要添加一个byte[] version,对应mysql/sqlserver中的类型是timestamp(时间戳),数据迁移添加方式
// version 不能为空
[Timestamp]
public byte[] Version { get; set; } = null!;


[Test]
public void TestVersion()
{
using var context = new EfCoreDemoContext();
var role = context.Roles.FirstOrDefault();
role.RoleName = "嫦娥";


context.SaveChanges();
}

//解决并发
[Test]
// 解决并发
public void TestResove()
{
using var context = new EfCoreDemoContext();
var role = context.Roles.FirstOrDefault();
role.RoleName = "齐天大圣";

var roleName = "齐天大圣-并发";
// 不需要SaveChange() 就已经会被更新
context.Database.ExecuteSqlInterpolated($"update role set role_name={roleName} where role_id=1");

var saved = false;
while (!saved)
{
try
{
// 尝试将更改保存到数据库
context.SaveChanges();
saved = true;
}
catch (DbUpdateConcurrencyException ex)
{
// 遍历所有冲突实体
foreach (var entry in ex.Entries)
{
// 筛选特定实体类型
if (entry.Entity is Role)
{
//获取当前上下文中的值(用户尝试提交的值)
var proposedValues = entry.CurrentValues;
//从数据库获取最新值(其他操作已修改的值)
var databaseValues = entry.GetDatabaseValues();
// 遍历所有属性处理冲突
foreach (var property in proposedValues.Properties)
{
var proposedValue = proposedValues[property];// 用户想保存的值
var databaseValue = databaseValues[property];// 数据库当前实际值

// TODO: 决定哪个值应该写入数据库
proposedValues[property] = proposedValue;
}

// 刷新原始值以绕过下一次并发检查
entry.OriginalValues.SetValues(databaseValues);
}
else
{
throw new NotSupportedException(
"不知道如何处理并发冲突 "
+ entry.Metadata.Name);
}
}
}
}
}

/// <summary>
/// 如果先有的数据库,后来手动添加Version?怎么办?
/// 2. update passport set version = CURRENT_TIMESTAMP(6);//在数据库中操作
/// 3. 将version设置为not null
///
/// </summary>
[Test]
public void TestRevove()
{
using var context = new EfCoreDemoContext();
var passport = context.Passports.FirstOrDefault();
passport.UserName = "编程任我行";

context.SaveChanges();
}

//GUID 属性配置为并发令牌
public class Person
{
public int PersonId { get; set; }
public string FirstName { get; set; }
[ConcurrencyCheck] // mysql-varchar(32), sqlserver-unitidentifier
public Guid Version { get; set; }
}
事务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
[Test]
public void TestTran()
{

using var context = new EfCoreDemoContext();
// 创建事务
using var tran = context.Database.BeginTransaction();

try
{
var role = context.Roles.FirstOrDefault();
role.RoleName = "吕布";
// 有Transaction情况下,此方法不会保存数据
context.SaveChanges(); // 默认情况下,SaveChange 自带事务


var passport = context.Passports.FirstOrDefault();
passport.UserName = "lvbu";
context.SaveChanges();

tran.Commit();
}
catch (Exception ex)
{
tran.Rollback();
Console.WriteLine(ex.Message);
}
}


/// <summary>
/// 共享连接与事务
/// </summary>
[Test]
public void TestShareConnection()
{
// ① 创建 MySQL 连接(未主动打开)
using var conn = new MySqlConnection("server=127.0.0.1;uid=root;pwd=123456;database=efcore_demo");

// ② 创建第一个 DbContext,注入共享连接
using var context = new EfCoreDemoContext(conn);

// ③ 开始事务(隔离级别为 Read Committed)
using var tran = context.Database.BeginTransaction(System.Data.IsolationLevel.ReadCommitted);

try
{
// ④ 查询并修改第一个实体
var role = context.Roles.FirstOrDefault();
role.RoleName = "貂蝉";
context.SaveChanges(); // ⑤ 第一次保存(在事务内)

// ⑥ 创建第二个 DbContext,共享同一连接
using var context1 = new EfCoreDemoContext(conn);
// ⑦ 共享第一个上下文的事务
context1.Database.UseTransaction(tran.GetDbTransaction());

// ⑧ 修改第二个实体
var passport = context1.Passports.FirstOrDefault();
passport.UserName = "diaochan";
context1.SaveChanges(); // ⑨ 第二次保存

tran.Commit(); // ⑩ 提交事务
}
catch (Exception ex)
{
tran.Rollback(); // ⑪ 异常时回滚
// 实际测试中应记录或断言异常
}
}

[Test]
public void TestTranScope()
{
try
{
// ① 创建 TransactionScope(隐式开启分布式事务)
using var tranScope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }
);

// ② 第一个 DbContext 操作
using var context = new EfCoreDemoContext();
var role = context.Roles.FirstOrDefault();
role.RoleName = "貂蝉-2";
context.SaveChanges(); // ③ 第一次保存

// ④ 第二个 DbContext 操作
using var context1 = new EfCoreDemoContext();
var passport = context1.Passports.FirstOrDefault();
passport.UserName = "diaochan-1..."; // 超长字符串
context1.SaveChanges(); // ⑤ 第二次保存

tranScope.Complete(); // ⑥ 标记事务完成
}
catch (Exception ex)
{
// ⑦ 事务自动回滚,此处处理异常
Console.WriteLine(ex.Message);
}
}

Dapper

NHibernate

版本控制系统

Git

背景

集中式

​ SVN:集中式版本控制系统,与 Git 相比,不支持本地化开发,但故障恢复速度较快,可以像 Git

一样实现版本回退。

​ 集中式版本控制系统,版本库是集中存放在中央服务器的,而干活的时候,用的都是自己的电脑,

所以要先从中央服务器取得最新的版本,然后开始干活,干完活了,再把自己的活推送给中央服务器。

中央服务器就好比是一个图书馆,你要改一本书,必须先从图书馆借出来,然后回到家自己改,改完

了,再放回图书馆。

分布式

​ Git:现在应用最为广泛的分布式版本控制系统,使用广泛,由于分布式的特点,支持本地化开发

和离线工作。

​ 分布式版本控制系统根本没有 “中央服务器”,每个人的电脑上都是一个完整的版本库,这样,你工作的时候,就不需要联网了,因为版本库就在你自己的电脑上。既然每个人电脑上都有一个完整的版本库

操作

Git 环境配置

基本操作
常用指令配置别名(可选)
获取本地仓库
基本操作指令
工作区(Working Directory

git_test 就是所谓的工作区

版本库(Repository

​ 工作区有一个隐藏目录 .git ,这个不算工作区,而是 Git 的版本库

​ Git 的版本库里存了很多东西,其中最重要的就是称为 stage(或者叫 index)的暂存区,还有 Git 为我们

自动创建的第一个分支 master ,以及指向 master 的一个指针叫 HEAD

查看提交日志 **(log)**

format ,可以定制记录的显示格式

版本回退 **/** 版本穿梭

文件回退

1
2
3
4
## 删除文件(如果没有commit的话,是可以回退回来的,因为当前文件并未从本地版本库移除)
git rm 文件名
## 文件恢复
git checkout head 文件名

仓库版本回退

作用:版本切换

命令形式:git reset --hard commitID(版本 id)

commitID 可以使用 git-log 或 git log 指令查看

git reflog

添加文件至忽略列表

​ 一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。 通常都是些自动

生成的文件,比如日志文件,或者编译过程中创建的临时文件等。 在这种情况下,我们可以在工作目录

中创建一个名为 .gitignore 的文件(文件名称固定),列出要忽略的文件模式。下面是一个示例(.net

常用的):

.Net 忽略文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# Build and Object Folders
bin/
obj/
# Nuget packages directory
packages/
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Rr]elease/
x64/
誉尚学教育
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.log
*.vspscc
*.vssscc
.builds
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*
# NCrunch
*.ncrunch*
.*crunch*.local.xml
# Installshield output folder
[Ee]xpress
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help
UpgradeLog*.XML
# Lightswitch
_Pvt_Extensions
GeneratedArtifacts
*.xap
ModelManifest.xml
# Backup file
*.bak
*.dll

Java 忽略文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# eclipse
*.pydevproject
.project
.metadata
bin/**
tmp/**
tmp/**/*
*.tmp
*.swp
*~.nib
local.properties
.classpath
.settings/
.loadpath
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# CDT-specific
.cproject
# PDT-specific
.buildpath
.factorypath
### IntelliJ IDEA ###
*.iml
*.ipr
*.iws
.idea
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
### NetBeans ###
nbproject/private/
build/
nbbuild/
dist/
nbdist/
.nb-gradle/
# java
*.class
# Package Files #
*.war
*.ear
#maven
target/
#gradle
.gradle
build/
gradle/
out/
!gradle/wrapper/gradle-wrapper.jar
.DS_Store
.log
#rebel
rebel.xml
log/
*.log
练习 **😗* 基础操作
1
2
3
git reset --hard commitID 版本库回退
git log # 查看日志
git-log # 以精简的方式显示提交记录
分支
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#查看本地分支
git branch
#创建本地分支
git branch dev
git checkout -b child #创建不存在并且切换到该分支
#切换分支(checkout)
git checkout dev
#合并分支(merge)
git merge xxx #是将xxx下面的文件合并到当前分支下面
#文件创建后添加并提交才会与当前分支有关联


#删除分支
git branch -d xxx

git branch -D xxx #不做任何检查,直接删除

解决冲突(难点)
合并提交

​ 在实际开发中,可能一个功能有会 N 次提交记录,甚至有时候给每次提交取名称都变成一件脑壳疼的问

题,并且这种 “重复性” 的提交如果大家都推送至远端仓库,会导致远端仓库的提交记录密密麻麻,不易

梳理。其实我们可以把这些 “重复性” 的提交合并为一次提交记录再推送至远端仓库

** 语法:****git rebase -i HEAD~** 最近几次提交次数

1
2
3
4
git rebase -i HEAD~3
#需要先git add. 然后git commit -m "xxx"
#然后进行 git rebase --continue
#之后git-log查看就可以看到了

里面的提示有:

pick:保留该 commit(缩写:p)

reword:保留该 commit,但我需要修改该 commit 的注释(缩写:r)

edit:保留该 commit, 但我要停下来修改该提交 (不仅仅修改注释)(缩写:e)

squash:将该 commit 和前一个 commit 合并(缩写:s)

fixup:将该 commit 和前一个 commit 合并,但我不要保留该提交的注释信息(缩写:f)

exec:执行 shell 命令(缩写:x)

drop:我要丢弃该 commit(缩写:d)

冲突解决

在 git rebase 过程中,可能会存在冲突,此时就需要解决冲突。

错误提示信息: git rebase -i resumeerror: could not apply … 。

1
2
3
4
5
6
# 查看冲突
$ git status
# 解决冲突之后,本地提交
$ git add .
# rebase 继续
$ git rebase --continue
开发中分支使用原则与流程

​ 几乎所有的版本控制系统都以某种形式支持分支。 使用分支意味着你可以把你的工作从开发主线上分离

开来进行重大的 Bug 修改、开发新的功能,以免影响开发主线。

在开发中,一般有如下分支使用原则与流程:

master (生产) 分支

​ 线上分支,主分支,中小规模项目作为线上运行的应用对应的分支;

develop**(开发)分支 **

​ 是从 master 创建的分支,一般作为开发部门的主要开发分支,如果没有其他并行开发不同期上线

要求,都可以在此版本进行开发,阶段开发完成后,需要是合并到 master 分支,准备上线。

feature/xxxx 分支

​ 从 develop 创建的分支,一般是同期并行开发,但不同期上线时创建的分支,分支上的研发任务完成后

合并到 develop 分支。

hotfix/xxxx 分支

​ 从 master 派生的分支,一般作为线上 bug 修复使用,修复完成后需要合并到 master、test、

develop 分支。

还有一些其他分支,在此不再详述,例如 test 分支(用于代码测试)、pre 分支(预上线分支)等等。

练习 **😗* 分支操作

git init 后需要提交一次后才能创建分支

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
###########################创建并切换到dev01分支,在dev01分支提交
# [master]创建分支dev01
git branch dev01
# [master]切换到dev01
git checkout dev01
# [dev01]创建文件file02.txt

# [dev01]将修改加入暂存区并提交到仓库,提交记录内容为:add file02 on dev
git add .
git commit -m 'add file02 on dev'
# [dev01]以精简的方式显示提交记录
git-log
###########################切换到master分支,将dev01合并到master分支
# [dev01]切换到master分支
git checkout master
# [master]合并dev01到master分支
git merge dev01
# [master]以精简的方式显示提交记录
git-log
# [master]查看文件变化(目录下也出现了file02.txt)

##########################删除dev01分支
# [master]删除dev01分支
git branch -d dev01
# [master]以精简的方式显示提交记录
git-log

Git**** 远程仓库

如何搭建 git 远程仓库

​ 比较常用的有 GitHub、码云、GitLab 等。

​ gitHub( 地址:https://github.com/ )是一个面向开源及私有软件项目的托管平台,因为只支持 Git 作

为唯一的版本库格式进行托管,故名 gitHub

​ 码云(地址: https://gitee.com/ )是国内的一个代码托管平台,由于服务器在国内,所以相比于

GitHub,码云速度会更快

​ GitLab (地址: https://about.gitlab.com/ )是一个用于仓库管理系统的开源项目,使用 Git 作 为代码

管理工具,并在此基础上搭建起来的 web 服务,一般用于在企业、学校等内部网络搭建 git 私服。

配置 SSH 公钥

作用:用于验证操作者的身份信息(相当于用户登录)。

操作远程仓库
推送到远程仓库

命令:git push [-f] [–set-upstream] [远端名称 [本地分支名][: 远端分支名] ]

如果远程分支名和本地分支名称相同,则可以只写本地分支

git push origin master

n -f 表示强制覆盖

n --set-upstream 推送到远端的同时并且建立起和远端分支的关联关系。

git push --set-upstream origin master:dev

注意:在 ****push 之前 一定要先 pull (拉取), 获取最新代码之后,解决冲突后(如果有冲突的话),才能 push 成功!!!

如果当前分支已经和远端分支关联,则可以省略分支名和远端名。

git push 将 master 分支推送到已关联的远端分支。

本地分支与远程分支的关联关系

查看关联关系我们可以使用 git branch -vv 命令

从远程仓库克隆

​ 如果已经有一个远端仓库,我们可以直接 clone 到本地。

​ 命令: git clone <仓库路径> [本地目录]

​ 本地目录可以省略,会自动生成一个目录

从远程仓库中抓取和拉取

​ 远程分支和本地的分支一样,我们可以进行 merge 操作,只是需要先把远端仓库里的更新都下载到本

地,再进行操作。

抓取 命令:git fetch [remote name] [branch name]

Ø 抓取指令就是将仓库里的更新都抓取到本地,不会进行合并

Ø 如果不指定远端名称和分支名,则抓取所有分支。

Ø 拉取 命令:git pull [remote name] [branch name]

Ø git pull --rebase origin master(用在合并代码的时候其作用就是在一个随机创建的分支上处理冲

突,避免了直接污染原来的分区

例如:git pull origin dev

Ø 拉取指令就是将远端仓库的修改拉到本地并自动进行合并,等同于 fetch+merge

Ø 如果不指定远端名称和分支名,则抓取所有并更新当前分支。

练习 **😗* 远程仓库操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
git remote -v
git remote
git remote set-url origin ssh地址
git clone xxx 目录
git pull origin master
git push origin master
git push --set-upstream origin master #将master分支推送到远程仓库,并与远程仓库的master分支绑定关联关系

#允许合并不相关的历史:
#使用 --allow-unrelated-histories 选项来执行 git pull 命令。打开你的终端,输入以下命令
git config --global credential.helper cache




##########################1-将本地仓库推送到远程仓库
# 完成4.1、4.2、4.3、4.4的操作

# [git_test01]添加远程仓库
git remote add origin git@gitee.com/**/**.git
# [git_test01]将master分支推送到远程仓库,并与远程仓库的master分支绑定关联关系
git push --set-upstream origin master
###########################2-将远程仓库克隆到本地
# 将远程仓库克隆到本地git_test02目录下
git clone git@gitee.com/**/**.git git_test02
# [git_test02]以精简的方式显示提交记录
git-log
###########################3-将本地修改推送到远程仓库
# [git_test01]创建文件file03.txt

# [git_test01]将修改加入暂存区并提交到仓库,提交记录内容为:add file03
git add .
git commit -m 'add file03'
# [git_test01]将master分支的修改推送到远程仓库
git push origin master
###########################4-将远程仓库的修改更新到本地
# [git_test02]将远程仓库修改再拉取到本地
git pull
# 以精简的方式显示提交记录
git-log
# 查看文件变化(目录下也出现了file03.txt)

开发工具结合 git (还有点操作问题)

Git 提交常见问题

  1. husky > commit-msg (node v14.16.0)

⧗ input: 你的消息 xxxx…

✖ subject may not be empty [subject-empty]

✖ type may not be empty [type-empty]

✖ found 2 problems, 0 warnings

ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint

原因:你提交的消息不符合规范

解决办法:,需要在前面加 feat: 。如果是 bug, 则前面加 bug: 。注意,冒号(英文)后面有空格

  1. hint: Updates were rejected because the tip of your current branch is behind

hint: its remote counterpart. Integrate the remote changes (e.g.

hint: 『git pull …』) before pushing again.

原因分析:

​ 是由于本地和远程仓库两者代码文件不同步,因此需要先 pull,进行合并然后再进行 push

解决方法:

​ 1、先使用 pull 命令:

​ git pull --rebase origin master

​ 2、再使用 push 命令:

​ git push -u origin master

Svn

maven

云与容器

Azure 或其他云服务

Docker

设计模式

微服务架构

项目篇

幸福旅游网

导入数据库

第一种打开并复制 (小数据可以,大数据的话就得使用命令行的方式),第二种使用环境变量设置并通过命令的方式进行导入

1
2
3
4
mysql
mysql -uroot -p +密码
use happy_travel 切换数据库
source xxx

数据库设计

为什么需要大图和小图的地址?

因为如果使用了 css 缩放技术,还是原来的大小 2M,大图加小图一块是 4M. 如果采取两个字段分别存储大图和小图的地址则是 2M+20k 的大小,大大提升了速度。

为什么需要 Version?

是通过乐观锁来控制并发人数的,会限制人数

索引设计

1
2
3
ALTER TABLE `happy_travel`.`tab_user`
ADD INDEX `idx_mobile`(`mobile`, `PASSWORD`(12)) USING BTREE;
#索引需要设置长度,联合索引解决存储空间和查询速度的问题

依赖组件安装

安装 docker

通过 CentOS7 安装 docker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#安装centos时需要勾选无线网连接打开否则就需要通过配置设置为yes才能查询到ip地址,需要修改相关的配置,加上enabled=1

$ wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O
/etc/yum.repos.d/docker-ce.repo
$ yum -y install docker-ce-19.03.9
$ systemctl enable docker && systemctl start docker
$ docker --version
Docker version 20.10.12, build e91ed57

#将docker 下载镜像地址设置为国内地址
$ cat > /etc/docker/daemon.json << EOF
{
"registry-mirrors": ["http://hub-mirror.c.163.com",
"https://registry.docker-cn.com","https://docker.mirrors.ustc.edu.cn"]
}
EOF

#重启docker
$ systemctl daemon-reload && systemctl restart docke

安装 redis

1
2
3
4
5
1.docker pull redis:7.0.5
2.mkdir ~/redis
3.cd ~/redis
4.docker run -d -p 6379:6379 --name=redis --privileged=true --restart=always -v
$PWD/conf/redis.conf:/etc/redis/redis.conf -v $PWD/data:/data redis:7.0.5 redisserver /etc/redis/redis.conf --appendonly yes

安装 rabbitmq

1
2
3
4
5
6
7
8
9
#下载带web管理客户端的镜像
docker pull rabbitmq:management

#创建容器并运行(15672是管理界面的端口,5672是服务的端口。这里顺便将管理系统的用户名和密码设置为admin admin)
docker run -d --name=rabbit --net=happy_travel --restart=always -e
RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=123456 -p 15672:15672 -p
5672:5672 rabbitmq:management

#打开网址 192.168.17.127:15672 账户admin 密码123456

项目搭建

创建 asp.net core 空的项目,并且创建三个类库 travel.data、travel.service、travel.CommonUtil,并且添加对应的引用包

travel.CommonUtil

1
2
3
4
5
1. StackExchange.Redis 2.6.96
2. protobuf-net 3.2.0
3. Newtonsoft.Json 12.0.1
4. Microsoft.Extensions.Configuration 6.0
5. Microsoft.Extensions.Configuration.Json 6.0

travel.data

1
2
3
Microsoft.EntityFrameworkCore.Design 6.0.4
Microsoft.EntityFrameworkCore.Tools 6.0.4
Pomelo.EntityFrameworkCore.MySql 6.0.2

反向工程

删除无参构造函数以及一些暂时用不上的

DDD 项目结构

是四层结构

表示层:为用户提供接口。使用应用层实现与用户交互.

应用层:表示层与领域层的中介,编排业务对象执行特定的应用程序任务。使用应用程序逻辑实现用例.

领域层:包含业务对象以及业务规则。是应用程序的核心.

基础设施层:提供通用的技术功能,支持更高的层,主要使用第三方类库

配置连接池

1
2
3
4
5
6
7
8
9
10
11
12
13

//连接池大小,视自己的服务器配置而定。
builder.Services.AddDbContextPool<HappyTravelContext>(p =>
{
p.UseMySql(builder.Configuration.GetConnectionString("mysql"), new
MySqlServerVersion("5.7"));
p.LogTo(Console.WriteLine, LogLevel.Information); // 在控制台打印SQL
p.EnableSensitiveDataLogging(true); // 打出SQL中的参数值
},150);

"ConnectionStrings": {
"mysql": "server=localhost;uid=root;pwd=123456;database=happy_travel"
},

组件配置

SeriLog 日志

travel.api
1
2
3
4
1. Serilog.AspNetCore 6.1.0 (.net 6 以上需要安装)
2. Serilog.Settings.Configuration 3.4.0(读取配置)
3. Serilog.Sinks.Console 4.0.1(日志输出到控制台,看个人需要)
4. Serilog.Sinks.File 5.0(日志输出到文件中)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//配置SeriLog日志    
builder.Host.UseSerilog((context, config) =>
config
.WriteTo.Console() // 输入到控制台
// 同时输出到文件中,并且按天输出(每天都会创建一个新的文件)
.WriteTo.File($"{AppContext.BaseDirectory}/logs/renwoxing.log",
rollingInterval: RollingInterval.Day
// fff:毫秒 zzz:时区
, outputTemplate: "{Timestamp:HH:mm:ss fff zzz} " +
"|| {Level} " + // Level:日志级别
"|| {SourceContext:1} " + //SourceContext:日志上下文
"|| {Message} " + // Message:日志内容
"|| {Exception} " + // Exception:异常信息
"||end {NewLine}" //end:结束标志 NewLine:换行

)
.MinimumLevel.Information() // 设置最小级别
.MinimumLevel.Override("Default", LogEventLevel.Information) // 默认设置
.MinimumLevel.Override("Microsoft", LogEventLevel.Error) // 只输出微软的错误日志
// 生命周期日志
.MinimumLevel.Override("Default.Hosting.Lifetime",
LogEventLevel.Information)
.Enrich.FromLogContext() // 将日志上下文也记录到日志中
);

配置 Autofac

1
2
3
//travel.api
Autofac 5.2.0
Autofac.Extensions.DependencyInjection 6.0
新建一个 AutofacModuleRegister(名称随意)继承 Autofac.Module 重写 Load 方法
1
2
3
4
5
6
7
8
9
10
11
12
/// <summary>
/// Autofac注册服务类
/// </summary>
public class AutofacModuleRegister:Module
{
protected override void Load(ContainerBuilder builder)
{
// 批量注册服务
builder.RegisterAssemblyTypes(Assembly.Load("HappyTravel.Service"))
.Where(a => a.Name.EndsWith("Service")).AsImplementedInterfaces();
}
}
Program.cs 类中将 DI 替换为 Autofac
1
2
3
4
5
6
7
8
// 将ServiceProvider 更换为Autofac
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureContainer<ContainerBuilder>(containerBuilder =>
{
// 注册刚刚自定义的Module类
containerBuilder.RegisterModule<AutofacModuleRegister>();
// 如果有多个Module,还可以继续添加
});

配置 AutoMap

1
2
//travel.data
AutoMapper.Extensions.Microsoft.DependencyInjection 11.0
1
2
3
4
5
// 批量注册Autofac配置文件
builder.Services.AddAutoMapper(p =>
{
p.AddMaps("HappyTravel.Data");
});

配置 Swagger

1
2
3
//travel.api
Swashbuckle.AspNetCore 6.2.3
IGeekFan.AspNetCore.Knife4jUI 0.0.13
travel.api 和 travel.data

启用 XML 注释

注册 Swagger 服务
1
2
3
4
5
6
7
8
9
10
11
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5026",
"launchUrl": "",//添加了这个
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
builder.Services.AddSwaggerGen(p => {
p.SwaggerDoc("v1", new OpenApiInfo()
{
Contact = new()
{
Email = "1310734881@qq.com",
Name = "编程任我行",
Url = new Uri("http://baidu.com")
},
Description = "幸福旅途网项目实战,欢迎学习,任我行出品,必属精品",
Title = "幸福旅途网项目实战"
});
//Bearer 的scheme定义
var securityScheme = new OpenApiSecurityScheme()
{
Description = "JWT Authorization 头使用Bearer 体系方案. 例:\"Authorization: Bearer 你的token\"",
Name = "Authorization",
//参数添加在头部
In = ParameterLocation.Header,
//使用Authorize头部
Type = SecuritySchemeType.Http,
//内容为以 bearer开头
Scheme = "Bearer",
BearerFormat = "JWT"
};
//把所有方法配置为增加bearer头部信息
var securityRequirement = new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "JWT认证"
}
},
new string[] {}
}
};
//注册到swagger中
p.AddSecurityDefinition("JWT认证", securityScheme);
p.AddSecurityRequirement(securityRequirement);
// 加载xml文档注释
p.IncludeXmlComments(AppContext.BaseDirectory +
Assembly.GetExecutingAssembly().GetName().Name + ".xml", true);
// 实体层的注释也需要加上
p.IncludeXmlComments(AppContext.BaseDirectory + "Travel.Data.xml");
});
var app = builder.Build();
app.UseSwagger(p =>
{
// 这里设置为v2版本,则UseSwaggerUI 中的 SwaggerEndpoint它的swagger.json的路径需要设置为 / swagger / v2 / swagger.json
// p.SerializeAsV2 = true; // 如果设置为V2,则Swagger默认UI不支持Bearer认证了,所以不要设置
});
// 原有的UI
app.UseSwaggerUI(p =>
{
p.SwaggerEndpoint("/swagger/v1/swagger.json", "幸福旅途网项目实战");
p.RoutePrefix = "swagger";// 默认值:swagger
});
// 自定义的UI
app.UseKnife4UI(p =>
{
p.SwaggerEndpoint("/swagger/v1/swagger.json", "幸福旅途网项目实战");
p.RoutePrefix = "";// 默认值:swagger
});
隐藏某些接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
  //travel.api/filters/  

/// 隐藏接口,不生成到swagger文档展示
/// 注意:如果不加[HiddenApi]标记的接口名称和加过标记的隐藏接口名称相同,则该普通接口也会被隐藏不显示,所以建议接口名称最好不要重复
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Property)]
public partial class HiddenApiAttribute : Attribute { }

/// <summary>
/// 隐藏接口不生成到swagger文档展示
/// </summary>
public class HiddenApiFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
foreach (ApiDescription apiDescription in context.ApiDescriptions)
{
if (Enumerable.OfType<HiddenApiAttribute>(apiDescription.CustomAttributes()).Any())
{
string key = "/" + apiDescription.RelativePath;
if (key.Contains("?"))
{
int idx = key.IndexOf("?", StringComparison.Ordinal);
key = key.Substring(0, idx);
}
swaggerDoc.Paths.Remove(key);
continue;
}
}

}
}


builder.Services.AddSwaggerGen(p =>
{
// ...
p.DocumentFilter<HiddenApiFilter>();
// ...
}

配置 JWT

1
2
3
4
5
//travel.service

System.IdentityModel.Tokens.Jwt 6.10.0
Microsoft.AspNetCore.Authentication.JwtBearer 6.0.4
Microsoft.IdentityModel.Tokens 6.10.0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//appsetting.json

"JwtTokenOption": {
"TokenExpireTime": 60, //Token过期时间,默认为60分钟
"Audience": "任我行", //受众,可以是你前端网址
"Issuer": "任我行", //签发人
// 如果RSA加密算法,则下面存放的是RSA的私钥(我这里用的RSA的私钥)
"SecurityKey":
"MIIEpAIBAAKCAQEA4cNvbucBVhIAzJMv/3aYYHlaojwdnGXdvEW3Qg8VhRgwJZyfRqEpJoRozwUluOH
i7ayyerjiKm3DAm6nJ/qA1teq+jSAtspcyrliL2msCgqgCISpsY35hlKWXfNRQHqEjc6dL7QEmKsLqo4
RclDZI5LHWYDWDprCDDFbHRGuY8vCgFYd4Y1wpEGzJ3JIL2VrBszIAB/Yn8LAFXJMZWmFlxRmDnWS9Su
Rc3XU9eMPK0L3rFeAVwMG/hqdo5KyrgseNCB3xUdxN44A1HMcTDB9DEPeTu8tCQoSki6gB22rxBO4ucv
cJXKAtjJTQBY6QF52te5FLBMnhejxfhq6hhcq9QIDAQABAoIBAQDI61p/s5yv0ePeLNvZmd0wdq/flGq
x5sJ4rP7UDoKTaZxnzMS/YewyeEKigy04JegEp95Lc8DsC7Uys/GVlc4V5egnNpSamOwOCwBDu+K9KQm
VqyMufnDkRxnFUSctoBvZ7FhdvPMeP5NpXXhPaX5lt1os4IkghEBQA5jX/QVbj65UJEiG1x5/aMUeyhu
EpKfxRvz1ZzeSSJsRMcThLd4hsY7TRwlGbPW8UVpTfPfAW89vKSGgvbBZ03NFofpU7b/HgZ7qrDa707I
GRxLfejaFRxW1ua69pl+O86bu5dKFHi8OJwrjSAj2pQxdyV26ThW6xvNYdUya+Ri7iqzvD5hdAoGBAPf
vDbcpVpycoU4HB54sNzz6W/1RIgnDL5KcGtBCUh8AKr/yE8QGbdKQheyfJWGlObQWXSAeQAHp5AYy+zo
okw4nkngpKUFAO/Uxfkkf1UB3QdGC/gsbB4b5vhWmwQtqN6d90TlwucvNplpoSe5HqjUcf86DctoMhw0
gi7rr5zhLAoGBAOkbu6ZfBveJPQev2auDvl8KhvcFgrMuXwlsB68/4HtSVTQy33f55KM7udEQSML+faq
dFycCU1hV3QVQcs/2oGaUulGZSUvZugR296WAUSZ4tUvPDnjnlWpt48nJNWM38PNLScOMhExvBwFwD30
eaZ0G5kZhTsKD7BL3mIDcZ6G/AoGBALx5gZenWSwc9ZJ3TZc0TZmRSLS9JH7/Xf65pLiEciEW42ifAd+
Wc44KR4SlRqmADOmVxp1P4aCSyMDdqJWBmqi79GBcCfyMdAfk4/d6t4YWIna+eZi/p204FQQoi7+9syk
kQBTGWLdRUhCQDwOQfxd43r+CtaCEvXWBNDeYdWEZAoGAEqRGIGrvCrKt78RvWtkBS2ZzHqQRLLUjooR
CJRCtqg8Og0siNd0FHMy08nQj7XwenptLc7Iq6iCLuVYSqHDnqOxx7f6dLvStfJfq/BBD7RiwHuzjEmY
qu8Un90Yg/9tEaKB3uKyvE9G5NLM6ed4JwiYAGlbDzqhwI6ArUPrPSs8CgYBOOmAe3r9+xLrLNnvlCdh
Dchw5gn8dknNcjguy3czhL4lOlMzEetod3l45ngA7dAeuOz6Tvz/+7ljVvIyFG1fzbsz7PQ0JYQqPWFa
ROGq3Umg2R/ba3uggya6MgJOvP2Vko0i0nuw2fmLp6vqfnVDyFYhF864iWg567NeywjHoOQ=="
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//travel.data/options/

public class JwtTokenOption
{
/// <summary>
/// Token 过期时间,默认为60分钟
/// </summary>
public int TokenExpireTime { get; set; } = 60;
/// <summary>
/// 接收人
/// </summary>
public string Audience { get; set; }
/// <summary>
/// 秘钥
/// </summary>
public string SecurityKey { get; set; }
/// <summary>
/// 签发人
/// </summary>
public string Issuer { get; set; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var jwtOption = builder.Configuration.GetSection("JwtTokenOption");
builder.Services.Configure<JwtTokenOption>(jwtOption);
JwtTokenOption jwtTokenOption = jwtOption.Get<JwtTokenOption>();
// 添加认证服务
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(p =>
{
var rsa = RSA.Create();
rsa.ImportRSAPrivateKey(Convert.FromBase64String(jwtTokenOption.SecurityKey),out _);
SecurityKey securityKey = new RsaSecurityKey(rsa);
// 校验JWT是否合法
p.TokenValidationParameters = new TokenValidationParameters()
{
ValidAlgorithms = new string[]{"RS256"},
ValidateIssuer = true,//是否验证Issuer
ValidateAudience = true,//是否验证Audience
ValidateLifetime = true,//是否验证失效时间
ClockSkew = TimeSpan.Zero,//时钟脉冲相位差
ValidateIssuerSigningKey = true,//是否验证SecurityKey
ValidAudience = jwtTokenOption.Audience,//Audience
ValidIssuer = jwtTokenOption.Issuer,//Issuer,这两项和前面签发jwt的设置一致
IssuerSigningKey = securityKey,//拿到SecurityKey
};
});
builder.Services.AddAuthorization();
// ....
app.UseAuthentication();
app.UseAuthorization();
生成 Token
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//travel.service 

public class TokenService: ITokenService
{
private readonly JwtTokenOption _jwtTokenOption;

/// <summary>
/// 注入服务
/// </summary>
/// <param name="jwtTokenOption"></param>
public TokenService(IOptionsMonitor<JwtTokenOption> jwtTokenOption)
{
_jwtTokenOption = jwtTokenOption.CurrentValue;
}

/// <summary>
/// 生成Token
/// </summary>
/// <param name="dto">当前登录人的用户</param>
/// <returns></returns>
public string GenerateToken(UserDto dto)
{
// 保存用户个人信息(不要放用户非常私密的信息)
var claims = new[]
{
new Claim("NickName", dto.Nickname),
new Claim("UserName",dto.Username),
new Claim("RoleName",dto.RoleName),
new Claim("RoleId",dto.RoleId.ToString()),
new Claim("UserId",dto.Id.ToString())
};


var rsa = RSA.Create();
rsa.ImportRSAPrivateKey(Convert.FromBase64String(_jwtTokenOption.SecurityKey), out _);
var credential = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256);


// payload 中的信息声明
var jwtSecurityToken = new JwtSecurityToken(
issuer: _jwtTokenOption.Issuer,
audience: _jwtTokenOption.Audience,
claims: claims,
expires: DateTime.Now.AddMinutes(_jwtTokenOption.TokenExpireTime),
signingCredentials: credential
);

var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);

return token;
}
}

internal interface ITokenService
{
public string GenerateToken(UserDto dto);
}

//Travel.data/Dtos/UserDto
/// <summary>
/// 用户信息
/// </summary>
public class UserDto
{
/// <summary>
/// 用户ID
/// </summary>
public long Id { get; set; }

/// <summary>
/// 角色ID
/// </summary>
public long RoleId { get; set; }

/// <summary>
/// 角色名称
/// </summary>
public string RoleName { get; set; } = null!;

/// <summary>
/// 用户名
/// </summary>
public string Username { get; set; } = null!;

/// <summary>
/// 真实姓名
/// </summary>
public string Nickname { get; set; } = null!;
/// <summary>
/// 生日
/// </summary>
public string? Birthday { get; set; }
/// <summary>
/// 1-男,2-女,3-未知
/// </summary>
public short Sex { get; set; }
/// <summary>
/// 手机号
/// </summary>
public string? Mobile { get; set; }
/// <summary>
/// 邮箱
/// </summary>
public string? Email { get; set; }
/// <summary>
/// 最后修改时间
/// </summary>
public DateTime UpdatedTime { get; set; }
}

Token 自动刷新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
//travel.api/filters/TokenActionFilter
//注意点:using Travel.CommonUtil;得引用这个包才行

/// <summary>
/// Token 自动刷新
/// </summary>
public class TokenActionFilter : IActionFilter
{
private readonly TokenService _tokenService;
private readonly JwtTokenOption _jwtTokenOption;

public TokenActionFilter(TokenService tokenService, IOptionsMonitor<JwtTokenOption> monitor)
{
_tokenService = tokenService;
_jwtTokenOption = monitor.CurrentValue;
}

public void OnActionExecuting(ActionExecutingContext context)
{
}

public void OnActionExecuted(ActionExecutedContext context)
{
// 只对需要认证授权的方法刷新token
if (!HasIAuthorize(context))
{
return;
}
if (context.HttpContext.User.Identity is { IsAuthenticated: true })
{
var objectResult = context.Result as ObjectResult;
var val = objectResult?.Value;
if (val == null)
{
return;
}
var type = objectResult!.DeclaredType; // 实际返回的类型

var userClaims = context.HttpContext.User.Claims.ToList();
// 到期时间
var exp = Convert.ToInt64(userClaims.FirstOrDefault(p => p.Type == "exp")!.Value);

// 判断token 是否过期: 拿到期时间-当前过期 = token 剩余可用时间
var timeSpan = DateTimeUtil.GetDataTime(exp).Subtract(DateTime.Now);
// 剩余的时间
var minutes = timeSpan.TotalMinutes;

// 如果剩余时间少于Token有效时间的一半,我们返回一个新的Token给前端
if (minutes < _jwtTokenOption.TokenExpireTime / 2.0)
{
var token = _tokenService.GenerateToken(new UserDto
{
Nickname = userClaims.FirstOrDefault(p => p.Type == "NickName")!.Value,
Username = userClaims.FirstOrDefault(p => p.Type == "UserName")!.Value,
RoleName = userClaims.FirstOrDefault(p => p.Type == "RoleName")!.Value,
RoleId = Convert.ToInt64(userClaims.FirstOrDefault(p => p.Type == "RoleId")!.Value),
Id = Convert.ToInt64(userClaims.FirstOrDefault(p => p.Type == "UserId")!.Value)
});
// 设置新的token,给前端重新存储
type!.GetProperty("Token")!.SetValue(val, token);
context.Result = new JsonResult(val);
}
}
}

// 判断是否含有Authorize
private bool HasIAuthorize(ActionExecutedContext context)
{
if (context.Filters.Any(filter => filter is IAuthorizationFilter))
{
return true;
}
// 终节点:里面包含了路由方法的所有元素信息(特性等信息)
var endpoint = context.HttpContext.GetEndpoint();
return endpoint?.Metadata.GetMetadata<IAuthorizeData>() != null;
}
}

配置 Redis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//appsetting.json

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"mysql": "server=localhost;uid=root;pwd=123456;database=happy_travel"
},
"Redis": "192.168.17.128:6379,password=123456"
}

即时通讯

travel.api/Hubs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/// <summary>
/// 通讯中心
/// </summary>
[Authorize]
public class TravelHub : Hub
{
/// <summary>
/// 当客户端连接成功时,保存当前用户对应的signalR连接ID
/// </summary>
/// <returns></returns>
public override Task OnConnectedAsync()
{
var userId = Convert.ToInt64(Context.User!.Claims.FirstOrDefault(p => p.Type.Equals("UserId"))?.Value);
CacheManager.Set(string.Format(Data.Consts.RedisKey.UserWebSocketConnectionId, userId), Context.ConnectionId);
return Task.CompletedTask; ;
}

/// <summary>
/// 当断开连接时,移除当前用户对应的SignalR连接ID
/// </summary>
/// <param name="exception"></param>
/// <returns></returns>
public override Task OnDisconnectedAsync(Exception? exception)
{
var userId = Convert.ToInt64(Context.User.Claims.FirstOrDefault(p => p.Type.Equals("UserId")).Value);
CacheManager.Remove(string.Format(Data.Consts.RedisKey.UserWebSocketConnectionId, userId));
return Task.CompletedTask; ;
}
}
travel.api/Consts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static class RedisKey
{
/// <summary>
/// 用户对应的SignalR的ConnectionID
/// </summary>
public const string UserWebSocketConnectionId = "UserWebSocketConnectionId_{0}";

/// <summary>
/// 所有的分类列表
/// </summary>
public const string AllCategoryList = "AllCategoryList";

/// <summary>
/// 用户的激活码
/// </summary>
public const string UserActiveCode = "UserActiveCode{0}";

/// <summary>
/// 用户忘记密码时发送的授权码
/// </summary>
public const string UserAuthorizeCode = "UserAuthorizeCode{0}";

/// <summary>
/// 角色权限列表
/// </summary>
public const string RoleMenuList = "RoleMenuList{0}";
}
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 配置允许跨域
builder.Services.AddCors(p =>
{
p.AddPolicy("HappyTravel.Client", p =>
{
p.AllowAnyHeader();
// 这里需要改成WithOrigins()方法,填写你实际的客户端地址
p.SetIsOriginAllowed(p => true);
p.AllowAnyMethod();
p.AllowCredentials(); // 主要是为了允许signalR跨域通讯
});
});
// 配置即时通讯
builder.Services.AddSignalR();
// ....
// 跨域中间件的顺序不能乱
app.UseCors("HappyTravel.Client");
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapHub<OrderHub>("/orderHub"); // 映射signalR通讯中心



// 添加认证服务
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(p =>
{
// ...
// SignalR通讯中的Token认证
p.Events = new JwtBearerEvents()
{
OnMessageReceived = context =>
{
var token = context.Request.Query["access_token"].ToString();
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrWhiteSpace(token) &&path.StartsWithSegments("/orderHub"))
{
context.Token = token;
}
return Task.CompletedTask;
}
};

事件总线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//travel.service

DotNetCore.CAP.RabbitMQ 6.0.1
DotNetCore.CAP.MySql 6.0.1 -- 因为需要支持分布式事务
DotNetCore.CAP.Dashboard 6.0.1

//appsettings.json
"RabbitMQ": {
"HostName": "127.0.0.1",
"VirtualHost": "/",
"UserName": "admin",
"Password": "123456",
"Port": "5672"
}
注册服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
            // 配置事件总线以及分布式事务
var rabbitConfig = builder.Configuration.GetSection("RabbitMQ");
builder.Services.Configure<RabbitMQOptions>(rabbitConfig);
var rabbitOptions = rabbitConfig.Get<RabbitMQOptions>();
builder.Services.AddCap(p =>
{
p.UseMySql(builder.Configuration.GetConnectionString("mysql") ??
string.Empty);
p.UseEntityFramework<HappyTravelContext>();
p.UseRabbitMQ(mq =>
{
mq.HostName = rabbitOptions.HostName;
mq.VirtualHost = rabbitOptions.VirtualHost;
mq.UserName = rabbitOptions.UserName;
mq.Password = rabbitOptions.Password;
mq.Port = rabbitOptions.Port;
});
p.UseDashboard(); // 注册仪表盘
// 仪表盘默认的访问地址是:http://localhost:xxx/cap,你可以在d.MatchPath配置项中修改cap路径后缀为其他的名字。
});

//暂时用不上,如果是用到控制器里就用不上,如果是用到service服务层就需要了
// OrderService 中实现了事件的订阅功能
builder.Services.AddTransient<IOrderService, OrderService>();

WebAPI 设置

统一返回格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//视频中是放入travel.api/modesl文件夹下面的,但是程序代码是放入travel.data下的

/// <summary>
/// 统一数据响应格式
/// </summary>
public class Results<T>
{
/// <summary>
/// 自定义的响应码,可以和http响应码一致,也可以不一致
/// </summary>
public int Code { get; set; }

/// <summary>
/// 中文消息提示
/// </summary>
public string? Msg { get; set; }

/// <summary>
/// 是否成功
/// </summary>
public bool Success { get; set; }
/// <summary>
/// 响应的数据
/// </summary>
public T? Data { get; set; }

/// <summary>
/// 返回的Token: 如果有值,则前端需要此这个值替旧的token值
/// </summary>
public string? Token { get; set; }

/// <summary>
/// 设置数据的结果
/// </summary>
/// <param name="data">数据</param>
/// <returns></returns>
public static Results<T> DataResult(T data)
{
return new Results<T> { Code = 1, Data = data, Msg = "请求成功",Success = true};
}

/// <summary>
/// 响应成功的结果
/// </summary>
/// <param name="msg"></param>
/// <returns></returns>
public static Results<T> SuccessResult(string msg="操作成功")
{
return new Results<T> { Code = 1, Data = default, Msg = msg,Success = true};
}

/// <summary>
/// 响应失败的结果
/// </summary>
/// <param name="msg"></param>
/// <returns></returns>
public static Results<T> FailResult(string msg="请求失败")
{
return new Results<T> { Code = -1, Data = default, Msg = msg,Success = false};
}

/// <summary>
/// 参数有误
/// </summary>
/// <param name="msg"></param>
/// <returns></returns>
public static Results<T> InValidParameter(string msg="参数有误")
{
return new Results<T> { Code = -1, Data = default, Msg = msg,Success = false};
}


/// <summary>
/// 获取结果
/// </summary>
/// <param name="code"></param>
/// <param name="msg"></param>
/// <param name="data"></param>
/// <param name="success"></param>
/// <returns></returns>
public static Results<T> GetResult(int code=0,string? msg= null,T? data = default,bool success=true)
{
return new Results<T> { Code = code, Data = data, Msg = msg,Success = success};
}

/// <summary>
/// 设置token结果
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
public static Results<T> TokenResult(string token)
{
return new Results<T> { Code = 1, Data = default, Msg = "请求成功",Success = true,Token = token};
}

}
JSON 序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
   /// <summary>
/// 时间转换器 格式:yyyy-MM-dd HH:mm:ss
/// </summary>

// usingSystem.Text.Json.Serialization;
public class DateConverter : JsonConverter<DateTime>
{
public override DateTime Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
var strDate = reader.GetString();
if (!string.IsNullOrWhiteSpace(strDate) && !strDate.Contains(" "))
{
strDate += " 00:00:00";
}
return DateTime.ParseExact(string.IsNullOrWhiteSpace(strDate) ? "1991-01-01 00:00:00" : strDate,
"yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture));
}

}
program.cs
1
2
3
4
5
6
7
8
9
10
builder.Services.AddControllers().AddJsonOptions(
options =>
{
// 忽略循环序列化
options.JsonSerializerOptions.ReferenceHandler =
ReferenceHandler.IgnoreCycles;
// 时间转换
options.JsonSerializerOptions.Converters.Add(new DateConverter());
}
);
异常处理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//travel.api/filters

/// <summary>
/// 全局异常过滤器
/// </summary>
public class TravelExceptionFilter:Attribute,IExceptionFilter
{
private readonly ILogger<TravelExceptionFilter> _logger;

public TravelExceptionFilter(ILogger<TravelExceptionFilter> logger)
{
_logger = logger;
}

/// <summary>
/// 当发生异常的时候会执行此方法
/// </summary>
/// <param name="context"></param>
/// <exception cref="NotImplementedException"></exception>
public void OnException(ExceptionContext context)
{
var values = context.RouteData.Values;
var controller = values["controller"];
var action = values["action"];
_logger.LogError($"控制器:{controller},方法:{action},详细信息:\n");
WriteDetailErrorMsg(context.Exception);
context.Result = new JsonResult(Results<string>.FailResult(context.Exception.Message));
}
/// <summary>
/// 递归获取内部异常信息
/// </summary>
/// <param name="exception"></param>
/// <returns></returns>
private void WriteDetailErrorMsg(Exception exception)
{
if (exception.InnerException!=null)
{
_logger.LogError(exception.StackTrace+"\n\n");
WriteDetailErrorMsg(exception.InnerException);
}
}
}
授权过滤器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/// <summary>
/// 授权过滤器
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class TravelAuthorizeAttribute:Attribute,IAuthorizationFilter,IAuthorizeData
{
public TravelAuthorizeAttribute(){}
/// <summary>
/// 策略名称
/// </summary>
public string? Policy { get; set; }
/// <summary>
/// 可支持角色
/// </summary>
public string? Roles { get; set; }
/// <summary>
/// 以逗号分隔的方案列表,从中构造用户信息
/// </summary>
public string? AuthenticationSchemes { get; set; }
public TravelAuthorizeAttribute(string policy) => this.Policy = policy;
/// <summary>
/// 授权时执行此方法
/// </summary>
public void OnAuthorization(AuthorizationFilterContext context)
{
// 需要排除具有AllowAnymons 这个标签的控制器
// 过滤掉带有AllowAnonymousFilter
if (HasAllowAnonymous(context))
{
return;
}

// 如果用户没有登录,则给出一个友好的提示(而不是返回401给前端)
if (!context.HttpContext.User.Identity.IsAuthenticated) // 判断是否登录
{
context.Result = new JsonResult(Results<string>.FailResult("Token无效"));
}

}


// 判断是否含有IAllowAnonymous
private bool HasAllowAnonymous(AuthorizationFilterContext context)
{
if (context.Filters.Any(filter => filter is IAllowAnonymousFilter))
{
return true;
}
// 终节点:里面包含了路由方法的所有元素信息(特性等信息)
var endpoint = context.HttpContext.GetEndpoint();
return endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null;
}
}
travel.api/controllers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/// <summary>
/// 首页
/// </summary>
[ApiController]
[Route("[controller]/[action]")]
public class HomeController:ControllerBase
{
/// <summary>
/// 测试
/// </summary>
/// <returns></returns>
[HttpGet]
[AllowAnonymous]
public Results<int> Test()
{

return Results<int>.DataResult(1);
}

}
program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
builder.Services.AddControllers(p =>
{
//加了这两句
p.Filters.Add<TokenActionFilter>();
p.Filters.Add<TravelExceptionFilter>();
}).AddJsonOptions(
options =>
{
// 忽略循环序列化
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
// 时间转换
options.JsonSerializerOptions.Converters.Add(new DateConverter());
}
);
设置授权策略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/// <summary>
/// 授权策略角色名称
/// </summary>

//travel.data/Consts
public static class AuthorizeRoleName
{
/// <summary>
/// 管理员角色
/// </summary>
public const string Administrator = "管理员";
/// <summary>
/// 商家
/// </summary>
public const string SellerAdministrator = "商家";

/// <summary>
/// 普通用户
/// </summary>
public const string TravelUser = "普通用户";

/// <summary>
/// 管理员或者商家都可操作
/// </summary>
public const string AdminOrSeller = "管理员或者商家都可操作";
}
program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 设置三种授权策略
builder.Services.AddAuthorization(p =>
{
// 管理员策略
p.AddPolicy(AuthorizeRoleName.Administrator, policy =>
{
policy.RequireClaim("RoleName", AuthorizeRoleName.Administrator);
});
// 商家策略
p.AddPolicy(AuthorizeRoleName.SellerAdministrator, policy =>
{
policy.RequireClaim("RoleName", AuthorizeRoleName.SellerAdministrator);
});
// 普通用户策略
p.AddPolicy(AuthorizeRoleName.TravelUser, policy =>
{
policy.RequireClaim("RoleName", AuthorizeRoleName.TravelUser);
});
// 管理员或者商家都可操作
p.AddPolicy(AuthorizeRoleName.AdminOrSeller, policy =>
{
policy.RequireClaim("RoleName", AuthorizeRoleName.SellerAdministrator, AuthorizeRoleName.Administrator);
});
});
设置基类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/// <summary>
/// Travel的基类
/// </summary>
public class BaseController:ControllerBase
{
/// <summary>
/// 当前用户登录ID
/// </summary>
public long LoginUserId
{
get
{
if (HttpContext.User.Identity is { IsAuthenticated: true })
{
return Convert.ToInt64(HttpContext.User.Claims.FirstOrDefault(
p => p.Type.Equals("UserId"))!.Value);
}

return 0;
}
}


/// <summary>
/// 当前用户RoleID
/// </summary>
public long CurrentRoleId
{
get
{
if (HttpContext.User.Identity is { IsAuthenticated: true })
{
return Convert.ToInt64(HttpContext.User.Claims.FirstOrDefault(
p => p.Type.Equals("RoleId"))!.Value);
}

return 0;
}
}
}
设置跨域
1
2
3
4
5
6
7
8
9
10
11
12
// 配置允许跨域
builder.Services.AddCors(p =>
{
p.AddPolicy("HappyTravel.Client", policy =>
{
// policy.AllowAnyOrigin(); // 允许任何客户端
policy.WithOrigins("这里换成你的客户端地址");
policy.AllowAnyMethods();
policy.AllowCredentials(); // 主要是为了signalR允许跨域通讯
});
});
app.UseCors("HappyTravel.Client");
公共上传
支持静态资源
1
app.UseStaticFiles();//放在跨域之前
在根目录新建固定文件夹: wwwroot
travel.api/controllers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/// <summary>
/// 文件上传
/// </summary>

//using Microsoft.Net.Http.Headers;
[ApiController]
[Route("[controller]/[action]")]
[TravelAuthorize]
public class UploadController:BaseController
{
private readonly IWebHostEnvironment _hostEnvironment;
/// <summary>
/// 注入服务
/// </summary>
/// <param name="hostEnvironment"></param>
public UploadController(IWebHostEnvironment hostEnvironment)
{
_hostEnvironment = hostEnvironment;
}

/// <summary>
/// 上传文件(支持多文件/大文件500M)
/// </summary>
/// <returns></returns>
[HttpPost]
[RequestFormLimits(MultipartBodyLengthLimit = 609715200)]
[RequestSizeLimit(609715200)]
public async Task<Results<List<string>>> UploadFile()
{
var request = HttpContext.Request;

if (!request.HasFormContentType ||
!MediaTypeHeaderValue.TryParse(request.ContentType, out var mediaTypeHeader) ||
string.IsNullOrEmpty(mediaTypeHeader.Boundary.Value))
{
return Results<List<string>>.FailResult("文件类型不支持");
}

var reader = new MultipartReader(mediaTypeHeader.Boundary.Value, request.Body);
var section = await reader.ReadNextSectionAsync();

List<string> serverFilePathList = new();
while (section != null)
{
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition,
out var contentDisposition);

if (hasContentDispositionHeader && contentDisposition!.DispositionType.Equals("form-data") &&
!string.IsNullOrEmpty(contentDisposition.FileName.Value))
{
// 获取文件后缀名
var extension = Path.GetExtension(contentDisposition.FileName.Value);
// 为文件重命名,防止文件重名
var fileName = DateTime.Now.ToString("yyyyMMddHHmmssfff") + extension;
// 文件保存的文件夹路径
var uploadPath = Path.Combine(_hostEnvironment.WebRootPath, "upload");
if (!Directory.Exists(uploadPath))
{
Directory.CreateDirectory(uploadPath);
}
var fileFullPath = Path.Combine(uploadPath, fileName);

try
{
using var targetStream = System.IO.File.Create(fileFullPath);
await section.Body.CopyToAsync(targetStream);
serverFilePathList.Add("/upload/"+fileName);
}
catch (Exception e)
{
Console.WriteLine(e);
}


}

section = await reader.ReadNextSectionAsync();
}

return Results<List<string>>.DataResult(serverFilePathList);
}


}
更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

潇潇先生 微信支付

微信支付

潇潇先生 支付宝

支付宝

潇潇先生 贝宝

贝宝