.NET 之 WebApi学习笔记 (持续更新)

118 阅读4分钟

理解项目结构

我们创建一个 Web API 项目之后,初始项目结构是这样的,如下图:

webapi1.png

  • Program.cs 文件代码分析:只保留几个主要代码所有 app.Usexxxx 的语句都用于添加中间件组件ASP.NET Core 只是一个中间件管道

    namespace WebApiDemo
    {
        public class Program
        {
            public static void Main(string[] args)
            {
                // 使用构建器
                var builder = WebApplication.CreateBuilder(args);
                // 构建应用程序
                var app = builder.Build();
                // 告诉应用程序 如果收到HTTP请求,重定向到HTTPS
                app.UseHttpsRedirection();
                // 持续运行
                app.Run();
            }
        }
    }
    

运行之后,控制台打印信息如下图

webapi2.png

  • Properties (属性)文件里面的launchSettings.json 配置文件,配置端口

    
    {
      "$schema": "https://json.schemastore.org/launchsettings.json",
      "profiles": {
        "http": {
          "commandName": "Project",
          "dotnetRunMessages": true,
          "launchBrowser": false,
          "applicationUrl": "http://localhost:5153", // HTTP 端口配置
          "environmentVariables": {
            "ASPNETCORE_ENVIRONMENT": "Development"
          }
        },
        "https": {
          "commandName": "Project",
          "dotnetRunMessages": true,
          "launchBrowser": true, // 运行时是否打开浏览器
          "applicationUrl": "https://localhost:7067;http://localhost:5153", // HTTPS 端口配置
          "environmentVariables": {
            "ASPNETCORE_ENVIRONMENT": "Development"
          }
        }
      }
    }
    

1、Controller 控制器 创建 Web API

1*、创建最小api

  • 举个栗子

    namespace WebApiDemo
    {
        public class Program
        {
            public static void Main(string[] args)
            {
                // 使用构建器
                var builder = WebApplication.CreateBuilder(args);
                // 构建应用程序
                var app = builder.Build();
                // 告诉应用程序 如果收到HTTP请求,重定向到HTTPS
                app.UseHttpsRedirection();
    ​
                // 最小API
                // Get请求
                app.MapGet("/", () => "Hello World!");
    ​
                // Get请求
                app.MapGet("/hello/{name}", (string name) => $"Hello {name}!");
    ​
                // Post请求,
                app.MapPost("/hello/{name}", (string name) => $"POST:Hello {name}!");
    ​
                // Put请求
                app.MapPut("/hello/{name}", (string name) => $"POST:Hello {name}!");
    ​
                // Delete请求
                app.MapDelete("/hello/{name}", (string name) => $"DELETE:Hello {name}!");
    ​
                // 持续运行
                app.Run();
            }
        }
    }
    

2、创建一个控制器

1、创建控制器(Controller)

Controller 是 ASP.NET Core Web API 中用于处理 HTTP 请求的组件,一个 Controller 通常负责一类相关业务,例如用户管理、订单管理、人员管理等。

1、创建 Controllers 文件夹

一般在项目根目录下创建一个 Controllers 文件夹,用于存放所有控制器类。

2、创建控制器类
  • 例如,在 Controllers 文件夹中新建一个 PersonController 类:
3、将普通类变成 Web API 控制器

要让 PersonController 能够处理 HTTP 请求,需要完成以下两步

  • 1、继承 ControllerBase

    ControllerBase 是所有 Web API 控制器的基类。Web API 项目一般都继承 ControllerBase;如果是 MVC 页面项目,则通常继承 Controller

    
    public class PersonController:ControllerBase { }
    
  • 2、添加 [ApiController] 特性

    告诉 ASP.NET Core:这是一个 Web API 控制器。

    
    [ApiController]
    public class PersonController : ControllerBase { }
    
  • 3、配置路由

    使用 [Route] 指定访问路径。

    api:接口统一前缀[controller]:表示控制器名称(去掉 Controller 后缀)

    下面代码对应的最终访问路径为:/api/person

    
    [ApiController] 
    [Route("api/[controller]")]
    public class PersonController : ControllerBase { }
    
  • 4、创建操作方法 Action

    控制器中的每一个公开方法(Action)通常对应一个接口,用于处理某一种 HTTP 请求。

    [HttpGet]:处理 GET 请求

    
    using Microsoft.AspNetCore.Mvc;
    ​
    [ApiController]
    [Route("api/[controller]")]
    public class PersonController : ControllerBase
    {
        [HttpGet]
        public string GetPerson()
        {
            return "Hello Person";
        }
    }
    

2、将 HTTP 请求映射到操作方法(Action)

在 ASP.NET Core Web API 中,HTTP 请求需要通过路由匹配,找到对应的 Controller 和 Action 方法

1、注册 Conroller 相关服务

