6

I have a model binder for a custom type Money. The binder works fine, but the built in binding/validation is kicking in and marking data as invalid.

My binder looks like this:

public class MoneyModelBinder : DefaultModelBinder 
{
    protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var money = (Money)bindingContext.Model;
        var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".Amount").AttemptedValue;
        var currencyCode = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".Iso3LetterCode").AttemptedValue;

        Money parsedValue;
        if (String.IsNullOrEmpty(value))
        {
            money.Amount = null;
            return;
        }

        var currency = Currency.FromIso3LetterCode(currencyCode);

        if(!Money.TryParse(value, currency, out parsedValue))
        {
            bindingContext.ModelState.AddModelError("Amount", string.Format("Unable to parse {0} as money", value));
        }
        else
        {
            money.Amount = parsedValue.Amount;
            money.Currency = parsedValue.Currency;
        }
    }
}

When a user types a value like "45,000" into a textbox my binder correctly picks up the value, parses it and sets it into the model.

The problem I have, is that the default validation then kicks in a states The value '45,000' is not valid for Amount, which as it's a decimal type makes sense, but I've already bound the data. How can I prevent the default binders binding data which I've already handled?

I'm not sure if this make any difference, but I'm using Html.EditorFor with and editor that looks like this:

@model Money

<div class="input-prepend">
  <span class="add-on">@Model.Currency.Symbol</span>
    @Html.TextBoxFor(m => m.Amount, new{
                            placeholder=string.Format("{0}", Model.Currency), 
                            @class="input-mini",
                            Value=String.Format("{0:n0}", Model.Amount)
                            })
    @Html.HiddenFor(x => x.Iso3LetterCode)
</div>
4

3 回答 3

5

您可以将Amount属性标记为只读:

[ReadOnly(true)]
public decimal? Amount { get; set; }
于 2012-11-18T18:53:34.443 回答
2

如果您的 ModelBinder 代码位于同一个项目中,您可以将属性设置器访问修饰符更改为内部。这样 DefaultModelBinder 看不到 setter。

public class MyViewModel{
 public Money Stash { get; internal set; }
}

另一种方法可能是简单地使用一个字段

public class MyViewModel{
 public Money Stash;
}

这背后的原因是 DefaultModelBinder 只绑定读写属性。上述两个建议都将阻止满足这些条件。

于 2012-11-17T02:02:14.787 回答
1

根据我的经验,有几种方法可以做到这一点(例如,允许绑定默认活页夹,但清除属性上的错误),但是这样做的方式可以让任何维护代码的人清楚地知道你的意图是覆盖不良行为,即:

protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
    if (propertyDescriptor.Name != "Amount")
    {
        base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
    }
}

在此处的 else 块中实际执行自定义绑定也可能有意义,但这由您决定。

于 2012-11-18T05:36:04.703 回答