Reputation: 75
I have two models, question and answer. I want to insert a list of answers thru ViewModel to a question but it seems in my post method my list is getting null. That might be a bad implementation as well, because I am returning a model of my question back when I post something and I guess my List is just getting null. How could I fix this?
Edit: I remade the controller and the view based on comments you gave me: Thats how it looks now, but seems my Answer List to be Empty again.
ViewModel:
public class ViewModel
{
public IEnumerable<Answer> Answers { get; set; }
public Question Question { get; set; }
}
Controller:
[Authorize]
public ActionResult Create()
{
ViewModel vm = new ViewModel();
ViewBag.BelongToTest = new SelectList(db.Tests, "TestId" , "TestTitle").FirstOrDefault();
vm.Question = new Question { Question_Text = String.Empty };
vm.Answers = new List<Answer> { new Answer { CorrectOrNot = false, AnswerText = "", OpenAnswerText = "" } };
return View(vm);
}
//
// POST: /Question/Create
[HttpPost]
[Authorize]
public ActionResult Create(ViewModel vm)
{
if (ModelState.IsValid)
{
vm.Question.BelongToTest = (from t in db.Tests
join m in db.Members on t.AddedByUser equals m.MemberId
where m.UserID == WebSecurity.CurrentUserId &&
t.AddedByUser == m.MemberId
orderby t.TestId descending
select t.TestId).FirstOrDefault();
db.Questions.Add(vm.Question);
db.SaveChanges();
if (vm.Answers != null)
{
foreach (var i in vm.Answers)
{
i.BelongToQuestion = vm.Question.QuestionId;
db.Answers.Add(i);
}
}
db.SaveChanges();
ViewBag.Message = "Data successfully saved!";
ModelState.Clear();
}
ViewBag.BelongToTest = new SelectList(db.Tests, "TestId", "TestTitle", vm.Question.BelongToTest);
vm.Question = new Question { Question_Text = String.Empty };
vm.Answers = new List<Answer> { new Answer { CorrectOrNot = false, AnswerText = "", OpenAnswerText = "" } };
return View("Create" , vm);
}
View:
@model MvcTestApplication.Models.ViewModel
@using MvcTestApplication.Models
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
@{
ViewBag.Title = "Create";
}
@using (Html.BeginForm("Create", "Question", FormMethod.Post)) {
<h2>Create</h2>
<table>
<tr>
<th>Question Name</th>
</tr>
<tr>
<td>@Html.EditorFor(model=>model.Question.Question_Text)</td>
</tr>
</table>
<table id="dataTable">
<tr>
<th>Correct?</th>
<th>Answer text</th>
<th>Open Answer</th>
</tr>
@foreach(var i in Model.Answers)
{
<tr>
<td>@Html.CheckBoxFor(model=>i.CorrectOrNot)</td>
<td>@Html.EditorFor(model=>i.AnswerText)</td>
<td>@Html.EditorFor(model=>i.OpenAnswerText)</td>
</tr>
}
</table>
<input type="button" id="addNew" value="Add Answer"/>
<input type="submit" value="Create" />
}
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
<script lang="javascript">
$(document).ready(function () {
//1. Add new row
$("#addNew").click(function (e) {
e.preventDefault();
var $tableBody = $("#dataTable");
var $trLast = $tableBody.find("tr:last");
var $trNew = $trLast.clone();
var suffix = $trNew.find(':input:first').attr('name').match(/\d+/);
$trNew.find("td:last").html('<a href="#" class="remove">Remove</a>');
$.each($trNew.find(':input'), function (i, val) {
// Replaced Name
var oldN = $(this).attr('name');
var newN = oldN.replace('[' + suffix + ']', '[' + (parseInt(suffix) + 1) + ']');
$(this).attr('name', newN);
//Replaced value
var type = $(this).attr('type');
if (type.toLowerCase() == "text") {
$(this).attr('value', '');
}
// If you have another Type then replace with default value
$(this).removeClass("input-validation-error");
});
$trLast.after($trNew);
// Re-assign Validation
var form = $("form")
.removeData("validator")
.removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse(form);
});
// 2. Remove
$('a.remove').live("click", function (e) {
e.preventDefault();
$(this).parent().parent().remove();
});
});
</script>
}
Upvotes: 2
Views: 3795
Reputation: 75
Thanks for the comments. They really helped me out. It was all correct that you say but there was something that was missing. My IEnumerable in the ViewModel simply does not allow me to index my values, instead using IList helped me out to index everything as it is supposed to be and everything works.
Upvotes: 0
Reputation: 10756
For the ModelBinder to bind to a List the HTML form must be sequentially indexed.
Your
<td>@Html.CheckBoxFor(model=>a.CorrectOrNot)</td>
<td>@Html.EditorFor(model=>a.AnswerText)</td>
<td>@Html.EditorFor(model=>a.OpenAnswerText)</td>
is creating something that will be bound to an individual answer. You need to render HTML that will be bound to a List, something like
@for (int i = 0; i < ((List<Answer>)ViewData["Answers"]).Count; i++)
{
<tr>
<td>@Html.CheckBoxFor(model=>((List<Answer>)ViewData["Answers"])[i].CorrectOrNot)</td>
<td>@Html.EditorFor(model=>((List<Answer>)ViewData["Answers"])[i].AnswerText)</td>
<td>@Html.EditorFor(model=>((List<Answer>)ViewData["Answers"])[i].OpenAnswerText)</td>
</tr>
}
Also, this looks pretty awful casting ViewData all over the place. It would generally be better, if you plan to keep this approach creating a real view model. You could pass that model to the view and it could wrapper both question and answer collections.
EDIT:
You still need to have a sequential index against your list which your edited implementation is not supplying. Something like
@for (int i = 0; i < Model.Answers.Count; i++)
{
<tr>
<td>@Html.CheckBoxFor(model=> Model.Answers[i].CorrectOrNot)</td>
<td>@Html.EditorFor(model=> Model.Answers[i].AnswerText)</td>
<td>@Html.EditorFor(model=> Model.Answers[i].OpenAnswerText)</td>
</tr>
}
Upvotes: 3
Reputation: 16013
ViewData is relevant when going from the controller to the view. It won't post back.
You should relay on the (model / parameter) binding that will take care of passing List<Answer> answerList
for you
Upvotes: 1
Reputation: 1099
ViewData is only to transfer the data between the view and controller. You can use session to transfer the data between the controller
Upvotes: 0