您现在的位置是:网站首页> 编程资料编程资料

ASP.NET Core MVC/WebApi基础系列2_实用技巧_

2023-05-24 417人已围观

简介 ASP.NET Core MVC/WebApi基础系列2_实用技巧_

>前言

好久没冒泡了,算起来估计有快半年没更新博客了,估计是我第一次停更如此之久,人总有懒惰的时候,时间越长越懒惰,但是呢,不学又不行,持续的惰性是不行dei,要不然会被时光所抛弃,技术所淘汰,好吧,进入今天的主题,本节内容,我们来讲讲.NET Core当中的模型绑定系统、模型绑定原理、自定义模型绑定、混合绑定、ApiController特性本质,可能有些园友已经看过,但是效果不太好哈,这篇是解释最为详细的一篇,建议已经学过我发布课程的童鞋也看下,本篇内容略长,请保持耐心,我只讲你们会用到的或者说能够学到东西的内容。

模型绑定系统

对于模型绑定,.NET Core给我们提供了[BindRequired]、[BindNever]、[FromHeader]、[FromQuery]、[FromRoute]、[FromForm]、[FromServices]、[FromBody]等特性,[BindRequired]和[BindNever]翻译成必须绑定,从不绑定我们称之为行为绑定,而紧跟后面的五个From,翻译成从哪里来,我们称之为来源绑定,下面我们详细介绍这两种绑定类型,本节内容使用版本.NET Core 2.2版本。

行为绑定

[BindRequired]表示参数的键必须要提供,但是并不关心参数的值是否为空,[BindNever]表示忽略对属性的绑定,行为绑定看似很简单,其实不然,待我娓娓道来,首先我们来看如下代码片段。

 public class Customer { [BindNever] public int Id { get; set; } } [Route("[controller]")] public class ModelBindController : Controller { [HttpPost] public IActionResult Post(Customer customer) { if (!ModelState.IsValid) { return BadRequest(ModelState); } return Ok(); } }

上述我们定义了一个Customer类,然后类中的id字段通过[BindNever]特性进行标识,接下来我们一切都通过Postman来发出请求

当我们如上发送请求时,响应将返回状态码200成功且id没有绑定上,符合我们的预期,其意思就是从不绑定属性id,好接下来我们将控制器上的Post方法参数添加[FromBody]标识看看,代码片段变成如下:

 [HttpPost] public IActionResult Post([FromBody]Customer customer) { if (!ModelState.IsValid) { return BadRequest(ModelState); } return Ok(); }

这是为何,我们通过[FromBody]特性标识后,此时也将属性id加上了[BindNever]特性(代码和如上一样,不重复贴了),结果id绑定上了,说明[BindNever]特性对通过[FromBody]特性标识的参数无效,情况真的是这样吗?接下来我们尝试将[BindNever]绑定到对象看看,如下:

 public class Customer { public int Id { get; set; } } [Route("[controller]")] public class ModelBindController : Controller { [HttpPost] public IActionResult Post([BindNever][FromBody]Customer customer) { if (!ModelState.IsValid) { return BadRequest(ModelState); } return Ok(); } }

上述我们将[BindNever]绑定到对象Customer上,同时对于[BindNever]和[FromBody]特性没有先后顺序,也就是说我们也可以将[FromBody]放在[BindNever]后面,接下来我们利用Postman再次发送如下请求。

此时我们可以明确看到,我们发送的请求包含id字段,且此时我们将[BindNever]绑定到对象上时,最终id则没绑定到对象上,达到我们的预期且验证通过,但是话说回来,将[BindNever]绑定到对象上毫无意义,因为此时对象上所有属性都将会被忽略。所以到这里我们可以得出[BindNever]对于[FromBody]特性请求的结论:

对于使用【FromBody】特性标识的请求,【BindNever】特性应用到模型上的属性时,此时绑定无效,应用到模型对象上时,此时将完全忽略对模型对象上的所有属性

