net core RESTful Api笔记⑤
PUT VS PATCH
put整体更新:资源所有字段都被重写或者设置成默认值。
patch局部更新:使用JsonPatchDocument发送变更的数据,对指定字段进行更新。
修改EmployessController
[HttpPut("{employeeId}")] public async Task<IActionResult> UpdateEmployeeForCompany(Guid companyId, Guid employeeId, EmployUpdateDto employ) { if (!await companyRepository.CompanyExistsAsync(companyId)) { return NotFound(); } var employeeEntity = await companyRepository.GetEmployeeAsync(companyId,employeeId); if (employeeEntity == null) { return NotFound(); } //entity转化成updatedto //把转化进来的employ的值更新到updateDto //把updatedto映射回entity mapper.Map(employ, employeeEntity); companyRepository.UpdateEmployee(employeeEntity); await companyRepository.SaveAsync(); return NoContent(); }
添加EmployUpdateDto
using Rountion.API.Eneities; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Rountine.API.Models { public class EmployUpdateDto { 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; } } }
结果:
PUT的更新和新增
正常的put如果传递uri没有找到,返回404,如果客户端允许生成uri,传递到服务器可以创建资源
修改EmployessController
[HttpPut("{employeeId}")] public async Task<IActionResult> UpdateEmployeeForCompany(Guid companyId, Guid employeeId, EmployUpdateDto employ) { if (!await companyRepository.CompanyExistsAsync(companyId)) { return NotFound(); } var employeeEntity = await companyRepository.GetEmployeeAsync(companyId,employeeId); if (employeeEntity == null) { var employeeToAddEntity = mapper.Map<Employee>(employ); employeeToAddEntity.Id = employeeId; companyRepository.AddEmployee(companyId,employeeToAddEntity); await companyRepository.SaveAsync(); var dtoToReturn = mapper.Map<EmployeeDto>(employeeToAddEntity); return CreatedAtRoute(nameof(GetemployesFromCompany), new { CompanyId = companyId, employeeId = dtoToReturn.Id }, dtoToReturn); } //entity转化成updatedto //把转化进来的employ的值更新到updateDto //把updatedto映射回entity mapper.Map(employ, employeeEntity); companyRepository.UpdateEmployee(employeeEntity); await companyRepository.SaveAsync(); return NoContent(); }
Patch用来局部更新,patch请求的body里面的数据格式是json patch:
Patch请求的media type是application/json-patch+json
json patch options:
add:{"op":"add","path":"/biscuit/1","value":{"name":"Ginger Nut"}}
Remove:{"op":"remove","path":"/biscuits"}
Move:{"op":"move","from":"/biscuits","path":"/cookies"}
Replice:{"op":"replice","path":"/biscuits/0/name","value":"Chocolate Digestive"}
Copy:{"op":"copy","from":"/biscuits/0","path":"/best_biscuit"}
Test:{"op":"test","path":"/best_biscuit/name","value":"Chocolate Digestive"}
修改EmployessController
[HttpPatch("{employeeId}")] public async Task<IActionResult> PartiallyUpdateEmployeeForCompany(Guid companyId,Guid employeeId,JsonPatchDocument<EmployUpdateDto> patchDocument) { if (!await companyRepository.CompanyExistsAsync(companyId)) { return NotFound(); } var employeeEntity = await companyRepository.GetEmployeeAsync(companyId, employeeId); if (employeeEntity == null) { return NotFound(); } var dotToPatch = mapper.Map<EmployUpdateDto>(employeeEntity); //需要处理验证错误 patchDocument.ApplyTo(dotToPatch); mapper.Map(dotToPatch,employeeEntity); companyRepository.UpdateEmployee(employeeEntity); await companyRepository.SaveAsync(); return NoContent(); }
JsonPatchDocument需要在startup里注册
services.AddControllers(setup => { //返回的不是请求类型,报错 setup.ReturnHttpNotAcceptable = true; }).AddNewtonsoftJson(setup=> { setup.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); }).AddXmlDataContractSerializerFormatters();
最后的结果:
这里其实对返回值和在没有查到情况下没有处理,处理方式:
[HttpPatch("{employeeId}")] public async Task<IActionResult> PartiallyUpdateEmployeeForCompany(Guid companyId,Guid employeeId,JsonPatchDocument<EmployUpdateDto> patchDocument) { if (!await companyRepository.CompanyExistsAsync(companyId)) { return NotFound(); } var employeeEntity = await companyRepository.GetEmployeeAsync(companyId, employeeId); if (employeeEntity == null) { var employeeDto = new EmployUpdateDto(); 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<Employee>(employeeToAdd); return CreatedAtRoute(nameof(GetemployesFromCompany), new { CompanyId = companyId, employeeId = dtoToReturn.Id }, dtoToReturn); } var dotToPatch = mapper.Map<EmployUpdateDto>(employeeEntity); //需要处理验证错误 //这个是检测JsonPatchDocument //不到对应映射的属性会ModelState出错 patchDocument.ApplyTo(dotToPatch,ModelState); //假如为不空的字段更新""就不行 if (TryValidateModel(dotToPatch)) { return ValidationProblem(ModelState); } mapper.Map(dotToPatch,employeeEntity); companyRepository.UpdateEmployee(employeeEntity); await companyRepository.SaveAsync(); return NoContent(); } /// <summary> /// 自定义422 /// </summary> /// <returns></returns> public override ActionResult ValidationProblem() { var options = HttpContext.RequestServices.GetRequiredService<IOptions<ApiBehaviorOptions>>(); return (ActionResult)options.Value.InvalidModelStateResponseFactory(ControllerContext); }
上面的自定义错误是需要在startup里配置的:AddControllers里
.ConfigureApiBehaviorOptions( setup=> { setup.InvalidModelStateResponseFactory = context => { var problemDetiles = new ValidationProblemDetails(context.ModelState) { Type = "http://ww.baidu.com", Title = "chucuo", Status = StatusCodes.Status422UnprocessableEntity, Detail = "看信息", Instance = context.HttpContext.Request.Path }; problemDetiles.Extensions.Add("traceId",context.HttpContext.TraceIdentifier); return new UnprocessableEntityObjectResult(problemDetiles) { ContentTypes = { "application/problem+json"} }; }; });
httpDelete:
[HttpDelete("{employeeId}")] public async Task<IActionResult> DeleteEmployeeFromCompany(Guid companyId, Guid employeeId) { if (!await companyRepository.CompanyExistsAsync(companyId)) { return NotFound(); } var employeeEntity = await companyRepository.GetEmployeeAsync(companyId, employeeId); if (employeeEntity == null) { return NotFound(); } companyRepository.DeleteEmployee(employeeEntity); await companyRepository.SaveAsync(); return NoContent(); }
对于删除公司和员工做法一样,不过需要在OnModelCreating的对应关系里标明级联删除DeleteBehavior.Cascade,如果不行就在controller的delete里加上employee加载到内存执行delete。