在入口文件 Program.cs 中,通过构建器注册 Controller 所需的服务builder.Services.AddControllers(); 告诉 ASP.NET Core:当前项目需要使用 Controller,请准备好运行 Controller 所需的相关功能注意:AddControllers() 只是让 Controller 具备被创建和执行的能力,并不会自动让 Controller 的路由生效

  • 作用:注册 Controller 的创建和执行机制 注册模型绑定功能 注册参数校验功能 注册 JSON 序列化和反序列化功能 注册 Action 执行、过滤器等 MVC 相关服务 使 Controller 可以通过依赖注入获取所需对象、
2、注册 Controller 路由

创建应用程序对象之后,通过 app.MapControllers(); 注册 Controller 路由告诉 ASP.NET Core:读取项目中的 Controller 路由,并将符合条件的 HTTP 请求交给对应的 Action 方法处理

  • 作用

    读取 Controller 和 Action 上配置的特性路由将 HTTP 请求方式和请求路径映射到对应的 Action将 Controller 路由添加到 ASP.NET Core 的端点路由系统中使客户端能够通过 URL 访问 Controller 中的接口

  • 举个栗子

    • PersonController.cs
    
    using Microsoft.AspNetCore.Mvc;
    ​
    [ApiController]
    // Controller 基础路由
    [Route("api/[controller]")]
    public class PersonController : ControllerBase
    {
        [HttpGet]
        public string GetPerson()
        {
            return "Hello Person";
        }
    ​
        // 需要 id 参数 GET /api/user/{id}
        [HttpGet("{id}")]
        public string GetPersonById(int id)
        {
            return $"Hello Person with ID: {id}";
        }
    }
    
    • Program.cs
    
    namespace WebApiDemo
    {
        public class Program
        {
            public static void Main(string[] args)
            {
                // 使用构建器
                var builder = WebApplication.CreateBuilder(args);
    ​
                // 注册控制器服务
                builder.Services.AddControllers();
    ​
                // 构建应用程序
                var app = builder.Build();
                // 告诉应用程序 如果收到HTTP请求,重定向到HTTPS
                app.UseHttpsRedirection();
    ​
                // 注册控制器
                app.MapControllers();
    ​
                // 持续运行
                app.Run();
            }
        }
    }
    

2、模型绑定

定义:将 HTTP 请求中的数据映射到操作方法参数的过程

过程:读取 HTTP 请求 ---> 找到数据 ---> 转换类型 ---> 赋值给参数可以理解为: HTTP 请求 ---> Model Binder (模型绑定器) ---> Controller (方法参数)

1、HTTP 请求中 数据来源

1、Route(路由)

可以写 [FromRoute] 来限制参数只能来着路由,如果来自其他地方就报错

  • 举个栗子

modelbinder4.png

```

GET https://localhost:7165/api/person/1/女
```

-   Controller

```

[HttpGet("{id}/{gender}")]
public string GetPerson(string id,[FromRoute] string gender)
{
    return $"Helloe,the person{id}'s gender is {gender}";
}
```

2、Query(查询字符串)

可以写 [FromQuery] 来限制参数来源

  • 举个栗子

modelbinder2.png

```

GET https://localhost:7165/api/person/1?gender=女
```

-   Controller

```

[HttpGet("{id}")]
public string GetPerson(string id,[FromQuery] string gender)
{
    return $"Helloe,the person{id}'s gender is {gender}";
}
```
  • 多个参数 栗子

    
    GET https://localhost:7165/api/person/1?gender=女&name=Omg
    
    • Contoller
    
    [HttpGet("{id}")]
    public string GetPerson(string id,[FromQuery] string gender,string name)
    {
        return $"Helloe,the person{id}'s gender is {gender} and name is {name}";
    }
    

3、Body(请求体)

请求体 也就是 我们前端平时写的 JSON 格式一般用于 POST 和 PUT 请求可以用 [FromBody] 限制参数来源

后端首先 需要先创建 类

  • 举个栗子

modelbinder3.png

```

POST https://localhost:7165/api/person
```

-   传参 JSON

```

{
    "id":1,
    "name":"YOUNG",
    "gender":"女",
    "age":22,
    "address":"重庆市渝北区"
}
```

-   Dto 类

```

namespace ControllerDemo1.Models
{
    public class PersonDto
    {
        public int Id { get; set; }
        public required string Name { get; set; }
        public string? Gender { get; set; }
        public int? Age { get; set; }
        public string? Address { get; set; }
​
    }
}
```

-   Controller

```

[HttpPost]
public string PostPerson([FromBody] PersonDto person)
{
    return $"POST信息:Id={person.Id},Name={person.Name},Gender={person.Gender},Age={person.Age},                  Address={person.Address}";
    // POST信息:Id=1,Name=YOUNG,Gender=女,Age=22,Address=重庆市渝北区
}
```

4、Form(表单)

可以通过 [FromForm] 限制参数来源

  • 举个栗子

modelbinder5.png

-   Controller

