从头编写 asp.net core 2.0 web api 基础框架 (2)
2017-10-09 18:23
911 查看
上一篇是: http://www.cnblogs.com/cgzl/p/7637250.html
Github源码地址是: https://github.com/solenovex/Building-asp.net-core-2-web-api-starter-template-from-scratch
本文讲的是里面的Step 2.
上一次, 我们使用asp.net core 2.0 建立了一个Empty project, 然后做了一些基本的配置, 并建立了两个Controller, 写了一些查询方法.
下面我们继续:
先看看Model, 其中的Id属性, 一般是创建的时候服务器自动生成的, 所以如果客户端在进行Post(创建)的时候, 它是不会提供Id属性的.
所以, 可以这样做, 再建立一个Dto, 专门用于创建: ProductCreation.cs:
这里去掉了Id和Materials这个导航属性.
其实也可以使用同一个Model来做所有的操作, 因为它们的大部分属性都是相同的, 但是,
还是建议针对查询, 创建, 修改, 使用单独的Model, 这样以后修改和重构会简单一些, 再说他们的验证也是不一样的.
[HttpPost] 表示请求的谓词是Post. 加上Controller的Route前缀, 那么访问这个Action的地址就应该是: 'api/product'
后边也可以跟着自定义的路由地址, 例如 [HttpPost("create")], 那么这个Action的路由地址就应该是: 'api/product/create'.
[FromBody] , 请求的body里面包含着方法需要的实体数据, 方法需要把这个数据Deserialize成ProductCreation, [FromBody]就是干这些活的.
客户端程序可能会发起一个Bad的Request, 导致数据不能被Deserialize, 这时候参数product就会变成null. 所以这是一个客户端发生的错误, 程序为让客户端知道是它引起了错误, 就应该返回一个Bad Request 400 (Bad Request表示客户端引起的错误)的 Status Code.
传递进来的model类型是 ProductCreation, 而我们最终操作的类型是Product, 所以需要进行一个Map操作, 目前还是挨个属性写代码进行Map吧, 以后会改成Automapper.
返回 [b]CreatedAtRoute:[/b] 对于POST, 建议的返回Status Code 是 201 (Created), 可以使用CreatedAtRoute这个内置的Helper Method. 它可以返回一个带有地址Header的Response, 这个Location Header将会包含一个URI, 通过这个URI可以找到我们新创建的实体数据. 这里就是指之前写的GetProduct(int id)这个方法. 但是这个Action必须有一个路由的名字才可以引用它, 所以在GetProduct方法上的Route这个attribute里面加上Name="GetProduct", 然后在CreatedAtRoute方法第一个参数写上这个名字就可以了, 尽管进行了引用, 但是Post方法走完的时候并不会调用GetProduct方法. CreatedAtRoute第二个参数就是对应着GetProduct的参数列表, 使用匿名类即可, 最后一个参数是我们刚刚创建的数据实体.
运行程序试验一下, 注意需要在Headers里面设置Content-Type: application/json. 结果如图:
View Code
然后我们用PUT进行实验单个属性修改:
这对这条数据:
我们修改name和price属性:
然后再看一下修改后的数据:
Description被设置成null. 这就是HTTP PUT标准的本意: 整体修改, 更新所有属性, 尽管你的代码可能不这么做.
针对Request Body这种情况, 有一个标准叫做 Json Patch RFC 6092, 它定义了一种json数据的结构 可以表示上面说的那些东西.
Json Patch定义的操作包含替换, 复制, 移除等操作.
这对我们的Product, 它的结构应该是这样的:
op 表示操作, replace 是指替换; path就是属性名, value就是值.
相应的Patch方法:
HttpPatch, 按约定方法有一个参数id, 还有一个JsonPatchDocument类型的参数, 它的泛型应该是用于Update的Dto, 所以选择的是ProductionModification. 如果使用Product这个Dto的话, 那么它包含id属性, 而id属性是不更改的. 但如果你没有针对不同的操作使用不同的Dto, 那么别忘了检查传入Dto的id 要和参数id一致才行.
然后把查询出来的product转化成用于更新的ProductModification这个Dto, 然后应用于Patch Document 就是指为toPatch这个model更新那些需要更新的属性, 是使用ApplyTo方法实现的.
但是这时候可能会出错, 比如说修改一个根本不存在的属性, 也就是说客户端可能引起了错误, 这时候就需要它进行验证, 并返回Bad Request. 所以就加上ModelState这个参数. 然后进行判断即可.
然后就是和PUT一样的更新操作, 把toPatch这个Update的Dto再整体更新给model. 其实里面不管怎么实现, 只要按约定执行就好.
然后按建议, 返回NoContent().
试一下:
然后查询一下:
与期待的结果一样.
然后试一下传入一个不存在的属性:
结果显示找不到这个属性.
再试一下, ProductModification 这个model上的验证: 例如删除name这个属性的值:
返回204, 表示成功, 但是name是必填的, 所以代码还有问题.
我们做了ModelState检查, 但是为什么没有验证出来呢? 这是因为, Patch方法的Model参数是JsonPatchDocument而不是ProductModification, 上面传进去的参数对于JsonPatchDocument来说是没有问题的.
所以我们需要对toPatch这个model进行验证:
使用TryValidateModel(xxx)对model进行手动验证, 结果也会反应在ModelState里面.
再试一次上面的操作:
这回对了.
按Http Delete约定, 参数为id, 如果操作成功就回NoContent();
试一下:
成功.
目前, CRUD最基本的操作先告一段落.
上班了比较忙了, 今天先写这些.....................................................
Github源码地址是: https://github.com/solenovex/Building-asp.net-core-2-web-api-starter-template-from-scratch
本文讲的是里面的Step 2.
上一次, 我们使用asp.net core 2.0 建立了一个Empty project, 然后做了一些基本的配置, 并建立了两个Controller, 写了一些查询方法.
下面我们继续:
POST
POST一般用来表示创建资源, 也就是新增.先看看Model, 其中的Id属性, 一般是创建的时候服务器自动生成的, 所以如果客户端在进行Post(创建)的时候, 它是不会提供Id属性的.
public class Product { public int Id { get; set; } public string Name { get; set; } public float Price { get; set; } public ICollection<Material> Materials { get; set; } }
所以, 可以这样做, 再建立一个Dto, 专门用于创建: ProductCreation.cs:
namespace CoreBackend.Api.Dtos { public class ProductCreation { public string Name { get; set; } public float Price { get; set; } } }
这里去掉了Id和Materials这个导航属性.
其实也可以使用同一个Model来做所有的操作, 因为它们的大部分属性都是相同的, 但是,
还是建议针对查询, 创建, 修改, 使用单独的Model, 这样以后修改和重构会简单一些, 再说他们的验证也是不一样的.
创建Post Action
[Route("{id}", Name = "GetProduct")] public IActionResult GetProduct(int id) { var product = ProductService.Current.Products.SingleOrDefault(x => x.Id == id); if (product == null) { return NotFound(); } return Ok(product); } [HttpPost] public IActionResult Post([FromBody] ProductCreation product) { if (product == null) { return BadRequest(); } var maxId = ProductService.Current.Products.Max(x => x.Id); var newProduct = new Product { Id = ++maxId, Name = product.Name, Price = product.Price }; ProductService.Current.Products.Add(newProduct); return CreatedAtRoute("GetProduct", new { id = newProduct.Id }, newProduct); }
[HttpPost] 表示请求的谓词是Post. 加上Controller的Route前缀, 那么访问这个Action的地址就应该是: 'api/product'
后边也可以跟着自定义的路由地址, 例如 [HttpPost("create")], 那么这个Action的路由地址就应该是: 'api/product/create'.
[FromBody] , 请求的body里面包含着方法需要的实体数据, 方法需要把这个数据Deserialize成ProductCreation, [FromBody]就是干这些活的.
客户端程序可能会发起一个Bad的Request, 导致数据不能被Deserialize, 这时候参数product就会变成null. 所以这是一个客户端发生的错误, 程序为让客户端知道是它引起了错误, 就应该返回一个Bad Request 400 (Bad Request表示客户端引起的错误)的 Status Code.
传递进来的model类型是 ProductCreation, 而我们最终操作的类型是Product, 所以需要进行一个Map操作, 目前还是挨个属性写代码进行Map吧, 以后会改成Automapper.
返回 [b]CreatedAtRoute:[/b] 对于POST, 建议的返回Status Code 是 201 (Created), 可以使用CreatedAtRoute这个内置的Helper Method. 它可以返回一个带有地址Header的Response, 这个Location Header将会包含一个URI, 通过这个URI可以找到我们新创建的实体数据. 这里就是指之前写的GetProduct(int id)这个方法. 但是这个Action必须有一个路由的名字才可以引用它, 所以在GetProduct方法上的Route这个attribute里面加上Name="GetProduct", 然后在CreatedAtRoute方法第一个参数写上这个名字就可以了, 尽管进行了引用, 但是Post方法走完的时候并不会调用GetProduct方法. CreatedAtRoute第二个参数就是对应着GetProduct的参数列表, 使用匿名类即可, 最后一个参数是我们刚刚创建的数据实体.
运行程序试验一下, 注意需要在Headers里面设置Content-Type: application/json. 结果如图:
[HttpPost] public IActionResult Post([FromBody] ProductCreation product) { if (product == null) { return BadRequest(); } if (product.Name == "产品") { ModelState.AddModelError("Name", "产品的名称不可以是'产品'二字"); } if (!ModelState.IsValid) { return BadRequest(ModelState); } var maxId = ProductService.Current.Products.Max(x => x.Id); var newProduct = new Product { Id = ++maxId, Name = product.Name, Price = product.Price, Description = product.Description }; ProductService.Current.Products.Add(newProduct); return CreatedAtRoute("GetProduct", new { id = newProduct.Id }, newProduct); } [HttpPut("{id}")] public IActionResult Put(int id, [FromBody] ProductModification product) { if (product == null) { return BadRequest(); } if (product.Name == "产品") { ModelState.AddModelError("Name", "产品的名称不可以是'产品'二字"); } if (!ModelState.IsValid) { return BadRequest(ModelState); } var model = ProductService.Current.Products.SingleOrDefault(x => x.Id == id); if (model == null) { return NotFound(); } model.Name = product.Name; model.Price = product.Price; model.Description = product.Description; return NoContent(); }
View Code
然后我们用PUT进行实验单个属性修改:
这对这条数据:
我们修改name和price属性:
然后再看一下修改后的数据:
Description被设置成null. 这就是HTTP PUT标准的本意: 整体修改, 更新所有属性, 尽管你的代码可能不这么做.
Patch 部分更新
Http Patch 就是做部分更新的, 它的Request Body应该包含需要更新的属性名 和 值, 甚至也可以包含针对这个属性要进行的相应操作.针对Request Body这种情况, 有一个标准叫做 Json Patch RFC 6092, 它定义了一种json数据的结构 可以表示上面说的那些东西.
Json Patch定义的操作包含替换, 复制, 移除等操作.
这对我们的Product, 它的结构应该是这样的:
op 表示操作, replace 是指替换; path就是属性名, value就是值.
相应的Patch方法:
[HttpPatch("{id}")] public IActionResult Patch(int id, [FromBody] JsonPatchDocument<ProductModification> patchDoc) { if (patchDoc == null) { return BadRequest(); } var model = ProductService.Current.Products.SingleOrDefault(x => x.Id == id); if (model == null) { return NotFound(); } var toPatch = new ProductModification { Name = model.Name, Description = model.Description, Price = model.Price }; patchDoc.ApplyTo(toPatch, ModelState); if (!ModelState.IsValid) { return BadRequest(ModelState); } model.Name = toPatch.Name; model.Description = toPatch.Description; model.Price = toPatch.Price; return NoContent(); }
HttpPatch, 按约定方法有一个参数id, 还有一个JsonPatchDocument类型的参数, 它的泛型应该是用于Update的Dto, 所以选择的是ProductionModification. 如果使用Product这个Dto的话, 那么它包含id属性, 而id属性是不更改的. 但如果你没有针对不同的操作使用不同的Dto, 那么别忘了检查传入Dto的id 要和参数id一致才行.
然后把查询出来的product转化成用于更新的ProductModification这个Dto, 然后应用于Patch Document 就是指为toPatch这个model更新那些需要更新的属性, 是使用ApplyTo方法实现的.
但是这时候可能会出错, 比如说修改一个根本不存在的属性, 也就是说客户端可能引起了错误, 这时候就需要它进行验证, 并返回Bad Request. 所以就加上ModelState这个参数. 然后进行判断即可.
然后就是和PUT一样的更新操作, 把toPatch这个Update的Dto再整体更新给model. 其实里面不管怎么实现, 只要按约定执行就好.
然后按建议, 返回NoContent().
试一下:
然后查询一下:
与期待的结果一样.
然后试一下传入一个不存在的属性:
结果显示找不到这个属性.
再试一下, ProductModification 这个model上的验证: 例如删除name这个属性的值:
返回204, 表示成功, 但是name是必填的, 所以代码还有问题.
我们做了ModelState检查, 但是为什么没有验证出来呢? 这是因为, Patch方法的Model参数是JsonPatchDocument而不是ProductModification, 上面传进去的参数对于JsonPatchDocument来说是没有问题的.
所以我们需要对toPatch这个model进行验证:
[HttpPatch("{id}")] public IActionResult Patch(int id, [FromBody] JsonPatchDocument<ProductModification> patchDoc) { if (patchDoc == null) { return BadRequest(); } var model = ProductService.Current.Products.SingleOrDefault(x => x.Id == id); if (model == null) { return NotFound(); } var toPatch = new ProductModification { Name = model.Name, Description = model.Description, Price = model.Price }; patchDoc.ApplyTo(toPatch, ModelState); if (!ModelState.IsValid) { return BadRequest(ModelState); } if (toPatch.Name == "产品") { ModelState.AddModelError("Name", "产品的名称不可以是'产品'二字"); } TryValidateModel(toPatch); if (!ModelState.IsValid) { return BadRequest(ModelState); } model.Name = toPatch.Name; model.Description = toPatch.Description; model.Price = toPatch.Price; return NoContent(); }
使用TryValidateModel(xxx)对model进行手动验证, 结果也会反应在ModelState里面.
再试一次上面的操作:
这回对了.
DELETE 删除
这个比较简单:[HttpDelete("{id}")] public IActionResult Delete(int id) { var model = ProductService.Current.Products.SingleOrDefault(x => x.Id == id); if (model == null) { return NotFound(); } ProductService.Current.Products.Remove(model); return NoContent(); }
按Http Delete约定, 参数为id, 如果操作成功就回NoContent();
试一下:
成功.
目前, CRUD最基本的操作先告一段落.
上班了比较忙了, 今天先写这些.....................................................
相关文章推荐
- 从头编写 asp.net core 2.0 web api 基础框架 (1)
- 从头编写 asp.net core 2.0 web api 基础框架 (3)
- 从头编写 asp.net core 2.0 web api 基础框架 (5) EF CRUD
- 从头编写 asp.net core 2.0 web api 基础框架 (1)
- 从头编写 asp.net core 2.0 web api 基础框架 (3)
- 从头编写 asp.net core 2.0 web api 基础框架 (4) EF配置
- 从头编写 asp.net core 2.0 web api 基础框架 (2)
- 从头编写 asp.net core 2.0 web api 基础框架 (5) EF CRUD
- 从头编写 asp.net core 2.0 web api 基础框架 (4) EF配置
- 从头编写 asp.net core 2.0 web api 基础框架 (1)
- 在 Asp.net core 2.0 的Web Api 添加logging
- asp.net core 2.0 web api基于JWT自定义策略授权
- 用VSCode开发一个asp.net core2.0+angular5项目(5): Angular5+asp.net core 2.0 web api文件上传
- 基础教程:ASP.NET Core 2.0 MVC筛选器
- asp.net core 2.0 web api基于JWT自定义策略授权
- asp.net core 2.0 web api基于JWT自定义策略授权
- 基础教程:上传/下载ASP.NET Core 2.0中的文件
- IdentityServer4 ASP.NET Core的OpenID Connect OAuth 2.0框架学习保护API
- 基础教程:视图中的ASP.NET Core 2.0 MVC依赖注入
- IdentityServer4 ASP.NET Core的OpenID Connect OAuth 2.0框架学习保护API