2010-05-19 59 views
0

改变(我的道歉,如果这似乎冗长 - 努力提供一切有关的代码)ASP.Net MVC2 CustomModelBinder没有工作......从MVC1

我刚刚升级到VS2010,和我现在有麻烦试图获取新的CustomModelBinder工作。

在MVC1我会写类似

public class AwardModelBinder: DefaultModelBinder 
{ 
    : 
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
    { 
     // do the base binding to bind all simple types 
     Award award = base.BindModel(controllerContext, bindingContext) as Award; 

     // Get complex values from ValueProvider dictionary 
     award.EffectiveFrom = Convert.ToDateTime(bindingContext.ValueProvider["Model.EffectiveFrom"].AttemptedValue.ToString()); 
     string sEffectiveTo = bindingContext.ValueProvider["Model.EffectiveTo"].AttemptedValue.ToString(); 
     if (sEffectiveTo.Length > 0) 
      award.EffectiveTo = Convert.ToDateTime(bindingContext.ValueProvider["Model.EffectiveTo"].AttemptedValue.ToString()); 
     // etc 

     return award; 
    } 
} 

当然,我会在Global.asax.cs中注册定制绑定:现在

protected void Application_Start() 
    { 
     RegisterRoutes(RouteTable.Routes); 

     // register custom model binders 
     ModelBinders.Binders.Add(typeof(Voucher), new VoucherModelBinder(DaoFactory.UserInstance("EH1303"))); 
     ModelBinders.Binders.Add(typeof(AwardCriterion), new AwardCriterionModelBinder(DaoFactory.UserInstance("EH1303"), new VOPSDaoFactory())); 
     ModelBinders.Binders.Add(typeof(SelectedVoucher), new SelectedVoucherModelBinder(DaoFactory.UserInstance("IT0706B"))); 
     ModelBinders.Binders.Add(typeof(Award), new AwardModelBinder(DaoFactory.UserInstance("IT0706B"))); 
    } 

,在MVC2,我发现我对base.BindModel的调用返回了一个对象,其中的一切都是空的,我不想迭代新的ValueProvider.GetValue()函数浮出水面的所有表单字段。

谷歌发现这个错误没有匹配,所以我假设我做错了什么。

这里是我的实际代码:

我的域对象(推断出你喜欢的封装孩子什么对象 - 我知道我需要为那些过于定制粘合剂,但三个“简单”字段(即基础。类型)标识,TradingName和BusinessIncorporated也回来空):

public class Customer 
{ 
    /// <summary> 
    /// Initializes a new instance of the Customer class. 
    /// </summary> 
    public Customer() 
    { 
     Applicant = new Person(); 
     Contact = new Person(); 
     BusinessContact = new ContactDetails(); 
     BankAccount = new BankAccount(); 
    } 

    /// <summary> 
    /// Gets or sets the unique customer identifier. 
    /// </summary> 
    public int Id { get; set; } 

    /// <summary> 
    /// Gets or sets the applicant details. 
    /// </summary> 
    public Person Applicant { get; set; } 

    /// <summary> 
    /// Gets or sets the customer's secondary contact. 
    /// </summary> 
    public Person Contact { get; set; } 

    /// <summary> 
    /// Gets or sets the trading name of the business. 
    /// </summary> 
    [Required(ErrorMessage = "Please enter your Business or Trading Name")] 
    [StringLength(50, ErrorMessage = "A maximum of 50 characters is permitted")] 
    public string TradingName { get; set; } 

    /// <summary> 
    /// Gets or sets the date the customer's business began trading. 
    /// </summary> 
    [Required(ErrorMessage = "You must supply the date your business started trading")] 
    [DateRange("01/01/1900", "01/01/2020", ErrorMessage = "This date must be between {0} and {1}")] 
    public DateTime BusinessIncorporated { get; set; } 

    /// <summary> 
    /// Gets or sets the contact details for the customer's business. 
    /// </summary> 
    public ContactDetails BusinessContact { get; set; } 

    /// <summary> 
    /// Gets or sets the customer's bank account details. 
    /// </summary> 
    public BankAccount BankAccount { get; set; } 
} 

我控制器的方法:

/// <summary> 
    /// Saves a Customer object from the submitted application form. 
    /// </summary> 
    /// <param name="customer">A populate instance of the Customer class.</param> 
    /// <returns>A partial view indicating success or failure.</returns> 
    /// <httpmethod>POST</httpmethod> 
    /// <url>/Customer/RegisterCustomerAccount</url> 
    [HttpPost] 
    [ValidateAntiForgeryToken] 
    public ActionResult RegisterCustomerAccount(Customer customer) 
    { 
     if (ModelState.IsValid) 
     { 
      // save the Customer 

      // return indication of success, or otherwise 
      return PartialView(); 
     } 
     else 
     { 
      ViewData.Model = customer; 

      // load necessary reference data into ViewData 
      ViewData["PersonTitles"] = new SelectList(ReferenceDataCache.Get("PersonTitle"), "Id", "Name"); 

      return PartialView("CustomerAccountRegistration", customer); 
     } 
    } 

我的定制绑定:

