2010-05-19 36 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; 
    } 
} 

我希望這可以幫助別人誰是遇到類似問題。

非常感謝克雷格的幫助。