关于HTTP HEAD 和 HTTP GET:

从执行性能来说,这两种其实并没有什么区别。最大的不同就是对于HTTP HEAD 来说,Api消费者请求接口数据时,如果是通过HTTP HEAD的方式去请求,

应该是不会把 Body返回回去的。那么它会返回什么呢? 比如说,Headers的一些响应头数据,例如Content-Type的一些资源信息。而HTTP GET是会将

Body里面的数据返回的。因此,可以通过HTTP HEAD去检测该 Api 是否存在资源,换一种说法就是该 Api 是否可用。 

关于如何给 Api 传递数据:

数据可以通过多种方式传递给 Api!

事实上,Bind Source Attribute 会告诉 Model 的绑定引擎从哪里去找到绑定。

Bind Source Attribute的六种方式:

  1. [FromBody],请求的Body
  2. [FromFrom],请求的Body中的form数据
  3. [FromHeader],请求的Header
  4. [FromQuery],QueryString 参数
  5. [FromRoute],当前请求中的路由参数
  6. [FromService],当做Action参数而注入的服务

默认情况下,ASP .NET Core会使用 Complex Object Model Binder,它会把数据从 Value Provides 那里提取出来

而 Value Provides的顺序是定义好的!

但是,我们在构建 Api 时,通常会使用 [ApiController] 这个 特性类,目的就是为了更好的构建 RESTful Api。

更改后:

  1. [FromBody],通常是用来推断复杂类型参数,例如Post方式提交的数据
  2. [FromFrom],通常是用来推断IFormFile和IFormFileCollection类型的Action参数,例如用来上传单个或多个文件
  3. [FromRoute],用来推断 Action的参数名和路由模板中的参数名一致的情况下
  4. [FromQuery],用来推断其他的Action参数

关于过滤和搜索:

过滤:

实际上这两者在实际的业务中通常应该是搭配使用的。

所谓过滤:就是过滤集合的一是,根据条件返回限定的集合数据

需求案例: 返回所有类型为国有企业的欧洲公司

分析:过滤条件自然是“国有企业”和“欧洲公司”

那么 uri 的设计就会是:GET  api/companies?Type=State-owned&regoin=Europe

所以过滤就是:我们把某个字段的名字和与之匹配的值一起传递给 Api ,并将这些以集合的方式返回

搜索:

搜索实际上超出了过滤的范围,针对搜索我们通常不会把要搜索的字段传递过去,而是只把要搜索的值传递给 Api,

然后 Api 自行决定应该对哪些字段来查找该值,一般是全文搜索

例如:api/companies?q=xxx

如果还不理解?

过滤:根据条件,将某一集合的数据按条件进行移除或选择

搜索:可以是空集合,根据要搜索的值,将数据添加到集合中,再返回

注意:过滤和搜索这些参数并不是资源的一部分。

案例代码:过滤员工性别(参数: genderDisplay)、搜索匹配数据(参数:q)

实现类处理业务逻辑:

分析:

首先第二个if判断,如果都为空,那么就是返回全部数据,什么也没发生。

第二个if判断性别参数是否为空,如果不是,那么就编写过滤性别的代码,在这之前将定义的items就是查询到该公司下的所有员工然后在处理其他事件。

第三个if判断搜索的值是否为空,如果不是,就编写模糊查询的代码,这里是多字段模糊查询

以上if执行完毕后,实际上并没有生成一个完整的 SQL 语句,实际上这样做就是为了性能,最后才通过 ToList返回集合,至于你是过滤还是搜索都无所谓!

public async Task<IEnumerable<Employee>> GetEmployeesAsync(Guid companyId, string genderDisplay, string q)
{
if (companyId==Guid.Empty)
{
throw new ArgumentNullException(nameof(companyId));
}
if (string.IsNullOrWhiteSpace(genderDisplay) || string.IsNullOrWhiteSpace(q))
{
return await _context.Employees
.Where(x => x.CompanyId == companyId)
.OrderBy(x => x.EmployeeNo)
.ToListAsync();
}
var items = _context.Employees.Where(x => x.CompanyId == companyId);
if (!string.IsNullOrWhiteSpace(genderDisplay))
{
genderDisplay = genderDisplay.Trim();
var gender = Enum.Parse<Gender>(genderDisplay);
items = items.Where(x => x.Gender == gender);
}
if (!string.IsNullOrWhiteSpace(q))
{
q = q.Trim();
items = items.Where(x => x.EmployeeNo.Contains(q) || x.FirstName.Contains(q) || x.LastName.Contains(q));
}
return await items
.OrderBy(x => x.EmployeeNo)
.ToListAsync();
}

