基于.net6.0WebApi教程
- 这次笔记适合新手学习,没有WebApi经验,有一定的C#基础
- 没有严格的代码规范,只有一些简单的理论和操作
- 文章中有什么不懂的地方可以提出,尽力会解决
- 文章中有什么错误的地方欢迎大家指出改正
Web API
创建Web Api项目
选择“ASP.NET Core Web API”模板
添加模型类
- 在“解决方案资源管理器”中,右键单击项目。 选择“添加” > “新建文件夹”。 将文件夹命名为 Models。
- 右键单击 Models 文件夹,然后选择“添加” > “类” 。 将类命名为 TodoItem,然后选择“添加”。
Id属性用作关系数据库中的唯一键。- 模型类可位于项目的任意位置,但按照惯例会使用 Models 文件夹。
添加数据库上下文
添加NuGet包
“Microsoft.EntityFrameworkCore”
添加TodooContext数据库上下文
右键单击 Models 文件夹,然后选择“添加” > “类” 。 将类命名为 TodoContext,然后单击“添加”。
using Microsoft.EntityFrameworkCore;
namespace TodoApi.Models
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}
public DbSet<TodoItem> TodoItem { get; set; } = null!;
}
}
数据库连接字符串
在appsettings.json文件中添加连接字符串ConnectionStrings
"ConnectionStrings": {
//本地数据库
"TOdoContext": "Server=(localdb)\mssqllocaldb;Database=TOdo;Trusted_Connection=True;MultipleActiveResultSets=true"
//服务器数据库
"TodoContext": "Server=xxx.xxx.xxx.xxx;Database=Todo;UId=xxx;Pwd=xxx"
}
创建基架或者手动添加包
右键Controller文件夹->添加->新建基架的项目
选择API->基于EntityFramework的API控制器
选择用到的模型类和数据库上下文
使用基架创建控制器会自动安装以下包:
Microsoft.EntityFrameworkCore.SqlServerMicrosoft.EntityFrameworkCore.ToolsMicrosoft.VisualStudio.Web.CodeGeneration.Design
也可以手动不用基架
注册数据库上下文
.net6 下是没了StartUp这个类,东西都写在了Program
//builder.Configuration.GetConnectionString("TodoContext"))引用数据库上下文的连接字符串
builder.Services.AddDbContext<TodoContext>(ops=>ops.UseSqlServer(builder.Configuration.GetConnectionString("TodoContext")));
数据迁移
打开程序包管理器控制台 第一次迁移
Add-Migration init
Add-Migration 做添加数据迁移文件,这时候还未对数据库进行任何操作,init表示初始化,也就是这个快照文件的名字,后面自己定义合理的名字 以后的迁移,EF会通过数据模型与快照文件进行对比来确定已更改的内容
Update-Database
Update-Database 这是根据最新的迁移快照修改数据库,可以在后面加个空格然后指定迁移文件
路由和URL
[Route("api/[controller]/[action])"]
会自动将[controller]替换为当前控制器的名字[action]指的就是这个控制器下的方法了
放到控制器类名上面就能给这个API设置路由,URL的访问路径是:localhost:xxxx/api/控制器名/方法
例如:http://localhost:5009/api/TodoItem/GetTodoItem
关于请求方式这件事
此篇文章所用的请求方式有:GET(用于返回数据实体)、POST(用于添加或修改数据)、DELETE(用于删除数据)
在方法的上一行添加[HttpGet]就能指定他的请求方式了,后面就能用get请求请求这个api了
[HttpGet]
public Task<ActionResult<TodoItem>> Test(){}
如果请求这里在加点料那就是[HttpGet("test")]就会把这个test追加到路径里了
URL的路径就是http://localhost:5009/api/TodoItem/Test/test
[HttpGet("id")]做一个占位符变量,做请求的时候需要把id放进去
比如这个http://localhost:5009/api/TodoItem/Test/666
可以在DELETE的时候用哦
也可以搜索指定的数据
返回值
Get方法的返回值类型是ActionResult<T>类型,这样会自动转化为JSON
关于增删改查这件事
会在根据基架创建的控制器稍微展开一丢丢讲解
添加和修改
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
//当传入的对象没有id时就是一串长长的0了,就自己创建了一个,实现添加的操作了
if(todoItem.Id == Guid.Parse("00000000-0000-0000-0000-000000000000"))
{
//新建一个Id
todoItem.Id = Guid.NewGuid();
//把要添加的实体数据放入Add中,这仅仅跟踪这条数据而没有对数据库进行操作
_context.TodoItem.Add(todoItem);
}else
{
//否则的话就对这条数据进行更新了咯
_context.Update(todoItem);
}
//这个方法就是把上下文中的所有更改保存到数据库中了
await _context.SaveChangesAsync();
//给GetTodoItem这个方法传个Id来查找然后返回
return CreatedAtAction("GetTodoItem", new { id = todoItem.Id }, todoItem);
}
添加成功的话返回的状态码是201
删除
//
[HttpGet("{id}")]
public async Task<IActionResult> DeleteTodoItem(Guid id)
{
//查找这个id的实体
var todoItem = await _context.TodoItem.FindAsync(id);
//避免出错就判断一下
if (todoItem == null)
{
return NotFound();
}
//这里需要放入实体数据进去跟踪所以上面就查找了咯
//_context.TodoItem.Remove(todoItem);
//常规的保存哈
await _context.SaveChangesAsync();
return NoContent();
}
删除的时候只需要把id传过去就好了
查询
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItem()
{
return await _context.TodoItem.ToListAsync();
}
原来就长这样的很简单都看得懂了吧 稍微修改一下,比如排序,筛选,起始和个数用来做分页
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItem(int start,int length,bool sort,string search)
{
//先获取一下数据
List<TodoItem> todoItems = await _context.TodoItem.ToListAsync();
//简单的写一下
//如果有搜索的话那就搜一下
if(!string.IsNullOrEmpty(search))
{
todoItems = todoItems.Where(x => x.Name.Contains(search)).ToList();
}
//简单的排下序,就按名字吧,可以继续添加参数来
if(sort)
{
todoItems = todoItems.OrderBy(x => x.Name).ToList();
}
//如果设置了长度的话那就这样咯
if(length > 0)
{
todoItems = todoItems.Skip(start).Take(length).ToList();
}
return todoItems;
}
小小的改变
从这里开始我就不用我就把TodoItem改成Student,后面就会有关联关系,学生班级专业这样子的,所以了解了上面的一些基础创建,尝试重新创建一个吧,加深印象
学生类
public class Student
{
public Guid Id {get;set;}
public string Name {get;set;}
public DateTime? AdmissionTime { get; set; }
public int Age { get; set; }
public string Email { get; set; }
public SexEnum Sex { get; set; }
public Classes Classes { get; set; }
}
验证特性
验证特性可以给模型属性添加验证,把特性放到属性的上面一行就行了
[StringLength(12)]//字符串长度
[Required]//非NULL
public string Name {get;set;}
docs.microsoft.com/zh-cn/aspne…
DTO
目前来讲获取数据的时候它是会把实体里的所有属性全都列出来
我可能并不需要某些属性,所以我想把它隐藏掉,省略某些属性能减少负载大小,避免过度发布的弱点
创建DTO文件
新建一个文件夹用来存放DTO文件,创建个DTO文件,里面的属性基本上和对应的实体模型差不多
只需要把显示显示的属性写出来就行了,这很显然不是一个好的写法,属性多起来就很麻烦还可能漏掉点什么,不过这是有办法的,有一个AutoMapper的库有相关操作
[HttpGet]
public async Task<ActionResult<IEnumerable<StudentDTO>>> GetStudent()
{
//有关联的话就要用Include了,然后.select返回一个dto类
List<StudentDTO> Students = await _context.Student.Include(x => x.Classes).Select(x => new StudentDTO
{
//相对应的赋值
Id = x.Id,
Name = x.Name,
ClassesName = x.Classes.Name
}).ToListAsync();
return Students;
}
最后取出来的结果
使用内置函数自动映射
//在后面添加以下就行了
_context.Student.Add(student).CurrentValues.SetValues(studentDTO);
使用AutoMapper自动映射属性
安装AutoMapper
注入
在Program.cs文件下添加以下注入代码
builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
创建AutoMapper配置文件
在根目录创建一个名为Profiles的文件夹,然后再文件夹中创建一个类
public class MapperProfile:Profile
{
//创建个构造函数
public MapperProfile()
{
//创建Map,左边是实体模型类,右边是DTO
CreateMap<Student, StudentDTO>();
}
}
初次在控制器使用AutoMapper
创建一个mapper
再写一个Get方法来测试一下
[HttpGet]
public async Task<IEnumerable<StudentDTO>> Get()
{
var resule = await _context.Student.Include(x => x.Classes).ToListAsync();
//尖括号里面写的是它的返回类型
//直接把实体数据放进括号
return _mapper.Map<IEnumerable<StudentDTO>>(resule);
}
测试返回结果
总结
- DTO的属性名字 = 关联实体类+属性名字,比如ClassesName就会自动关联Classes.Name
- 如果指定是某个关联属性的话需要在
CreateMap里设置,比如属性名不对应的时候,或者c=>c.Classes.Name + "测试"后面就会多出测试两个字
//假设DTO里有个ClassesName1,需要把Classes.Name映射过去,有多个就做多个.ForMember()
CreateMap<Student,StudentDTO>().ForMember(a=>a.ClassesName1,b=>b.MapFrom(c=>c.Classes.Name));
//学生的性别用的枚举,可以这样做,把Sex和SexNam互换
CreateMap<Student, StudentDTO>().ForMember(x => x.SexName, y => y.MapFrom(a => a.Sex));
CreateMap<StudentDTO, Student>().ForMember(x=>x.Sex ,y=>y.MapFrom(a=>a.SexName));
HTTP状态
200状态码
200作为一个成功的状态码
用个获取数据来做例子,如果它找到数据了,那就是正常的回应200
我添加搜索用户它找不到他也是200,这样我们就添加点信息告诉使用者目前的情况
这个返回类型改成了这样子
Task<IActionResult>
public async Task<IActionResult> GetStudent(int start,int length,bool sort,string search)
返回的 如果没有这个用户返回成功状态码,但是添加了信息提醒,正常就返回正常的数据
if (Students.Count == 0)
return Ok("没有这个用户");
return Ok(Students);
404状态码
//在delete的时候找不到用户可以这样传
return NotFound("找不到用户");
结构化传参
一开始设置get穿得参数有很多,需要更多的参数的时候就会变得很长
public async Task<ActionResult<IEnumerable<TodoItem>>> GetStudent(int start,int length,bool sort,string search)
- 现在修改一下
- 创建一个
Parameter文件夹用来存放参数类型 - 创建一个
SelectParameter类用来存放我们Get请求时的参数
public class SelectParameter
{
public string? Search { get; set; }
public bool Sort { get; set; }
public int Start { get; set; }
public int Length { get; set; }
public int? MinAge { get; set; }
public int? MaxAge { get; set; }
private string _age;
/// <summary>
/// 年龄段 1-18
/// </summary>
public string? Age { get { return _age; }
set {
if(valiue !=null)
{
//正则表达式判断一下是不是需要的形式
Regex regex = new Regex(@"^\d*-\d*$");
if(regex.Match(value).Success)
{
//分割一下分别放入两个属性里
MinAge = Int32.Parse(value.Split("-")[0]);
MaxAge = Int32.Parse(value.Split("-")[1]);
}
}
_age = value;
}
}
}
- 回到控制器改一下Get方法
//因为我们是Get形式传参,一般会在写在URL里,所以来源方式就写成了[FromQuery]
public async Task<IActionResult> GetStudent([FromQuery] SelectParameter select)
{
if(select.MaxAge !=null)
{
Students = Students.Where(x => x.Age >= select.MinAge && x.Age <= select.MaxAge).ToList();
}
if (Students.Count == 0)
return Ok("没有这个用户");
return Ok(Students);
}
常用来源标签功能
如果不设置[FromQuery]的话,就会自动的设置成通常使用的,类的形式的话就会是[FromBody],所以上面那个需要自己手动的设置一下,不设置的时候会出现415Unsupported Media Type这个错误
[FromRoute]
这预设了一个参数[FromRoute] URL是这样的http://localhost:5009/api/Student/GetFrom/test/666
//Route这里有{参数}的时候就会预设FromRoute了
[HttpGet("test/{str}")]
public List<string> GetFrom(string str)
{
List<string> list = new List<string>();
list.Add(str);
return list;
}
如果指定了[FromQuery]那url就是http://localhost:5009/api/Student/GetFrom/test/666?str=6666
[HttpGet("test/{str}")]
public List<string> GetFrom([FromQuery] string str)
{
List<string> list = new List<string>();
list.Add(str);
return list;
}
[FromQuery]
将刚才的测试代码稍微修改一下 URL:http://localhost:5009/api/Student/GetFrom?str=66655 可以看到和上面有点点不同,需要把给个?参数名=参数
//这个就是预设[FromQuery]
[HttpGet]
public List<string> GetFrom(string str)
{
List<string> list = new List<string>();
list.Add(str);
return list;
}
[FromBody]
使用类来做类型的时候就用到了,在做post的时候用到很多的,用[FromBody]的时候传的值需要时JSON才可行
[HttpGet]
public List<string> GetFrom(SelectParameter select)
{
List<string> list = new List<string>();
list.Add(select.Search);
return list;
}
[FromFrom]
在mvc的时候做表单提交submit的时候就是预设这玩意儿
- 当Route上有变量的时候,
[HttpGet("test/{str}")]就会预设[FromRoute] - 没有变量的时候,
[HttpGet("test")]就是预设[FromQuery] - 当我是个类的时候
Test(SelectParameter select)就会预设[FromBody] - 做表单提交的时候submit,这个时候才会用到
[FromFrom]
总结
到这里就结束了,能简单的创建个api,这一章的代码写的有点low乱乱的,都是在写demo记录一些基础知识,也是局限于我的水平,下一章会尽量先规划好,一起期待下一章吧^∀^·
Gitee:gitee.com/zzh16430/as…