Model is getting null on post on Child Partial page MVC5
I'm trying to get all the data back after user fill the form but when i try to access model as a action parameter I always get model as null. Therefore i tried using FormCollection but still couldn't figure how can i access List of PossibleAnswerModels buried inside QuestionModel. Your help is very much appreciated
UPDATE
After reading Chris Pratt comment I updated everything to following But i still can't get the TestModel populated. Everytime Questions(TestModel test, FormCollection formCollection) do POST Index Action gets called I even tried putting a [HttpPost] public ActionResult Questions(TestModel test, FormCollection formCollection) but that doesn't get filled with View data either. please help
///////////////Models//////////////
[Serializable]
public class TestModel
{
private QuestionModel _currenQuestionModel;
public int TestID { get; set; }
public string TestName { get; set; }
public string Instructions { get; set; }
public double TestTime { get; set; }
public QuestionModel CurrenQuestionModel
{
get
{
if (_currenQuestionModel == null &&
Questions != null &&
Questions.Count > 0)
_currenQuestionModel = Questions.First();
return _currenQuestionModel;
}
set { _currenQuestionModel = value; }
}
public List<QuestionModel> Questions { get; set; }
}
public class QuestionModel
{
public int QuestionID { get; set; }
public string Question { get; set; }
public bool HasMultipleAnswers { get; set; }
public IList<PossibleAnswerModel> PossibleAnswers { get; set; }
}
public class PossibleAnswerModel
{
public bool IsSelected { get; set; }
public string DisplayText { get; set; }
}
}
////////////////// Controller /////////////////
public class TestController : Controller
{
public ActionResult Index(int id)
{
var model = _testColletion.FirstOrDefault(r => r.TestID == id);
return View(model);
}
[ChildActionOnly]
[HttpGet]
public ActionResult Questions(TestModel test)
{
var testModel = _testColletion.Single(r => r.TestID == test.TestID);
return PartialView("_Start", testModel);
}
[HttpPost]
public ActionResult Questions(TestModel test, FormCollection formCollection)
{
var q = formCollection.Count >2 ? formCollection.GetValues(1):null;
var testModel = _testColletion.FirstOrDefault(r => r.TestID == test.TestID);
//if (questionID == 0)
//{
// testModel.CurrenQuestionModel = testModel.Questions.First();
//}
if (!string.IsNullOrWhiteSpace(Request["next"]))
{
var nextQuestionIndex =
testModel.Questions.FindIndex(r => r.QuestionID == testModel.CurrenQuestionModel.QuestionID) + 1;
testModel.CurrenQuestionModel = testModel.Questions[nextQuestionIndex];
}
else if (!string.IsNullOrWhiteSpace(Request["prev"]))
{
var prevQuestionIndex =
testModel.Questions.FindIndex(r => r.QuestionID == testModel.CurrenQuestionModel.QuestionID) - 1;
testModel.CurrenQuestionModel = testModel.Questions[prevQuestionIndex];
}
return PartialView("_Question", testModel);
}
private static List<TestModel> _testColletion = new List<TestModel>()
{
new TestModel()
{
TestID = 1,
TestName = "ASP.NET",
Instructions = "Please choose from appropriate options",
TestTime = 2.40,
Questions = new List<QuestionModel>()
{
new QuestionModel(){QuestionID = 1, Question = "Question 1"}
}
},
new TestModel()
{
TestID = 2,
TestName = "ASP.NET MVC",
Instructions = "Please choose from appropriate options",
TestTime = 1.00,
Questions = new List<QuestionModel>()
{
new QuestionModel(){QuestionID = 1, HasMultipleAnswers=true, Question = "Question 1", PossibleAnswers = new List<PossibleAnswerModel>()
{
new PossibleAnswerModel(){DisplayText = "Possible Answer 1"},
new PossibleAnswerModel(){DisplayText = "Possible Answer 2"},
new PossibleAnswerModel(){DisplayText = "Possible Answer 3"},
new PossibleAnswerModel(){DisplayText = "Possible Answer 4"},
}},
new QuestionModel(){QuestionID = 2, HasMultipleAnswers=true, Question = "Question 2"},
new QuestionModel(){QuestionID = 3, HasMultipleAnswers=true, Question = "Question 3"},
new QuestionModel(){QuestionID = 4, HasMultipleAnswers=true, Question = "Question 4"},
new QuestionModel(){QuestionID = 5, HasMultipleAnswers=true, Question = "Question 5"},
}
},
new TestModel()
{
TestID = 3,
TestName = "ASP.NET Spring",
Instructions = "Please choose from appropriate options",
TestTime = 1.00,
Questions = new List<QuestionModel>()
{
new QuestionModel(){QuestionID = 1, Question = "Question 1"},
new QuestionModel(){QuestionID = 2, Question = "Question 2"},
new QuestionModel(){QuestionID = 3, Question = "Question 3"},
new QuestionModel(){QuestionID = 4, Question = "Question 4"},
}
},
new TestModel()
{
TestID = 4,
TestName = ".NET C#",
Instructions = "Please choose from appropriate options",
TestTime = 4.40,
Questions = new List<QuestionModel>()
{
new QuestionModel(){QuestionID = 1, Question = "Question 1"},
new QuestionModel(){QuestionID = 2, Question = "Question 2"}
}
}
};
}
/////////// Inside _Question.cshtml partial view///////////////
@model InterviewQ.MVC.Models.TestModel
@using (Html.BeginForm())
{
@Html.ValidationSummary(true)
@*@Html.HiddenFor(r => r.CurrentQuestionID)*@
<div>
<br />
<h4>Question @Model.CurrenQuestionModel.QuestionID</h4>
<hr />
<p>@Model.CurrenQuestionModel.Question</p>
</div>
<p>
@if (Model.CurrenQuestionModel.HasMultipleAnswers)
{
@Html.Partial("_MultipleAnswerQuestionView", Model.CurrenQuestionModel)
}
</p>
<p>
@if (Model.CurrenQuestionModel.QuestionID > 0 && Model.CurrenQuestionModel.QuestionID < Model.Questions.Count)
{
if (Model.CurrenQuestionModel.QuestionID > 1)
{
<input type="submit" class="btn btn-default" value="Previous" name="prev" />
}
<input type="submit" class="btn btn-default" value="Next" name="next" />
}
@if (Model.CurrenQuestionModel.QuestionID == Model.Questions.Count)
{
<input type="submit" class="btn btn-default" value="Finish" name="finish" />
}
</p>
}
//////////// Inside _MultipleAnswerQuestionView.cshtml partial view///////////////
@model InterviewQ.MVC.Models.QuestionModel
@if (!Model.HasMultipleAnswers)
{
throw new InvalidOperationException("This answer optioin template doesn't support this type of questions");
}
@for(var i=0; i< Model.PossibleAnswers.Count; i++)
{
<div class="row">
<div class="col-lg-6">
<div class="input-group">
<span class="input-group-addon">
<input type="checkbox" value="@Model.PossibleAnswers[i].IsSelected" name="possibleAnswers">
</span>
<p>
@Model.PossibleAnswers[i].DisplayText
</p>
</div><!-- /input-group -->
</div><!-- /.col-lg-6 -->
</div><!-- /.row -->
}
But Still I Can't get @Model.PossibleAnswers[i] value in the post am i missing any thing
The problem is that none of your posted data matches up to anything on the model. Take a few examples:
When you pull out the CurrentQuestion
, it's removed from the context of Model.Questions
, so generated field names will start with CurrentQuestion
instead of Questions[N]
, where N is the index of that question. Right off the bat, there's no way to ever populate that Questions
property, and you have no CurrentQuestion
property on your model for the data to bind to.
When you pass CurrentQuestion
into your partial, you loose even this context, so now all your field names in the partial will have no prefix at all. For example, something like Html.TextBoxFor(m => m.Foo)
will end up with a name of simply Foo
, which again does not match the structure of your model.
Then, inside your partial, you're using a foreach
which discards even more context. As you iterate through Model.PossibleAnswers
each field name would end up as something like item.IsSelected
instead of something the modelbinder could actually use like PossibleAnswers[N].IsSelected
, again where N is the index of the item in PossibleAnswers
.
On top of everything else, your checkbox doesn't even have a name attribute. So no value will ever be posted. Period.
Long and short, you're at best posting back field names that the modelbinder has no way to possibly match up with anything on your model, and at worst, you're not posting anything at all in some cases. No wonder you're getting a null model.
At all times, you must maintain property context so that Razor can generate field names that the modelbinder can use. So instead of passing CurrentQuestion
, you should pass Model.Questions[N]
to the partial. When iterating over lists that will have editable fields, you need to use for
rather than foreach
, so that you can pass the indexed property to give Razor the context it needs: Model.PossibleAnswers[N].IsSelected
.