```

[HttpPost]
public string PostPerson([FromForm] PersonDto person)
{
    return $"POST信息:Id={person.Id},Name={person.Name},Gender={person.Gender},Age={person.Age},                  Address={person.Address}";
    // POST信息:Id=1,Name=YOUNG,Gender=女,Age=22,Address=重庆市渝北区
}
```

5、Header(请求头)

可以通过 [FromHeader] 限制参数来源

  • 举个栗子

modelbinder1.png

```

GET https://localhost:7165/api/person
```

-   Controller

```

[HttpGet]
public string GetAuthorization([FromHeader(Name = "MyAuthorization")] string MyAuthorization)
{
    return $"The Authorization is {MyAuthorization}";
    // The Authorization is this is MyAuthorization
}
```

3、数据注解 模型验证

定义:当客户端提交数据到 API 时,后端自动检查这个数据是否符合要求一般发生在 API层

最常见的验证方式 DataAnnotations (.NET自带的一套验证特性)命名空间:


using System.ComponentModel.DataAnnotations;
  • 举个栗子 (使用数据注解 进行 单个 属性验证)

    
    using System.ComponentModel.DataAnnotations;
    ​
    namespace WebApiDemo1.Dtos;
    ​
    public class PersonDtos
    {
        public required int Id { get; set; }
    ​
        [Required(ErrorMessage ="name is required!!!")] // 必须填写
        //[StringLength(20)] // 限制字符串长度
        [StringLength(20, MinimumLength = 2, ErrorMessage = "长度最小必须为2")]
        public required string Name { get; set; }
    ​
        [Range(0, 100, ErrorMessage = "年龄范围在0-100")]
        public int Age { get; set; }
    ​
        [MinLength(5)] // 最短
        [MaxLength(50)] // 最长
        public string? Description { get; set; }
    ​
        [EmailAddress] // 邮箱验证格式
        public string? Email { get; set; }
    }
    
  • 注意

    区分 requied 和 [Required]

    required 是 C# 语言特性,创建对象时必须初始化,否则编译器报警

    [Required] 其实是 [RequiredAttribute] 缩写

4、ValidationAttribute 模型验证

1、步骤

  • 1、创建一个自定义 Attribute 类,并继承 ValidationAttribute

    一般自定义验证类都会以 Attribute 结尾,实际使用的时候可以不写 Attribute

    • 举个栗子

    EducationValidatateAttribute : ValidationAttribute继承 ValidationAttribute 就说明 EducationValidatateAttribute 是一个验证属性

    
    using System.ComponentModel.DataAnnotations;
    ​
    namespace WebApiDemo1.Models.Validations;
    ​
    public class EducationValidatateAttribute : ValidationAttribute
    {
    }
    
  • 2、重写 IsValid 方法

    • ValidationResult 是方法的返回类型

      • 验证成功:return ValidationResult.Success;
      • 验证失败:return new ValidationResult("不能包含空格");
      • ValidationResult? = 返回一个验证结果,也可能表示没有错误
    • object? value 是当前正在验证的属性值

      • 假设将改验证属性写在 DTO 里面的 Name 字段上面,请求{“name”:“Lily”}那么 ASP.NET Core 调用 IsValid 的时候,value就是 Lily
    • ValidationContext validationContext :当前正在验证哪个对象、哪个属性

    value

modelbinder4.png

validationContext

webapi3.png


using System.ComponentModel.DataAnnotations;
​
namespace WebApiDemo1.Models.Validations;
​
public class TestValidateAttribute : ValidationAttribute
{
    protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
    {
      // 验证规则
    }
}
  • 3、加在 DTO 属性上

2、举个栗子

  • PersonDto.cs

using System.ComponentModel.DataAnnotations;
using WebApiDemo1.Models.Validations;
​
namespace WebApiDemo1.Models;
​
public class PersonDto
{
    [Required(ErrorMessage = "Name is asked!")]
    public required string Name { get; set; }
​
    [Required(ErrorMessage = "Age is asked!")]
    [Range(0, 120, ErrorMessage = "Age's range is 0-120")]
    public int Age { get; set; }
​
    [Required]
    [EducationValidatate]
    public required string Education { get; set; }
​
    public string? Gender { get; set; }
}
  • EducationValidatateAttribute.cs

using System.ComponentModel.DataAnnotations;
​
namespace WebApiDemo1.Models.Validations;
​
public class EducationValidatateAttribute : ValidationAttribute
{
    protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
    {
        if (value is not (object?)"PRIMARY" and not (object?)"SECONDARY" and not (object?)"UNIVERSITY")
        {
            return new ValidationResult("Please enter the correct educational background:PRIMARY; SECONDARY; UNIVERSITY");
        }
​
        return ValidationResult.Success;
​
    }
}

  • TestDto.cs

[TestValidate]
public class TestDto
{
    public int LeftNum { get; set; }
    public int RightNum { get; set; }
    public required string Symbol { get; set; }
}
  • TestValidateAttribute.cs

