1、 winform应用程序是一种智能客户端技术,我们可以使用winform应用程序帮助我们获得信息或者传输信息等。
2、属性
Name:在后台要获得前台的控件对象,需要使用Name属性。
visible:指示一个控件是否可见。
Enabled:指示一个控件是否可用。
3、
事件:发生一件事情。
注册事件:双击控件注册的都是控件默认被选中的那个事件。
触发事件:
4、
在Main函数当中创建的窗体对象,我们称之为这个窗体应用程序的主窗体。
也就意味着,当你将主窗体关闭后,整个应用程序都关闭了。
5、TextBox控件
Wordwrap:指示文本框是否换行。
Passwordchar:让文本框显示一个单一的字符
scollBars:是否显示滚动条
事件:Textchanged当文本框中的内容发生改变的时候触发这个事件。
6、跑马灯练习
abcde
bcdea
cdeab
private void timer1_Tick(object sender, EventArgs e)
{
//abcde
label1.Text = label1.Text.Substring(1) + label1.Text.Substring(0, 1);
}
7、Timer
在指定的时间间隔内做一件指定的事情
8、单选和多选
RadioButton
CheckBox
默认情况下,在一个窗体中,所有的单选按钮只允许选中一个,可以使用groupbox进行分组
9、MDI窗体的设计:MDI(Multiple Document Interface)多文档界面,与此对应就有单文档界面 (SDI)
private void 显示子窗体ToolStripMenuItem_Click(object sender, EventArgs e)
{
//显示子窗体
Form2 form2 = new Form2();
form2.MdiParent = this;
form2.Show();
Form3 form3 = new Form3();
form3.MdiParent = this;
form3.Show();
Form4 form4 = new Form4();
form4.MdiParent = this;
form4.Show();
}
private void 横向排列ToolStripMenuItem_Click(object sender, EventArgs e)
{
LayoutMdi(MdiLayout.TileHorizontal);
}
private void 纵向排列ToolStripMenuItem_Click(object sender, EventArgs e)
{
LayoutMdi(MdiLayout.TileVertical);
}
PictureBox
(1)主要逻辑代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsTransPicture
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
///
/// 点击更换上一张图片
///
///
///
private void button1_Click(object sender, EventArgs e)
{
i--;
if (i<0)
{
i = picturePaths.Length-1;
}
pictureBox1.Image = Image.FromFile(picturePaths[i]);
}
private void Form1_Load(object sender, EventArgs e)
{
//设置图片显示模式,不设置可能会看不到 Zoom:缩放
pictureBox1.SizeMode = PictureBoxSizeMode.Zoom;
pictureBox1.Image = Image.FromFile(@"C:\Users\kevin\Desktop\Picture\Alan Olav Walker.jpg");
}
int i = 0;
//获得指定文件夹下的所有文件的全路径
string[] picturePaths = Directory.GetFiles(@"C:\Users\kevin\Desktop\Picture");
///
/// 点击更换下一张图片
///
///
///
private void button2_Click(object sender, EventArgs e)
{
i++;
if (i == picturePaths.Length)
{
i = 0;
}
pictureBox1.Image = Image.FromFile(picturePaths[i]);
}
}
}
(2)实现
对话框
系统自带的一些对话框类:
OpenFileDialog 打开文件对话框
private void btnOpenFile_Click(object sender, EventArgs e)
{
var fileContent = string.Empty;
var filePath = string.Empty;
using (OpenFileDialog openFileDialog = new OpenFileDialog())
{
openFileDialog.InitialDirectory = "D:\\";
openFileDialog.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";
openFileDialog.FilterIndex = 2;
openFileDialog.RestoreDirectory = true;
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
//获取需要打开的文件路径
filePath = openFileDialog.FileName;
//读取文件内容
var fileStream = openFileDialog.OpenFile();
using (StreamReader reader = new StreamReader(fileStream))
{
fileContent = reader.ReadToEnd();
}
}
}
MessageBox.Show(fileContent, "当前路径下文件内容: " + filePath, MessageBoxButtons.OK);
}
SaveFileDialog 保存文件对话框
private void btnSaveFile_Click(object sender, EventArgs e)
{
Stream myStream;
SaveFileDialog saveFileDialog1 = new SaveFileDialog();
saveFileDialog1.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";
saveFileDialog1.FilterIndex = 2;
saveFileDialog1.RestoreDirectory = true;
if (saveFileDialog1.ShowDialog() == DialogResult.OK)
{
if ((myStream = saveFileDialog1.OpenFile()) != null)
{
// Code to write the stream goes here.
myStream.Close();
}
}
}
FolderBrowserDialog 目录选择对话框
private void btnInputSrc_Click(object sender, EventArgs e)
{
FolderBrowserDialog fdlg = new FolderBrowserDialog();
if (fdlg.ShowDialog() == DialogResult.OK)
InputSrc.Text = fdlg.SelectedPath;
}
private void bntOutputSrc_Click(object sender, EventArgs e)
{
FolderBrowserDialog fdlg = new FolderBrowserDialog();
if (fdlg.ShowDialog() == DialogResult.OK)
OutputSrc.Text = fdlg.SelectedPath;
}
ColorDialog颜色选择对话框
FontDialog 字体选择对括框
图标下载:https://www.iconfont.cn
选取文件,选取目录
//选取目录
//string debugPath = null;
//FolderBrowserDialog fbd = new FolderBrowserDialog();
//fbd.ShowDialog();
//debugPath = fbd.SelectedPath;
//选取文件
OpenFileDialog ofd = new OpenFileDialog();
ofd.ShowDialog();
string xmlPath = ofd.FileName; //路径
//string fileName = Path.GetFileName(xmlPath);//文件名
练习:实现一个学生的信息的编辑器
-学号,姓名,性别,手机号
-将数据保存到文件
-启动时从文件读取
界面布局
保存功能
点击保存时,将界面的数据保存到文件中
添加NewtonSoft.json支持
添加学生类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StudentInfoEdit
{
internal class Student
{
public int Id { get; set; }
public string Name { get; set; }
public string Sex { get; set; }
public string Phone { get; set; }
public Student()
{
}
public Student(int id, string name, string sex, string phone)
{
Id = id;
Name = name;
Sex = sex;
Phone = phone;
}
}
}
添加按钮事件处理,将数据保存为Json,存到文件中
private void saveButton_Click(object sender, EventArgs e)
{
Student student = new Student();
student.Id = Convert.ToInt32(idField.Text.Trim());
student.Name = nameField.Text.Trim();
student.Sex = sexField.Text.Trim();
student.Phone = phoneField.Text.Trim();
//Json 支持
string jsonStr = JsonConvert.SerializeObject(student,Formatting.Indented);
ReadWriteTextFile.Write(@"D:\C#学习\WinForm\student.txt", jsonStr,Encoding.UTF8);
MessageBox.Show("操作成功");
}
加载功能
当程序启动时,自动读取student.txt中的数据
在构造方法中加载
public Form1()
{
InitializeComponent();
//加载数据方法
LoadData();
//设置下拉列表的值
//sexField.Items.Add("男");
//sexField.Items.Add("女");
}
读取文件,转成json
private void LoadData()
{
//读取文件
var sutdentInfo = ReadWriteTextFile.Read(@"D:\C#学习\WinForm\student.txt", Encoding.UTF8);
//转成Student
Student student = JsonConvert.DeserializeObject(sutdentInfo);
//MessageBox.Show(student.Name);
//将数据显示在界面上
idField.Text = student.Id.ToString();
nameField.Text = student.Name.ToString();
phoneField.Text = student.Phone.ToString();
//if (student.Sex.ToString().Equals("false"))
//{
// student.Sex = "男";
//}
sexField.Text = student.Sex.ToString();
}
将数据显示到界面
需求:根据22-A-01-001-0001-002列查询的重复语句有多少条,并将条数总数打印出来
进行Winform页面设计
进行代码设计
(1)KeyValueModel类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WinFormsModel
{
public class KeyValueModel
{
public K Key { get; set; }
public V Value { get; set; }
}
}
(2)Form1.cs类
using System.Diagnostics;
using System.Text;
namespace WinFormsModel
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btnInputSrc_Click(object sender, EventArgs e)
{
FolderBrowserDialog fdlg = new FolderBrowserDialog();
if (fdlg.ShowDialog() == DialogResult.OK)
InputSrc.Text = fdlg.SelectedPath;
}
private void btnOutputSrc_Click(object sender, EventArgs e)
{
FolderBrowserDialog fdlg = new FolderBrowserDialog();
if (fdlg.ShowDialog() == DialogResult.OK)
OutputSrc.Text = fdlg.SelectedPath;
}
///
/// 打开目录
///
/// 目录路径
private static void OpenFolder(string folderPath)
{
if (string.IsNullOrEmpty(folderPath)) return;
Process process = new Process();
ProcessStartInfo psi = new ProcessStartInfo("Explorer.exe");
psi.Arguments = folderPath;
process.StartInfo = psi;
try
{
process.Start();
}
catch
{
throw;
}
finally
{
process?.Close();
}
}
private void ReadINI()
{
string confi = Application.StartupPath + "\\conf.txt";
if (File.Exists(confi))
{
StreamReader sr = new StreamReader(confi, System.Text.Encoding.UTF8);
string st = sr.ReadLine();
InputSrc.Text = st;
st = sr.ReadLine();
OutputSrc.Text = st;
sr.Close();
}
}
private void WriteINI()
{
string confi = Application.StartupPath + "\\conf.txt";
StreamWriter sr = new StreamWriter(confi, false, System.Text.Encoding.UTF8);
sr.WriteLine(InputSrc.Text);
sr.WriteLine(OutputSrc.Text);
sr.Close();
}
private void Form1_Load(object sender, EventArgs e)
{
ReadINI();
}
private void btnCheck_Click(object sender, EventArgs e)
{
if (Directory.Exists(InputSrc.Text) && Directory.Exists(OutputSrc.Text))
{
WriteINI();
string input = InputSrc.Text;
string output = OutputSrc.Text;
//得到所有的tsv文件
var files = Directory.GetFiles(input, "*.tsv", SearchOption.AllDirectories);
//定义一个字典,利用字典的键不能重复判断有没有重复的句子编号
var dic = new Dictionary();
//定义一个重复句子集合
var repeatlist = new List();
//定义一个repeatKV接收重复的句子编号
var repeatKV = new List>();
foreach (string file in files)
{
//读取文件内容
var tsvcontents =File.ReadAllLines(file);
//遍历每一行内容
for (int i = 1; i < tsvcontents.Length; i++)
{
//以tab键进行分隔,得到一个内容数组
var sub = tsvcontents[i].Split('\t');
//取出编号
//if (sub[4] != null)
//{
// alllist.Add(sub[4]);
//}
//如果有重复的键,则将键加入dic字典
if (dic.ContainsKey(sub[4]) == false)
{
dic.Add(sub[4], i);
}
else
{
repeatKV.Add(new KeyValueModel
{
Key = sub[4],
Value = i
});
}
}
}
var res = from kv in repeatKV
group kv by kv.Key into k //按照Key属性进行分组
select new { count = k.Count()+1, key = k.Key }; //k.Key:表示按照哪个属性分的组
var content = string.Empty;
foreach (var k in res)
{
content = k.key + "\t" + k.count + "\r\n";
repeatlist.Add(content);
rtbmsg.AppendText(content);
}
//var eyValueModels = IsRepeat(repeatlist);
var basePath = Path.Combine(output, "重复编号.txt");
//File.WriteAllLines(basePath, repeatlist, Encoding.UTF8); //不使用异步 + Encoding.UTF8写入生成的编码格式:UTF8-BOM
File.WriteAllLines(basePath, repeatlist); //不使用异步写入生成的编码格式:UTF8
//File.WriteAllLinesAsync(basePath, repeatlist); //使用异步写入生成的编码格式:UTF8
//File.WriteAllLinesAsync(basePath, repeatlist, Encoding.UTF8); //使用异步 + Encoding.UTF8写入生成的编码格式:UTF8-BOM
OpenFolder(output);
}
}
}
}
tsv文件内容(部分)
ja en mn
22 A 01 22-A-01-001-0001-002 地震...
22 A 01 22-A-01-001-0001-002 地震...
....
此处省略一万条
....
22 A 01 22-A-01-001-0001-002 地震...
22 A 01 22-A-01-001-0001-006 地震...
22 A 01 22-A-01-001-0001-007 地震...
运行测试
\r\n和\n的区别:
一、含义不同:
\r是回车符,\n是换行符。在C语言中,除了表示除法和注释符号,换行和转义字符都是使用反斜杠的,所以这里的斜杠应该指的是反斜杠。\r\n是回车加换行,\n是换行。要注意的是\r是回车,操作在当前行,而\n则是跳到下一行。
二、用法不同:
(1)Unix 系统里,每行结尾只有“<换行>”,即“\n”
(2)Windows系统里面,每行结尾是“<回车><换行>”,即“ \r\n”
(3)Mac系统里,每行结尾是“<回车>”,即“\n”
一个直接后果是,Unix/Mac系统下的文件在Windows里打开的话,所有文字会变成一行;而Windows里的文件在Unix/Mac下打开的话,在每行的结尾可能会多出一个^M符号。
关于写文件的时候生成的文件格式问题:
File.WriteAllLines(basePath,list); //不使用异步写入生成的编码格式:UTF8
File.WriteAllLinesAsync(basePath,list); //使用异步写入生成的编码格式:UTF8
File.WriteAllLines(basePath,list, Encoding.UTF8); //不使用异步 + Encoding.UTF8写入生成的编码格式:UTF8-BOM
File.WriteAllLinesAsync(basePath,list, Encoding.UTF8); //使用异步 + Encoding.UTF8写入生成的编码格式:UTF8-BOM
准备
推荐直接安装VS2022正式版,.NET环境全部自带,最省事儿!
1、 Visual Studio 2022正式版
下载地址: https://visualstudio.microsoft.com/zh-hans/vs/
3个版本,目前都是英文版的
社区版 vs_Community.exe
专业版 vs_professional
企业版 vs_community
社区版不需要激活码
专业版激活码:
TD244-P4NB7-YQ6XK-Y8MMM-YWV2J
企业版激活码:
VHF9H-NXBBB-638P6-6JHCY-88JWH
tips:在线安装时,如果下载不了安装文件,则是需要设置dns为8.8.8.8 114.114.114.114
2、 VS2022是带了.NET6的SDK,但运行时有依赖,建议单独下载Hosting Bundle并安装
下载地址: https://dotnet.microsoft.com/download/dotnet/6.0
dotnet-hosting-6.0.0-win.exe
3、不建议使用2019了,,只有MAC是VS2019
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
ASP.NET 是新一代 ASP 。它与经典 ASP 是不兼容的,但 ASP.NET 可能包括经典 ASP。
ASP.NET 页面是经过编译的,这使得它们的运行速度比经典 ASP 快。
ASP.NET 具有更好的语言支持,有一大套的用户控件和基于 XML 的组件,并集成了用户身份验证。
ASP.NET 页面的扩展名是 .aspx ,通常是用 VB (Visual Basic) 或者 C# (C sharp) 编写。
在 ASP.NET 中的控件可以用不同的语言(包括 C++ 和 Java)编写。
当浏览器请求 ASP.NET 文件时,ASP.NET 引擎读取文件,编译和执行脚本文件,并将结果以普通的 HTML 页面返回给浏览器。
ASP.NET 支持三种不同的开发模式:
Web Pages(Web 页面)、MVC(Model View Controller 模型-视图-控制器)、Web Forms(Web 窗体):
Web Pages 单页面模式 | MVC 模型-视图-控制器 | Web Forms 事件驱动模式 | ||
---|---|---|---|---|
最简单的 ASP.NET 模式。 与 PHP 和经典 ASP 相似。 内置了数据库、视频、图形、社交媒体等模板和帮助器。 | MVC 将 Web 应用程序分成 3 个不同的组成部分: 模型负责数据 视图负责显示 控制器负责输入 | 传统的 ASP.NET 事件驱动开发模式: 带有服务器控件、服务器事件和服务器代码的网页。 |
Web Pages:
Web Pages 是三种创建 ASP.NET 网站和 Web 应用程序的编程模式中的一种。
其他两种编程模式是 Web Forms 和 MVC(Model View Controller 模型-视图-控制器)。
Web Pages 是开发 ASP.NET 网页最简单的开发模式。它提供了一种简单的方式来将 HTML、CSS、JavaScript 和服务器脚本结合起来:
Web Pages 内置了数据库、视频、图形、社交媒体和其他更多的 Web Helpers,因此很容易扩展。
Web Pages Demo
Hello Web Pages
现在向实例中添加一些 Razor 代码:
Web Pages Demo
Hello Web Pages
The time is @DateTime.Now
该页面中包含普通的 HTML 标记,除此之外,还添加了一个 @ 标识的 Razor 代码。
Razor 代码能够在服务器上实时地完成多有的动作,并将结果显示出来。(您可以指定格式化选项,否则只会显示默认项。)
什么是 Razor ?
主要的 Razor C# 语法规则
Razor 代码块包含在 @{ … } 中
内联表达式(变量和函数)以 @ 开头
代码语句用分号结束
变量使用 var 关键字声明
(1)变量是用来存储数据的。
(2)一个变量的名称必须以字母字符开头,并且不能包含空格或者保留字符。
(3)一个变量可以是一个指定的类型,表示它所存储的数据类型。string 变量存储字符串值(“Welcome to RUNOOB.COM”),integer 变量存储数字值(103),date 变量存储日期值,等等。
(4)变量使用 var 关键字声明,或通过使用类型(如果您想声明类型)声明都可以,但是 ASP.NET 通常能自动确定数据类型。var关键字一般声明为局部变量,全局变量不能用var声明。
// 使用 var 关键字:
var greeting = "Welcome to China";
var counter = 103;
var today = DateTime.Today;
// 使用数据类型:
string greeting = "Welcome to China";
int counter = 103;
DateTime today = DateTime.Today;
字符串用引号括起来
C# 代码区分大小写
C# 文件的扩展名是 .cshtml
@{ var myMessage = "Hello World"; }
The value of myMessage is: @myMessage
@{
var greeting = "Welcome to our site!";
var weekDay = DateTime.Now.DayOfWeek;
var greetingMessage = greeting + " Today is: " + weekDay;
}
The greeting is: @greetingMessage
输出:
The value of myMessage is: Hello World
The greeting is: Welcome to our site! Here in Huston it is: Saturday
复杂网站–使用MVC
MVC 是三种 ASP.NET 编程模式中的一种。
MVC 是一种使用 MVC(Model View Controller 模型-视图-控制器)设计创建 Web 应用程序的模式:
MVC 模式同时提供了对 HTML、CSS 和 JavaScript 的完全控制。
MVC 模式定义 Web 应用程序 带有三个逻辑层:业务层(模型逻辑)显示层(视图逻辑)输入控制(控制器逻辑 |
---|
**Model(模型)**是应用程序中用于处理应用程序数据逻辑的部分。
通常模型对象负责在数据库中存取数据。
**View(视图)**是应用程序中处理数据显示的部分。
通常视图是依据模型数据创建的。
**Controller(控制器)**是应用程序中处理用户交互的部分。
通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
MVC 分层有助于管理复杂的应用程序,因为您可以在一个时间内专门关注一个方面。例如,您可以在不依赖业务逻辑的情况下专注于视图设计。同时也让应用程序的测试更加容易。
MVC 分层同时也简化了分组开发。不同的开发人员可同时开发视图、控制器逻辑和业务逻辑。
添加数据库控制器
数据库控制器可以通过以下几个简单的步骤来创建:
在 Solution Explorer(解决方案资源管理器)中,右击 Controllers 文件夹,选择 Add 和 Controller。
选择模板:Controller with read/write actions and views, using Entity Framework
选择模型类:MovieDB (MvcDemo.Models)
选择 data context 类:MovieDBContext (MvcDemo.Models)
选择视图 Razor (CSHTML)
设置控制器名称为 MoviesController。
点击 Add
Visual Web Developer 将创建以下文件:
添加数据库视图
在 Movies 文件夹中,会自动创建以下文件:
MVC项目:
M(模型model)–用来传递数据
C(控制器Controller)–HTTP请求到了MEB服务器使用控制器来处理这些请求(处理请求,数据处理,生成响应)
作用:
(1)在构造函数中注入服务
(2)执行动作方法
(3)向视图传递数据,HTML、PDF、EXCEL、JOSN
(4)将结果附带状态码返回给客户端(例如:200 OK)
V (View) --Razor视图(.cshtml文件)
(1)视图搜索方式:
/Views/Home/xxx.cshtml
/Views/Shared/xxx.cshtml
/Pages/Shared/xxx.cshtml
启动项目打开该页面
Areas:什么情况下会用到,一个项目有多个系统,或者一个分前段(客户)后端(网站管理)
ControllerBase基类:
(1)不支持视图
(2)后面WebAPI也是用这个基类
(3) htppContext(请求和响应,包括有关的链接信息、包括一些中间件以及和身份验证和授权User对象)
(4) Request(HTTP请求,请求头,查询字符串,内容类型)
(5)Response()
Controller类:
(1)数据传递–ViewData(键值对的字典–只能在当前请求里面保存数据)/ViewBag是对vViewData的包装好处编写人性化/TempData(跨请求保存数据,生命周期长一点)
(2)处理视图的方法:
View ()返回和动作方法同名的视图
过滤器Filter:
作用–向多个控制器和动作方法添加一些功能的时候,可以使用或者自定义的过滤器。
(1)标注在控制器–对整个控制器产生作用
(2)标注在动作方法上面–对这个动作方法起作用
(3)通过服务的形式配置–全局作用
并行编程在多核CPU
并发编程:榨干CPU,充分利用CPU资源
Thread—》ThreadPool线程池—》Task任务(async await)
异步编程:提高服务器吞吐量(I/0)
//async:1、使标注的方法变成异步方法 2、使调用方法时前面加的await生效 3、异步编程返回值: 无返回值:Task 有返回值:Task<返回值类型>
public async Task Index()
{
//创建角色
await roleManager.CreateAsync(new IdentityRole(AdminRole));
//创建管理员用户
IdentityUser user = new IdentityUser { UserName = UserEmail ,Email = UserEmail,EmailConfirmed = true}; //EmailConfirmed = true:Email用户验证为true,就可以登录了,否则登录不了
await userManager.CreateAsync(user,UserEmail);
//把用户添加到角色
await userManager.AddToRoleAsync(user,AdminRole);
//return View();
//Redirect:重定向跳转页面,也就是直接跳转到指定页面 "/":表示首页
return Redirect("/");
}
PM> add-migration CreateIdentitySchema
Build started...
Build succeeded.
//第一个错
The Entity Framework tools version '6.0.4' is older than that of the runtime '6.0.9'. Update the tools for the latest features and bug fixes. See https://aka.ms/AAc1fbw for more information.
System.TypeLoadException: Method 'Create' in type 'MySql.Data.EntityFrameworkCore.Query.Internal.MySQLSqlTranslatingExpressionVisitorFactory' from assembly 'MySql.Data.EntityFrameworkCore, Version=8.0.22.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d' does not have an implementation.
...
//第二个错
Method 'Create' in type 'MySql.Data.EntityFrameworkCore.Query.Internal.MySQLSqlTranslatingExpressionVisitorFactory' from assembly 'MySql.Data.EntityFrameworkCore, Version=8.0.22.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d' does not have an implementation.
第一个错的意思是:实体框架工具版本“6.0.4”比运行时版本“6.0.9”更旧。更新工具以获得最新的特性和bug修复。
接下来以管理员身份打开CMD 运行以下命令:
dotnet tool update --global dotnet-ef
重启visual studio即可
如果以上操作还是不行的话,则是因为你导入的包的版本不一致,需要全部更新为6.0.9,如下
连接MySQL
1、通过NuGet添加相关的包:
Pomelo.EntityFrameworkCore.MySql
注:
网上很多资料都是关于引用 MySql.Data.EntityFrameworkCore 连接MySql的,但MySql.Data.EntityFrameworkCore目前好像已经弃用了。引用 MySql.Data.EntityFrameworkCore后,会报以下错误,所以不要安装这个包,安装了的话需要移除,只需要使用上面那个就行了:
MySql.Data.EntityFrameworkCore.Query.Internal.MySQLSqlTranslatingExpressionVisitorFactory' from assembly 'MySql.Data.EntityFrameworkCore, Version=8.0.22.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d' does not have an implementation
2、新增MySql数据库连接配置
在 在appsettings.json 新增数据库连接
{
//新增数据库连接
"ConnectionStrings": {
"DefaultConnection": "Server=127.0.0.1;UserId=root;Password=123456;Database=webmvc"
},
//下面这些是原来的不用管
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
3、在Program.cs中注册服务
// 注册服务
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext(builder => builder.UseMySql(connectionString, new MySqlServerVersion(new Version(8, 0, 11))));
下面是我的Program.cs代码,有需要的话可以借鉴(此处创建的是一个MVC项目):
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebMvcMysql.Data;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
//builder.Services.AddDbContext(options =>
// options.UseSqlServer(connectionString));
builder.Services.AddDbContext(builder => builder.UseMySql(connectionString, new MySqlServerVersion(new Version(8, 0, 11))));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores();
builder.Services.AddRazorPages();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();
app.Run();
注: 这里必须指定 ServerVersion,否则无法编译通过,会报第二个错误
4、调试运行,访问页面成功
到这来,连接MySQL 已完成
创建项目
连接mysql数据库
1)通过NuGet添加相关的包:
Pomelo.EntityFrameworkCore.MySql
注:
网上很多资料都是关于引用 MySql.Data.EntityFrameworkCore 连接MySql的,但MySql.Data.EntityFrameworkCore目前好像已经弃用了。引用 MySql.Data.EntityFrameworkCore后,会报以下错误,所以不要安装这个包,安装了的话需要移除,只需要使用上面那个就行了:
MySql.Data.EntityFrameworkCore.Query.Internal.MySQLSqlTranslatingExpressionVisitorFactory' from assembly 'MySql.Data.EntityFrameworkCore, Version=8.0.22.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d' does not have an implementation
2)新增MySql数据库连接配置
在appsettings.json 新增数据库连接
{
//新增数据库连接
"ConnectionStrings": {
"DefaultConnection": "Server=127.0.0.1;UserId=root;Password=123456;Database=webmvc"
},
//下面这些是原来的不用管
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
3)在Program.cs中注册服务
// 注册服务
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext(builder => builder.UseMySql(connectionString, new MySqlServerVersion(new Version(8, 0, 11))));
下面是我的Program.cs代码,有需要的话可以借鉴(此处创建的是一个MVC项目):
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebMvcMysql.Data;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
//builder.Services.AddDbContext(options =>
// options.UseSqlServer(connectionString));
builder.Services.AddDbContext(builder => builder.UseMySql(connectionString, new MySqlServerVersion(new Version(8, 0, 11))));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores();
builder.Services.AddRazorPages();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();
app.Run();
注: 这里必须指定 ServerVersion,否则无法编译通过,会报第二个错误
4)删除系统生成的迁移文件夹,因为系统默认连接的是SQL Server,否则直接执行Update-Database或者进行下面的迁移会报错
5)在程序包管理控制台执行命令即可生成以mysql数据库的迁移文件夹,有问题参照 2.3.2
add-migration CreateIdentitySchema
启动项目,点击注册
点击进行迁移
刷新页面,并点击最后的蓝色字体进行邮箱确认,否则会登录不进系统
迁移成功之后便会在数据库中自动生成下面这些表
数据库表分析
登录系统
访问权限设置
//在方法名上方添加:[Authorize(Roles ="admin")],表示对方法访问进行授权限制访问(过滤器)
//需要对应的角色才能登录Index页面
[Authorize(Roles ="admin")]
public IActionResult Index()
{
return View();
}
再次登录系统被拒
Route过滤器自定义路由
[Route("error")] //使用过滤器自定义路由
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
新建Product类
namespace WebMvcMysql.Models
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; } //价格
public short Stock { get; set; } //库存
public string? ImageUrl { get; set; } //图片路径
public IFormFile? FormFile { get; set; } //图片
}
}
将其添加到
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using WebMvcMysql.Models;
namespace WebMvcMysql.Data
{
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions options)
: base(options)
{
}
//需要在此增加,否则不能生成对应的数据库表
public DbSet Products { get; set; }
}
}
执行命令:add-migration addproduct
报错,具体就是说数据库中没有对应的类型,不能形成映射,所以这个类型不需要映射
The property 'Product.FormFile' is of an interface type ('IFormFile'). If it is a navigation, manually configure the relationship for this property by casting it to a mapped entity type. Otherwise, ignore the property using the [NotMapped] attribute or 'Ignore' in 'OnModelCreating'.
修改实体类
using System.ComponentModel.DataAnnotations.Schema;
namespace WebMvcMysql.Models
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; } //价格
public short Stock { get; set; } //库存
public string? ImageUrl { get; set; } //图片路径
[NotMapped] //不映射的注解
public IFormFile? FormFile { get; set; } //图片
}
}
继续
add-migration addproduct //执行迁移命令
update-database //更新数据库命令,不执行此语句数据库中不会有对应的表
CRUD
添加数据库控制器:
1)生成方式一(不建议使用,使用下面的生成方式二去生成):
自动生成
2)生成方式二:
出现如下不用管,重新继续操作一遍就可以了
自动生成如下
启动项目访问页面,https://localhost:7134/products/index
格式:https://localhost:7134/控制器名称/页面名称
添加Products,点击即可访问
1、遵守HTTP
2、https://www.baidu.com/—180.101.49.11(域名–localhost) :443(端口号)(https加密,http明文)5000,5001,资源路径
3、get请求(一般用于资源请求,可以带参数),POST请求(注册,登陆)
4、需要的基础知识HTNL5/CSS3/JavaScript(TypeScript),C#基础进阶、EFCore,Bootstarp
在Program.cs中编写代码
var builder = WebApplication.CreateBuilder(args); //创建默认web主机
var app = builder.Build(); //创建
//判断是否是开发环境,可以去launchSettings.json文件中查看environmentVariables
//if (!app.Environment.IsDevelopment())
//{
// app.UseHsts(); //添加安全访问中间件
//}
app.UseHttpsRedirection(); //自动跳转到HTTPS,用HTTPS链接去访问
app.MapGet("/", () => "Hello World!");
app.Run(); //运行,阻塞调用
启动项目进行测试,默认是开发环境
launchSettings.json
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:8836",
"sslPort": 44311
}
},
"profiles": {
"RazorPages.web": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7036;http://localhost:5036",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
//"ASPNETCORE_ENVIRONMENT": "Production" //部署上线后设置为Production
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
添加类
修改类
using Microsoft.AspNetCore.Builder;
namespace RazorPages.web
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app,IWebHostEnvironment env)
{
//判断是否是开发环境,可以去launchSettings.json文件中查看environmentVariables
if (!env.IsDevelopment())
{
app.UseHsts(); //添加安全访问中间件
}
app.UseRouting(); //启动终结点路由(做选择)
app.UseHttpsRedirection(); //自动跳转到HTTPS,用HTTPS链接去访问
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", () => "Hello World!");
});
}
}
}
启动项目:在Program.cs写入以下代码
//var builder = WebApplication.CreateBuilder(args); //创建默认web主机
//var app = builder.Build(); //创建
判断是否是开发环境,可以去launchSettings.json文件中查看environmentVariables
//if (!app.Environment.IsDevelopment())
//{
// app.UseHsts(); //添加安全访问中间件
//}
//app.UseHttpsRedirection(); //自动跳转到HTTPS,用HTTPS链接去访问
//app.MapGet("/", () => "Hello World!");
//app.Run(); //运行,阻塞调用
using RazorPages.web;
//启动项目的方法
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup();
}).Build().Run();
传递静态文件,新建文件夹命名为wwwroot
新建一个index.html文件在wwwroot文件夹下
编辑index.html文件
小舒的网页
这是一个静态页面
在Startup文件中添加如下代码
启动项目访问页面
设置默认路由
app.UseDefaultFiles(); //index.html,default.html
using Microsoft.AspNetCore.Builder;
namespace RazorPages.web
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app,IWebHostEnvironment env)
{
//判断是否是开发环境,可以去launchSettings.json文件中查看environmentVariables
if (!env.IsDevelopment())
{
app.UseHsts(); //添加安全访问中间件
}
app.UseRouting(); //启动终结点路由(做选择)
app.UseHttpsRedirection(); //自动跳转到HTTPS,用HTTPS链接去访问
app.UseDefaultFiles(); //index.html,default.html
app.UseStaticFiles(); //添加启动静态文件页面
app.UseEndpoints(endpoints =>
{
//页面访问路由,可自定义
endpoints.MapGet("/xiaoshu", () => "Hello World!");
});
}
}
}
准备,文件夹名称必须是Pages,否则会找不到
在Startup文件中修改页面访问路由
编辑Razor页面
@page
@model RazorPages.web.Pages.IndexModel
@{
//这里可以写C#代码,还可以写http请求的时候执行的方法,比如OnGet(get请求时执行),OnPost(post请求时执行)
Model.Name="我是动态页面";
}
Razor的网页
@Model.Name
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPages.web.Pages
{
public class IndexModel : PageModel
{
public string? Name { get; set; }
public void OnGet()
{
}
}
}
启动项目
Nlog:http://t.csdn.cn/581eS
Log4net:http://t.csdn.cn/1zAsR
Nuget引入程序包
准备好配置文件 log4net.config
配置读取配置文件生效
//Nuget引入:
//1、Log4Net
//2、microsoft.extensions.logging.log4net.aspnetcore
builder.Logging.AddLog4Net("CfgFile/log4net.config");
注入得到log4net实例开始写日志
创建一个日志控制器Log4netController
using Microsoft.AspNetCore.Mvc;
namespace WebMvc.Controllers
{
public class Log4netController : Controller
{
private readonly ILogger _Logger;
private readonly ILoggerFactory _LoggerFactory;
public Log4netController(ILogger logger,ILoggerFactory loggerFactory)
{
this._Logger = logger;
this._Logger.LogInformation($"{this.GetType().Name}被构造了 ---> _Logger");
this._LoggerFactory = loggerFactory;
ILogger < Log4netController > _Logger2 =this._LoggerFactory.CreateLogger();
_Logger2.LogInformation($"{this.GetType().Name}被构造了 ---> _Logger2");
}
public IActionResult Index()
{
ILogger _Logger3 = this._LoggerFactory.CreateLogger();
_Logger3.LogInformation($"Index被执行了");
this._Logger.LogInformation($"Index被执行了");
return View();
}
}
}
创建一个Razor视图
@{
ViewData["Title"] = "Index";
}
Index
访问页面,项目链接+控制器名字+index页面
生成日志
项目路径下生成日志文件
分析:可能是数据库没有连接上,日志一般不写入数据库,我也不深究了,这里仅仅是提供一个思路,在下面的webapi项目中我使用配置文件成功连接上了数据库,有兴趣的可以在下面的内容中学习并结合这里的知识去试一下!
Log4net写Mysql:http://t.csdn.cn/xhWIK,有需要的自己看链接文章即可
第一种项目包含模型、视图、控制器等,类似于MVC项目(与MVC项目的区别是:MVC项目是带Razor视图的),其Controller类继承的是Controller类
第二种项目只能写Web Api,因为其Controller类继承的是ControllerBase基类,所以只支持写WebApi项目
小知识:
Controller类继承于ControllerBase,是其子类,在Controller类中提供了一些操作View(视图)的方法
ControllerBase类中没有支持View的方法,所以只能写Web Api
WebAPI是一种用来开发系统间接口、设备接口API的技术,基于Http协议,请求和返回格式结果默认是json格式
比 WCF 更简单、更通用,比 WebService更节省流量、更简洁。
WebAPI和普通ASP.Net MVC的区别:
WebAPl是开发接口的技术,用户不会直接和WebAPI打交道,因此WebAPI也不会生成界面
特点:
a. webapi的Action方法返回值直接返回对象,专注于数据
b. webapi更符合Restful的风格
c.有利于独立于lIS部署(selfhost、winform、windows service、控制台)
d. Action可以直接声明为async
基于“Http谓词语义”进行通讯协议的设计,带来的好处:
i.可以为不同类型做不同的权限控制;
ii.不再需要“Delete”. “AddNew”这样的Action名字,根据请求的类型就可以判断.
iii.返回报文的格式也确定,不用再约定返回状态码,充分利用Http状态码
iv.有利于系统优化,浏览器可以自动缓存Get请求
v. Get没有副作用,是幂等的(1的任何次方都是1),可以重试
a.提交方式
b. 报文头 参数接收
c. 报文体 [FormBody]参数接收
d. 默认路由规则=》api/{controller}/{action}/{id}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
namespace WebAPITest
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API 配置和服务
// Web API 路由
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}", //这是自动生成的!
//routeTemplate: "api/{controller}/{action}/{id}",//实际开发中使用最多的就是这个,action:方法名
defaults: new { id = RouteParameter.Optional }
);
}
}
}
Web API的参数,接收参数的方法大概以下几种:
FromBody // application/json
FromForm //前端的默认消息类型
FromHeader //从请求头里获取
FromQuery //从参数里获取
FromRoute //从路由中获取
FromServices//这个Jwt篇讲解
a. GET
// 示例:GET api/values
[HttpGet]
public IEnumerable Get()
{
return new string[] { "value1", "value2" };
}
// 示例:GET api/values/5
[HttpGet]
public string Get(int id)
{
return "value";
}
public class Login
{
public string Name { get; set; }
public string Pwd { get; set; }
}
//GET api/values/login?name=张三&pwd=123456
[HttpGet]
public IEnumerable Login([FromUri] Login login)
{
return new string[] { login.Name,login.Pwd};
}
b. POST
// 示例:POST api/values
[HttpPost]
public string Post([FromBody] string value)
{
return "添加成功";
}
i. public string AddNew2(LoginModel model)
ii. public string AddNew2([FromBody]LoginModel model)
//POST api/values/LoginPost1
[HttpPost]
public void LoginPost1(Login login)
{
}
[HttpPost]
public void LoginPost2([FromBody] Login login)
{
}
提交的数据是ContentType=" application/json”方式提交
i. 即参数设置成报文体{userName:“admin3”,password:“123”)
ii. 参数也可以用模型对象
c.PUT
// 示例:PUT api/values/5
[HttpPut]
public string Put(int id, [FromBody] string value)
{
return "修改成功" + id;
}
d.DELETE
// 示例:DELETE api/values/5
[HttpDelete]
public string Delete(int id)
{
return "删除成功";
}
e.通过自定义路由规则来捕获参数(get、post都适用)
i. WebAPI可以通过[Route]和[RoutePrefix]来自定义路由
ii. [RoutePrefix]作用于Controller例:[RoutePrefix( "api/Person”)]
iii. [Route]作用于Action 例 : [Route(“GetByld2”)]
iv.例如:
[Route(“Login/{phoneNum}/{password}”)] 自定义路由
[HttpPost]
public string Login(string phoneNum, string password)
可以通过/Login/33/44访问
//示例:可以通过https://localhost:44386/Login/22/33访问
[Route("Login/{phoneNum}/{password}")]
[HttpPost]
public string Login(string phoneNum,string password){
return phoneNum + password;
}
状态码:
2开头:成功(例:200,有返回结果;204,无返回结果)
3开头:都是错
4开头:服务器报错
5开头:代码报错
Status code
http status code是reponse的一部分,它提供了这些信息:请求是否成功,失败的原因
web api能涉及到的status codes主要是这些:
200: OK
201: Created,创建了新的资源
204:无内容No Content,例如删除成功
400: Bad Request,指的是客户端的请求错误
401:未授权Unauthorized
403:禁止操作Forbidden.验证成功,但是没法访问相应的资源404: Not Found
409:有冲突Conflict.
500: Internal Server Error,服务器发生了错误
a.普通类型(默认GET请求)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
namespace WebAPITest
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API 配置和服务
// Web API 路由
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
//routeTemplate: "api/{controller}/{id}", //这是自动生成的!
//自定义默认路由
routeTemplate: "api/{controller}/{action}/{id}", //实际开发中使用最多的就是这个,action:方法名
defaults: new { id = RouteParameter.Optional }
);
}
}
}
//普通类型
public string GetString()
{
return "";
}
b. void,这样客户端会得到204的状态码,尽量别这样干(没拿到结果的成功)
c. lHttpActionResult类型
i.可以调用ApiController 中的Ok)、NotFound()、Json(new {a=1,b=2})、Content()、Redirect()等方法。
//IIHttpActionResult类型
public IHttpActionResult GetHttpActionResult()
{
return Json(new {a=1,b=2,c=3});
}
d. HttpResponseMessage
i、可以做更精细化的返回内容控制,比如返回二进制文件、设置返回报文头
ii.例如:
[HttpPost]
public HttpResponseMessage Test()
{
HttpContent httpContent = new StringContent("hello");
return new HttpResponseMessage { Content= httpContent ,StatusCode= HttpStatusCode.OK};
}
Web API的异常处理
a. ASP.Net Web API错误处理:对于Action 中的异常,默认就是返回500状态码,报文体是Json格式这样也是最好的
b.对于“ld不存在”、“年龄不合法”等这类的错误既可以通过自动以状态码的方式返回(不太够用),也可以自定义下面这种类型作为返回值(然后在文档中约定:О代表成功、1代表用户名不能为空、2代表金额超限)
//Web Api 的异常处理
//a.默认就是返回500状态码,报文体是Json格式
public string GetError(int id)
{
if (id == 0)
{
throw new Exception("这是一个错误");
}
else
{
return "我是" + id;
}
}
//b.业务报错处理,对于“ld不存在”、“年龄不合法”等这类的错误既可以通过自动以状态码的方式返回(不太够用),也可以自定义下面这种类型作为返回值(然后在文档中约定:О代表成功、1代表用户名不能为空、2代表金额超限)
public class ApiResult
{
public int Code { get; set; }
public string Message { get; set; }
public T Value { get; set; }
}
public ApiResult GetApiResult(int id)
{
if (id == 0)
{
return new ApiResult { Message = "出错了" };
}
else
{
return new ApiResult { Message = "对了" };
}
}
//c.未处理异常的处理IExceptionFilter、WebApiConfig中config.Filter.Add(new ExceptionFilter());
public string GetExceptionFilter(int id)
{
int a = id / 0;
return "等于" + a;
}
将报错信息写入错误文本
(1)自定义类
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http.Filters;
namespace WebAPITest
{
//自定义类继承异常过滤器,在WebApiConfig中实现
public class ExceptionFilter : IExceptionFilter
{
public bool AllowMultiple => false;
public async Task ExecuteExceptionFilterAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
{
//throw new NotImplementedException();
using (StreamWriter writer = File.AppendText("E:/error.txt"))
{
await writer.WriteLineAsync(actionExecutedContext.Exception.ToString());
}
}
}
}
(2)在WebApiConfig类中实现
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
namespace WebAPITest
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API 配置和服务
// Web API 路由
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
//routeTemplate: "api/{controller}/{id}", //默认路由匹配规则
routeTemplate: "api/{controller}/{action}/{id}", //自定义路由匹配规则:实际开发中使用最多的就是这个,action:方法名
defaults: new { id = RouteParameter.Optional }
);
//异常处理
config.Filters.Add(new ExceptionFilter());
}
}
}
Web API的多版本管理
a.旧版接口做一个代码分支,除了进行 bug修改外,旧版本接口不再做改动;新接口代码继续演化升级.在客户端请求的时候带着要请求的接口版本号,在服务器端选择合适的版本代码进行处理。
b.技术处理方法
i.(最推荐)不同版本用不同的域名: v1.api.tgh.com 、 v2.api.tgh.com 、 v3…
ii.在url、报文头等中带不同的版本信息,用Nginx等做反向代理服务器,然后将http://api.tgh.com/api/v1/User/1和http://api.tgh.com/api/v2/User/1转到不同的服务器处理
iii.多个版本的Controller共处在一个项目中,然后使用[RoutePrefix]或者llttpControllerSelector根据报文头、路径等选择不同的Controller 执行
Web API连接mysql数据库
HUI前端框架:http://www.h-ui.net/
启动 Visual Studio ,在开始页里选择“新建项目”。或者从“文件”菜单里,选择“新建项目”。选择ASP .NET Web 应用程序
输入项目名称
选择web api创建即可
得到如下页面
在Model文件夹下新建Users类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace WebApiMysql.Models
{
public class Users
{
public int UserID { get; set; }
public string UserName { get; set; }
public string UserEmail { get; set; }
}
}
然后就可以添加控制器了,控制器就是控制层,在“Controllers”下有一个“HomeController.cs”的文件,它是一个传统的 ASP.NET MVC 控制器。它只是负责处理站点的HTML页,跟Web API没有直接关系。这里需要手动添加一个API控制器
给控制器命名
向控制器中加入以下代码
public class UsersController : ApiController
{
//MySqlConnection对象是一个数据库连接对象,主要功能是建立与物理数据库的连接;简单来说,就是连接到本机数据库的一个对象
private static MySqlConnection getMySqlConnection()
{
MySqlConnection mysql = new MySqlConnection(ConfigurationManager.ConnectionStrings["MySqlConnection"].ConnectionString);
return mysql;
}
//MySqlCommand对象是一个数据命令对象,主要功能是向数据库发送增删改查操作的语句
public static MySqlCommand getSqlCommand(String sql, MySqlConnection mysql)
{
MySqlCommand mySqlCommand = new MySqlCommand(sql, mysql);
return mySqlCommand;
}
// GET api/users/Get
///
/// 查找所有用户
///
/// 返回查找到的所有用户集
///
[HttpGet]
public IEnumerable Get()
{
List listUser = new List();
MySqlConnection mysql = getMySqlConnection();
MySqlCommand mySqlCommand = getSqlCommand("select * from user", mysql);
mysql.Open();
//用MySqlDataReader对象读取数据
MySqlDataReader reader = mySqlCommand.ExecuteReader();
try
{
while (reader.Read())
{
if (reader.HasRows)
{
Users user = new Users();
user.UserID = reader.GetInt32("UserID");
user.UserName = reader.GetString("username");
user.UserEmail = reader.GetString("useremail");
listUser.Add(user);
}
}
}
catch
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
finally
{
mysql.Close();
}
return listUser;
}
// GET api/Users/GetUserByID/2
///
/// 根据id查询用户
///
/// 用户id
/// 返回一个用户
///
[HttpGet]
public Users GetUserByID(int id)
{
Users user = new Users();
MySqlConnection mysql = getMySqlConnection();
MySqlCommand mySqlCommand = getSqlCommand("select * from USER where UserID=" + id, mysql);
mysql.Open();
MySqlDataReader reader = mySqlCommand.ExecuteReader();
try
{
while (reader.Read())
{
if (reader.HasRows)
{
user.UserID = reader.GetInt32(0);
user.UserName = reader.GetString(1);
user.UserEmail = reader.GetString(2);
}
}
reader.Close();
}
catch
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
finally
{
mysql.Close();
}
return user;
}
//GET api/Users/GetUserByName/?username=xx
///
/// 根据用户名查找用户
///
/// 用户名
/// 返回一个查找结果集
///
[HttpGet]
public IEnumerable GetUserByName(string userName)
{
List listuser = new List();
MySqlConnection mysql = getMySqlConnection();
MySqlCommand mySqlCommand = getSqlCommand("select * from USER where username like'%" + userName + "%'", mysql);
mysql.Open();
MySqlDataReader reader = mySqlCommand.ExecuteReader();
try
{
while (reader.Read())
{
if (reader.HasRows)
{
Users user = new Users();
user.UserID = reader.GetInt32("UserID");
user.UserName = reader.GetString("USERNAME");
user.UserEmail = reader.GetString("USEREMAIL");
listuser.Add(user);
}
}
reader.Close();
}
catch
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
finally
{
mysql.Close();
}
return listuser;
}
// POST api/Users/AddUser
public void Post([FromBody] string value)
{
}
// PUT api/Users/
public void Put(int id, [FromBody] string value)
{
}
// DELETE api/Users/
public void Delete(int id)
{
}
}
此处会报错,跟着下面的步骤走就能解决(一键导包:Alt + Enter):
(1)Users报错,导包即可
(2)MySqlConnection报错,安装Mysql.Data包
(3)ConfigurationManager报错,这里也是导包就行
UsersController处代码,主要补充了Get方法的逻辑。数据从mysql数据库中获取。例中MySql对应user表中数据如下
获取数据共写了三个get方法,第一个是获取所有user数据,第二个根据Users 的ID获取对应Users,第三个根据UserName获取Users。为了能够通过MySql获取数据,需要在Web.config的节点中添加 ;name为connectionString对应名称,connectionString中为连接mysql的配置;代码中通过ConfigurationManager.ConnectionStrings[“name”].ConnectionString获取mysql配置。
完成代码编写后,启动项目即可对进行代码测试。这里使用的是一个谷歌插件RESTED,可自行下载
使用Web api进行访问
即可查询
文档注释配置
配置前
开启生成包含API文档的文件功能
方法一:
方法二:
在Program类中增加如下代码
builder.Services.AddSwaggerGen(options =>
{
//注释
var xmlFileName = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
//第二个参数为是否显示控制器注释,我选择true
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory,xmlFileName),true);
});
在Controller类中增加文档注释(其他类中增加了也可以看到)
启动项目即可看到文档注释
控制器类注释:
实体类注释:
注意
需要修改为如下才会显示:
返回数据的时间格式化
安装包microsoft.aspnetcore.mvc.newtonsoftjson
格式化之前:
进行格式化:
(1)在Program类中增加如下代码
builder.Services.AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
});
(2)格式化后
Api版本管理
下图中可以构建不同的版本
在Program类中添加如下代码
//生成多个文档显示
typeof(ApiVersions).GetEnumNames().ToList().ForEach(version =>
{
//添加文档介绍
options.SwaggerDoc(version, new Microsoft.OpenApi.Models.OpenApiInfo
{
Title = $"项目名",
Version = version,
Description = $"项目名:{version}版本"
});
});
app.UseSwaggerUI(options =>
{
//options.SwaggerEndpoint($"/swagger/V1/swagger.json",$"版本选择:V1");
//如果只有一个版本也要和上方保持一致
typeof(ApiVersions).GetEnumNames().ToList().ForEach(version =>
{
//切换版本操作
//参数一时使用的哪个json文件,参数二就是个名{字
options.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"版本选择:{version}");
});
});
最后在控制器方法上表明它是属于哪个版本的特性
[ApiExplorerSettings(GroupName ="V1")] //如果不加,则默认两个版本都有;加上就是某个版本独有
切换回单版本
修改一下代码就行:
//单版本显示
options.SwaggerDoc("V1", new Microsoft.OpenApi.Models.OpenApiInfo
{
Title = $"项目名",
Version = "V1",
Description = $"项目名:V1版本"
});
//生成多个版本显示,获取枚举类中的每个枚举的名字
//typeof(ApiVersions).GetEnumNames().ToList().ForEach(version =>
//{
// //添加文档介绍
// options.SwaggerDoc(version, new Microsoft.OpenApi.Models.OpenApiInfo
// {
// Title = $"项目名",
// Version = version,
// Description = $"项目名:{version}版本"
// });
//});
//单版本显示
options.SwaggerEndpoint($"/swagger/V1/swagger.json",$"版本选择:V1");
//生成多个版本显示,如果只有一个版本要和上方保持一致
//typeof(ApiVersions).GetEnumNames().ToList().ForEach(version =>
//{
// //切换版本操作
// //参数一时使用的哪个json文件,参数二就是个名字
// options.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"版本选择:{version}");
//});
Program类(入口文件)
从上到下,依次执行
从 C# 9 开始,无需在应用程序项目中的Program显式包含 Main
方法。
IOC容器:
new object();
var x = new object();
x.xxx;
有了IOC就不需要new对象了,直接提前进行注入
依赖注入:
///
/// 构造函数
///
///
public WeatherForecastController(ILogger logger)
{
_logger = logger;
}
直接给构造函数传一个需要的参数,由IOC容器去创建,我直接使用就行了
入口文件中注册服务就是使用的这种思想!
使用配置文件
appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"a": "1",
"User": {
"UserName": "张三",
"Age": 18
}
}
在Program类中编写如下代码
var Logging = builder.Configuration["Logging"]; //获取一级目录
var AllowedHosts = builder.Configuration["AllowedHosts"]; //获取AllowedHosts的值
var Default = builder.Configuration["Logging:LogLevel:Default"]; //获取Logging:LogLevel:Default的值
var a = builder.Configuration["a"]; //获取a的值
var inta = builder.Configuration.GetValue("a"); //获取a的值并转换为int类型
//获取并序列化为某某类型 Configuration.GetSection("xxx").Get<某某类型>();
var user = builder.Configuration.GetSection("User").Get();
新建User类
public class User
{
public string UserName { get; set; }
public int Age { get; set; }
}
在控制器类中进行注入
三层架构就是为了符合“高内聚,低耦合”思想,把各个功能模块划分为表示层(UI)、业务逻辑层(BLL)和数据访问层(DAL)三层架构,各层之间采用接口相互访问,并通过对象模型的实体类(Model)作为数据传递的载体,不同的对象模型的实体类一般对应于数据库的不同表,实体类的属性与数据库表的字段名一致。
根据以上依赖模型依次添加项目引用(这里做一个示范):
建一个web api项目,项目开始之前需要导入的包,不导入包可能迁移不会成功(必须导入!!!)
根据DB First方法(详见本人博客:http://t.csdn.cn/SWVoM 11.3.2)连接数据库(如有问题可私信博主,有时间可提供帮助)
需要用到的ApplicationDbContext类和实体类
using Microsoft.EntityFrameworkCore;
namespace Models
{
///
/// 数据库上下文类
///
public class ApplicationDbContext : DbContext
{
//主要是框架在用,有时候用户也可能用到
public ApplicationDbContext()
{
}
//DbContextOptions:选项模式,实现参数动态化
public ApplicationDbContext(DbContextOptions options) : base(options)
{
}
///
/// 数据表
///
public DbSet user { get; set; } //这个属性叫什么名字,数据库中的表就叫什么名字
public DbSet weatherForecast { get; set; } //这个属性叫什么名字,数据库中的表就叫什么名字
}
}
namespace Models
{
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
public int Age { get; set; }
}
}
namespace Models
{
public class WeatherForecast
{
public int Id { get; set; }
///
/// 时间
///
public DateTime Date { get; set; }
///
/// 摄氏度
///
public int TemperatureC { get; set; }
///
/// 华氏度
///
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
///
/// 总结
///
public string? Summary { get; set; }
}
}
迁移生成数据库表之后,在数据库中增加字段信息以便后续查询
新建一个DbTestController webapi控制器
using Microsoft.AspNetCore.Mvc;
using Models;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace WebApiStudy.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class DbTestController : ControllerBase
{
//Db就相当于一个数据库,可以通过 Db.user 获取数据库中的表
private ApplicationDbContext Db { get;}
///
/// 生成一个构造函数,创建一个数据库对象db,ApplicationDbContext就是一个数据库对象创建一个数据库对象db,ApplicationDbContext就是一个数据库对象
///
///
public DbTestController(ApplicationDbContext db)
{
Db = db;
}
//查询单个用户
[HttpGet]
public List GetUsers()
{
var query = Db.user.Where(s => s.Id > 0)
.Where(s => s.UserName == "小雨").ToList();
return query;
}
}
}
启动项目执行查询,根据以下步骤即可查询到数据库中对应的内容
根据Request URL也可以进行查询,下面提供两种访问方式
编写响应实体类
namespace WebApiStudy.Models
{
public class ResultModel
{
//返回结果
public bool result { get; set; }
//错误信息
public string message { get; set; }
public ResultModel(bool result, string message)
{
this.result = result;
this.message = message;
}
}
}
编写DbTestController类的web api
using Microsoft.AspNetCore.Mvc;
using Models;
using WebApiStudy.Models;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace WebApiStudy.Controllers
{
[Route("api/[controller]")] //可以自定义路由
[ApiController]
public class DbTestController : ControllerBase
{
//Db就相当于一个数据库,Db = webapistudy,可以通过 Db.user 获取数据库中的表
private ApplicationDbContext Db { get;}
///
/// 生成一个构造函数,创建一个数据库对象db,ApplicationDbContext就是一个数据库对象
///
///
public DbTestController(ApplicationDbContext db)
{
Db = db;
}
//查询指定的单个用户 api/DbTest/GetUser
///
/// 查询名字为小雨的用户信息
///
///
[Route("[action]")] //可以自定义路由
[HttpGet]
public List GetUser()
{
var user = Db.user.Where(s => s.Id > 0)
.Where(s => s.UserName == "小雨").ToList();
return user;
}
///
/// 查询所有用户
///
///
[Route("[action]")] //可以自定义路由
[HttpGet]
public List GetAllUser()
{
var user = Db.user.Where(s => s.Id > 0)
.Where(s => s.UserName!=null).ToList();
return user;
}
// HttpPost api/DbTest/GetUserByID/1
///
/// 根据id查询用户
///
/// 用户id
///
[Route("[action]/{id}")] //可以自定义路由
[HttpPost]
public IQueryable GetUserByID(int id)
{
var user = Db.user.Where(s => s.Id == id);
return user;
}
// HttpPost api/DbTest/GetUserByName/name
///
/// 根据Name查询用户
///
/// 用户名字
///
[Route("[action]/{name}")] //可以自定义路由
[HttpPost]
public IQueryable GetUserByName(string name)
{
var user = Db.user.Where(s => s.UserName == name);
return user;
}
///
/// 修改用户年龄
///
/// 输入用户名
/// 修改用户年龄
///
[Route("[action]/{name}")] //可以自定义路由
[HttpPatch]
public ResultModel EditUser(string name,int age)
{
if (name != null)
{
var user = Db.user.Where(s => s.Id > 0)
.Where(s => s.UserName.Contains(name)).Select(s => new User { UserName = s.UserName, Id = s.Id, Age = age }).ToList().FirstOrDefault();
if (user != null)
{
Db.user.Update(user);
Db.SaveChanges();
return new ResultModel(true, "修改成功");
}
else
{
return new ResultModel(false, "修改失败,没有找到对应的用户");
}
}
return new ResultModel(false, "修改失败,输入的用户名为空");
}
///
/// 添加用户
///
///
///
///
[Route("[action]")] //可以自定义路由
[HttpPost]
public ResultModel AddUser(string name, int age)
{
Db.user.AddRange(new User
{
UserName = name,
Age = age
});
Db.SaveChanges();
return new ResultModel(true, "添加成功");
}
///
/// 根据id删除用户
///
///
///
[Route("[action]")] //可以自定义路由
[HttpPost]
public ResultModel DeleteUser(int id)
{
var user = Db.user.Where(s => s.Id == id).ToList().FirstOrDefault();
if (user == null)
{
return new ResultModel(false, "删除失败,没有找到对应的用户");
}
else
{
Db.user.Remove(user);
Db.SaveChanges();
return new ResultModel(true, "删除成功");
}
}
}
}
启动项目即可访问对应的Api
升级版
using Microsoft.AspNetCore.Mvc;
using Models;
using WebApiStudy.Models;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace WebApiStudy.Controllers
{
[Route("api/[controller]")] //可以自定义路由
[ApiController]
public class DbTestController : ControllerBase
{
//Db就相当于一个数据库,Db = webapistudy,可以通过 Db.user 获取数据库中的表
private ApplicationDbContext Db { get;}
///
/// 生成一个构造函数,创建一个数据库对象db,ApplicationDbContext就是一个数据库对象
///
///
public DbTestController(ApplicationDbContext db)
{
Db = db;
}
//查询指定的单个用户 api/DbTest/GetUser
///
/// 查询名字为小雨的用户信息
///
///
[Route("[action]")] //可以自定义路由
[HttpGet]
public List GetUser()
{
var user = Db.user.Where(s => s.Id > 0)
.Where(s => s.UserName == "小雨").ToList();
return user;
}
///
/// 查询所有用户
///
///
[Route("[action]")] //可以自定义路由
[HttpGet]
public List GetAllUser()
{
var user = Db.user.Where(s => s.Id > 0)
.Where(s => s.UserName!=null).ToList();
return user;
}
///
/// 根据id查询用户
///
/// 用户id
///
//[Route("[action]/{id}")] //可以自定义路由 HttpPost api/DbTest/GetUserByID/1
[Route("[action]")] //可以自定义路由 HttpPost api/DbTest/GetUserByID?id=1
[HttpGet]
public List GetUserByID_Get(int id)
{
var user = Db.user.Where(s => s.Id == id).ToList();
return user;
}
///
/// 根据id查询用户
///
/// 用户id
///
//[Route("[action]/{id}")] //可以自定义路由 HttpPost api/DbTest/GetUserByID/1
[Route("[action]")] //可以自定义路由 HttpPost api/DbTest/GetUserByID?id=1
[HttpPost]
public List GetUserByID_Post(int id)
{
var user = Db.user.Where(s => s.Id == id).ToList();
return user;
}
// HttpPost api/DbTest/GetUserByName/xiaoming
///
/// 根据Name查询用户
///
/// 用户名字
///
[Route("[action]/{name}")] //可以自定义路由
[HttpPost]
public IQueryable GetUserByName(string name)
{
var user = Db.user.Where(s => s.UserName == name);
return user;
}
///
/// 修改用户年龄
///
/// 输入用户名
/// 修改用户年龄
///
[Route("[action]/{name}")] //可以自定义路由
[HttpPatch]
public ResultModel EditUser(string name,int age)
{
if (name != null)
{
var user = Db.user.Where(s => s.Id > 0)
.Where(s => s.UserName.Contains(name)).Select(s => new User { UserName = s.UserName, Id = s.Id, Age = age }).ToList().FirstOrDefault();
if (user != null)
{
Db.user.Update(user);
Db.SaveChanges();
return new ResultModel(true, "修改成功");
}
else
{
return new ResultModel(false, "修改失败,没有找到对应的用户");
}
}
return new ResultModel(false, "修改失败,输入的用户名为空");
}
///
/// 添加用户
///
///
///
///
[Route("[action]")] //可以自定义路由
[HttpPost]
public ResultModel AddUser1(string name, int age)
{
Db.user.AddRange(new User
{
UserName = name,
Age = age
});
Db.SaveChanges();
return new ResultModel(true, "添加成功");
}
///
/// 添加用户
///
///
///
///
[Route("[action]")] //可以自定义路由
[HttpPost]
public ResultModel AddUser2(User user)
{
Db.user.AddRange(new User
{
UserName = user.UserName,
Age = user.Age
});
Db.SaveChanges();
return new ResultModel(true, "添加成功");
}
///
/// 根据id删除用户
///
///
///
[Route("[action]")] //可以自定义路由
[HttpDelete]
public ResultModel DeleteUserByID(int id)
{
var user = Db.user.Where(s => s.Id == id).ToList().FirstOrDefault();
if (user == null)
{
return new ResultModel(false, "删除失败,没有找到对应的用户");
}
else
{
Db.user.Remove(user);
Db.SaveChanges();
return new ResultModel(true, "删除成功");
}
}
}
}
启动项目即可访问对应的API
post和get请求的区别:
一、功能不同
1、get是从服务器上获取数据。
2、post是向服务器传送数据。
二、过程不同
1、get是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到。
2、post是通过HTTP post机制,将表单内各个字段与其内容放置在HTML HEADER内一起传送到ACTION属性所指的URL地址。用户看不到这个过程。
三、获取值不同
1、对于get方式,服务器端用Request.QueryString获取变量的值。
2、对于post方式,服务器端用Request.Form获取提交的数据。
四、传送数据量不同
1、get传送的数据量较小,不能大于2KB。
2、post传送的数据量较大,一般被默认为不受限制。但理论上,IIS4中最大量为80KB,IIS5中为100KB。
五、安全性不同
1、get安全性非常低。
2、post安全性较高。
开发中用的较多的是Transient、AddScoped
Transient
瞬态
瞬态模式,服务在每次请求时被创建你今天相了十次亲,每个都是不同的人
AddScoped
区域模式
作用域模式,服务在每次请求时被创建,整个请求过程中都贯穿使用这个创建的服务
你今天出去相了十次亲,今天这十个都是同一个人
AddSingleton
单例模式
单例模式,服务在第一次请求时被创建,其后的每次请求都沿用这个已创建的服务。
你每天都相亲十次,永远都是那同一个人
官网:https://fluentvalidation.net/
Nuget安装
FluentValidation.AspNetCore
在Program中进行依赖注入
//在Program中进行依赖注入,注册服务
builder.Services.AddFluentValidation(options =>
{
options.RegisterValidatorsFromAssembly(Assembly.GetExecutingAssembly());
});
使用AddUser2方法进行验证
///
/// 添加用户
///
///
///
///
[Route("[action]")] //可以自定义路由
[HttpPost]
public ResultModel AddUser2(User user)
{
Db.user.AddRange(new User
{
UserName = user.UserName,
Age = user.Age
});
Db.SaveChanges();
return new ResultModel(true, "添加成功");
}
FluentValidation符合开发的单一职责标准
使用方式:创建一个类UserValidator,继承AbstractValidator 泛型类,T为需要验证的实体类,甚至可以操作数据库,只需要构造函数中,然后进行数据的查询和对比
Must()自定义验证规则,使用RuleFor对每个字段单独做校验
//例子:
public class WeChatInfoInsertDtovalidator : AbstractValidator {
//可以直接在这个构造函数注入数据库上下文
public WeChatInfoInsertDtovalidator() {
//对每个字段进行单独校验
RuleFor(it => it.Appid)
.NotNull().withMessage("AppId不能为空")
.Must(v => v.contains(" ")).withMessage("AppId不能包含空格");
RuleFor(it => it.AppSecret)
.NotNull().withMessage("Appseret不能为空格")
.Must(v => v.contains(" ")).withMessage("Appseret不能包含空格");
RuleFor(it => it.code)
.NotNull().withMessage("编号不能为空")
.Must(v => v.Contains("")).withMessage("编号不能包含空格");
}
}
using FluentValidation;
using Models;
namespace WebApiStudy.Validators
{
public class UserValidator : AbstractValidator
{
public UserValidator()
{
RuleFor(it => it.UserName)
.NotNull()
.Must(v=> v.Contains("xxx"))
.WithMessage("必须包含xxx");
}
}
}
启动项目,重新进行AddUser2方法api调用
Request body
{
"id": 0,
"userName": "string",
"age": 0
}
Response body
{
"errors": {
"UserName": [
"必须包含xxx"
]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-53e12fe3d84c5c1acb1335a1fb805364-6dc373e559739a8e-00"
}
配置跨域
#region 配置跨域
builder.Services.AddCors(c =>
{
c.AddPolicy("Cors", policy =>
{
policy.AllowAnyOrigin()
.AllowAnyHeader() //Ensures that the policy allows any header
.AllowAnyMethod();
});
});
#endregion
使用跨域(注意命名需要一致:Core)
app.UseCors("Cors");
参考学习博文:http://t.csdn.cn/PZGZi
在介绍JWT之前,我们先来回顾一下利用token进行用户身份验证的流程:
这种基于token的认证方式相比传统的session认证方式更节约服务器资源,并且对移动端和分布式更加友好。其优点如下:
支持跨域访问:cookie是无法跨域的,而token由于没有用到cookie(前提是将token放到请求头中),所以跨域后不会存在信息丢失问题
无状态:token机制在服务端不需要存储session信息,因为token自身包含了所有登录用户的信息,所以可以减轻服务端压力
更适用CDN:可以通过内容分发网络请求服务端的所有资料
更适用于移动端:当客户端是非浏览器平台时,cookie是不被支持的,此时采用token认证方式会简单很多
无需考虑CSRF:由于不再依赖cookie,所以采用token认证方式不会发生CSRF,所以也就无需考虑CSRF的防御
而JWT就是上述流程当中token的一种具体实现方式,其全称是JSON Web Token,官网地址:https://jwt.io/
通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT
token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。
JWT的认证流程如下:
首先,前端通过Web表单将自己的用户名和密码发送到后端的接口,这个过程一般是一个POST请求。建议的方式是通过SSL加密的传输(HTTPS),从而避免敏感信息被嗅探
后端核对用户名和密码成功后,将包含用户信息的数据作为JWT的Payload,将其与JWT Header分别进行Base64编码拼接后签名,形成一个JWT Token,形成的JWT Token就是一个如同lll.zzz.xxx的字符串
后端将JWT Token字符串作为登录成功的结果返回给前端。前端可以将返回的结果保存在浏览器中,退出登录时删除保存的JWT Token即可
前端在每次请求时将JWT Token放入HTTP请求头中的Authorization属性中(解决XSS和XSRF问题)
后端检查前端传过来的JWT Token,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等
验证通过后,后端解析出JWT Token中包含的用户信息,进行其他逻辑操作(一般是根据用户信息得到权限等),返回结果
1、传统Session认证的弊端
我们知道HTTP本身是一种无状态的协议,这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,认证通过后HTTP协议
不会记录下认证后的状态,那么下一次请求时,用户还要再一次进行认证,因为根据HTTP协议,我们并不知道是哪个用户发出的请求,
所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在用户首次登录成功后,在服务器存储一份用户登录的信息,这份登录信
息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,
这是传统的基于session认证的过程
然而,传统的session认证有如下的问题:
每个用户的登录信息都会保存到服务器的session中,随着用户的增多,服务器开销会明显增大
由于session是存在与服务器的物理内存中,所以在分布式系统中,这种方式将会失效。虽然可以将session统一保存到Redis中,但是这样做无疑增加了系统的复杂性,对于不需要redis的应用也会白白多引入一个缓存中间件
对于非浏览器的客户端、手机移动端等不适用,因为session依赖于cookie,而移动端经常没有cookie
因为session认证本质基于cookie,所以如果cookie被截获,用户很容易收到跨站请求伪造攻击。并且如果浏览器禁用了cookie,这种方式也会失效
前后端分离系统中更加不适用,后端部署复杂,前端发送的请求往往经过多个中间件到达后端,cookie中关于session的信息会转发多次
由于基于Cookie,而cookie无法跨域,所以session的认证也无法跨域,对单点登录不适用
2、JWT认证的优势
对比传统的session认证方式,JWT的优势是:
简洁:JWT Token数据量小,传输速度也很快
因为JWT Token是以JSON加密形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持
不需要在服务端保存会话信息,也就是说不依赖于cookie和session,所以没有了传统session认证的弊端,特别适用于分布式微服务
单点登录友好:使用Session进行身份认证的话,由于cookie无法跨域,难以实现单点登录。但是,使用token进行认证的话, token可以被保存在客户端的任意位置的内存中,不一定是cookie,所以不依赖cookie,不会存在这些问题
适合移动端应用:使用Session进行身份认证的话,需要保存一份信息在服务器端,而且这种方式会依赖到Cookie(需要 Cookie 保存 SessionId),所以不适合移动端
因为这些优势,目前无论单体应用还是分布式应用,都更加推荐用JWT token的方式进行用户认证
3、JWT结构
JWT由3部分组成:标头(Header)、有效载荷(Payload)和签名(Signature)。在传输的时候,会将JWT的3部分分别进行Base64编码后用.进
行连接形成最终传输的字符串
JWTString = Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret)
JWTString=Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header)+“.”+base64UrlEncode(payload),secret)
1.Header
JWT头是一个描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存
{
“alg”: “HS256”,
“typ”: “JWT”
}
2.Payload
有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
除以上默认字段外,我们还可以自定义私有字段,一般会把包含用户信息的数据放到payload中,如下例:
{
“sub”: “1234567890”,
“name”: “Helen”,
“admin”: true
}
请注意,默认情况下JWT是未加密的,因为只是采用base64算法,拿到JWT字符串后可以转换回原本的JSON数据,任何人都可以解读其内容,因此不要构建隐私信息字段,比如用户的密码一定不能保存到JWT中,以防止信息泄露。JWT只是适合在网络中传输一些非敏感的信息
3.Signature
签名哈希部分是对上面两部分数据签名,需要使用base64编码后的header和payload数据,通过指定的算法生成哈希,以确保数据不会被篡改。首先,需要指定一个密钥(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用header中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名
HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret)
HMACSHA256(base64UrlEncode(header)+“.”+base64UrlEncode(payload),secret)
在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用.分隔,就构成整个JWT对象
注意JWT每部分的作用,在服务端接收到客户端发送过来的JWT token之后:
header和payload可以直接利用base64解码出原文,从header中获取哈希签名的算法,从payload中获取有效数据
signature由于使用了不可逆的加密算法,无法解码出原文,它的作用是校验token有没有被篡改。服务端获取header中的加密算法之后,利用该算法加上secretKey对header、payload进行加密,比对加密后的数据和客户端发送过来的是否一致。注意secretKey只能保存在服务端,而且对于不同的加密算法其含义有所不同,一般对于MD5类型的摘要加密算法,secretKey实际上代表的是盐值
4、JWT的种类
其实JWT(JSON Web Token)指的是一种规范,这种规范允许我们使用JWT在两个组织之间传递安全可靠的信息,JWT的具体实现可以分为以下几种:
nonsecure JWT:未经过签名,不安全的JWT
JWS:经过签名的JWT
JWE:payload部分经过加密的JWT
1.nonsecure JWT
未经过签名,不安全的JWT。其header部分没有指定签名算法
{
“alg”: “none”,
“typ”: “JWT”
}
并且也没有Signature部分
2.JWS
JWS ,也就是JWT Signature,其结构就是在之前nonsecure JWT的基础上,在头部声明签名算法,并在最后添加上签名。创建签名,是保证jwt不能被他人随意篡改。我们通常使用的JWT一般都是JWS
为了完成签名,除了用到header信息和payload信息外,还需要算法的密钥,也就是secretKey。加密的算法一般有2类:
对称加密:secretKey指加密密钥,可以生成签名与验签
非对称加密:secretKey指私钥,只用来生成签名,不能用来验签(验签用的是公钥)
JWT的密钥或者密钥对,一般统一称为JSON Web Key,也就是JWK
到目前为止,jwt的签名算法有三种:
HMAC【哈希消息验证码(对称)】:HS256/HS384/HS512
RSASSA【RSA签名算法(非对称)】(RS256/RS384/RS512)
ECDSA【椭圆曲线数据签名算法(非对称)】(ES256/ES384/ES512)
参考学习链接:https://gitee.com/laozhangIsPhi/Blog.Core,使用其中JwtHelper类进行修改了一下,有需要可自行使用
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace WebApiStudy.Common
{
public class JwtHelper
{
///
/// 颁发JWT字符串
///
///
///
public static string IssueJwt(TokenModelJwt tokenModel)
{
//var claims = new Claim[] //old
var claims = new List
{
/*
* 特别重要:
1、这里将用户的部分信息,比如 uid 存到了Claim 中,如果你想知道如何在其他地方将这个 uid从 Token 中取出来,请看下边的SerializeJwt() 方法,或者在整个解决方案,搜索这个方法,看哪里使用了!
2、你也可以研究下 HttpContext.User.Claims ,具体的你可以看看 Policys/PermissionHandler.cs 类中是如何使用的。
*/
new Claim("UserId",tokenModel.UserId.ToString()),
new Claim("UserName",tokenModel.UserName),
//new Claim(ClaimTypes.Role,tokenModel.Role),//为了解决一个用户多个角色(比如:Admin,System),用下边的方法
};
// 可以将一个用户的多个角色全部赋予;
if (!string.IsNullOrWhiteSpace(tokenModel.Role))
{
claims.AddRange(tokenModel.Role.Split(',').Select(s => new Claim(ClaimTypes.Role, s)));
claims.Add(new Claim("Role", tokenModel.Role));
}
//秘钥 (SymmetricSecurityKey 对安全性的要求,密钥的长度太短会报出异常)
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenModel.Secret));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var jwt = new JwtSecurityToken(
issuer: tokenModel.Issuer,
audience: tokenModel.Audience,
expires: DateTime.Now.AddSeconds(tokenModel.Expires),
signingCredentials: creds,
claims: claims
);
var jwtHandler = new JwtSecurityTokenHandler();
var token = jwtHandler.WriteToken(jwt);
return token;
}
///
/// 不检查有效性并解析
///
///
///
public static TokenModelJwt SerializeJwt(string jwtStr)
{
//不校验,直接解析token
var jwtToken = new JwtSecurityTokenHandler().ReadJwtToken(jwtStr);
var tokenJwt = JsonConvert.DeserializeObject(jwtToken.Payload.SerializeToJson());
return tokenJwt;
}
///
/// 令牌
///
public class TokenModelJwt
{
public int UserId { get; set; }
public string UserName { get; set; }
public string Issuer { get; set; }
public string Audience { get; set; }
public string Secret { get; set; }
public int Expires { get; set; }
public string Role { get; set; }
}
}
}
报错解决:
在配置文件中写上我们需要的配置文件
"Jwt": {
"Secret": "sghkadhkabhahjbdskanladhnjsfbdsknadnkjsb", //不要太短,建议16位+
"Issuer": "xiaoshu",
"Audience": "xiaoshu"
}
新建一个JwtController控制器
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using WebApiStudy.Common;
using static WebApiStudy.Common.JwtHelper;
namespace WebApiStudy.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class JwtController : ControllerBase
{
//需要读取配置文件,创建一个构造器
public JwtController(IConfiguration configuration)
{
Configuration = configuration;
}
private IConfiguration Configuration { get; }
[HttpPost]
public string CreateToken()
{
var tokenModel = Configuration.GetSection("Jwt").Get();
tokenModel.UserName = "张三";
tokenModel.UserId = 1;
tokenModel.Role = "Admin";
return JwtHelper.CreateJwt(tokenModel);
}
}
}
启动项目,获取jwt字符串
在Program中添加jwt配置代码,然后运行项目
#region jwt配置
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
Description = "在下框中输入请求头中需要添加的Jwt授权Token:Bearer Token",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
BearerFormat = "Jwt",
Scheme = "Bearer"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},new string[] { }
}
});
#endregion
然后在program中再加上Jwt的验证(注册服务)
#region jwt验证中间件
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
var tokenModel = builder.Configuration.GetSection("Jwt").Get();
var secretByte = Encoding.UTF8.GetBytes(tokenModel.Secret);
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuer = tokenModel.Issuer,
ValidateAudience = true,
ValidAudience = tokenModel.Audience,
ValidateLifetime = true,
IssuerSigningKey = new SymmetricSecurityKey(secretByte)
};
options.Events = new JwtBearerEvents
{
OnChallenge = context =>
{
return Task.FromResult(0);
},
OnForbidden = context =>
{
return Task.FromResult(0);
}
};
});
#endregion
安装以下包:
在最后加上
//jwt身份验证中间件
app.UseAuthentication();
在JwtController控制器中新增一个方法来测试是否授权成功
///
/// 解析token
///
///
[HttpGet]
[Authorize]
public IActionResult DeToken() //[FromHeader]:从请求头中获取数据
{
return Ok();
}
启动项目,先获取jwt字符串
进行jwt认证
输入格式:Bearer+(空格)+jwt字符串,点击授权即可,然后关闭状态栏
测试是否授权成功
解析token并返回数据
///
/// 解析token并返回数据
///
///
[HttpGet]
[Authorize]
public IActionResult GetJwtToken([FromHeader] string Authorization) //[FromHeader]:从请求头中获取数据
{
//Bearer :Bearer后面接了一个空格
var token = JwtHelper.SerializeJwt(Authorization.Replace("Bearer ", ""));
return Ok(token);
}
启动项目重新进行授权并返回数据
如果修改角色
///
/// 解析token并返回数据
///
///
[HttpGet]
[Authorize(Roles ="xiaoshu")]
public IActionResult GetJwtToken([FromHeader] string Authorization) //[FromHeader]:从请求头中获取数据
{
var token = JwtHelper.SerializeJwt(Authorization.Replace("Bearer ", ""));
return Ok(token);
}
则会报错403:角色权限不符合
新建一个用户数据接收的类(不能用实体类去接收,因为它涉及到数据库的一些操作不能随意更改)
namespace WebApiStudy
{
///
/// 接收前端数据的类,可指定接收什么样的
///
public class LoginDto
{
public string Code { get; set; }
public string Password { get; set; }
}
}
在JwtController中增加一个参数,用于接收前端传的参数
[HttpPost]
public string CreateToken(LoginDto dto)
{
var tokenModel = Configuration.GetSection("Jwt").Get();
tokenModel.UserName = "张三";
tokenModel.UserId = 1;
tokenModel.Role = "Admin";
return JwtHelper.CreateJwt(tokenModel);
}
启动项目,即可看到规定需要传入哪些值
依赖注入
//进行缓存的依赖注入
builder.Services.AddMemoryCache();
新建一个缓存控制器
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
namespace WebApiStudy.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class MemoryCacheController : ControllerBase
{
private readonly IMemoryCache cache;
//通过构造函数的方法获取
public MemoryCacheController(IMemoryCache cache)
{
this.cache = cache;
}
[HttpPost]
public string Set(string Name)
{
var key = Guid.NewGuid().ToString();
cache.Set(key, Name,TimeSpan.FromSeconds(100)); //TimeSpan.FromSeconds(100):缓存有效期
return key;
}
[HttpGet]
public string Get(string key)
{
return cache.Get(key).ToString();
}
}
}
通过Name生成一个GUID
从缓存中通过GUID获取Name
当缓存有效期过期时
编写一个控制器
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace WebApiStudy.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class FileController : ControllerBase
{
[HttpPost]
public bool Upload(IFormFile file)
{
var filename = file.FileName;
var path = @"D:\C#学习\Test\";
using (var stream = new FileStream(path + filename, FileMode.Create))
{
file.CopyTo(stream);
}
return true;
}
}
}
执行进行复制文件操作
功能:对象与对象之间的映射
安装automapper.extensions.microsoft.dependencyinjection
准备工作
创建一个User映射类
namespace WebApiStudy.Dto
{
///
/// User映射类
///
public class UserDto
{
public int Id { get; set; }
}
}
创建一个MapperProfile类
using AutoMapper;
using Models;
using WebApiStudy.Dto;
namespace WebApiStudy.Profiles
{
public class MapperProfile:Profile
{
public MapperProfile()
{
CreateMap();
}
}
}
在program中进行依赖注入
//进行AutoMapper依赖注入
builder.Services.AddAutoMapper(typeof(WebApiStudy.Profiles.MapperProfile));
新建MapperController控制器
using AutoMapper;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Models;
using WebApiStudy.Dto;
namespace WebApiStudy.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class MapperController : ControllerBase
{
public MapperController(IMapper mapper)
{
Mapper = mapper;
}
public IMapper Mapper { get; }
[HttpPost]
public UserDto Test(User user)
{
//User, UserDto:第一个参数是源,第二个参数是目标
return Mapper.Map(user);
}
}
}
进行测试并查看返回结果
复杂映射
using AutoMapper;
using Models;
using WebApiStudy.Dto;
namespace WebApiStudy.Profiles
{
public class MapperProfile:Profile
{
public MapperProfile()
{
//CreateMap();
//复杂映射
//CreateMap().ForMember(dest=> dest.Id,opt=>opt.MapFrom(user => user.Id+"名字"));
CreateMap().ForMember(dest=> dest.Id,opt=>opt.MapFrom(user => user.Id+1));
}
}
}
excel导入导出学习链接:https://www.cnblogs.com/codelove/p/15117226.html
ISS发布学习链接:【Net 6 WebApi简单入门-哔哩哔哩】 https://b23.tv/KthBIg9
关于构造函数中的参数创建属性还是字段的探讨:
创建属性
public JwtController(IConfiguration configuration)
{
Configuration = configuration;
}
private IConfiguration Configuration { get; }
创建字段
private readonly IConfiguration configuration;
//需要读取配置文件,创建一个构造器
public JwtController(IConfiguration configuration)
{
this.configuration = configuration;
}
总结:
两种方法创建的都是一个IConfiguration对象:configuration,并且都是只读的
区别:
(1)属性是项目一启动就开辟空间(在内存中指向一个地址),没有赋值的时候为null;如果不赋值,编译不会报错,但是运行会报值空异常
(2)字段是需要调用构造函数的时候才会去给它开辟空间顺便就赋值了,不调用的时候为null