I have a form where we want to enter multiple deals for a single customer. The viewmodel for the form looks like this:
public class TradeSpendingEntryViewModel
{
public TradeSpendingEntryViewModel()
{
Records = new List<TradeSpendingEntryViewModelRecord>();
}
public string CustomerNumber { get; set; }
public DateTime Date { get; set; }
public SelectList PlanningYears { get; set; }
public List<TradeSpendingEntryViewModelRecord> Records { get; set; }
}
We want to be able to add and remove record entry rows dynamically with javascript. Each record in the collection is:
public class TradeSpendingEntryViewModelRecord
{
public TradeSpendingEntryViewModelRecord()
{
}
public string LOB { get; set; }
public string ProductCode { get; set; }
public SelectList AllowType { get; set; }
public int Cases { get; set; }
public bool EndCurrentDeal { get; set; }
public Single DealRate { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public string Comments { get; set; }
}
When I attempt to post data to my controller, I get an error indicating "No parameterless constructor defined for this object:
[HttpPost]
public ActionResult Index(TradeSpendingEntryViewModel vm)
{
try
{
if (ModelState.IsValid)
{
return RedirectToAction("Index");
}
// TODO: Add insert logic here
return View(vm);
}
catch
{
return View();
}
}
Stack trace indicates it happens during the model binding:
[MissingMethodException: No parameterless constructor defined for this object.] System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck) +0 System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark) +113 System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark) +232 System.Activator.CreateInstance(Type type, Boolean nonPublic) +83 System.Activator.CreateInstance(Type type) +6 System.Web.Mvc.DefaultModelBinder.CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) +183
My issue must be stemming from how I've setup my view, the markup of which I have based off this article http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
@using (Html.BeginForm())
{
<header class="clearfix">
<img src="../../Content/images/logo.png" alt="Irving Consumer Products" />
<h1>Enter Customer Deals</h1>
</header>
<hr />
<fieldset>
<legend>Customer details</legend>
<table>
<tr>
<th>
Date:
</th>
<td>
<input type="date" disabled="disabled" value="@DateTime.Today.ToShortDateString()" />
</tr>
<tr>
<th>
Customer Number:
</th>
<td>
@Html.TextBoxFor(m => m.CustomerNumber, new { id = "customer-number" })
<a href="#" id="change-customer-number">Change me</a>
</td>
</tr>
<tr>
<th>
Customer Name:
</th>
<td>
<input type="text" id="customer-name" disabled="disabled" />
</td>
<tr>
<th>
Planning Year:
</th>
<td>
@Html.DropDownList("PlanningYear", Model.PlanningYears)
<a href="#" id="change-planning-year">Change me</a>
</td>
</tr>
</table>
</fieldset>
<div class="buttonGroup">
<input type="button" value="Add line" id="add-line">
<input type="button" value="Copy line" id="copy-line">
<input type="button" value="Delete line" id="delete-line">
</div>
<hr />
<table id="tradeSpendingEntry">
<thead>
<tr>
<th>
</th>
<th>
Line of business
</th>
<th>
Product Code
</th>
<th>
Product Description
</th>
<th>
Allowance
<br />
Type
</th>
<th>
Cases
</th>
<th>
End Current Deal
</th>
<th>
Start Date
</th>
<th>
End Date
</th>
<th>
Rate
</th>
</tr>
</thead>
@foreach (var r in Model.Records)
{
<tbody data-entry-index="0">
<tr>
<td>
<input type="checkbox" />
</td>
<td>
<input type="text" name="records[0].LOB" class="lobSelect" value="@r.LOB">
</td>
<td>
<!--<input type="hidden" name="records[0]ProductCodeSelected" value="@r.ProductCode" />-->
<input type="text" name="records[0].ProductCode" value="@r.ProductCode">
</td>
<td>
<input type="text" class="product-description" disabled="disabled" />
</td>
<td>
@Html.DropDownList("records[0].AllowType", r.AllowType)
</td>
<td>
<input name="records[0].Cases" type="number" />
</td>
<td>
<select name="records[0].EndCurrentDeal">
<option value="true" selected="selected">Yes</option>
<option value="false">No</option>
</select>
</td>
<td>
<input type="date" name="records[0].StartDate" />
</td>
<td>
<input type="date" name="records[0].EndDate" />
</td>
<td>
<input type="text" name="records[0].DealRate" />
</td>
</tr>
<tr>
<td></td>
<td>
Comments:
</td>
<td colspan="8">
<input type="text" class="comment" name="records[0].Comments" />
</td>
</tr>
</tbody>
}
</table>
<footer>
<div class="buttonGroup">
<input type="submit" value="Submit Changes">
<input type="button" value="Main Menu">
<input type="button" value="View Customer Deals">
</div>
</footer>
}
So, my hope was that the fields in the first fieldset would map to the immediate properties of the TradeSpendingEntryViewModel object (CustomerName, Date, PlanningYears). Then, for each representing a TradeSpendingEntryViewModelRecord would be bound as an item in the TradeSpendingEntryViewModel.Records collection. Instead, I just get a mysterious "No parameterless constructor defined" exception, despite both the ViewModel and the record object both having paramaterless constructors.
My question is, can I use the default model binder using the conventions indicated in the aforementioned article, or do I need to build a custom model binder for this purpose?
For completeness sake, the following is the markup of the form that gets generated after a user dynamically adds a line to the form via javascript:
<form method="post" action="/TradeSpendingEntry/Index">
<header class="clearfix">
<img alt="Irving Consumer Products" src="../../Content/images/logo.png">
<h1>Enter Customer Deals</h1>
</header>
<hr>
<fieldset>
<legend>Customer details</legend>
<table>
<tbody>
<tr>
<th> Date: </th>
<td>
<input id="dp1363608756704" class="hasDatepicker" type="date" value="18/03/2013" disabled="disabled" style="background-color: rgb(238, 238, 238);">
</td>
</tr>
<tr>
<th> Customer Number: </th>
<td>
<input id="customer-number" type="text" value="" name="CustomerNumber">
<a id="change-customer-number" href="#">Change me</a>
</td>
</tr>
<tr>
<th> Customer Name: </th>
<td>
<input id="customer-name" type="text" disabled="disabled" style="background-color: rgb(238, 238, 238);">
</td>
</tr>
<tr>
<th> Planning Year: </th>
<td>
<select id="PlanningYears" name="PlanningYears">
<option value="2011">2011</option>
<option value="2012">2012</option>
<option value="2013">2013</option>
<option value="2014">2014</option>
</select>
<a id="change-planning-year" href="#">Change me</a>
</td>
</tr>
</tbody>
</table>
</fieldset>
<div class="buttonGroup">
<input id="add-line" type="button" value="Add line">
<input id="copy-line" type="button" value="Copy line">
<input id="delete-line" type="button" value="Delete line">
</div>
<hr>
<table id="tradeSpendingEntry">
<thead>
<tr>
<th> </th>
<th> Line of business </th>
<th> Product Code </th>
<th> Product Description </th>
<th>
Allowance
<br>
Type
</th>
<th> Cases </th>
<th> End Current Deal </th>
<th> Start Date </th>
<th> End Date </th>
<th> Rate </th>
</tr>
</thead>
<tbody data-entry-index="0">
<tr>
<td>
<input type="checkbox">
</td>
<td>
<input class="lobSelect" type="text" name="records[0].LOB">
</td>
<td>
<input type="text" name="records[0].ProductCode">
</td>
<td>
<input class="product-description" type="text" disabled="disabled" style="background-color: rgb(238, 238, 238);">
</td>
<td>
<select id="records_0__AllowType" name="records[0].AllowType">
<option selected="selected">BillBack$</option>
<option>Billback%</option>
<option>O&A%</option>
<option>Coop%</option>
<option>VR%</option>
<option>Lump - O&A$</option>
<option>Lump - CP$</option>
<option>Lump - VR$</option>
<option>Lump - BB$</option>
</select>
</td>
<td>
<input type="number" name="records[0].Cases">
</td>
<td>
<select name="records[0].EndCurrentDeal">
<option selected="selected" value="true">Yes</option>
<option value="false">No</option>
</select>
</td>
<td>
<input id="dp1363608756707" class="hasDatepicker" type="date" name="records[0].StartDate">
</td>
<td>
<input id="dp1363608756708" class="hasDatepicker" type="date" name="records[0].EndDate">
</td>
<td>
<input type="text" name="records[0].DealRate">
</td>
</tr>
<tr>
<td></td>
<td> Comments: </td>
<td colspan="8">
<input class="comment" type="text" name="records[0].Comments">
</td>
</tr>
</tbody>
<tbody data-entry-index="1">
<tr>
<td>
<input type="checkbox">
</td>
<td>
<input class="lobSelect" type="text" name="records[1].LOB">
</td>
<td>
<input type="text" name="records[1].ProductCode">
</td>
<td>
<input class="product-description" type="text" disabled="disabled" style="background-color: rgb(238, 238, 238);">
</td>
<td>
<select id="records_0__AllowType" name="records[1].AllowType">
<option selected="selected">BillBack$</option>
<option>Billback%</option>
<option>O&A%</option>
<option>Coop%</option>
<option>VR%</option>
<option>Lump - O&A$</option>
<option>Lump - CP$</option>
<option>Lump - VR$</option>
<option>Lump - BB$</option>
</select>
</td>
<td>
<input type="number" name="records[1].Cases">
</td>
<td>
<select name="records[1].EndCurrentDeal">
<option selected="selected" value="true">Yes</option>
<option value="false">No</option>
</select>
</td>
<td>
<input id="dp1363608756709" class="hasDatepicker" type="date" name="records[1].StartDate">
</td>
<td>
<input id="dp1363608756710" class="hasDatepicker" type="date" name="records[1].EndDate">
</td>
<td>
<input type="text" name="records[1].DealRate">
</td>
</tr>
<tr>
<td></td>
<td> Comments: </td>
<td colspan="8">
<input class="comment" type="text" name="records[1].Comments">
</td>
</tr>
</tbody>
</table>
<footer>
<div class="buttonGroup">
<input type="submit" value="Submit Changes">
<input type="button" value="Main Menu">
<input type="button" value="View Customer Deals">
</div>
</footer>
</form>
Without a doubt it is the SelectList
. I had the same problem just yesterday.
If you look at the SelectList all of it's constructors require a parameter. http://msdn.microsoft.com/en-us/library/system.web.mvc.selectlist(v=vs.108).aspx
The problem is when your Action
is called public ActionResult Index(TradeSpendingEntryViewModel vm)
The controller tries to bind the data sent back on POST
to TradeSpendingEntryViewModel
upon which it needs to set the value for PlanningYears
which it tires to do by creating a new SelectList
To fix the problem, you either need to make the SelectList a private variable aka a backing field, and then set it to an empty list by default. This gives it the parameterized constructor it needs:
//select list
private SelectList planningYears = new SelectList(new List<YourObject>());
public SelectList PlanningYears
{
get
{
return planningYears;
}
set
{
locations = planningYears;
}
}
Or change PlanningYears to a List<> and convert it to a select list on the view.
@Html.DropDownListFor(m => m.PlanningYears , new SelectList(Model.PlanningYears ), "choose", null)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With