[Route("[controller]")] public class ModelBindController : Controller { [HttpPost] public IActionResult Post(Employee customer) { if (!ModelState.IsValid) { return BadRequest(ModelState); } return Ok(); }
从如上图响应结果看出,此时默认的模型绑定系统将不再适用,因为我们加上了币种符号,所以此时我们必须实现自定义的模型绑定,接下来我们通过两种不同的方式来实现自定义模型绑定。
货币符号自定义模型绑定方式(一)
我们知道对于货币符号可以通过NumberStyles.Currency来指定,有了解过模型绑定原理的童鞋应该知道对于在.NET Core默认的ModelBinderProviders集合中并有DecimalModelBinderProvider,而是FloatingPointTypeModelBinderProvider来支持货币符号,而对应背后的具体实现是DecimalModelBinder,所以我们大可借助于内置已经实现的DecimalModelBinder来实现自定义模型绑定,所以此时我们仅仅只需要实现IModelBinderProvider接口,而IModelBinder接口对应的就是DecimalModelBinder内置已经实现,代码如下:
public class RMBModelBinderProvider : IModelBinderProvider { private readonly ILoggerFactory _loggerFactory; public RMBModelBinderProvider(ILoggerFactory loggerFactory) { _loggerFactory = loggerFactory; } public IModelBinder GetBinder(ModelBinderProviderContext context) { //元数据为复杂类型直接跳过 if (context.Metadata.IsComplexType) { return null; } //上下文中获取元数据类型非decimal类型直接跳过 if (context.Metadata.ModelType != typeof(decimal)) { return null; } return new DecimalModelBinder(NumberStyles.Currency, _loggerFactory); } }
接下来则是将我们上述实现的RMBModelBinderProvider添加到ModelBinderProviders集合中去,这里需要注意,我们知道最终得到具体的ModelBinder,内置是采用遍历集合而实现,一旦找到直接跳出,所以我们将自定义实现的ModelBinderProvider强烈建议添加到集合中首位即使用Insert方法,而不是Add方法,如下:
services.AddMvc(options => { var loggerFactory = _serviceProvider.GetService<ILoggerFactory>(); options.ModelBinderProviders.Insert(0, new RMBModelBinderProvider(loggerFactory)); }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
货币符号自定义模型绑定方式(二)
上述我们是采用内置提供给我们的DecimalModelBinder解决了货币符号问题,接下来我们将通过特性来实现指定属性为货币符号,首先我们定义如下接口解析属性值是否成功与否
public interface IRMB { decimal RMB(string modelValue, out bool success); }
然后写一个如下RMB属性特性实现上述接口。
[AttributeUsage(AttributeTargets.Property)] public class RMBAttribute : Attribute, IRMB { private static NumberStyles styles = NumberStyles.Currency; private CultureInfo CultureInfo = new CultureInfo("zh-cn"); public decimal RMB(string modelValue, out bool success) { success = decimal.TryParse(modelValue, styles, CultureInfo, out var valueDecimal); return valueDecimal; } }
接下来我们则是实现IModelBinderProvider接口,然后在此接口实现中去获取模型元数据类型中的属性是否实现了上述RMB特性,如果是,我们则实例化ModelBinder并将RMB特性传递过去并得到其值,完整代码如下:
public class RMBAttributeModelBinderProvider : IModelBinderProvider { private readonly ILoggerFactory _loggerFactory; public RMBAttributeModelBinderProvider(ILoggerFactory loggerFactory) { _loggerFactory = loggerFactory; } public IModelBinder GetBinder(ModelBinderProviderContext context) { if (!context.Metadata.IsComplexType) { var propertyName = context.Metadata.PropertyName; var propertyInfo = context.Metadata.ContainerMetadata.ModelType.GetProperty(propertyName); var attribute = propertyInfo.GetCustomAttributes(typeof(RMBAttribute), false).FirstOrDefault(); if (attribute != null) { return new RMBAttributeModelBinder(context.Metadata.ModelType, attribute as RMBAttribute, _loggerFactory); } } return null; } }
public class RMBAttributeModelBinder : IModelBinder { IRMB rMB; private SimpleTypeModelBinder modelBinder; public RMBAttributeModelBinder(Type type, RMBAttribute attribute, ILoggerFactory loggerFactory) { rMB = attribute as IRMB; modelBinder = new SimpleTypeModelBinder(type, loggerFactory); } public Task BindModelAsync(ModelBindingContext bindingContext) { var modelName = bindingContext.ModelName; var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName); if (valueProviderResult != ValueProviderResult.None) { bindingContext.ModelState.SetModelValue(modelName, valueProviderResult); var valueString = valueProviderResult.FirstValue; var result = rMB.RMB(valueString, out bool success); if (success) { bindingContext.Result = ModelBindingResult.Success(result); return Task.CompletedTask; } } return modelBinder.BindModelAsync(bindingContext); } }
最后则是添加到集合中去并在属性Salary上使用RMB特性,比如ModelBinderContext和ModelBinderProviderContext上下文是什么,无非就是模型元数据和一些参数罢了,这里就不一一解释了,自己调试还会了解的更多。如下: