- ASP.NET Core WebAPI学习-1
- ASP.NET Core WebAPI学习-2
- ASP.NET Core WebAPI学习-3
- ASP.NET Core WebAPI学习-4
- ASP.NET Core WebAPI学习-5
- ASP.NET Core WebAPI学习-6
查询和搜索
数据绑定
数据可以通过多种方式来传给API。
Binding Source Attributes 会告诉 Model 的绑定引擎从哪里找到绑定源。
共有以下六种 Binding Source Attributes:
[FromBody] 请求的 Body
[FromForm] 请求的 Body 中的 form数据
[FromHeader] 请求的 Header
[FromQuery] Query string 参数
[FromRoute] 当前请求中的路由数据
[FromService] 作为 Action 参数而注入的服务
默认情况下ASP.NET Core 会使用 Complex Object Model Binder,它会把数据从Value Providers那里提取出来,而Value Providers的顺序是定义好的。
但是我们构建API时通常会使用 [ApiController] 这个属性,为了更好的适应API它改变了上面的规则。更改后的规则如下:
[FromBody] 通常是用来推断复杂类型参数的。
[FromForm] 通常用来推断IFormFile和IFormFileCollection类型的Action参数。
[FromRoute] 用来推断Action的参数名和路由模板中的参数名一致的情况。
[FromQuery] 用来推断其它的Action参数。
按照这些规则,在Action的参数前面使用这些属性,就可以避免让我们手动去寻找绑定源。当默认的行为规则需要被重写的时候,也可以使用这些 Binding Source Attributes.
过滤
过滤集合的意思就是指根据条件限定返回的集合。
例如我想返回所有类型为国有企业的欧洲公司。则URI为:GET /api/companies?type=State-owned®ion=Europe
所以过滤就是指:我们把某个字段的名字以及想要让该字段匹配的值一起传递给API,并将这些作为返回的集合的一部分。
搜索
针对集合进行搜索是指根据预定义的一些规则,把符合条件的数据添加到集合里面。
搜索实际上超出了过滤的范围。针对搜索,通常不会把要匹配的字段名传递过去,通常会把要搜索的值传递给API,然后API自行决定应该对哪些字段来查找该值。经常会是全文搜索。
例如:GET /api/companies?q=xxx
过滤 vs 搜索
可以看出来过滤和搜索是不同的。
过滤:首先是一个完整的集合,然后根据条件把匹配/不匹配的数据项移除。
搜索:首先是一个空的集合,然后根据条件把匹配/不匹配的数据项往里面添加。
但需要注意的是:
过滤和搜索这些参数并不是资源的一部分。
只允许针对资源的字段进行过滤。
过滤:
[HttpGet]
public async Task<ActionResult<IEnumerable<EmployeeDto>>>
GetEmployeesForCompany(Guid companyId, [FromQuery(Name = "gender")]string genderDisplay)
{
if (!await companyRepository.CompanyExistsAsync(companyId))
{
return NotFound();
}
var employees = await companyRepository.GetEmployeesAsync(companyId, genderDisplay);
var employeeDtos = mapper.Map<IEnumerable<EmployeeDto>>(employees);
return Ok(employeeDtos);
}
public async Task<IEnumerable<Employee>> GetEmployeesAsync(Guid companyId, string gender)
{
if (companyId == Guid.Empty)
{
throw new ArgumentNullException(nameof(companyId));
}
if (string.IsNullOrWhiteSpace(gender))
{
return await context.Employees
.Where(x => x.CompanyId == companyId)
.OrderBy(x => x.EmployeeNo)
.ToListAsync();
}
//判断Gender是否转换成功
Gender gender1;
var isGender = Enum.TryParse<Gender>(gender.Trim(), out gender1);
if (isGender)
{
return await context.Employees
.Where(x => x.CompanyId == companyId && x.Gender == gender1)
.OrderBy(x => x.EmployeeNo)
.ToListAsync();
}
else
{
throw new ArgumentNullException(nameof(gender));
}
}
查询
注意如果使用复杂类型查询,这里的CompanyDtoParameter时,API默认绑定的类型为从Body中获取,会出现415(Unsupported Media Type)错误,这时要在参数前面加上FromQuery特性,让参数从查询字符串中获取
- 因为ApiController的复杂类型参数默认从FromBody中推断,所以加入[FromQuery]即可
- 如果不指定Content-Type也会出现该错误,在Post的时候,在Header头部添加Content-Type:Application/json
[HttpGet(Name = nameof(GetCompanies))]
[HttpHead]
public async Task<ActionResult<IEnumerable<CompanyDto>>> GetCompanies([FromQuery]CompanyDtoParameter parameter = null)
{
var companies = await companyRepository.GetCompaniesAsync(parameter);
var previousPageLink = companies.HasPrevious
? CreateCompaniesResourceUri(parameter, ResourceUriType.PreviousePage)
: null;
var nextPageLink = companies.HasNext
? CreateCompaniesResourceUri(parameter, ResourceUriType.NextPage)
: null;
var paginationMetadata = new
{
totalCount = companies.TotalCount,
pageSize = companies.PageSize,
currentPage = companies.CurrentPage,
totalPages = companies.TotalPages,
previousPageLink,
nextPageLink
};
Response.Headers.Add("X-Pagination", JsonSerializer.Serialize(paginationMetadata, new JsonSerializerOptions()
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
}));
var companyDtos = mapper.Map<IEnumerable<CompanyDto>>(companies);
return Ok(companyDtos);
}
创建资源
注意几个方法的使用:
UnprocessableEntity: 返回422错误
BadRequest: 返回400错误
CreatedAtRoute:返回创建成功的201状态码
[HttpPost]
public async Task<ActionResult<CompanyDto>> CreateCompany([FromBody]CompanyAddDto company)
{
if (!ModelState.IsValid)
{
//Creates an Microsoft.AspNetCore.Mvc.UnprocessableEntityObjectResult that produces
//a Microsoft.AspNetCore.Http.StatusCodes.Status422UnprocessableEntity response.
return UnprocessableEntity(ModelState);
}
if (company == null)
{
//Creates an Microsoft.AspNetCore.Mvc.BadRequestResult that produces
//a Microsoft.AspNetCore.Http.StatusCodes.Status400BadRequest response.
return BadRequest();
}
var entity = mapper.Map<Company>(company);
companyRepository.AddCompany(entity);
await companyRepository.SaveAsync();
var returndto = mapper.Map<CompanyDto>(entity);
//Creates a Microsoft.AspNetCore.Mvc.CreatedAtRouteResult object that produces
//a Microsoft.AspNetCore.Http.StatusCodes.Status201Created response.
return CreatedAtRoute(nameof(GetCompany), routeValues: new { companyId = returndto.Id }, value: returndto);
}
发送请求:
请求创建实体成功的响应:
请求创建实体时传递的参数错误时的响应:
请求创建实体时传递的参数错误时的响应:
自定义模型绑定器
定义:
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace Routine.Api.Helpers
{
/// <summary>
/// 自定义Model绑定器
/// </summary>
public class ArrayModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
//判断传递的参数的类型是否为枚举类型
if (!bindingContext.ModelMetadata.IsEnumerableType)
{
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}
//判断传递的值是否为空,如果为空的话直接返回,在Controller中返回BadRequest
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ToString();
if (string.IsNullOrWhiteSpace(value))
{
bindingContext.Result = ModelBindingResult.Success(null);
return Task.CompletedTask;
}
/*利用反射把string转换为guid数组*/
//获取guid类型
var elementType = bindingContext.ModelType.GetTypeInfo().GenericTypeArguments[0];
//获取转换器是为了把字符串类型转换为guid类型
var converter = TypeDescriptor.GetConverter(elementType);
//把string转换为object数组
var values = value.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => converter.ConvertFromString(x.Trim())).ToArray();
//把object数组转换为guid数组
var typedValues = Array.CreateInstance(elementType, values.Length);
values.CopyTo(typedValues,0);
bindingContext.Model = typedValues;
bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
return Task.CompletedTask;
}
}
}
使用:
/// <summary>
/// 使用自定义模型绑定器,查询公司集合
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
[HttpGet("({ids})", Name = nameof(GetCompanyCollection))]
public async Task<IActionResult> GetCompanyCollection(
[FromRoute]
[ModelBinder(BinderType =typeof(ArrayModelBinder))]
IEnumerable<Guid> ids)
{
if (ids == null)
{
return BadRequest();
}
var entities = await companyRepository.GetCompaniesAsync(ids);
if (ids.Count() != entities.Count())
{
return NotFound();
}
var dtoToReturn = mapper.Map<IEnumerable<CompanyDto>>(entities);
return Ok(dtoToReturn);
}
安全性和幂等性
安全性是指方法执行后并不会改变资源的表述
幂等性是指方法无论执行多少次都会得到同样的结果
HTTP Options
API消费者如何知道某个API是否允许被访问?
OPTION请求可以获取针对某个Web API的通信选项的信息
使用:
[HttpOptions]
public IActionResult GetCompaniesOptions()
{
Response.Headers.Add("Allow", "GET,POST,OPTIONS");
return Ok();
}
请求响应:
415 Unsupported Media Type 错误信息
这个是请求的数据格式不对或者没有,需要添加或更改Content-Type