using System.ComponentModel.DataAnnotations;
​
namespace WebApiDemo1.Models.Validations;
​
public class TestValidateAttribute : ValidationAttribute
{
    protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
    {
        var test = validationContext.ObjectInstance as TestDto;
        if (test == null)
        {
            return new ValidationResult("No Test!!!");
        }
        if (test.LeftNum > test.RightNum && test.Symbol != ">")
        {
            return new ValidationResult("LeftNum IS BIGGER THAN RightNum");
        }
        else if (test.LeftNum < test.RightNum && test.Symbol != "<")
        {
            return new ValidationResult("LeftNum IS SMALLER THAN RightNum");
        }
        else if (test.LeftNum == test.RightNum && test.Symbol != "=")
        {
            return new ValidationResult("LeftNum  Equal RightNum");
        }
​
        return ValidationResult.Success;
​
    }
}

5、WebApi 返回类型

返回类型决定:Controller 方法能返回什么内容;最终HTTP响应长什么样

1、直接返回普通类型

直接返回 字符串、数字、布尔值这些,不需要考虑 HTTP 状态码

  • 举个栗子

[ApiController]
[Route("api/[controller]")]
public class PersonController:ControllerBase
{
    [HttpGet]
    public string GetPerson()
    {
        return "Person" ;
    }
}

2、直接返回 DTO 对象

假设现在有一个 Dto对象 就可以 new 一个返回对象

ASP.NET Core 会自动序列化成 JSON,不需要自己写 JsonConvert.SerializeObject(...)

  • 举个栗子

[ApiController]
[Route("api/[controller]")]
public class PersonController:ControllerBase
{
    // 返回单个 PersonDto 对象
    [HttpGet("{id}")]
    public PersonDto GetPerson(string id)
    {
        return new PersonDto
        {
            Name = $"YOUNG + {id}",
            Age = 23,
            Gender = "Female"
        };
    }
​
    // 返回 PersonDto 数组
    [HttpGet]
    public List<PersonDto> GetPersonList()
    {
        return new List<PersonDto>
        {
            new()
            {
                Name = "YOUNG",
                Age = 22,
                Gender = "Female"
            },
            new()
            {
                Name = "GNAY",
                Age = 24,
                Gender = "Male"
            }
        };
    }
}

3、IActionResult

可以灵活返回不同 HTTP 状态BadRequest()、NotFound()、Ok() 这些方法,它们都定义在 ControllerBase 中。

  • 举个栗子

[ApiController]
[Route("api/[controller]")]
public class PersonController: ControllerBase
{
    [HttpGet("{id}")]
    public IActionResult GetPerson(int id)
    {
        if (id < 0)
        {
            return BadRequest();
        };
​
        if(id ==0)
        {
            return Unauthorized();
        }
​
        if(id == 100)
        {
            return NotFound();
        }
​
        var person = new PersonDto
        {
            Name = "YOUNG",
            Age = 24,
            Gender = "Female"
        };
​
        return Ok(person);
    }
}

4、ActionResult<T>

控制器方法执行后返回的结果类型

ActionResult<T> 和 IActionResult 差不多,实际上就是 具体数据类型 T + IActionResult可以返回一个对象 也可以 Ok(对象)

如果需要返回明确的 DTO 可以使用 ActionResult<T> 因为一眼就知道需要返回什么对象

  • 举个栗子

[ApiController]
[Route("api/[controller]")]
public class PersonController : ControllerBase
{
    [HttpGet("{id}")]
    public ActionResult<PersonDto> GetPerson(int id)
    {
        if (id < 0)
        {
            return BadRequest();
        }
​
        if (id == 0)
        {
            return Unauthorized();
        }
​
        if (id == 100)
        {
            return NotFound();
        }
​
        return new PersonDto
        {
            Name = "YOUNG",
            Age = 24,
            Gender = "Female"
        };
    }
}

1、异步接口 async Task

异步接口通常用 Task<T> 如果直接写 Task 相当于没有返回内容,就像一般的删除接口一样

async:告诉编译器 这个方法里面可能会用 await

Task:告诉调用方 这个方法是异步的,结果以后才完成


public async Task<返回类型> 方法名()
{
    var result = await 异步操作();
​
    return result;
}

2、常见的 HTTP 返回方法

  • 状态码:200 【成功】

    return Ok();
    
  • 状态码:201 【创建成功】

    return Created();
    
  • 状态码:400 【参数错误】

    400 Bad Request
    
  • 状态码:404 【找不到数据】

    return NotFound();
    
  • 状态码:401 【未登录】

    return Unauthorized();
    
  • 状态码:403 【禁止访问】

    
    return Forbid();
    
  • 状态码:204 【没有返回内容】

    
    return NoContent();
    

6、内存存储库 Repository

Repository:存储库;专门负责数据访问

