ASP.NET MVC3 Model Binding using IEnumerable (Cannot infer type from) - asp.net-mvc-3

I have a model class (edited for brevity)
Model Class
public class GridModel
{
public string ItemNumber { get; set; }
public int OnHandQty { get; set; }
}
public class Shipment
{
public string shipTrackingNo {get; set;}
public IEnumerable<GridModel> ItemsShipped { get; set;}
{
cshtml page
#model Namespc.Models.Shipment
<link href="../../Content/CSS/Grid/Grid.css" rel="stylesheet" type="text/css" />
<script src="../../Scripts/ECommerce.Grid.js" type="text/javascript"></script>
<div id="_shipmentDetailGrid">
<table class="TableStyle">
<tr class="GridRowStyle">
<td width="130px" ></td>
#foreach (var Item in Model.ItemsShipped)
{
<td width="70px" align="center">
#html.LabelFor(item.OnHandQty) <-- Cannot infer type from usage
</td>
}
</tr>
I want to be able to bind item.OnHandQty that resides in the IEnumerable collection. How can you have a Model class and also an IEnumerable collection of a custom class (or rather you own class)?

Well, what is the type of the items stored in ItemsShipped? You should use the generic version of IEnumerable to indicate what types are stored within it.
If your class was named Item, you would declare it IEnumerable<Item> then, when iterating at run time, ie #foreach (var Item in Model.ItemsShipped), the type of Item will be strongly-typed instead of a plain object.

Instead of this:
#foreach (var Item in Model.ItemsShipped)
{
<td width="70px" align="center">
#html.LabelFor(item.OnHandQty) <-- Cannot infer type from usage
</td>
}
Do this:
#Html.DisplayFor(model => model.ItemsShipped)
Then create a custom display template (placed in Views/Shared/DisplayTemplates/GridModel.cshtml):
#model Namespc.Models.GridModel
<td width="70px" align="center">
#html.LabelFor(model => model.OnHandQty)
</td>
I've got a feeling it's not working because you're not passing an expression to the LabelFor method.
The above is much nicer and robust than an explicit for loop.

Related

How do I bind table values to a Map and post with Spring MVC and Thymeleaf?

I have an entry form where users select a subset of items which populate a table. I need to bind each row's first and and third column value to a key and value, respectively, enter them into a Map<Integer, Integer> passed in through the controller, and post the data. I've poured through many different solutions online and have yet to find one that works. The map always returns empty.
Wrapper class for Map
#Getter #Setter
public class ItemForm {
private Map<Integer, Integer> items = new HashMap<>();
}
Controllers
#GetMapping(...)
public String showItemPage(Model model) {
...
model.addAttribute("itemForm", new ItemForm());
...
}
#PostMapping(...)
public String processItemUpdate(#ModelAttribute("itemForm") ItemForm itemForm, BindingResult bindingResult) {
...
}
Template
<tr th:each="item : *{items}">
<td>
<input type="text" th:value="${item.key}">
</td>
<td></td>
<td>
<input type="text" th:field="*{items[__${item.key}__]}">
</td>
</tr>
I understand that I will need something like th:field="*{items[__${item.key}__]}" to access the map, but as to extracting and combining the key-value pair I'm a bit lost.
edit:
Is something along these lines possible?
#Getter #Setter
public class ItemFormPair {
private int ID, quantity;
}
#Getter #Setter
public class ItemForm {
private List<ItemFormPair> items = new ArrayList<>();
}
<tr th:each="item, stat : *{items}">
<td>
<input type="text" th:field="*{items[__${stat.index}__].ID}">
</td>
<td></td>
<td>
<input type="text" th:field="*{items[__${stat.index}__].quantity}">
</td>
</tr>
edit:
I don't really want to spend any more time on this problem and there doesn't appear to be an elegant solution available so I'm simply going to use an Ajax POST request.
You bound the single key/values of the map to the form but not the map itself. That won't work that way. I'm quite sure there is no way to get the map as whole piece back from the form. Maybe with a converter.
An alternative could be to assign name/id to the input fields and read all key/values back to map in the processItemUpdate method:
This solution works on my site. I redefined my answer more precisely:
input.html
<!DOCTYPE HTML>
<html lang="de" xmlns:th="http://www.thymeleaf.org">
<head />
<body>
<form th:action="#{/inputPost}" method="post" th:fragment="form">
<table>
<tr th:each="item,iter : ${itemForm.items.entrySet()}">
<td><input type="text" th:id="${iter.index + '.ID'}"
th:name="${iter.index + '.ID'}" th:value="${item.key}"></td>
<td><input type="text" th:id="${iter.index + '.VALUE'}"
th:name="${iter.index + '.VALUE'}" th:value="${item.value}"></td>
</tr>
</table>
<input type="submit" name="Send" value="Send" /> <input type="submit"
name="Add" value="Add new Line" />
</form>
</body>
</html>
success.html
<!DOCTYPE HTML>
<html lang="de" xmlns:th="http://www.thymeleaf.org">
<head></head>
<body>
<table border="1">
<tr th:each="item : ${itemForm.items.entrySet()}">
<td th:text="${item.key}"></td>
<td th:text="${item.value}"></td>
</tr>
</table>
</body>
</html>
Controller
#GetMapping("/inputForm")
public String dummy(Model model) {
ItemForm form = new ItemForm();
form.getItems().put(form.getItems().size(), 42);
model.addAttribute("itemForm", form);
return "input";
}
#PostMapping("/inputPost")
public String processItemUpdate(HttpServletRequest request, Model model) {
Map<String, String[]> params = request.getParameterMap();
ItemForm form = new ItemForm();
String operation = null;
for (Entry<String, String[]> entry : params.entrySet()) {
if (entry.getKey().endsWith(".ID")) { // only react on ID values. The values will be directly fetched from
// map
String[] tokens = StringUtils.split(entry.getKey(), ".");
Integer id = Integer.parseInt(tokens[0]);
Integer idValue = Integer.parseInt(entry.getValue()[0]);
String[] value = params.get(id + ".VALUE"); // fetch the value to defined ID
Integer valueValue = Integer.parseInt(value[0]);
form.getItems().put(idValue, valueValue);
} else if (entry.getKey().equalsIgnoreCase("Send")) { // determine operation
operation = "send";
} else if (entry.getKey().equalsIgnoreCase("Add")) { // determine operation
operation = "add";
}
}
model.addAttribute("itemForm", form);
if (operation.equals("add")) { // add a new line and resend form again
form.getItems().put(form.getItems().size(), 42);
return "input";
}
return "success";
}

How to initialize the id of a button with a value from model

I am retrieving a list of values using List and I am using for loop. The requirement is here I have two buttons, one to submit and the other to delete a record.When I click btnDelete button I want to delete that particular record.
I am having the object Product as
public partial class Product
{
public int Id { get; set; }
public string Model_Name { get; set; }
public Nullable<int> Price { get; set; }
public Nullable<int> Total_Products { get; set; }
}
Now I want the property Id of the Product to come and sit in the button's Id so that I could retrieve the complete record and delete it using Jquery. I have done with jquery here I just want to assign the button's Id with the value from Model.
I am having the following code
#model List<Shop_Online.com.Models.Product>
<script type="text/javascript">
//some script here
</script>
#using (Html.BeginForm())
{
<div style="width: 860px; margin: 0 auto" class="main">
<table border="1" style="font-family: Verdana; font-size: 13px" class="table">
<tr style="background-color: #f2f2f2; height: 30px">
<th colspan="4">ITEM</th>
<th>DELIEVERY DETAILS</th>
<th>QTY</th>
<th>SUB TOTAL</th>
<th>Remove Items</th>
</tr>
<tbody>
#for(int i=0;i<Model.Count;i++)
{
<tr>
#Html.HiddenFor(x=>x[i].Id)
<td colspan="4" > #Html.DisplayFor(x=>x[i].Model_Name)</td>
<td style="width: 39%">Free Delievery Delivered in 2-3 business days.</td>
<td>#Html.TextBoxFor(x=>x[i].Total_Products)</td>
<td>#Html.DisplayFor(x=>x[i].Price)</td>
<td>
//something like this
//btnDelete
<button type="submit" class="btnDelete" value="x[i].Id" id="x[i].Id"
name="Action">Remove</button></td>
</tr>
}
</tbody>
</table>
<input type="submit" value="Submit" id="btnPost"/>
</div>
}
If I use foreach loop then I have
#foreach(var item in model) then I can use the following within block as
<button type="submit" class="remove" value="#item.Id" id="#item.Id"
name="Action">Remove</button>
Thanks
As #Mark Shechenko already wrote:
<button type="submit" class="btnDelete" value="#Model[i].Id" id="#Model[i].Id" name="Action">Remove</button>
BUT... If the Id is a number, you have to remember that there are rules for ID and NAME attributes in html, see the answer to this question: What are valid values for the id attribute in HTML?

Mvc3 IEnumerable<QuestionModel> have a List<QuestionOptionModel> property. When I post, I get null list

I'm making a survey application. Survey has Questions and Questions have QuestionOption. haveThere is a example here.
I am trying to use this technique in a large form with a list(List) but when I post back, the Viewmodel.Order that should’ve contained list of items and activities return with the lists empty.
My QuestionModel.cs like this.
public int Id { get; set; }
public string QuestionText { get; set; }
public System.Nullable<bool> OptionType1 { get; set; }
public System.Nullable<bool> OptionType2 { get; set; }
public List<QuestionOptionModel> OptionList = new List<QuestionOptionModel>();
When I post back "IEnumerable questions" List OptionList comes null. How can I do this?
public ActionResult CallSurvey()
{
IEnumerable<QuestionModel> questionModelList = (IEnumerable<QuestionModel>)SessionHelper.GetSessionObject(SessionKeys.SurveyKey);
questionModelList = questionSrv.GetQuestionModel();
return View(questionModelList);
}
questionModelList include all my survey question and question options. When I post it, post back is coming with only null optionList.
[HttpPost]
public ActionResult CallSurvey(IEnumerable<QuestionModel> questions)
{ ..... }
CallSurvey.cshtml
<body>
#using ((Html.BeginForm()))
{
#ViewBag.Test
<section class="slides layout-regular template-kendo">
#foreach (var item in Model)
{<article>
#Html.Partial("QuestionEditor", item)
</article>
}
<div class="slide-area" id="prev-slide-area"></div>
<div class="slide-area" id="next-slide-area"></div>
</section>
}
</body>
QuestionEditor.cshtml
#model LSMM.Business.Model.Survey.QuestionModel
#using LSMM.Web.Helpers
<div>
#using (Html.BeginCollectionItem("Questions"))
{
<table id="table1">
<tr>
<td>
<div id="#Model.Id" class="hint">
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.QuestionText)
#Html.HiddenFor(m => m.OptionType1)
#Html.HiddenFor(m => m.OptionType2)
#for (int i = 0; i < Model.OptionList.Count; ++i)
{
#Html.LabelFor(m => m.OptionList[i].Id)
#Html.LabelFor(m => m.OptionList[i].QuestionId)
#Html.LabelFor(m => m.OptionList[i].Checked)
#Html.LabelFor(m => m.OptionList[i].Description)
#Html.LabelFor(m => m.OptionList[i])
}
<span id="sorular">#Model.Id. #Model.QuestionText</span>
<br />
<br />
</div>
</td>
</tr>
<tr>
<td>
<div class="hint2">
#Html.Partial("QuestionOptionEditor", Model)
</div>
</td>
<td>
<div id="#Model.Id-Img">
<h2 style="top: 200px; right: 0;">
<img src="../../Content/css/img/#Model.Id-Img.png"></h2>
</div>
</td>
</tr>
</table>
and QuestionOptionEditor.cshtml
#model LSMM.Business.Model.Survey.QuestionModel
#using LSMM.Web.Helpers
#foreach (var option in #Model.OptionList)
{
<p>
#if (#Model.OptionType1 == false)
{
#Html.Partial("QuestionOptionModel", option)
}
else
{
#Html.Partial("../Shared/DisplayTemplates/QuestionOptionModel", option)
}
</p>
}
Here QuestionOptionModel views like this;
#model LSMM.Business.Model.Survey.QuestionOptionModel
#(Html.RadioButtonFor(m => m.Id, true, new { Id = #Model.Id, Name = #Model.QuestionId })) #Html.Label("Evet")
<br />
<br />
#(Html.RadioButtonFor(m => m.Id, false ,new { Id=#Model.Id, Name = #Model.QuestionId})) #Html.Label("Hayır")
The name attribute on your radio buttons is not "correct" according to the naming rules used by the default ModelBinder. That's why you aren't seeing the values you expect, the ModelBinder couldn't find what it was looking for.
This is easy to fix and you should end up with less code.
Take advantage of the framework and let it do work for you:
MVC can loop IEnumerables for you. Let it. There's little reason to write foreach loops in views these days.
Get rid of those partials. Use MVC's Templates feature instead.
Then your view can be as simple as:
<body>
#using ((Html.BeginForm()))
{
#ViewBag.Test
<section class="slides layout-regular template-kendo">
<table id="table1">
#* Table header goes here *#
#Html.EditorFor(model => model.OptionList)
</table>
<div class="slide-area" id="prev-slide-area"></div>
<div class="slide-area" id="next-slide-area"></div>
</section>
}
</body>
When you use an EditorTemplate or a DisplayTemplate, MVC will automatically fix the name attribute on your form fields. It won't do that with a partial.
Another suggestion, also if you don't mind. Get rid of those nulla-bools on your model and write different QuestionOptionModel types. DisplayTemplate/EditorTemplates are wise to the type system and MVC will pick the template that matches the type it is presented.
So you could have another class that derives from QuestionOptionModel:
public class ExtendedQuestionOptionModel : QuestionOptionModel
{
//Stuff goes here.
}
and you could have a List<QuestionOptionModel> hanging off your model that contains:
QuestionOptionModel
ExtendedQuestionOptionModel
QuestionOptionModel
and when you do this:
#Html.EditorFor(model => model.QuestionOptions)
MVC will go through the option list and render the Editor Template for QuestionOptionModel, then the Editor Template for ExtendedQuestionOptionModel, then the Editor Template for QuestionOptionModel again.
The game you want to play is to try to minimize the C# code in the views, because the views cannot be unit tested. Code that isn't unit tested will eventually come back and get you.
Your question is abit ambiguous. Frankly i hope this is what you are loooking for : IEnumerable can be transformed to list using
List<foo>listFoo =iEnumerableEntityFoo.toList()

MVC3 radiobuttons do not behave like they should

I have a problem that's starting to drive me crazy.. I've narrowed my problem down to the following test case:
Model:
public class TestModel
{
public bool TestBool { get; set; }
}
Index View:
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#Html.Action("Test")
Test View:
#model IEnumerable<Test_GUI.Models.TestModel>
#using (Html.BeginForm()) {
<table>
#Html.EditorForModel()
</table>
<input type="submit" value="OK"/>
}
Editor template;
<tr>
<td>
Yes
#Html.RadioButtonFor(m => m.TestBool, Model.TestBool)
</td>
<td>
No
#Html.RadioButtonFor(m => m.TestBool, !Model.TestBool)
</td>
In the TestController, I create two instances of TestModel with a value of false and pass them to the view. But the radiobuttons are rendered as checked, and they also return as true if I post the form..
I've tried many other ways to display the radiobuttons, but nothing seems to work. This seems like to correct way to do it.
I must be able use the current value of the boolean, so I cannot use fixed true or false values in the view. If I do use a fixed true/false, I do get the correct values on the form and in the controller after posting the form..
Try like this:
<td>
Yes
#Html.RadioButtonFor(m => m.TestBool, true)
</td>
<td>
No
#Html.RadioButtonFor(m => m.TestBool, false)
</td>

Validating TextBox input for a list that is a property of an MVC3 ViewModel

I realize I'm a little late to the party, but... I'm working in my first MVC project, and have been able to get a handle on most of what needs to be done. Most of the project simply reads data and pumps the data into charts. However, I have one View whose model looks like this (the parent class properties are not important here):
public class Class1 : ParentClass
{
public List<ChildClass> ChildClassList{get;set;}
}
and the ChildClass looks like this:
public class ChildClass
{
public int Property1{get;set;}
public int Property2{get;set;}
public string Property3{get;set;}
public int? ID{get;set;}
[Editable(true)]
public decimal? Property4{get;set;}
}
Now, retreiving the data is not an issue. I can loop through the list, and create a table for editing like this:
<% foreach(var g in Model.ChildClassList){%>
<tr>
<td style="text-align: right;">
<%= Html.Label(g.Property3)%>
</td>
<td>
<%=Html.TextBox(Model.ParentProperty.ToString() + "_" + g.Property2, (g.Property4.HasValue ? g.Property4.Value.ToString("C") : "$0.00"))%>
</td>
</tr>
<% }%>
After cruising through this site for the past couple of days, it dawned on me that I can validate the input on the server-side, in the POST method (there is a "Save" button at the bottom of the form), but (a)how do I get the validation error message back to the user, (b)perform the validation client-side?.
I must mention also that this view uses the values in the list to create a portion of a chart, prior to being rendered as a table.
On the server-side in the [HttpPost] action, you can check the validity of the model like this:
[HttpPost]
public ActionResult Save(Class1 model)
{
if (!ModelState.IsValid)
return View(model);
// Code to save model.
}
You also need to update your View to show the errors:
<%= Html.ValidationSummary(false, "Please fix these errors.")
<% foreach(var g in Model.ChildClassList){%>
<tr>
<td style="text-align: right;">
<%= Html.Label(g.Property3)%>
</td>
<td>
<%=Html.TextBox(Model.ParentProperty.ToString() + "_" + g.Property2, (g.Property4.HasValue ? g.Property4.Value.ToString("C") : "$0.00"))%>
<%= Html.ValidationMessageFor(model => g.Property4)
</td>
</tr>
<% }%>
If you want to enable it client-side, you need to use unobstrusive client validation, which you can do by updating your web.config:
<configuration>
<appSettings>
<add key="ClientValidationEnabled" value="true"/>
<add key="UnobtrusiveJavaScriptEnabled" value="true"/>
</appSettings>
</configuration>
Also you need the following JS libraries:
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js" type="text/javascript"></script>
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.9/jquery-ui.min.js" type="text/javascript"></script>
<script src="//ajax.microsoft.com/ajax/jQuery.Validate/1.7/jQuery.Validate.min.js" type="text/javascript"></script>
<script src="//ajax.aspnetcdn.com/ajax/mvc/3.0/jquery.validate.unobtrusive.min.js" type="text/javascript"></script>
On a side note, try and avoid loops to render out your View. It's unnecessary code soup, which can be avoided by the use of editor templates.

Resources