尝试创建一个custom model binder,它将(比如)“$15,481”转换回小数。
您正在寻找的结果是将输入验证为货币而不是小数,但您可能需要在模型绑定发生之前清理输入,因此您将使用描述 Scrub 操作的接口。
public interface IScrubberAttribute
{
object Scrub(string modelValue, out bool success);
}
接下来,添加CurrencyScrubberAttribute,它将解析用户输入以查看它是否是有效的货币格式。 C# 的decimal.TryParse 有一个重载,它接受NumberStyle 和CultureInfo,这是如何进行货币验证的。您会注意到此时这只适用于美元货币 ($),但只需要设置 CultureInfo 来处理其他货币。
[AttributeUsage(AttributeTargets.Property)]
public class CurrencyScrubberAttribute : Attribute, IScrubberAttribute
{
private static NumberStyles _currencyStyle = NumberStyles.Currency;
private CultureInfo _culture = new CultureInfo("en-US");
public object Scrub(string modelValue, out bool success)
{
var modelDecimal = 0M;
success = decimal.TryParse(
modelValue,
_currencyStyle,
_culture,
out modelDecimal
);
return modelDecimal;
}
}
使用新的CurrencyScrubberAttribute,如下所示:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
[DisplayFormat(DataFormatString = "{0:C0}", ApplyFormatInEditMode = true)]
[CurrencyScrubber]
public decimal Price { get; set; }
}
添加模型绑定器。对于像 Product 这样的强类型模型,ComplexTypeModelBinderProvider 接受挑战,然后为每个属性创建一个绑定器。
public class ScrubbingModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
if (!context.Metadata.IsComplexType&&context.Metadata.PropertyName!=null)
{
// Look for scrubber attributes
var propName = context.Metadata.PropertyName;
var propInfo = context.Metadata.ContainerType.GetProperty(propName);
// Only one scrubber attribute can be applied to each property
var attribute = propInfo.GetCustomAttributes(typeof(IScrubberAttribute), false).FirstOrDefault();
if (attribute != null)
return new ScrubbingModelBinder(context.Metadata.ModelType, attribute as IScrubberAttribute);
}
return null;
}
}
模型绑定器将处理具有IScrubberAttribute 的简单类型,但如果出于某种原因我们不打算处理绑定,我们会将其传递给 SimpleTypeModelBinder 来处理它.
public class ScrubbingModelBinder : IModelBinder
{
IScrubberAttribute _attribute;
SimpleTypeModelBinder _baseBinder;
public ScrubbingModelBinder(Type type, IScrubberAttribute attribute)
{
if (type == null) throw new ArgumentNullException(nameof(type));
_attribute = attribute as IScrubberAttribute;
_baseBinder = new SimpleTypeModelBinder(type);
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));
// Check the value sent in
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult != ValueProviderResult.None)
{
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
// Attempt to scrub the input value
var valueAsString = valueProviderResult.FirstValue;
var success = true;
var result = _attribute.Scrub(valueAsString, out success);
if (success)
{
bindingContext.Result = ModelBindingResult.Success(result);
return Task.CompletedTask;
}
}
// If we haven't handled it, then we'll let the base SimpleTypeModelBinder handle it
return _baseBinder.BindModelAsync(bindingContext);
}
}
在 ConfigureServices 中添加
services.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new ScrubbingModelBinderProvider());
});