不用数据库,把数据暂时放在程序内存里的一个集合中,再通过一个 Repository 类统一增删改查

在模拟的时候,可以不用数据库,将数据写在 内存存储库里面。数据存在程序运行时的 RAM 里。数据只在程序运行期间存在,不会永久保存。

内存存储库的作用就是 不让Controller 自己处理输入如:Add(...)、Remove(...) 等数据处理操作数据处理操作都放在 Repository 文件里面

  • 举个栗子(静态类)存储库

/** PersonRepository.cs */
namespace WebApiDemo1.Models.Repositories;
​
public static class PersonRepository
{
    private static List<PersonDto> _persons = new()
    {
        new PersonDto{Id = 1, Name = "Young", Age = 20 },
        new PersonDto{ Id = 2, Name = "Gnay", Age = 22 },
        new PersonDto{ Id = 3, Name = "Lily", Age = 20 },
        new PersonDto{ Id = 4, Name = "Harwon", Age = 22 },
    };
​
    public static List<PersonDto> GetAll()
    {
        return _persons;
    }
​
    public static PersonDto? GetById(int id)
    {
        return _persons.FirstOrDefault(x => x.Id == id);
    }
​
    public static void Add(PersonDto person)
    {
        _persons.Add(person);
    }
​
    public static void Delete(int id)
    {
        var person = _persons.FirstOrDefault(x => x.Id == id);
​
        if (person != null)
        {
            _persons.Remove(person);
        }
    }
​
    public static bool PersonExits(int id)
    {
        return _persons.Any(x => x.Id == id);
    }
}
/** PersonController.cs */ 
using Microsoft.AspNetCore.Mvc;
using WebApiDemo1.Models;
using WebApiDemo1.Models.Repositories;
​
namespace WebApiDemo1.Controllers;
​
[ApiController]
[Route("api/[controller]")]
public class PersonController : ControllerBase
{
    [HttpGet]
    public ActionResult<List<PersonDto>> GetPersonList()
    {
        return Ok(PersonRepository.GetAll());
    }
​
    [HttpGet("{id}")]
    public ActionResult<PersonDto> GetPerson(int id)
    {
        var person = PersonRepository.GetById(id);
        if (person == null)
        {
            return NotFound();
        }
        return Ok(person);
    }
​
    [HttpPost]
    public ActionResult<PersonDto> PostPerson(PersonDto person)
    {
        PersonRepository.Add(person);
        return Created();               
    }
​
    [HttpDelete("{id}")]
    public ActionResult DeletePerson(int id)
    {
        var person = PersonRepository.GetById(id);
        if (person == null)
        {
            return NotFound();
        }
        PersonRepository.Delete(person.Id);
        return Ok();
    }
}

7、操作过滤器 ActionFilter

可以把后端 Filter 类比成前端里的“请求拦截器 / 路由守卫 / axios interceptor”

处理“控制器方法执行前后都要做的公共逻辑”;可以理解为:在 Controller 的 Action 方法执行前后,自动插入一段代码

通过使用 ASP.NET Core 中的筛选器,可在请求处理管道中的特定阶段之前或之后运行代码。

一个请求可能会经历:


HTTP 请求
   ↓
路由匹配
   ↓
Controller
   ↓
过滤器
   ↓
Action 方法
   ↓
过滤器
   ↓
HTTP 响应
  • 作用

    • 日志记录:记录哪个接口被调用
    • 参数记录:检查参数是否合法
    • 性能统计:计算接口执行耗时
    • 修改参数:Action执行前修改参数
    • 修改返回值:Action执行后修改结果
    • 统一业务处理:做重复的公共逻辑

一个过滤器需要 继承 ActionFilterAttribute

ActionFilterAttribute 是 ASP.NET Core 给我们提供的一个基类

  • OnActionExecuting:Action 方法执行之前 触发
  • OnActionExecuted:Action 方法执行之后 触发
  • context:当前 Action 请求的上下文信息,可以通过context.ActionArguments["参数"]获取指定参数
  • context.Result:可以提前终止 Action
  • 举个栗子
/** PersonValidateIdFilterAttribute.cs */
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using WebApiDemo1.Models.Repositories;
​
namespace WebApiDemo1.Filters;
​
/** 验证 id 是否正确 */
// 1、继承 ActionFilterAttribute
public class PersonValidateIdFilterAttribute : ActionFilterAttribute
{
    // 2、重写方法 OnActionExecuting(Action执行之前触发)
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // 调用父类 ActionFilterAttribute 的 OnActionExecuting() 方法
        // 可以理解 为先让父类做它原本该做的事情
        // 然后再执行我们自己的代码
        base.OnActionExecuting(context);
​
        // 3、通过 context.ActionArguments 获取id
        var id = context.ActionArguments["id"] as int?;
​
        // 4、判断 id 值是否正确
        if (id.HasValue)
        {
            if (id.Value <= 0)
            {
                // 5、添加错误 给 id 加一个验证错误“Id is Invalid!”
                // AddModelError("字段","发生了什么错误")
                context.ModelState.AddModelError("id", "Id Is Invalid!");
​
                // 6、把 ModelState 中的验证错误整理成规范的 HTTP 错误响应。
                var problemDetails = new ValidationProblemDetails(context.ModelState)
                {
                    Title = "This Is A 400 Error Title",
                    Status = StatusCodes.Status400BadRequest
                };
​
                // 7、指定当前请求的响应结果
                // 注意:一旦在 OnActionExecuting 中给 context.Result 赋值,
                // Controller 的 Action 就不会继续执行。
                context.Result = new BadRequestObjectResult(problemDetails);
            }
            else if (!PersonRepository.PersonExits(id.Value))
            {
                context.ModelState.AddModelError("id", "Person Is Not Found!");
                var problemDetalis = new ValidationProblemDetails(context.ModelState)
                {
                    Title = "This Is A 404 Error Title",
                    Status = StatusCodes.Status404NotFound
                };
                context.Result = new BadRequestObjectResult(problemDetalis);
            }
        }
    }
}
/** PersonController.cs */ 
using Microsoft.AspNetCore.Mvc;
using WebApiDemo1.Filters;
using WebApiDemo1.Models;
using WebApiDemo1.Models.Repositories;
​
namespace WebApiDemo1.Controllers;
​
[ApiController]
[Route("api/[controller]")]
public class PersonController : ControllerBase
{
    [HttpGet("{id}")]
    // 8、将操作过滤添加在接口位置
    [PersonValidateIdFilter]
    public ActionResult<PersonDto> GetPerson(int id)
    {
        var person = PersonRepository.GetById(id);
        if (person == null)
        {
            return NotFound();
        }
        return Ok(person);
    }
}

1、创建端点

Create() 和 CreateAtAction() 的区别

  • Created() 只告诉客户端“创建成功了,并告诉你资源地址”;

  • CreatedAtAction() 是 ControllerBase 提供的方法,用于返回 201 Created,并基于某个 Action 生成 Location 响应头

    • 作用:

      • 返回 HTTP 201 Created
      • 根据指定的 Action 自动生成新资源 URL
      • 可以把新创建的资源放进响应体
    • 使用时 有四个参数,四个参数都可以为空

      • actionName(string);指定用于生成 URL 的 Action 名称
      • controllerName(string);指定目标 Controller 名称
      • routeValues(object);提供生成 URL 所需的路由值
      • value(object);放入 HTTP 响应体的数据
    • CreatedAtAction(actionName)

    • CreatedAtAction(actionName, value)

    • CreatedAtAction(actionName, routeValues, value)

    • CreatedAtAction(actionName, controllerName, routeValues, value)

  • 举个栗子

    要求:新建 person ,名称不允许重复

/** Controller */
[HttpPost]
public ActionResult<PersonDto> PostPerson(PersonDto person)
{
    PersonRepository.AddPerson(person);
    // nameOf:用于获取 某个变量、类型、方法、属性、参数等“名称”的字符串
    return CreatedAtAction(
        nameof(GetPerson),
        new { id = person.Id },
        person);
}

/** Repository */
public static void AddPerson(PersonDto person)
{
    int maxId = _persons.Count != 0 ? _persons.Max(x => x.Id) : 1;
    person.Id = maxId + 1;
​
    _persons.Add(person);
}
​
public static bool IsPersonRepeat(PersonDto person)
{
    var exitPerson = _persons.Any(x => x.Name == person.Name);
    if (exitPerson)
    {
        return true;
    }
    return false;
}

/** ActionFilter */
public class PersonValidatePersonExitFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        base.OnActionExecuting(context);
        var person = context.ActionArguments["person"] as PersonDto;
        if(person == null)
        {
            context.ModelState.AddModelError("person", "Person Is Be Null");
            var problemDetails = new ValidationProblemDetails(context.ModelState)
            {
                Title = "This Is A 400 Error!",
                Status = StatusCodes.Status400BadRequest
            };
            context.Result = new BadRequestObjectResult(problemDetails);
        }
        else if(PersonRepository.IsPersonRepeat(person))
        {
            context.ModelState.AddModelError("name", "Person Is Exited!");
            var problemDetails = new ValidationProblemDetails(context.ModelState)
            {
                Title = "This Is A 400 Error!",
                Status = StatusCodes.Status400BadRequest
            };
            context.Result = new BadRequestObjectResult(problemDetails);
        }
    }
}

8、异常过滤器 ExceptionFilter

when 是 C# 语言本身的异常筛选语法

ExceptionFilter 通常指 ASP.NET Core MVC 里的异常过滤器

1、C# 的异常处理语句

C# 的异常处理语句包括:throw、try-catch、try-finally、try-cath-finally

