services.AddMvc(options => { var loggerFactory = _serviceProvider.GetService<ILoggerFactory>(); options.ModelBinderProviders.Insert(0, new RMBAttributeModelBinderProvider(loggerFactory)); }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
public class Employee { [Required] [RMB] public decimal Salary { get; set; } }
混合绑定
什么是混合绑定呢?就是将不同的绑定模式混合在一起使用,有的人可说了,你这和没讲有什么区别,好了,我来举一个例子,比如我们想将URL上的参数绑定到【FromBody】特性的参数上,前提是在URL上的参数在【FromBody】参数没有,好像还是有点模糊,来,上代码。
[Route("[controller]")] public class ModelBindController : Controller { [HttpPost("{id:int}")] public IActionResult Post([FromBody]Employee customer) { if (!ModelState.IsValid) { return BadRequest(ModelState); } return Ok(); } }
public class Employee { public int Id { get; set; } [Required] public decimal Salary { get; set; } }
如上示意图想必已经很明确了,在Body中我们并未指定属性Id,但是我们想要将路由中的id也就是4绑定到【FromBody】标识的参数Employee的属性Id,例子跟实际不是合理的,只是为了演示混合绑定,这点请忽略。问题已经阐述的非常明确了,不知您是否有了解决思路,既然是【FromBody】,内置已经实现的BodyModelBinder我们依然要绑定,我们只需要将路由中的值绑定到Employee对象中的id即可,来,我们首先实现IModelBinderProvider接口,如下:
public class MixModelBinderProvider : IModelBinderProvider { private readonly IList<IInputFormatter> _formatters; private readonly IHttpRequestStreamReaderFactory _readerFactory; public MixModelBinderProvider(IList<IInputFormatter> formatters, IHttpRequestStreamReaderFactory readerFactory) { _formatters = formatters; _readerFactory = readerFactory; } public IModelBinder GetBinder(ModelBinderProviderContext context) { //如果上下文为空,返回空 if (context == null) { throw new ArgumentNullException(nameof(context)); } //如果元数据模型类型为Employee实例化MixModelBinder if (context.Metadata.ModelType == typeof(Employee)) { return new MixModelBinder(_formatters, _readerFactory); } return null; } }
接下来则是实现IModelBinder接口诺,绑定【FromBody】特性请求参数,绑定属性Id。
public class MixModelBinder : IModelBinder { private readonly BodyModelBinder bodyModelBinder; public MixModelBinder(IList<IInputFormatter> formatters, IHttpRequestStreamReaderFactory readerFactory) { //原来【FromBody】绑定参数依然要绑定,所以需要实例化BodyModelBinder bodyModelBinder = new BodyModelBinder(formatters, readerFactory); } public Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); } //绑定【FromBody】特性请求参数 bodyModelBinder.BindModelAsync(bindingContext); if (!bindingContext.Result.IsModelSet) { return null; } //获取绑定对象 var model = bindingContext.Result.Model; //绑定属性Id if (model is Employee employee) { var idString = bindingContext.ValueProvider.GetValue("id").FirstValue; if (int.TryParse(idString, out var id)) { employee.Id = id; } bindingContext.Result = ModelBindingResult.Success(model); } return Task.CompletedTask; } }
其实到这里我们应该更加明白,【BindRequired】和【BindNever】特性只针对MVC模型绑定系统起作用,而对于【FromBody】特性的请求参数与Input Formatter有关,也就是与所用的序列化和反序列化框架有关。接下来我们添加自定义实现的混合绑定类,如下: