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 (!awaitcompanyRepository.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。