throw 抛出异常try 语句 捕获和处理执行中 可能发生的异常

  • when:异常筛选器

    根据特定条件或属性处理异常​

    • 基础语法
    
    try
    {
        // 可能出错的代码
    }
    catch (Exception ex) when (条件)
    {
        // 条件为 true 才进入
    }
    
    • 举个栗子
    
    try
    {
        throw new Exception("用户不存在");
    }
    catch (Exception ex) when (ex.Message.Contains("用户不存在"))
    {
        Console.WriteLine("返回用户不存在");
    }
    ​
    // 多个判断
    try
    {
        throw new Exception("网络连接失败");
    }
    catch (Exception ex) when (ex.Message.Contains("数据库"))
    {
        Console.WriteLine("数据库异常");
    }
    catch (Exception ex) when (ex.Message.Contains("网络"))
    {
        Console.WriteLine("网络异常");
    }
    catch (Exception ex)
    {
        Console.WriteLine("其他异常");
    }
    

2、ExceptionFilter 异常过滤器

ASP.NET Core 里的 Exception Filter,它属于 MVC Filter 管道

  • 基础写法

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
​
public class MyExceptionFilterAttribute : ExceptionFilterAttribute
{
    public override void OnException(ExceptionContext context)
    {
        context.Result = new ObjectResult(new
        {
            message = "服务器发生异常"
        })
        {
            StatusCode = 500
        };
​
        context.ExceptionHandled = true;
    }
}
  • 举个栗子

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using WebApiDemo1.Models.Repositories;
​
namespace WebApiDemo1.Filters.ExceptionFilters;
​
// 1、继承 ExceptionFilterAttribute
public class PersonValidatePersonExitExceptionFilter : ExceptionFilterAttribute
{
    // 2、重写方法 OnException(异常过滤器的核心入口)
    public override void OnException(ExceptionContext context)
    {
        base.OnException(context);
​
        // 3、获取id(从路由中获取id)
        var strId = context.RouteData.Values["id"] as string;
​
        // 4、判断
        if(int.TryParse(strId, out int intId))
        {
            if (!PersonRepository.PersonExits(intId))
            {
                context.ModelState.AddModelError("Person","Person Is Not Found!");
                var prombleDetail = new ValidationProblemDetails(context.ModelState)
                {
                    Status = StatusCodes.Status500InternalServerError
                };
                context.Result = new BadRequestObjectResult(prombleDetail);
            }
        }
    }
}

3、ActionFilter 和 ExceptionFilter 的区别

ActionFilter:关注 Action 执行前后要做什么;在 Action 执行前后插入逻辑

ExceptionFilter:关注 Action 执行时出了异常怎么办;处理未被捕获的异常

9、EntityFramework Core

Entity Framework Core,通常简称 EF Core,是 .NET 里最常用的 ORM(对象关系映射) 框架之一ORM(Object Relational Mapper):将 C# 对象 与 数据库 建立连接

作用:EF Core 使用 C# 对象来操作数据库,而不是用 SQL 语句

在使用之前,我们需要安装4个 NuGet 包(因为我们数据库是使用的MySql)

  • EntityFrameworkCore.Design 设计:提供 EF Core 设计时功能

    • 创建 Migration
    • 删除 Migration
    • 数据库反向工程
    • 根据数据库生成实体类
    • 设计时创建 DbContext
    • dotnet ef 命令运行时提供支持
  • EntityFrameworkCore.Tools 工具:提供 Visual Studio 的 EF Core 命令工具

  • EntityFrameworkCore.SqlServer 数据库:SQL Server 的 数据库提供程序

  • EntityFrameworkCore.MySql:EF Core 连接 MySQL 数据库用的驱动/适配器

Design   → EF Core 设计时干活
Tools    → 你用命令控制 EF Core
SqlServer→ EF Core 操作 SQL Server
EntityFrameworkCore.Design 和 EntityFrameworkCore.Tools (通常一起安装)

开始
↓
Tools                                
↓
Design
↓
分析 DbContext
↓
生成 Migration          
Microsoft.EntityFrameworkCore.SqlServer

程序运行
  ↓
DbContext
  ↓
EF Core
  ↓
SqlServer Provider
  ↓
SQL Server
  • EntityFramworkCore 的两种工作方式

    • 1、code first: 先创建数据库的内存表示,再迁移到实际数据库
    • 2、db first:先创建数据库表,再生成内存表示

1、code first

先写 C# 实体类和 DbContext,然后让 EF Core 根据代码帮你创建数据库和表

  • 流程
C# 实体类
   ↓
DbContext
   ↓
Migration 迁移
   ↓
SQL Server
   ↓
生成数据库表

1、编写 appsetting.json 文件

我们先要在 Properties 文件夹下面的 launchSetting.json文件里面确认我们当前的运行环境如下图,运行环境为 Development,我们就去修改 appsetting.Development.json 文件