控制器调用:

通过[FromQuery]的Name来指定参数匹配的名称是什么,比如:gender或者是genderDisplay

  public async Task<ActionResult<IEnumerable<EmployeeDto>>>
GetEmployeesForCompany(Guid companyId,[FromQuery(Name = "gender")] string genderDisplay,string q)
{
if (! await _companyRepository.CompanyExistsAsync(companyId))
{
return NotFound();
}
var employees =await _companyRepository.GetEmployeesAsync(companyId, genderDisplay,q);
var employeeDtos = _mapper.Map<IEnumerable<EmployeeDto>>(employees);
return Ok(employeeDtos);
}

接口测试:

还需要考虑一种情况:

在实际业务当中呢,这种搜索过滤的条件肯定不止一两个,一般是多个属性进行搜索或者过滤,这个时候,如果也按照查询字符串的方式传递给 Api ,那么就会显得非常的复杂也很容易写错。

那怎么办呢?

很简单,其实只需要写一个对应的类就好了,把需要查询的字段属性全部放到类里面。

这样就算后期想再增加条件属性只需要编写类里面的代码,无需在,Api 接口中在去增加参数。

添加一个CompanyParameters类:

分析:分别定义公司名称属性字段和全文搜索属性字段

namespace Routine.Api.ResoureParameters
{
public class CompanyDtoParameters
{
public string CompanyName { get; set; }
public string SearchTerm { get; set; }
}
}

业务逻辑类:

public async Task<IEnumerable<Company>> GetCompaniesAsync(CompanyDtoParameters companyParameters)
{
if (companyParameters==null)
{
throw new ArgumentNullException(nameof(companyParameters));
} if (string.IsNullOrWhiteSpace(companyParameters.CompanyName) &&
string.IsNullOrWhiteSpace(companyParameters.SearchTerm))
{
return await _context.Companies.ToListAsync();
}
var queryableCompany = _context.Companies as IQueryable<Company>;
if (!string.IsNullOrWhiteSpace(companyParameters.CompanyName))
{
companyParameters.CompanyName = companyParameters.CompanyName.Trim();
queryableCompany = queryableCompany.Where(x => x.Name == companyParameters.CompanyName);
}
if (!string.IsNullOrWhiteSpace(companyParameters.SearchTerm))
{
companyParameters.SearchTerm = companyParameters.SearchTerm.Trim();
queryableCompany = queryableCompany.Where(x => x.Name.Contains(companyParameters.SearchTerm) ||
x.Introduction.Contains(companyParameters.SearchTerm));
}
return await queryableCompany.ToListAsync();
}

控制器调用:

注意:需要加上[FromQuery]标记,不然会出现状态码为 415 ,也就是不支持的媒体类型(MediaType)

分析:此时方法的参数是一个类,就相当于它是一个复杂的数据类型,这个时候请求 Api 的时候它可能会认为绑定源是来自于QueryString查询字符串。

所以我们需要手动指定一些绑定源。

 [HttpGet]
public async Task<ActionResult<IEnumerable<CompanyDto>>> GetCompanies([FromQuery]CompanyDtoParameters companyDtoParameters)
{
var companies =await _companyRepository.GetCompaniesAsync(companyDtoParameters);
var companyDtos = _mapper.Map<IEnumerable<CompanyDto>>(companies);
return Ok(companyDtos);
}

重新测试接口:

接口测试成功!

关于HTTP 方法的安全性与幂等性:

安全性是指方法执行后并不会改变资源的表述,例如 GET 它只是查询获取资源,它并不会改变资源的表述,所以它是安全的。

幂等性是指方法无论执行多少次都会返回得到同样的结果,例如对资源进行修改,而修改的内容是一样的,所以无论修改多少次得到的结果也是一样的,例如 HTTP PUT 就是幂等的。

案例1:如何创建 POST 父资源(Company):

