I am working on an online library using ASP.NET MVC.
This is my view model for the library management page:
public class ManageViewModel
{
public IPagedList<ManageBookViewModel> WholeInventory;
public IPagedList<ManageBookViewModel> CurrentInventory;
public bool OldInventoryIsShown { get; set; } = false;
}
In the corresponding view I have a checkbox for whether or not to show the old inventory and a local variable modelList, which I would like to set to Model.WholeInventory if the checkbox is checked and to Model.CurrentInventory otherwise. I use modelList to display a table with all the books and I would need its value to be reset every time I (un)check the checkbox in order for the list to be properly displayed.
Is this possible? How would I go about doing this?
In my view I currently have:
<label class="switch">
<input id="OldInventoryIsShown" name="OldInventoryIsShown" type="checkbox" />
<span class="slider round"></span>
</label>
#{
var modelList = Model.OldInventoryIsShown ? Model.WholeInventory : Model.CurrentInventory;
}
#using (Html.BeginForm())
{
<table id="bookInventory" class="table table-hover">
<thead>
<tr>
<th>Author</th>
<th>Title</th>
....
</tr>
</thead>
#foreach (var entry in modelList)
{
<tr>
<td>#Html.DisplayFor(modelItem => entry.Author)</td>
<td>#Html.DisplayFor(modelItem => entry.Title)</td>
....
</tr>
}
</table>
<p>Page #(modelList.PageCount < modelList.PageNumber ? 0 : modelList.PageNumber) of #modelList.PageCount</p>
#Html.PagedListPager(modelList, page => Url.Action("Manage", page }))
}
The controller action:
public ActionResult Manage(int? page)
{
var wholeInventory = _bookService.GetBooksIncludingDisabled().Select(b => Mapper.Map<Book, ManageBookViewModel>(b));
var currentInventory = _bookService.GetBooks().Select(b => Mapper.Map<Book, ManageBookViewModel>(b));
int pageSize = 3;
int pageNumber = page ?? 1;
var model = new ManageViewModel
{
WholeInventory = wholeInventory.ToPagedList(pageNumber, pageSize),
CurrentInventory = currentInventory.ToPagedList(pageNumber, pageSize)
};
return View(model);
}
Models:
Book.cs
public class Book
{
public int BookId { get; set; }
[Required]
[MinLength(1)]
public string Title { get; set; }
[Required]
[MinLength(1)]
public string Author { get; set; }
....
public bool IsDisabled { get; set; } = false;
public virtual ICollection<UserBook> UserBooks { get; set; }
}
ManageBookViewModel.cs
public class ManageBookViewModel
{
public int BookId { get; set; }
[Required(AllowEmptyStrings = false, ErrorMessage = "Enter the book title")]
public string Title { get; set; }
[Required(AllowEmptyStrings = false, ErrorMessage = "Enter the book author.")]
public string Author { get; set; }
....
public bool IsDisabled { get; set; }
}
Your ManageViewModel needs to include only one property for the paged list and it should be IPagedList<Book> (see explanation below)
public class ManageViewModel
{
public IPagedList<Book> Inventory;
[Display(Name = "Include old inventory")]
public bool OldInventoryIsShown { get; set; }
... // any other search/filter properties
}
and your view needs to include the checkbox inside the <form> element, and the form should be making a GET to your controller method. Then you also need to include the current value of OldInventoryIsShown as a route value in the #Html.PagedListPager() method so that the current filter is retained when paging.
#model ManageViewModel
...
#using (Html.BeginForm("Manage", "ControllerName", FormMethod.Get))
{
#Html.CheckBoxFor(m => m.OldInventoryIsShown)
#Html.LabelFor(m => m.OldInventoryIsShown)
... // any other search/filter properties
<input type="submit" value="search" />
}
<table id="bookInventory" class="table table-hover">
....
</table>
<p>Page #(modelList.PageCount < modelList.PageNumber ? 0 : modelList.PageNumber) of #modelList.PageCount</p>
#Html.PagedListPager(modelList, page =>
Url.Action("Manage", new { page = page, oldInventoryIsShown = Model.OldInventoryIsShown })) // plus any other search/filter properties
Finally in the controller method you need a parameter for the value of the bool property an modify your query based on that value.
public ActionResult Manage(int? page, bool oldInventoryIsShown)
{
int pageSize = 3;
int pageNumber = page ?? 1;
IQueryable<Book> inventory = db.Books;
if (!oldInventoryIsShown)
{
inventory = inventory.Where(x => !x.IsDisabled);
}
ManageViewModel model = new ManageViewModel
{
Inventory = inventory.ToPagedList(pageNumber, pageSize),
OldInventoryIsShown = oldInventoryIsShown
};
return View(model);
}
You current controller code is terribly inefficient. Lets assume your table has 10,000 Book records, and 5,000 of those are 'disabled' (archived). You current code first gets all 10,0000 records and adds them to memory. Then you map all then to a view model. Then you call another query to get another 5,0000 records (which are just duplicates of what you already have), which you add to memory and map to a view model. But all you want in the view is 3 records (the value of pageSize) so you have done thousands of times of extra unnecessary processing.
In your case, there is no need for a view model (although if you did need one, you would use the StaticPagedList methods - refer this answer for an example). Your query should be using your db context to generate an IQueryable<Book> so that only the results you need are returned from the database (internally the ToPagedList() method uses .Skip() and .Take() on IQueryable<T>)
Related
I have a number of awards in my view and within each award there is a corresponding list of qualifications. I have created a ViewModel to display each award and with a click of a button a modal should appear with its relevant qualifications which can be marked as completed/updated by the user. However on the Post of the data it is not binding to my ViewModel in my controller method. The data is appearing in my view as expected with each Award only showing its relevant qualifications. I have used FormCollection to access some of the fields for testing purposes and the data is being posted back. Any help would be great!
ViewModel
public class CandidateExtended
{
public CandidateExtended()
{
this.Qualifications = new List<Qualification_Extended>();
}
public int AwardID { get; set; }
public int FrameworkID { get; set; }
public string ULN { get; set; }
public string Forename { get; set; }
public string Surname { get; set; }
public string TitleShort { get; set; }
public string TitleFull { get; set; }
public DateTime DOB { get; set; }
public string Award { get; set; }
public int AwardLevel { get; set; }
public string Status { get; set; }
public string Completion { get; set; }
public string SelectedRoute { get; set; }
public List<Qualification_Extended> Qualifications { get; set; }
public void addQualification(Qualification_Extended qualification)
{
Qualifications.Add(qualification);
}
}
Controller
[HttpGet]
public ActionResult Index()
{
var awardDetails = (from award in db.award
join candidate in db.candidate
on award.ULN equals candidate.ULN
join framework in db.framework
on award.QAN equals framework.QAN
where award.OrganisationIdentityID == organisationID
select new AwardDetails_Extended
{
AwardID = award.AwardID,
ULN = award.ULN,
AwardStatus = award.AwardStatus,
Forename = candidate.Forename,
Surname = candidate.Surname,
DOB = candidate.DOB,
FrameworkID = framework.FrameworkID,
TitleFull = framework.TitleFull,
TitleShort = framework.TitleShort,
AwardLevel = framework.AwardLevel,
Award = framework.Award,
Completion = framework.Completion
}).ToList();
var qualificationDetails = (from candidateQualification in db.candidateQualification
join qualification in db.qualification
on candidateQualification.QualificationID equals qualification.QualificationID
select new Qualification_Extended
{
ID = candidateQualification.ID,
QualificationID = candidateQualification.QualificationID,
ULN = candidateQualification.ULN,
FrameworkID = candidateQualification.FrameworkID,
Achieved = candidateQualification.Achieved,
DateAchieved = candidateQualification.DateAchieved
}).ToList();
List<CandidateExtended> candidateVM = new List<CandidateExtended>();
foreach (var item in awardDetails)
{
CandidateExtended vm = new CandidateExtended();
vm.AwardID = item.AwardID;
vm.FrameworkID = item.FrameworkID;
vm.ULN = item.ULN;
vm.Forename = item.Forename;
vm.Surname = item.Surname;
vm.DOB = item.DOB;
vm.TitleShort = item.TitleShort;
vm.TitleFull = item.TitleFull;
vm.Award = item.Award;
vm.AwardLevel = item.AwardLevel;
vm.Status = item.AwardStatus;
vm.Completion = item.Completion;
vm.SelectedRoute = item.SelectedRoute;
foreach (var qualification in qualificationDetails)
{
if (qualification.ULN == item.ULN && qualification.FrameworkID == item.FrameworkID)
{
vm.addQualification(qualification);
}
}
candidateVM.Add(vm);
}
return View(candidateVM);
}
View
#using (Html.BeginForm("UpdateAward", "Organisation", FormMethod.Post))
{
#Html.HiddenFor(a => award.AwardID)
<div class="row">
<div class="col-md-12">
<div class="row org-row-main">
<div class="col-md-7"><h4 class="org-type">Qualification</h4></div>
<div class="col-md-2"><h5 class="org-completed">Completed</h5></div>
<div class="col-md-3"><h5 class="org-date">Date</h5></div>
</div>
<hr class="org-hr"/>
#for (int i = 0; i < award.Qualifications.Count(); i++)
{
var qualification = award.Qualifications[i];
<div class="row org-row">
<div class="col-md-7">
#Html.HiddenFor(a => award.Qualifications[i].ID)
</div>
<div class="col-md-2">
#Html.CheckBoxFor(a => award.Qualifications[i].Achieved)
</div>
<div class="col-md-3">#Html.TextBoxFor(a => award.Qualifications[i].DateAchieved, "{0:dd/MM/yyyy}")
</div>
</div>
}
</div>
</div>
<button type="submit" class="btn admin-button" style="margin-top: 0;">Save</button>
}
UpdateAward
[HttpPost]
public ActionResult UpdateAward(CandidateExtended model, FormCollection collection)
{
return RedirectToAction("Index", "Login");
}
First (and you may already have this, but we can't see it): your View should start with a line containing #model List<CandidateExtended> (prefix the inner Type with the proper namespace).
Then in the View you should use Model, which is by definition of the exact type specified after the #model keyword.
We see that you are using award, we can't see where it comes from, presumably it is set using something like var award = Model[j] or foreach (var award in Model).
Never use such temporary or helper variables (for efficiency) in a View to render a Form; the View needs the fully qualified name of all objects, e.g. Model.Item[x].SubItem[y] in order to generate Form field names that can be used for Model Binding.
E.g. this : #Html.HiddenFor(a => award.Qualifications[i].ID)
should be: #Html.HiddenFor(a => Model[j].Qualifications[i].ID)
And make the same change in all other places.
Then do as was already suggested, use the List<...> in your Controller Post method.
Finally also please remove the FormCollection, it is not needed if you have everything set up as described here. Decent MVC code never uses FormCollection, ever.
Try calling the posted method on a separate button instead of BeginForm and it should work.
I have a page that contains multiple forms to edit questions for a single quiz, each question has its own list of answers. So for each question inside this quiz there is a form for which a user can edit the question (and answers), See below:
#model OLTINT.Areas.admin.ViewModels.OldQuizQAViewModel
<h1>Edit #Model.QuizTitle quiz</h1>
<hr />
<p class="breadcrumb">
#Html.ActionLink(HttpUtility.HtmlDecode("◄") + " Back to List", "Quizzes", new { id = Model.CourseID }, new { #class = "" })
</p>
#for (int j = 0; j < Model.OldQuizQuestions.Count(); j++)
{
using (Ajax.BeginForm("EditQuiz", "Course", null, new AjaxOptions
{
HttpMethod = "POST",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "button"
}))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.QuizID)
#Html.HiddenFor(model => model.OldQuizQuestions[j].QuizQuestionID)
<p class="form_title">Question number #Model.OldQuizQuestions[j].Order</p>
<div class="resize_input">#Html.EditorFor(model => model.OldQuizQuestions[j].Question)</div>
<p class="form_title">#Html.LabelFor(model => model.OldQuizQuestions[j].Type)</p>
<div class="resize_input">#Html.DropDownListFor(model => model.OldQuizQuestions[j].Type, ViewBag.types, "Please choose...", new { #class = "chosen-select" })</div>
<p class="form_title">Choose correct answers</p>
Char x = 'a';
for (int i = 0; i < Model.OldQuizQuestions[j].OldQuizAnswers.Count(); i++)
{
x++;
if (i == 0)
{
x = 'a';
}
<div style="display:table; width:100%;">
<div class="divTableCell" style="padding:0 10px 10px 0; vertical-align:middle; min-width:6%;">
#Html.CheckBoxFor(model => model.OldQuizQuestions[j].OldQuizAnswers[i].Correct, new { style = "" })
#Html.LabelFor(model => model.OldQuizQuestions[j].OldQuizAnswers[i].Correct, "["+ x +"]")
</div>
<div class="divTableCell quiz_input">
#Html.HiddenFor(model => model.OldQuizQuestions[j].OldQuizAnswers[i].QuizAnsID)
#Html.EditorFor(model => model.OldQuizQuestions[j].OldQuizAnswers[i].Answer)
</div>
</div>
}
<div class="button_container">
<p id="button"></p>
#Html.ActionLink("Delete this question", "DeleteQuestion", new { id = Model.OldQuizQuestions[j].QuizQuestionID }, new { #class = "button button_red button_not_full_width" })
<input type="submit" value="Save" class="button button_orange button_not_full_width" />
</div>
<hr />
}
}
OldQuizQAViewModel:
public class OldQuizQAViewModel
{
public int CourseID { get; set; }
public int? QuizID { get; set; }
public string QuizTitle { get; set; }
public IList<OldQuizQuestions> OldQuizQuestions { get; set; }
}
OldQuizQuestions:
public class OldQuizQuestions
{
[Key]
public int QuizQuestionID { get; set; }
public int OldQuizID { get; set; }
[Required]
public string Question { get; set; }
[Required]
public int Order { get; set; }
[Required]
public int Type { get; set; }
public virtual IList<OldQuizAnswers> OldQuizAnswers { get; set; }
public virtual OldQuiz OldQuiz { get; set; }
}
OldQuizAnswers:
public class OldQuizAnswers
{
[Key]
public int QuizAnsID { get; set; }
public int QuizQuestionID { get; set; }
public string Answer { get; set; }
public int Order { get; set; }
public bool Correct { get; set; }
public bool Chosen { get; set; }
public virtual OldQuizQuestions OldQuizQuestions { get; set; }
}
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult EditQuiz(OldQuizQAViewModel model)
{
var questiondata = model.OldQuizQuestions.Single();
if (ModelState.IsValid)
{
OldQuizQuestions updatequestion = db.OldQuizQuestions
.SingleOrDefault(x => x.QuizQuestionID == questiondata.QuizQuestionID);
updatequestion.Question = questiondata.Question;
updatequestion.Type = questiondata.Type;
db.Entry(updatequestion).State = EntityState.Modified;
db.SaveChanges();
foreach (var answer in questiondata.OldQuizAnswers)
{
var updateanswer = updatequestion.OldQuizAnswers
.First(x => x.QuizAnsID == answer.QuizAnsID);
updateanswer.Answer = answer.Answer;
updateanswer.Correct = answer.Correct;
db.Entry(updateanswer).State = EntityState.Modified;
db.SaveChanges();
}
return Content("<span style='font-weight:300; font-size:1.2em; color: green; '>Saved!</span>");
}
return Content("<span class='errortext'>Please correct the marked fields!</span>");
}
Now this works fine if I want to edit the first question but when I edit anything else my controller just says null but when I check the data that's being posted everything is there (for example when i try to edit question 2):
I've had a look around on here at the many queries about model binding to a list but none have helped. Can anyone see where i'm going wrong with this?
The issue you are facing is caused by a misunderstanding of how asp.net model binding works in relation to lists. For example looking at the view model used in your controller action EditQuiz.
public class OldQuizQAViewModel
{
public int CourseID { get; set; }
public int? QuizID { get; set; }
public string QuizTitle { get; set; }
public IList<OldQuizQuestions> OldQuizQuestions { get; set; }
}
In order for the model binding to work with a IList or any other collection, asp-net expects the form data you post to have sequential indexes. The form data you are sending over POST already has a working example of model binding with collections implemented. Looking at the form data:
The highlighted properties show how to correctly bind to a collection, in that you set the values for the property Correct in the OldQuizAnswers model for each index of IList in OldQuizQAViewModel and pass these all at once in a single request.
Whereas in the same request you only pass the data for OldQuizQuestions of specific index you wish these values to be bound to in the IList collection.
This is why the fist time you post, the model binding works successfully as you are referencing the first index ([0]), whereas on the second POST you reference the second index (1) but not the first, causing the model binding to fail.
See herefor more information on how model binding works.
Have you tried adding a #Html.HiddenFor(model => model.OldQuizQuestions[j]) ?
I read this "It creates a hidden input on the form for the field (from your model) that you pass it.
It is useful for fields in your Model/ViewModel that you need to persist on the page and have passed back when another call is made but shouldn't be seen by the user."
Answer comes from https://stackoverflow.com/a/3866720/8404545
Might be the problem here
I am currently stuck at some point of my implementation where I use a ViewModel class to display data, but I need to post values from other object which are equal to given values in the ViewModel. Here are both model classes
ViewModel.cs
public class ViewModel
{
public IList<Answer> Answers { get; set; }
public Question Questions { get; set; }
}
UserScore.cs
public partial class UserScore
{
public int ScoreID { get; set; }
public int U_Id { get; set; }
public int A_Id { get; set; }
public bool CorrectAnswer { get; set; }
public int Q_Id { get; set; }
public virtual Answer Answer { get; set; }
public virtual Member Member { get; set; }
public virtual Question Question { get; set; }
}
So far so good. I am using an object of Question and List of Answers to display the data I need in my Controller
Controller.cs
public ActionResult TakeTest(int id=0)
{
ViewModel vm = new ViewModel();
Test t = db.Tests.Find(id);
if (t == null)
{
return HttpNotFound();
}
vm.Questions = (from q in db.Questions
join tt in db.Tests on q.BelongToTest equals tt.TestId
where q.BelongToTest == id
select q).FirstOrDefault();
vm.Answers = new List<Answer>(from a in db.Answers
join q in db.Questions on a.BelongToQuestion equals q.QuestionId
join tt in db.Tests on q.BelongToTest equals tt.TestId
where q.BelongToTest == id &&
a.BelongToQuestion == vm.Questions.QuestionId
select a).ToList();
foreach (var i in vm.Answers)
{
i.CorrectOrNot = false;
}
return View(vm);
}
View.cshtml
#model MvcTestApplication.Models.ViewModel
#using MvcTestApplication.Models
#{
ViewBag.Title = "TakeTest";
}
<h2>TakeTest</h2>
#using (Html.BeginForm()) {
<table>
<tr>
<th>Question Name</th>
</tr>
<tr>
<td>#Html.DisplayFor(model => model.Questions.Question_Text)</td>
</tr>
</table>
<table id="dataTable">
<tr>
<th>Correct?</th>
<th>Answer text</th>
<th>Open Answer</th>
</tr>
#for(int i = 0; i < Model.Answers.Count; i++)
{
<tr>
<td>#Html.CheckBoxFor(m => m.Answers[i].CorrectOrNot)</td>
<td>#Html.DisplayFor(m => m.Answers[i].AnswerText)</td>
<td>#Html.EditorFor(m => m.Answers[i].OpenAnswerText)</td>
</tr>
}
</table>
if(ViewBag.Message != null)
{
<script>
$(document).ready(function(){
alert('#ViewBag.Message');
});
</script>
}
<input type="submit" value="Next Question" />
}
Now in my post method I need to get the value of vm.Question.QuestionId and AnswerId of the Answer list, set them to be equal to UserScore.Q_Id and UserScore.A_Id. How can I do that ? I tried many ways but with no success.
Controller.cs
[HttpPost]
[Authorize]
public ActionResult TakeTest(ViewModel vm)
{
if (ModelState.IsValid)
{
UserScore us = new UserScore();
us.U_Id = (from m in db.Members
where m.UserID == WebSecurity.CurrentUserId
select m.MemberId).FirstOrDefault();
us.A_Id = 49;
//us.A_Id = vm.Questions.QuestionID returns NULL
us.Q_Id = 150;
db.UserScores.Add(us);
db.SaveChanges();
}
return View(vm);
}
In general I need to know how to bind this vm.something to us.something because Question appears to be null all the time.
These values are null, because they are not present in your view. You will need to keep them in your View in the form of a hidden control. The ViewModel that you receive in the post can only construct the ViewModel using values present in the View. Since there is no ID maintained in the View, the constructed ViewModel has a null ID.
You can use
#Html.HiddenFor(model => model.Questions.ID)
and for your answer ID
#Html.HiddenFor(m => m.Answers[i].ID)
vm.Questions.QuestionID returns NULL because u haven't used that on the view anywhere. A easy hack would be to use a hidden field to capture the value or you should be initializing your viewmodel again and follow the logic in the post method.
You should use an hiddenfor helpers in your view and try something like that.
#Html.hiddenfor(m=>vm.Questions.QuestionId)
Give it a try : Html.HiddenFor value property not getting set
I have both a CheckBox list and a Radio list in a form but when the form is submitted the selected options for either list are not passed to the ActionResult. What am I missing?
if (Model.IsMultipleChocie)
{
foreach (var Choice in Model.Choices)
{
<li>
#Html.CheckBox("Answers", Choice.Value)
#Html.Label(Choice.Key)
</li>
}
}
else
{
foreach (var Choice in Model.Choices)
{
<li>
#Html.RadioButton("Answers", Choice.Value)
#Html.Label(Choice.Key)
</li>
}
}
public ActionResult ProcessVote(int[] Answers, int UserID, string UserIP, int ID)
#Html.CheckBox("Answers", Choice.Value) does not set the value attribute so you would need to use #Html.CheckBox("Answers", new { value = Choice.Value }). However as noted in the comment below, #Html.Checkbox() renders a second hidden input with value="false" so you posting back an array which includesintand bool values so binding will fail.
Either create the checkboxes manually
<input type="checkbox" name="answer" value=#Choice.Value />
or create a view model to represent what you want to display. For example
public class AnswerVM
{
public int ID { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
public class MyVM
{
public bool IsMultipleChoice { get; set; }
public List<AnswerVM> Answers { get; set; }
public int? SelectedAnswer { get; set ; } // for single choice
}
View
#model MyVM
....
if (Model.IsMultipleChoice)
{
for(int i = 0; i < Model.Answers.Count; i++)
{
#Html.HiddenFor(m => m.Answers[i].ID)
#Html.CheckBoxFor(m => m.Answers[i].IsSelected)
#Html.LabelFor(m => m.Answers[i].IsSelected, Model.Answers[i].Name) // associate label with the checkbox
}
}
else
{
foreach(var answer in Model.Answers)
{
#Html.RadioButtonFor(m => m.SelectedAnswer, #answer.ID)
#Html.DisplayFor(m => m.Answers[i].Name)
}
}
Controller
[HttpPost]
public ActionResult YourMethod(MyVM model)
{
if(model.SelectedAnswer.HasValue)
{
// property SelectedAnswer contains the ID of the selected answer (from radio button)
}
else
{
foreach(AnswerVM answer in model.Answers)
{
if (answer.IsSelected)
{
// answer.ID contains the ID of the checked answers (from checkboxes)
Note: RadioButtonFor() renders duplicate id attributes so you can give each a unique ID (say based in answer.ID) so you can use an associated <label for ..>
I am creating a small website which allows users to create questions and exams (and also take those exams). Facing a problem with the Create-examview. The user has to be allowed to check any Questions that should be added to the Exam.
Using the following action to create the view, which passes the Questions in a ViewBag:
public ActionResult Create()
{
QuestionaireDbContext db = new QuestionaireDbContext();
ViewBag.Questions = db.Questions;
return View();
}
In my view then, I can call ViewBag.Questions and (should be able to?) use those to create checkboxes for each Question.
I have tried using the extesionmethod for HtmlHelper, CheckBoxList, which I got through NuGet. But the Html.CheckBoxList doesn't seem to be picked up. I tried adding the using as suggested in their documents but that didn't work either.
How can I create a checkbox for each Question and allow the user to select a variaty of them?
My Exam and Question-models for reference:
public class Exam
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime CreationDate { get; set; }
public ICollection<Question> Questions { get; set; }
}
public class Question
{
public enum Answers
{
A,
B,
C,
D
}
public int Id { get; set; }
public string Name { get; set; }
public string AnswerA { get; set; }
public string AnswerB { get; set; }
public string AnswerC { get; set; }
public string AnswerD { get; set; }
public Answers Correct { get; set; }
}
You will need to create view models to represent what you want to bind to. One possible solution might be
public class ExamVM
{
public int ID { get; set; }
public string Name { get; set; }
public List<QuestionVM> Questions { get; set; }
}
public class QuestionVM
{
public int ID { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
In you Create method, initialize and populate the ExamVM details including the collection of possible questions, then in the view
#model YourAssembly.ExamVM
#using (Html.BeginForm())
{
#Html.HiddenFor(m => m.ID)
#Html.DisplayFor(m => m.Name)
for (int i = 0; i < Model.Questions; i++)
{
#Html.HiddenFor(m => m.Questions[i].ID)
#Html.CheckBoxFor(m => m.Questions[i].IsSelected)
#Html.DisplayFor(m => m.Questions[i].Name)
}
<input type="submit" value="Save" />
}
Post method
[HttpPost]
public ActionResult Create(ExamVM model)
{
foreach(QuestionVM q in model.Questions)
{
if (q.IsSelected)
{
// Save the value of exam.ID and question ID to the database
Create a folder called EditorTemplates in your Views/Shared folder.
create a new empty view called _QuestionEditor and add the following code.
#model Question
#Html.HiddenFor(model => model.Id)
#Html.DisplayFor(model => model.Name)
//use DisplayFor or LabelFor accordingly
#Html.CheckboxFor(model => true, Model.AnswerA)
#Html.CheckboxFor(model => true, Model.AnswerB)
#Html.CheckboxFor(model => true, Model.AnswerC)
#Html.CheckboxFor(model => true, Model.AnswerD)
now in your main view use it as follows
#foreach(var question in ViewBag.Questions){
#Html.EditorFor(item => question)
//you can specify the template name i.e `"_QuestionEditor"` as the second parameter
//if you have more than one editor template for the same type
}
This doesn't take into account how you submit your data as you haven't provided any code for that or the model that you use to get the data back into your post action