对于来自URL或者表单上的请求,【BindNever】特性应用到模型上的属性时,此时绑定无效,应用到模型对象时,此时将完全忽略对模型对象上的所有属性

好了,接下来我们再来看看[BindRequired],我们继续给出如下代码:

 public class Customer { [BindRequired] public int Id { get; set; } } [Route("[controller]")] public class ModelBindController : Controller { [HttpPost] public IActionResult Post(Customer customer) { if (!ModelState.IsValid) { return BadRequest(ModelState); } return Ok(); } }

通过[BindRequired]特性标识属性,我们基于表单的请求且未给出属性id的值,此时属性未绑定上且验证未通过,符合我们预期。接下来我们再来看看【FromBody】特性标识的请求,代码就不给出了,我们只是在对象上加上了[FromBody]而已,我们看看最终结果。

此时从表面上看好像达到了我们的预期,在这里即使我们对属性id不指定【BindRequired】特性,结果也是一样验证未通过,这是为何,因为默认情况下,在.NET Core中对于【FromBody】特性标识的对象不可为空,内置进行了处理,我们进行如下设置允许为空。

 services.AddMvc(options=> { options.AllowEmptyInputInBodyModelBinding = true; }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

我们进行上述设置后,我们不给定属性id的值,肯定会验证通过对不对,那我们接下来再给定一个属性Age呢,然后发出请求不包含Age属性,如下

 public class Customer { [BindRequired] public int Id { get; set; } [BindRequired] public int Age { get; set; } }

到这里我们发现我们对属性Age添加了【BindRequired】特性,此时验证却是通过的,我们再加思考一番,或许是我们给定的属性Age是int有默认值为0,所以验证通过,好想法,你可以继续添加一个字符串类型的属性,然后添加【BindRequired】特性,同时最后请求中不包含该属性,此时结果依然是验证通过的(不信自己试试)。

此时我们发现通过[FromBody]特性标识的请求,我们将默认对象不可空的情况排除在外,说明[BindRequired]特性标识的属性对[FromBody]特性标识的请求无效,同时呢,我们转到[BindRequired]特性的定义有如下解释:

// 摘要:
// Indicates that a property is required for model binding. When applied to a property, the model binding system requires a value for that property. When applied to
// a type, the model binding system requires values for all properties that type defines.

翻译过来不难理解,当我们通过[BindRequired]特性标识时,说明在模型绑定时属性是必须给出的,当应用到属性时,要求模型绑定系统必须验证此属性的值必须要给出,当应用到类型时,要求模型绑定系统必须验证类型中定义的所有属性必须有值。这个解释让我们无法信服,对于基于URL或者基于表单的请求和【FromBody】特性的请求明显有区别,但是定义却是一概而论。到这里我们遗漏到了一个【Required】特性,我们添加一个Address属性,然后请求中不包含Address属性,

 public class Customer { [BindRequired] public int Id { get; set; } [BindRequired] public int Age { get; set; } [Required] public string Address { get; set; } }

从上图看出使用【FromBody】标识的请求,通过Required特性标识属性也符合预期,当然对于URL和表单请求也符合预期,在此不再演示。我并未看过源码,我大胆猜测下是否是如下原因才有其区别呢(个人猜测)

解释都在强调模型绑定系统,所以在.NET Core中出现的【BindNever】和【BindRequired】特性专为.NET Core MVC模型绑定系统而设计,而对于【FromBody】特性标识后,因为其进行属性的序列化和反序列化与Input Formatter有关,比如通过JSON.NET,所以至于属性的忽略和映射与否和我们使用序列化和反序列化的框架有关,由我们自己来定义,比如使用JSON.NET则属性忽略使用【JsonIgnore】。

所以说基于【FromBody】特性标识的请求,是否映射,是否必须由我们使用的序列化和反序列化框架决定,在.NET Core中默认是JSON.NET,所以对于如上属性是否必须提供,我们需要使用JSON.NET中的Api,比如如下。

 public class Customer { [JsonProperty(Required = Required.Always)] public int Id { get; set; } [JsonProperty(Required = Required.Always)] public int Age { get; set; } }

请求参数安全也是需要我们考虑的因素,比如如下我们对象包含IsAdmin属性,我们后台会根据该属性值判断是否为对应角色进行UI的渲染,我们可以通过[Bind]特性应用于对象指定映射哪些属性,此时请求中参数即使显式指定了该参数值也不会进行映射(这里仅仅只是举例说明,例子可能并非合理),代码如下:

 public class Customer { public int Id { get; set; } public int Age { get; set; } public string Address { get; set; } public bool IsAdmin { get; set; } } [Route("[controller]")] public class ModelBindController : Controller { [HttpPost] public IActionResult Post( [Bind(nameof(Customer.Id),nameof(Customer.Age),nameof(Customer.Address) )] Customer customer) { if (!ModelState.IsValid) { return BadRequest(ModelState); } return Ok(); } }

来源绑定

在.NET Core中出现了不同的特性,比如上述我们所讲解的行为绑定,然后是接下来我们要讲解的来源绑定,它们出现的意义和作用在哪里呢?它比.NET中的模型绑定更加灵活,而不是一样,为何灵活不是我嘴上说说而已,通过实际例子证明给你看,每一个新功能或特性的出现是为了解决对应的问题或改善对应的问题,首先我们来看如下代码:

 [Route("[controller]")] public class ModelBindController : Controller { [HttpPost("{id:int}")] public IActionResult Post(int id, Customer customer) { if (!ModelState.IsValid) { return BadRequest(ModelState); } return Ok(); } }

我们通过路由指定id为4,然后url上指定为3,你猜映射到后台id上的参数结果是4还是3呢,在customer上的参数id是4还是3呢?

从上图我们看到id是4,而customer对象中的id值为2,我们从中可以得出一个什么结论呢,来,我们进行如下总结。

在.NET Core中,默认情况下参数绑定存在优先级,路由的优先级大于表单的优先级,表单的优先级大于URL的优先级即(路由>表单>URL)

这是默认情况下的优先级,为什么说在.NET Core中非常灵活呢,因为我们可以通过来源进行显式绑定,比如强制指定id来源于查询字符串,而customer中的id源于查询路由,如下:

 [HttpPost("{id:int}")] public IActionResult Post([FromQuery]int id, [FromRoute] Customer customer) { if (!ModelState.IsValid) { return BadRequest(ModelState); } return Ok(); }

还有什么[FromForm]、[FromServices]、[FromHeader]等来源绑定都是强制指定参数到底是来源于表单、请求头、查询字符串、路由还是Body,到这里无需我再过多讲解了,一个例子足以说明其灵活性。

模型绑定(强大支持举例)

上述讲解来源绑定我们认识到其灵活性,可能有部分童鞋压根都不知道.NET Core中对模型绑定的强大支持,哪里强大了,在讲解模型绑定原理之前,来给大家举几个实际的例子来说明,首先我们来看如下请求代码:

对于如上请求,我们大部分的做法则是通过如下创建一个类来接受上述URL参数。

 public class Example { public int A { get; set; } public int B { get; set; } public int C { get; set; } } [Route("[controller]")] public class ModelBindController : Controller { [HttpGet] public IActionResult Post(Example employee) { return Ok(); } }

这种常见做法在ASP.NET MVC/Web Api中也是支持的,好了,接下来我们将上述控制器代码进行如下修改后在.NET Core中是支持的,而在.NET MVC/Web Api中是不支持的,不信,您可以试试。

提示:
                    本文由整理自网络,如有侵权请联系本站删除!
                    
本站声明:
1、本站所有资源均来源于互联网,不保证100%完整、不提供任何技术支持;
2、本站所发布的文章以及附件仅限用于学习和研究目的;不得将用于商业或者非法用途;否则由此产生的法律后果,本站概不负责!

-六神源码网