在创建资源请求之前,首先要明确一个理念,那就是创建资源的 DTO 是否要和 GET 请求查询的 Dto 的属性字段

内容一致呢?

答案是不应该将POST,GET请求使用同一个 Dto 。

其实原因很简单,仔细想想,其实POST Action方法请求的资源大部分业务情况与 GET Action 请求的资源情况是不一样的

尽管有时候可能作为查询的Dto属性和作为创建资源的POST Dto属性一样。这个时候也应该将它们分开使用。

因为在未来业务处理中 DTO 中的属性可能随时都在发生改变。

所以,这样分开写 DTO 的好处就是方便后期的重构。

简单点来说就是针对 查询、创建、更新三大类小块我们都应该使用不同的 DTO。

对 Company 这个 Entity Model做一下 POST 创建资源的请求:

建立 CompanyAddDto类:

using System;

namespace Routine.Api.Models
{
public class CompanyAddDto
{
public string Name { get; set; }
public string Introduction { get; set; }
}
}

对比一下GET Action 请求的 Dto ,即 CompanyDto:

实际上两者一般来说属性很可能根据业务情况不一样!!!

using System;

namespace Routine.Api.Models
{
public class CompanyDto
{
public Guid Id { get; set; }
public string CompanyName { get; set; }
}
}

注意,别忘记添加 mapper 映射关系了,这里就是从 CompanyAddDto 映射到 Employee(Entity Model),因为是添加到数据库里面。

因为CompanyDto和Company属性字段并没有什么改变,所以不需要对专门的字段进行配置

using AutoMapper;
using Routine.Api.Entities;
using Routine.Api.Models; namespace Routine.Api.Profiles
{
public class CompanyProfiles:Profile
{
public CompanyProfiles()
{
CreateMap<CompanyAddDto, Company>();
}
}
}

在控制器添加 POST 请求的方法:

需要注意在资源添加后还需要重新映射回 GET 请求查询资源的 Dto!

[HttpPost]
public async Task<ActionResult<CompanyDto>> CreateCompany(CompanyAddDto company) //如果 companyAddDto为空,ASP.NET Core会自动返回 400错误,这是因为 [ApiController] Attribute的作用
{
//需要将资源映射到 EntityModel
var entity = _mapper.Map<Company>(company);
_companyRepository.AddCompany(entity);
await _companyRepository.SaveAsync(); //此时添加完成后,返回出去的还是Dto,所有还需要进行一次映射
var returnDto = _mapper.Map<CompanyDto>(entity); //CreatedAtRoute,会返回一些响应头的资源运行我们返回带着一个地址的head,而这个head含有一个uri,例如 201 表示添加成功,还有就是 uri,通过这个uri可以找到这个新创建的资源
//参数1:生成uri名称,与返回的GET方法名一样,参数2:路由值,参数3:对象值
return CreatedAtRoute(nameof(GetCompany), new { companyId= returnDto.Id}, returnDto);
}

关于返回的 CreatedAtRoute方法,注释标注了作用和对应的参数,第一个参数 GetCompany 对应的就是GET Action标注的路由名称,如下:

接下来进行 POST 请求的接口测试,打开 Postman 工具。

返回状态码 201 表示 Post 成功!

再看看Headers里面给我们带回了什么信息:

这实际上就是返回 CreatedAtRoute 方法的作用,会带着刚刚添加的资源的 uri 地址

案例2:如何创建 POST 子资源(Employee):

创建子资源其实和创建父资源差不多。

同样添加 EmployeeAddDto 类:

