ASP.NET Core WebAPI学习-6
- 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
Web API 增删改方法
PUT vs PATCH
PUT:整体更新/替换
资源所有的字段都被重写了,或者是设置为该字段的默认值
PATCH: 局部更新
使用JsonPatchDocument发送变更的数据,对资源指定的字段进行更新
[HttpPut("{employeeId}")]
public async Task<IActionResult> UpdateEmployeeForCompany(
Guid companyId,
Guid employeeId,
EmployeeUpdateDto employee)
{
if (!await companyRepository.CompanyExistsAsync(companyId))
{
return NotFound();
}
var employeeEntity = await companyRepository.GetEmployeeAsync(companyId, employeeId);
if (employeeEntity == null)
{
var employeeToAddEntity = mapper.Map<Employee>(employee);
employeeToAddEntity.Id = employeeId;
companyRepository.AddEmployee(companyId, employeeToAddEntity);
await companyRepository.SaveAsync();
var dtoToReturn = mapper.Map<EmployeeDto>(employeeToAddEntity);
return CreatedAtRoute(nameof(GetEmployeeForCompany), new
{
companyId,
employeeId = dtoToReturn.Id
}, dtoToReturn);
}
//entity 转化为updateDto
//把传进来的employee的值更新到updateDto
//把updateDto映射回entity
mapper.Map(employee, employeeEntity);
companyRepository.UpdateEmployee(employeeEntity);
await companyRepository.SaveAsync();
return NoContent(); //204
}
Patch使用方法:
/// <summary>
/// 局部更新Employee
/// </summary>
/// <param name="companyId"></param>
/// <param name="employeeId"></param>
/// <param name="patchDocument"></param>
/// <returns></returns>
[HttpPatch("{employeeId}")]
public async Task<IActionResult> PartiallyUpdateEmployeeForCompany(
Guid companyId,
Guid employeeId,
JsonPatchDocument<EmployeeUpdateDto> patchDocument)
{
if (!await companyRepository.CompanyExistsAsync(companyId))
{
return NotFound();
}
var employeeEntity = await companyRepository.GetEmployeeAsync(companyId, employeeId);
//如果不存在则创建
if (employeeEntity == null)
{
var employeeDto = new EmployeeUpdateDto();
patchDocument.ApplyTo(employeeDto, ModelState);
if (!TryValidateModel(employeeDto))
{
return ValidationProblem(ModelState);
}
var employeeToAdd = mapper.Map<Employee>(employeeDto);
employeeToAdd.Id = employeeId;
companyRepository.AddEmployee(companyId, employeeToAdd);
await companyRepository.SaveAsync();
var dtoToReturn = mapper.Map<EmployeeDto>(employeeToAdd);
return CreatedAtRoute(nameof(GetEmployeeForCompany), new
{
companyId,
employeeId = dtoToReturn.Id
}, dtoToReturn);
}
var dtoToPatch = mapper.Map<EmployeeUpdateDto>(employeeEntity);
//需要处理验证错误
patchDocument.ApplyTo(dtoToPatch, ModelState);
if (!TryValidateModel(dtoToPatch))
{
return ValidationProblem(ModelState);//400 BadRequest
}
mapper.Map(dtoToPatch, employeeEntity);
companyRepository.UpdateEmployee(employeeEntity);
await companyRepository.SaveAsync();
return NoContent();
}
POST vs PUT
POST:
- 用来创建资源
- 服务器端负责URI的生成
PUT: - 必须使用一个已知的URI
- 如果URI对应的资源不存在,那么应该返回404错误
PUT用来新增或更新
PATCH
- 用来做局部更新的
- PATCH请求Body里面的数据格式为JSON PATCH (RFC 6902)
- PATCH请求的media type是application/json-patch+json
使用Newtonsoft.JSON替换3.1默认的json库
- 安装Microsoft.AspNetCore.Mvc.NewtonsoftJson包
- 在Startup.cs的ConfigureServices配置
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(configure: setup =>
{
setup.ReturnHttpNotAcceptable = true;
})
.AddNewtonsoftJson(setup =>
{
setup.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver();
})
}
在Controller里面使用自定义的错误验证报告信息
/// <summary>
/// 使用自定义的错误验证报告信息
/// </summary>
/// <param name="modelStateDictionary"></param>
/// <returns></returns>
public override ActionResult ValidationProblem([ActionResultObjectValue] ModelStateDictionary modelStateDictionary)
{
var options = HttpContext.RequestServices
.GetRequiredService<IOptions<ApiBehaviorOptions>>();
return (ActionResult)options.Value.InvalidModelStateResponseFactory(ControllerContext);
}
HttpDelete删除
[HttpDelete]
public async Task<IActionResult> DeleteEmployeeForCompany(Guid companyId, Guid employeeId)
{
if (!await companyRepository.CompanyExistsAsync(companyId))
{
return NotFound();
}
var employeeEnttiy = await companyRepository.GetEmployeeAsync(companyId, employeeId);
if (employeeEnttiy == null)
{
return NotFound();
}
companyRepository.DeleteEmployee(employeeEnttiy);
await companyRepository.SaveAsync();
return NoContent();
}
针对集合资源的分页
集合资源的数量通常比较大,需要对它们进行分页查询
避免性能问题
参数通过Query String进行传递
api/companies?pageNumber=1&pageSize=5
每页的笔数需要进行控制
默认就应该分页
应该对底层的数据存储进行分页
查询分页参数定义:
namespace Routine.Api.ResourceParameters
{
public class CompanyDtoParameter
{
private const int MaxPageSize = 20;
public string CompanyName { get; set; }
public string SearchTerm { get; set; }
public int PageNumber { get; set; } = 1;
private int pageSize = 5;
public int PageSize
{
get => pageSize;
set => pageSize = (value > MaxPageSize) ? MaxPageSize : value;
}
}
}
PagedList类定义
ResourceUriType:
namespace Routine.Api.Helpers
{
public enum ResourceUriType
{
PreviousePage,
NextPage
}
}
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Routine.Api.Helpers
{
public class PagedList<T> : List<T>
{
public int CurrentPage { get; set; }
public int TotalPages { get; set; }
public int PageSize { get; set; }
public int TotalCount { get; set; }
public bool HasPrevious => CurrentPage > 1;
public bool HasNext => CurrentPage < TotalPages;
public PagedList(List<T> items, int count, int pageNumber, int pageSize)
{
TotalCount = count;
PageSize = pageSize;
CurrentPage = pageNumber;
TotalPages = (int)Math.Ceiling(count / (double)PageSize);
AddRange(items);
}
public static async Task<PagedList<T>> CreateAsync(IQueryable<T> source, int pageNumber, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync();
return new PagedList<T>(items, count, pageNumber, pageSize);
}
}
}
查询定义:
public async Task<PagedList<Company>> GetCompaniesAsync(CompanyDtoParameter parameter)
{
if (parameter == null)
{
throw new ArgumentNullException(nameof(parameter));
}
var queryExpression = context.Companies as IQueryable<Company>;
if (!string.IsNullOrWhiteSpace(parameter.CompanyName))
{
queryExpression = queryExpression.Where(x => x.Name == parameter.CompanyName.Trim());
}
if (!string.IsNullOrWhiteSpace(parameter.SearchTerm))
{
queryExpression = queryExpression.Where(x => x.Name == parameter.SearchTerm.Trim() ||
x.Introduction.Contains(parameter.SearchTerm.Trim()));
}
//分页
//queryExpression = queryExpression
// .Skip(parameter.PageSize * (parameter.PageNumber - 1))
// .Take(parameter.PageSize);
//return await queryExpression.ToListAsync();
return await PagedList<Company>.CreateAsync(queryExpression, parameter.PageNumber, parameter.PageSize);
}
Controller中使用分页:
[HttpGet(Name = nameof(GetCompanies))]
[HttpHead]
public async Task<ActionResult<IEnumerable<CompanyDto>>> GetCompanies([FromQuery]CompanyDtoParameter parameter = null)
{
//var companies = await companyRepository.GetCompaniesAsync();
//return Ok(companies);
//CompanyDtoParameter parameter = null;
var companies = await companyRepository.GetCompaniesAsync(parameter);
//var companyDtos = new List<CompanyDto>();
//foreach (var item in companies)
//{
// companyDtos.Add(new CompanyDto()
// {
// Id = item.Id,
// CompanyName = item.Name
// });
//}
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头部,添加分页信息
Response.Headers.Add("X-Pagination", JsonSerializer.Serialize(paginationMetadata, new JsonSerializerOptions()
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
}));
var companyDtos = mapper.Map<IEnumerable<CompanyDto>>(companies);
return Ok(companyDtos);
}
/// <summary>
/// 创建分页url
/// </summary>
/// <param name="parameter"></param>
/// <param name="type"></param>
/// <returns></returns>
private string CreateCompaniesResourceUri(CompanyDtoParameter parameter, ResourceUriType type)
{
switch (type)
{
case ResourceUriType.PreviousePage:
return Url.Link(nameof(GetCompanies), new
{
pageNumber = parameter.PageNumber - 1,
pageSize = parameter.PageSize,
companyName = parameter.CompanyName,
searchTerm = parameter.SearchTerm
});
case ResourceUriType.NextPage:
return Url.Link(nameof(GetCompanies), new
{
pageNumber = parameter.PageNumber + 1,
pageSize = parameter.PageSize,
companyName = parameter.CompanyName,
searchTerm = parameter.SearchTerm
});
default:
return Url.Link(nameof(GetCompanies), new
{
pageNumber = parameter.PageNumber,
pageSize = parameter.PageSize,
companyName = parameter.CompanyName,
searchTerm = parameter.SearchTerm
});
}
}