基于.net6创建WebApi笔记

1,446 阅读9分钟

基于.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文件夹->添加->新建基架的项目 image.png

选择API->基于EntityFramework的API控制器 image.png 选择用到的模型类和数据库上下文 image.png 使用基架创建控制器会自动安装以下包:

  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Tools
  • Microsoft.VisualStudio.Web.CodeGeneration.Design

也可以手动不用基架

注册数据库上下文

.net6 下是没了StartUp这个类,东西都写在了Program

//builder.Configuration.GetConnectionString("TodoContext"))引用数据库上下文的连接字符串
builder.Services.AddDbContext<TodoContext>(ops=>ops.UseSqlServer(builder.Configuration.GetConnectionString("TodoContext")));

数据迁移

image.png

打开程序包管理器控制台 第一次迁移

Add-Migration init

Add-Migration 做添加数据迁移文件,这时候还未对数据库进行任何操作,init表示初始化,也就是这个快照文件的名字,后面自己定义合理的名字 以后的迁移,EF会通过数据模型与快照文件进行对比来确定已更改的内容

Update-Database

Update-Database 这是根据最新的迁移快照修改数据库,可以在后面加个空格然后指定迁移文件

路由和URL

image.png

[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

目前来讲获取数据的时候它是会把实体里的所有属性全都列出来

image.png 我可能并不需要某些属性,所以我想把它隐藏掉,省略某些属性能减少负载大小,避免过度发布的弱点

创建DTO文件

新建一个文件夹用来存放DTO文件,创建个DTO文件,里面的属性基本上和对应的实体模型差不多

image.png

只需要把显示显示的属性写出来就行了,这很显然不是一个好的写法,属性多起来就很麻烦还可能漏掉点什么,不过这是有办法的,有一个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;
        }

最后取出来的结果 image.png

使用内置函数自动映射

//在后面添加以下就行了
_context.Student.Add(student).CurrentValues.SetValues(studentDTO);

使用AutoMapper自动映射属性

安装AutoMapper

image.png

注入

Program.cs文件下添加以下注入代码

builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());

创建AutoMapper配置文件

在根目录创建一个名为Profiles的文件夹,然后再文件夹中创建一个类

public class MapperProfile:Profile
    {
        //创建个构造函数
        public MapperProfile()
        {
            //创建Map,左边是实体模型类,右边是DTO
            CreateMap<Student, StudentDTO>();
        }
    }

初次在控制器使用AutoMapper

创建一个mapper

image.png

再写一个Get方法来测试一下

[HttpGet]
        public async Task<IEnumerable<StudentDTO>> Get()
        {
            var resule = await _context.Student.Include(x => x.Classes).ToListAsync();
            //尖括号里面写的是它的返回类型
            //直接把实体数据放进括号
            return _mapper.Map<IEnumerable<StudentDTO>>(resule);
        }

测试返回结果

image.png

总结

  • 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 image.png 我添加搜索用户它找不到他也是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);

image.png

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…