【问题标题】:ASP.Net MVC2 CustomModelBinder not working... Changed from MVC1ASP.Net MVC2 CustomModelBinder 不工作...从 MVC1 更改
【发布时间】:2010-05-20 18:48:47
【问题描述】:

(如果这看起来很冗长,我很抱歉 - 试图提供所有相关代码)

我刚刚升级到 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 也即将推出返回空):

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();
    }

...在我看来是一个 sn-p(这可能是前缀问题吗?)

    <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>

【问题讨论】:

    标签: asp.net-mvc-2 modelbinders


    【解决方案1】:

    模型绑定在 MVC 2 中发生了显着变化。它充满了“陷阱”——甚至比 MVC 1 中的更多。例如,an empty value in your form will make binding fail。这些都没有很好的记录。实际上,诊断这些东西的唯一好方法是build with the MVC source code 并跟踪绑定。

    我很高兴源代码可用;没有它我会迷路的。

    【讨论】:

    • 谢谢克雷格-我将尝试按照您的建议使用 MVC 源代码进行构建-您的 MS Connect 链接似乎提出了模型前缀被推断的问题-有没有办法设置它(现在),在尝试绑定之前在被覆盖的 BindModel 中说?如果我必须在绑定之前检查每个字段是否为空白,您的回答确实暗示我将进行每个字段绑定...... gah!
    • 为简单模型设置前缀的最简单方法是[Bind]。对于复杂模型,它可以是参数名称。我的连接报告是关于空字符串的key,而不是value。这种情况(非常)不常见,但一旦发生就会是灾难性的。没有什么要检查的;绑定失败。唯一的解决办法是去掉表单中的空键。
    • 是的,抱歉,我刚刚注意到在我的 ValueProvider 的 FormValueProvider._prefixes 列表中……有一个没有价值(没有键)。我会去追捕并报告。非常感谢。
    • 嗨,克雷格。如果没有 ID,我似乎找不到任何东西(起初我以为是我的 AntiForgeryToken,但确实存在)。空键是 ValueProvider 的 _prefixes 字典中的第一项,在我的视图中的第一个字段之前)。难倒!
    • 已解决。看起来 _prefixes 字典中的空键(至少对我而言)是一条红鲱鱼……请参阅下面的答案。
    【解决方案2】:

    下载并使用 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;
        }
    }
    

    我希望这对遇到类似问题的其他人有所帮助。

    非常感谢克雷格的帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-06-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多