EfCore1.png

  • appsetting.Development.json

    ConnectionStrings:数据库连接的设置


{
  "ConnectionStrings": {
    "Default": "Server=127.0.0.1;port=3036;Dtatbase=my_test_db;Uid=young;Pwd=123456"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  }
}

2、创建数据库实体

  • Person.cs

    PersonDto 是给接口用的Person 是给数据库用的


public class Person
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public int Age { get; set; }
}

3、创建 DbContext

DbContext 可以理解为创建数据库

DbSet 可以理解为创建数据库 表

  • ApplicationDbContext.cs

using Microsoft.EntityFrameworkCore;
using WebApiDemo1.Models.Entities;
​
namespace WebApiDemo1.Data;
​
public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) 
    : base(options) { }
​
    // 创建一张 Person 表
    public DbSet<Person> Person { get; set; }
​
    // 生成数据
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
​
        modelBuilder.Entity<Person>().HasData(
            new Person { Id = 1, Name = "Young", Age = 20 },
            new Person { Id = 2, Name = "Gnay", Age = 22 },
            new Person { Id = 3, Name = "Lily", Age = 20 },
            new Person { Id = 4, Name = "Harwon", Age = 22 }
        );
    }
}

4、在 Program.cs 注册数据库

  • Program.cs

// 注册数据库
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
    var connectionString = builder.Configuration.GetConnectionString("Default");
​
    options.UseMySql(
        connectionString,
        ServerVersion.AutoDetect(connectionString));
});

5、执行迁移命令

  • 初始化迁移

迁移命令有两种写法

  • dotnet ef migrations add Init:普通终端、PowerShell、CMD、VS Code、Rider、CI/CD使用
  • Add-Migration Init:Visual Studio 的 Package Manager Console在 工具 ---> NuGet 包管理器 ---> 程序包管理器控制台 输入

两种写法都会生产一个 迁移文件 “Init”

如下图,运行命令之后生成了一个 Migrations 迁移文件夹

EFCore2.png

  • 执行迁移
  • dotnet ef database update Init:普通终端、PowerShell、CMD、VS Code、Rider、CI/CD使用
  • Update-Database Init:Visual Studio 的 Package Manager Console

如不不写 Init 也极速迁移名称,那就是执行最新的迁移语句\

执行完迁移之后,数据库多出来两张表,一张 Person 表,一张 存储迁移历史 的表

EFCore3.png

2、使用 数据库 查询

1、Get 获取全部数据

  • 举个栗子

/** PersonController.cs */
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using WebApiDemo1.Data;
using WebApiDemo1.Models.Dtos;
​
namespace WebApiDemo1.Controllers;
​
[ApiController]
[Route("api/[controller]")]
public class PersonController : ControllerBase
{
    // 1、把 ApplicationDbContext 注入 Controller
    // ASP.NET Core 会通过依赖注入把数据库上下文传进来
    private readonly ApplicationDbContext _dbContext;
    public PersonController(ApplicationDbContext dbContext)
    {
        _dbContext = dbContext;
    }
​
    [HttpGet]
    public async Task<ActionResult <List<PersonDto>>> GetPersonList()
    {
        var personList = await _dbContext.Person.ToListAsync();
        return Ok(personList);
    }
}

0、杂七杂八

1、静态修饰符 static

static可以修饰类,使类成为静态类。这样在此类中只能定义静态的方法和静态的变量

  • 修饰类

    静态类不能被实例化,也就是说不能使用new关键字创建静态类类型的变量

  • 修饰变量

    static可以修饰变量,注意:这个变量只能是成员变量,而不能是局部变量

  • 修饰方法

    static可以修饰方法,Main方法必须用static修饰, 因为它是与程序共存亡的,是程序的入口和程序的大门

    • 静态方法是不属于特定对象的方法
    • 静态方法可以直接访问静态成员(包括静态字段、静态方法)
    • 静态方法不可以直接访问实例成员,并且静态方法也不能直接调用实例方法
  • 修饰构造函数

    static可以修饰,使构造函数成为静态构造函数。 并且不能在再使用其他修饰符,并且不能是实例构造函数

    • 不能继承
    • 静态构造函数没有访问修饰符,没有参数,只有一个static修饰符
    • 静态构造函数只执行一次

2、构造函数和方法 区别

构造函数的名称 必须与 类名相同,并且没有返回值 类型等。使用 new 对象() 调用用于设置对象的初始状态

方法必须有返回类型。使用类名.方法()调用

  • 举个栗子

    
    public class Person
    {
        public string Name { get; set; }
    ​
        // 构造函数
        public Person(string name)
        {
            Name = name;
        }
    ​
        // 方法
        public void SayHello()
        {
            Console.WriteLine($"你好,我是{Name}");
        }
    }
    
    class Person
          ↓
    new Person()
          ↓
    调用构造函数
          ↓
    Person 对象创建完成
          ↓
    person.SayHello()
          ↓
    调用普通方法