I have a list of questions that contains a list of options. They questions have a type field that allows the question to be a radio button or a checklist etc...
I am trying to create the view in such a way that I will get back the questions and the options. Right now I don't care about the responses.
As best I can tell I am following the rules for ASP.NET out of the box model binding for Lists which were blogged about in the following two places.
Scott Hanselman's blog: http://bit.ly/1fYAWCs
Phil Haack's blog: http://bit.ly/1fYBokd
Is it possible to do what I am trying to do with the out of the box model binder, or will I need to implement my own custom model binder?
If I do need to implement a custom model binder, can you point me to some straightforward examples or blog posts on how to go about that?
Ultimately I want to use the list of questions and the list of responses to write a custom attribute in data annotations to validate the model (by making sure every question has at least one answer), while also using unobtrusive validation on the client.
If there are better solutions I'm open to ideas.
Below is a contrived example to demonstrate my problem.
ViewModel:
public class Questionnaire
{
private List<QuestionResponse> _questions;
[UIHint("QuestionResponse")]
public List<QuestionResponse> Questions
{
get { return _questions ?? (_questions = new List<QuestionResponse>()); }
set { _questions = value; }
}
public List<int> Responses;
}
Model:
public class QuestionResponse
{
public int QuestionId;
public string QuestionType;
public string QuestionCode;
public string QuestionName;
public string OpenResponse;
public List<QuestionOptions> Options;
}
public class QuestionOptions
{
public int OptionId;
public string OptionType;
public string OptionName;
public string OptionDescription;
public string OptionResponse;
public bool Selected;
}
Controller Methods:
[HttpGet]
public ActionResult Questionnaire()
{
#region HardCodedDataz
var questions = new List<QuestionResponse>
{
new QuestionResponse
{
QuestionId = 1,
OpenResponse = null,
QuestionCode = "1",
QuestionName = "What kind of movies do you like?",
QuestionType = "Checklist",
Options = new List<QuestionOptions>
{
new QuestionOptions
{
OptionDescription = "Horror",
OptionId = 1,
OptionName = "Horror",
OptionResponse = null,
OptionType = "boolean",
Selected = false
},
new QuestionOptions()
{
OptionDescription = "Family",
OptionId = 2,
OptionName = "Family",
OptionResponse = null,
OptionType = "boolean",
Selected = false
},
new QuestionOptions()
{
OptionDescription = "Comedy",
OptionId = 3,
OptionName = "Comedy",
OptionResponse = null,
OptionType = "boolean",
Selected = false
},
new QuestionOptions()
{
OptionDescription = "Adventure",
OptionId = 4,
OptionName = "Adventure",
OptionResponse = null,
OptionType = "boolean",
Selected = false
},
new QuestionOptions()
{
OptionDescription = "Drama",
OptionId = 5,
OptionName = "Drama",
OptionResponse = null,
OptionType = "boolean",
Selected = false
}
}
},
new QuestionResponse()
{
QuestionId = 2,
OpenResponse = null,
QuestionCode = "2",
QuestionName = "Which university did you attend in your first year of college?",
QuestionType = "RadioButton",
Options = new List<QuestionOptions>
{
new QuestionOptions()
{
OptionDescription = "Cornell",
OptionId = 1,
OptionName = "Cornell",
OptionResponse = null,
OptionType = "boolean",
Selected = false
},
new QuestionOptions()
{
OptionDescription = "Columbia",
OptionId = 2,
OptionName = "Columbia",
OptionResponse = null,
OptionType = "boolean",
Selected = false
},
new QuestionOptions()
{
OptionDescription = "Harvard",
OptionId = 3,
OptionName = "Harvard",
OptionResponse = null,
OptionType = "boolean",
Selected = false
},
new QuestionOptions()
{
OptionDescription = "Yale",
OptionId = 4,
OptionName = "Yale",
OptionResponse = null,
OptionType = "boolean",
Selected = false
},
new QuestionOptions()
{
OptionDescription = "Princeton",
OptionId = 5,
OptionName = "Princeton",
OptionResponse = null,
OptionType = "boolean",
Selected = false
}
}
}
};
#endregion
var model = new Questionnaire()
{
Questions = questions,
Responses = new List<int>()
};
return PartialView(model);
}
[HttpPost]
public JsonResult Questionnaire(List<QuestionResponse> questions, List<int> responses)
{
return Json(new
{
success = "some success message",
error = "some error message"
});
}
View:
@using TestApplication.Models
@model Questionnaire
@using (Html.BeginForm("Questionnaire", "Home", FormMethod.Post))
{
<div>
@Html.EditorFor(m => m.Questions)
</div>
<div>
<button type="submit">Save</button>
</div>
}
Custom Editor Template:
Note: the hidden fields are solely for the sake of populating the model on the post request.
@using System.Web.Mvc.Html
@using TestApplication.Models
@model List<QuestionResponse>
@if (Model != null)
{
for (int i = 0; i < Model.Count; i++)
{
<p><strong>@Model[i].QuestionName</strong></p>
@Html.HiddenFor(m => m[i].QuestionName);
@Html.HiddenFor(m => m[i].QuestionType)
@Html.HiddenFor(m => m[i].QuestionCode)
@Html.HiddenFor(m => m[i].QuestionId)
if (Model[i].QuestionType == "Checklist")
{
for (int j = 0; j < Model[i].Options.Count; j++)
{
<div>
<label>@Model[i].Options[j].OptionDescription</label>
@Html.CheckBoxFor(m => m[i].Options[j].Selected)
@Html.HiddenFor(m => m[i].Options[j].OptionDescription)
@Html.HiddenFor(m => m[i].Options[j].OptionName)
@Html.HiddenFor(m => m[i].Options[j].OptionId)
@Html.HiddenFor(m => m[i].Options[j].OptionType)
</div>
}
}
else if (Model[i].QuestionType == "RadioButton")
{
for (int j = 0; j < Model[i].Options.Count; j++)
{
var name = "Questions[" + i + "].Options[" + j + "].Selected";
var id = Model[i].Options[j].OptionId;
<div>
<label>@Model[i].Options[j].OptionDescription</label>
<input id="@id" type="radio" name="@name" value="" />
@Html.HiddenFor(m => m[i].Options[j].OptionDescription)
@Html.HiddenFor(m => m[i].Options[j].OptionName)
@Html.HiddenFor(m => m[i].Options[j].OptionId)
@Html.HiddenFor(m => m[i].Options[j].OptionType)
</div>
}
}
}
}