(道歉,如果这看起来很冗长 - 试图提供所有相关代码)
我刚刚升级到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的调用返回一个对象,其中所有内容都为null,而我根本不想迭代所有表单字段浮出水面通过新的ValueProvider.GetValue()函数。
Google发现此错误没有匹配,所以我认为我做错了。
这是我的实际代码:
我的域对象(推断您对封装的子对象的喜好 - 我知道我也需要自定义绑定器,但三个“简单”字段(即基本类型)Id,TradingName和BusinessIncorporated也即将推出返回null):
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>
答案 0 :(得分:1)
模型绑定在MVC 2中发生了显着变化。它充满了“陷阱” - 甚至比在MVC 1中还要多。例如,an empty value in your form will make binding fail。这些都没有详细记录。实际上,诊断这些东西的唯一好方法是build with the MVC source code并追踪绑定。
我很高兴源代码可用;没有它,我会迷失方向。
答案 1 :(得分: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被推断为“customer”(我的域名对象类型),因此第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;
}
}
我希望这可以帮助其他有类似问题的人。
非常感谢克雷格的帮助。