public class CustomerModelBinder : DefaultModelBinder 
{ 
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
    { 
     ValueProviderResult vpResult = bindingContext 
      .ValueProvider.GetValue(bindingContext.ModelName); 
     // vpResult is null 

     // MVC2 - ValueProvider is now an IValueProvider, not dictionary based anymore 
     if (bindingContext.ValueProvider.GetValue("Model.Applicant.Title") != null) 
     { 
      // works 
     } 

     Customer customer = base.BindModel(controllerContext, bindingContext) as Customer; 
     // customer instanitated with null (etc) throughout 

     return customer; 
    } 
} 

我的文件夹登记:

/// <summary> 
    /// Application_Start is called once when the web application is first accessed. 
    /// </summary> 
    protected void Application_Start() 
    { 
     RegisterRoutes(RouteTable.Routes); 

     // register custom model binders 
     ModelBinders.Binders.Add(typeof(Customer), new CustomerModelBinder()); 

     ReferenceDataCache.Populate(); 
    } 

...从我的观点摘要(难道这是一个前缀问题?)

<div class="inputContainer"> 
     <label class="above" for="Model_Applicant_Title" accesskey="t"><span class="accesskey">T</span>itle<span class="mandatoryfield">*</span></label> 
     <%= Html.DropDownList("Model.Applicant.Title", ViewData["PersonTitles"] as SelectList, "Select ...", 
      new { @class = "validate[required]" })%> 
     <% Html.ValidationMessageFor(model => model.Applicant.Title); %> 
    </div> 
    <div class="inputContainer"> 
     <label class="above" for="Model_Applicant_Forename" accesskey="f"><span class="accesskey">F</span>orename/First name<span class="mandatoryfield">*</span></label> 
     <%= Html.TextBox("Model.Applicant.Forename", Html.Encode(Model.Applicant.Forename), 
          new { @class = "validate[required,custom[onlyLetter],length[2,20]]", 
           title="Enter your forename", 
           maxlength = 20, size = 20, autocomplete = "off", 
            onkeypress = "return maskInput(event,re_mask_alpha);" 
          })%> 
    </div> 
    <div class="inputContainer"> 
     <label class="above" for="Model_Applicant_MiddleInitials" accesskey="i">Middle <span class="accesskey">I</span>nitial(s)</label> 
     <%= Html.TextBox("Model.Applicant.MiddleInitials", Html.Encode(Model.Applicant.MiddleInitials), 
          new { @class = "validate[optional,custom[onlyLetter],length[0,8]]", 
            title = "Please enter your middle initial(s)", 
            maxlength = 8, 
            size = 8, 
            autocomplete = "off", 
            onkeypress = "return maskInput(event,re_mask_alpha);" 
          })%> 
    </div> 

回答

1

模型绑定在MVC 2显著变化它充满了“陷阱” - 甚至超过了MVC 1.例如,an empty value in your form will make binding fail。这些都没有很好的记录。实际上,诊断这种东西的唯一好方法是build with the MVC source code并通过绑定进行追踪。

我很高兴源代码可用;没有它我会迷失方向。

+0

感谢克雷格 - 我会尝试按照你的建议与MVC源码建立 - 你的MS连接似乎提出了一个模型前缀被推断的问题 - 有没有一种方法来设置(现在),说在在尝试绑定之前重写BindModel? 你的回答确实意味着我正在按照字段绑定,如果我必须在绑定前检查每个字段是否空白...... gah! – Ian 2010-05-19 13:53:25

+0

设置简单模型前缀的最简单方法是'[绑定]'。对于复杂的模型,它可以是参数名称。我的连接报告是关于空字符串的*键*,而不是*值*。这是(非常)不常见的,但当它发生时是灾难性的。没什么可检查的;绑定只是失败。唯一的解决办法是摆脱窗体中的空键。 – 2010-05-19 13:56:12

+0

是的,对不起,我刚刚注意到,在我的ValueProvider的FormValueProvider._prefixes列表中,有一个没有值的键(没有键)。我会去追捕并报告。非常感谢。 – Ian 2010-05-19 14:14:49

1

使用MVC2 RTM源代码(感谢Craig的链接)下载和构建之后,我能够浏览MVC代码,并发现在BindProperty方法(在DefaultModelBinder.cs的第178行),那里是一个测试:

protected virtual void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor) { 
    // need to skip properties that aren't part of the request, else we might hit a StackOverflowException 
    string fullPropertyKey = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name); 
    if (!bindingContext.ValueProvider.ContainsPrefix(fullPropertyKey)) { 
     return; 
    } 
    : 

......这个ValueProvider字典包含与基本自定义模型绑定的BindingContext中的MODELNAME属性前缀键。

在我的情况下,bindingContext.ModelName被推断为“客户”(从我的域对象类型,我猜),因此181行的测试总是失败,因此退出BindProperty而不绑定我的表单值。

这是我的新的自定义模型绑定代码:

public class CustomerModelBinder : DefaultModelBinder 
{ 
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
    { 
     // vitally important that we set what is the prefix of values specified in view 
     // (usually "Model" if you've rendered a strongly-typed view after setting ViewData.Model) 
     bindingContext.ModelName = "Model"; 
     Customer customer = base.BindModel(controllerContext, bindingContext) as Customer; 

     return customer; 
    } 
} 

我希望这可以帮助别人谁是遇到类似问题。

非常感谢克雷格的帮助。