using System;
using Routine.Api.Entities; namespace Routine.Api.Models
{
public class EmployeeAddDto
{
public string EmployeeNo { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Gender Gender { get; set; }
public DateTime DateOfBirth { get; set; }
}
}

再来对比 EmployeeDto:

可以看出这两个类的区别还是很大的

using System;

namespace Routine.Api.Models
{
public class EmployeeDto
{
public Guid Id { get; set; }
public Guid CompanyId { get; set; }
public string EmployeeNo { get; set; }
public string Name { get; set; }
public string GenderDispaly { get; set; }
public int Age { get; set; }
}
}

在控制器添加 POST 请求的方法:

需要注意的是,因为Employee作为子资源所以需要带着 CompanyId 回去

[HttpPost]
public async Task<ActionResult<EmployeeDto>> CreateEmployeeForCompany(Guid companyId, EmployeeAddDto employee)
{
if (!await _companyRepository.CompanyExistsAsync(companyId))
{
return NotFound();
}
var entity = _mapper.Map<Employee>(employee);
_companyRepository.AddEmployee(companyId,entity);
await _companyRepository.SaveAsync();
var dtoToReturn = _mapper.Map<EmployeeDto>(entity);
return CreatedAtRoute(nameof(GetEmployeeForCompany), new
{
companyId,
employeeId = dtoToReturn.Id
}, dtoToReturn);
}

接下来进行 POST 请求的接口测试,打开 Postman 工具。

返回201,接口测试成功!

同样来看看 Headers里面返回的一些资源:

将刚刚添加的资源以 uri 形式返回。

案例3:同时创建父子资源:

业务需求:同时创建多个子资源 employee

需要在创建POST Action的Company方法上扩展一下就行了,在 CompanyAddDto中添加Employees属性集合:

最好和Entity Model的 Employee中 Employee一样,这样就无效对这个属性在映射的时候做配置了。

接口测试:

POST 成功 !

案例4:刚刚添加了多个子资源,那么如何添加多个父资源呢?

这就需要重新写一个 uri ,因为当前 api/companies uri 是针对于单个 Company的 Post 创建资源。

既然重新写一个uri,那么直接创建一个新的控制器,标注 Attribute [ApiController]为 api/companycollections

ConpanyCollectionController控制器代码:

编写一个构造函数的依赖注入,分别注入 AutoMapper 以及 CompanyRepository 业务逻辑类

分析:既然是创建多个Company,那么返回的也是一个 IEnumerable的Dto集合,参数也是一个为 IEnumerable的 CompanyAddDto集合,这没有什么问题。

然后循环添加数据就好了。在此之前还是一样的,需要将 CompanyAddDto 映射到 Entity Model对应的Company 里面!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Routine.Api.Entities;
using Routine.Api.Models;
using Routine.Api.Service; namespace Routine.Api.Controllers
{
[ApiController]
[Route("api/companycollections")]
public class CompanyCollectionsController:ControllerBase
{
private readonly IMapper _mapper;
private readonly ICompanyRepository _companyRepository; public CompanyCollectionsController(IMapper mapper,ICompanyRepository companyRepository)
{
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
_companyRepository = companyRepository ?? throw new ArgumentNullException(nameof(companyRepository));
} [HttpPost]
public async Task<ActionResult<IEnumerable<CompanyDto>>> CreateCompanyCollection(
IEnumerable<CompanyAddDto> companyCollection)
{
var companyEntities = _mapper.Map<IEnumerable<Company>>(companyCollection);
foreach (var company in companyEntities)
{
_companyRepository.AddCompany(company);
}
await _companyRepository.SaveAsync();
return Ok();
}
}
}

这里先测试是否会返回一个 Ok 200 的状态码

接口测试:

返回 200,测试成功!

使用 .NET Core 3.x 构建RESTful Api(第三部分)的更多相关文章

  1. 使用ASP.NET Core 3.x 构建 RESTful API - 2. 什么是RESTful API

    1. 使用ASP.NET Core 3.x 构建 RESTful API - 1.准备工作 什么是REST REST一词最早是在2000年,由Roy Fielding在他的博士论文<Archit ...

  2. 使用 .NET Core 3.x 构建 RESTFUL Api

    准备工作:在此之前你需要了解关于.NET .Core的基础,前面几篇文章已经介绍:https://www.cnblogs.com/hcyesdo/p/12834345.html 首先需要明确一点的就是 ...

  3. 使用ASP.NET Core 3.x 构建 RESTful API - 1.准备工作

    以前写过ASP.NET Core 2.x的REST API文章,今年再更新一下到3.0版本. 先决条件 我在B站有一个非常入门的ASP.NET Core 3.0的视频教程,如果您对ASP.NET Co ...

  4. 使用ASP.NET Core 3.x 构建 RESTful API - 3.4 内容协商

    现在,当谈论起 RESTful Web API 的时候,人们总会想到 JSON.但是实际上,JSON 和 RESTful API 没有半毛钱关系,只不过 JSON 恰好是RESTful API 结果的 ...

  5. 使用ASP.NET Core 3.x 构建 RESTful API - 3.1 资源命名

    之前讲了RESTful API的统一资源接口这个约束,里面提到了资源是通过URI来进行识别的,每个资源都有自己的URI.URI里还涉及到资源的名称,而针对资源的名称却没有一个标准来进行规范,但是业界还 ...

  6. 使用ASP.NET Core 3.x 构建 RESTful API - 4.2 过滤和搜索

    向Web API传递参数 数据可以通过多种方式来传给API. Binding Source Attributes 会告诉 Model 的绑定引擎从哪里找到绑定源. 共有以下六种 Binding Sou ...

  7. 使用ASP.NET Core 3.x 构建 RESTful API - 3.2 路由和HTTP方法

    ASP.NET Core 3.x 的路由 路由机制会把一个请求的URI映射到一个Controller上面的Action,所以当你发送一个HTTP请求的时候,MVC框架会解析这个请求的URI,并尝试着把 ...

  8. 使用ASP.NET Core 3.x 构建 RESTful API - 3.3 状态码、错误/故障、ProblemDetails

    HTTP状态码 HTTP状态码会告诉API的消费者以下事情: 请求是否执行成功了 如果请求失败了,那么谁为它负责 HTTP的状态码有很多,但是Web API不一定需要支持所有的状态码.HTTP状态码一 ...

  9. 使用ASP.NET Core 3.x 构建 RESTful API - 5.1 输入验证

    说到验证,那就需要做三件事: 定义验证规则 按验证规则进行检查 报告验证的错误.在把错误报告给API消费者的时候,报告里并不包含到底是服务端还是API消费者引起的错误,这是状态码的工作.而通常响应的B ...

随机推荐

  1. 修改ElementUI样式的几种方式

    ElementUI是一款非常强大的前端UI组件库,它默认定义了很多美观的样式,但是我们在实际开发过程中不可避免地遇到需要修改ElementUI默认样式.下面总结了几种修改默认样式的方法. 1. 新建全 ...

  2. Arrays.sort() ----- TimSort

    Arrays.sort() Arrays.sort()对于基本类型使用的是DualPivotQuicksort双轴快速排序,而对于非基本类型使用的是TimSort,一种源自合并排序和插入排序的混合稳定 ...

  3. 一些非常实用的git命令

    阅读目录 一.前言 二.git branch 和 git checkout 三.git clone 和 git remote 四.git pull 和 git push 五.git merge 和 g ...

  4. Python对列表去重的各种方法

    一.循环去重   二.用 set() 去重 1.set()对list去重 2.list 是有序的,用 sort() 把顺序改回来  三.利用 dict 的属性来去重 1.用 dict 的 fromke ...

  5. Zuul token FIlter 验证失败结果输出

    1.背景 用 postman 测试 zuul 网关 路由成功和失败的时候,发现 路由成功的时候,返回的结构体 是 json 格式,但是路由失败的时候,返回的是空. 结构体居然不一样,这对调用方来说也要 ...

  6. flask的第一次尝试

    from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return "Hello ...

  7. 5.19 省选模拟赛 小B的图 最小生成树 LCT

    LINK:小B的图 这道题就比较容易了. 容易想到将询问离线 然后 从小到大排序 那么显然是优先放正图(x+k)的边. 考虑随着x的增大 那么负图上的边会逐渐加进来 一条边被加进来当且仅当 其权值小于 ...

  8. SpringBoot+Shiro+JWT前后端分离实现用户权限和接口权限控制

    1. 引入需要的依赖 我使用的是原生jwt的依赖包,在maven仓库中有好多衍生的jwt依赖包,可自己在maven仓库中选择,实现大同小异. <dependency> <groupI ...

  9. Python自动化运维 技术与最佳实践PDF高清完整版免费下载|百度云盘|Python基础教程免费电子书

    点击获取提取码:7bl4 一.内容简介 <python自动化运维:技术与最佳实践>一书在中国运维领域将有"划时代"的重要意义:一方面,这是国内第一本从纵.深和实践角度探 ...

  10. maven个人配置

    settings.xml 文件修改一下内容 本地 <localRepository>D:\maven\repository</localRepository> 远程:修改成国内 ...