I want to do a single OR multiple answer quiz using radiobuttonfor and checkboxFor, but I cannot make it work. The problem with all example I see is that the Question model also register the SelectedAnswer, but in my case I want each possible answer to be selectable since some questions will have multiples answers, and thus the isSelected property is directly inside the Answer model.
Therefore, for questions with single answers, when I try to create my model using RadioButtonFor(m => m[question].Answers[answerToDisplayId].IsSelected), every answer is in its own group and is not unchecked when I check another answer from that question (basically it behave like a checkBoxFor)
What I currently have: The question model
public enum questionfield
{
Chaser, Beater, Seeker, Contact, Process, Other
};
public enum QuestionDifficulty
{
Basic, Advanced
};
public enum AnswerType
{
SingleAnswer, MultipleAnswer
}
public class Question
{
public int Id { get; set; }
[Required(ErrorMessage = "Question name not valid")]
public string Name { get; set; }
[Required]
public QuestionField Field { get; set; }
[Required]
public QuestionDifficulty Difficulty { get; set; }
[Required]
public bool IsVideo { get; set; }
public string VideoURL { get; set; }
[Required]
public string QuestionText { get; set; }
[Required]
public AnswerType AnswerType { get; set; }
[Required]
public List<Answer> Answers { get; set; }
[Required]
public String AnswerExplanation { get; set; }
Answer model :
public class Answer
{
public int Id { get; set; }
public String Answertext { get; set; }
public Boolean IsTrue { get; set; }
public Boolean IsSelected { get; set; }
}
The view :
<div>
<!-- For each Question, display a new div with the Title, the question code, the question text, the video if there is one, then the possible answers depending on the type of answers-->
#using(Html.BeginForm("QuizzResult", "Home"))
{
for(int i = 0; i < Model.Count; i++)
{
<div class="QuizzQuestion">
<div class="QuizzQuestionTitle">#Model[i].Id : #Model[i].Name</div> #Html.HiddenFor(m => Model[i].Id)
<div class="QuizzQuestiontext">#Model[i].QuestionText</div>
#if(#Model[i].IsVideo)
{
<div class="QuizzQuestionVideoContainer">
<iframe class="QuizzQuestionVideo" id="ytplayer" type="text/html"
src="#Model[i].VideoURL"
frameborder="0"></iframe>
</div>
}
<div class="RadioButtonAnswers">
#if (#Model[i].AnswerType == QRefTrain3.Models.AnswerType.SingleAnswer)
{
for (int j = 0; j < Model[i].Answers.Count; j++)
{
#Model[i].Answers[j].Answertext #Html.RadioButtonFor(m => m[i].Answers[j].IsSelected, true)
#Html.HiddenFor(m => Model[i].Answers[j].IsTrue)
}
}
</div>
</div>
}
<input type="submit" value="Validate Answers"/>
}
</div>
The controller :
[HttpPost]
public ActionResult QuizzResult(List<Question> answers)
{
foreach(Question a in answers)
{
var b = Request.Form[a.Id.ToString()];
}
Result result = new Result();
foreach (Question q in answers)
{
result.QuestionsAskedIds.Add(q.Id);
if (Question.IsGoodAnswer(q))
{
result.GoodAnswersIds.Add(q.Id);
}
}
if (User.Identity.IsAuthenticated)
{
result.User = Dal.Instance.GetUserByName(HttpContext.User.Identity.Name);
Dal.Instance.CreateResult(result);
}
return View("QuizResult", result);
}
What would be the good way to do this? Thank you!
In case someone will see this :
The solution I found was to change the model : Instead of having one IsSelected parameter per answer, add a List selectedAnswers to your Question model. Then, in the view, add your radiobutton like this :
#Html.RadioButtonFor(m => m[i].SelectedAnswers, Model[i].Answers[j].Id)
You will store the id of each selected answer for this question ine the SelectedAnswers list. You can then create your results using this data.
Related
This question already has answers here:
Asp.Net MVC: Why is my view passing NULL models back to my controller?
(2 answers)
Closed 5 years ago.
I need to display a specific form in a view with a model. The model is like that:
This is the final object filled what I need :
public class ObjetTotal
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Numero { get; set; }
public string Value { get; set; }
}
I choose to cut the form into two differents parts :
First a "static" part where the user can put common values for the differents ObjetsTotal.
Second a "variable" part where the user put differents values for the differents ObjetsTotal.
The final aim is that the user doest'n have to type, the same thing for all the objects ObjetTotal.
So, I create other objects (I don't know if it's a good practice) which represents the differents part of the form.
The static part with MainObjet and the variable part with Numbers. I put these two object into an other object "Mix" which contains one "MainObjet" and a list of "Numbers".
public class MainObjet
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class Numbers
{
public string Numero { get; set; }
public string Value { get; set; }
}
public class Mix
{
public MainObjet obj { get; set; }
public IEnumerable<Numbers> num { get; set; }
public Mix()
{
obj = new MainObjet();
num = new List<Numbers>();
}
}
Then I want to render the model Mix in a view to have the two parts of the form.
I've try this :
#model App.Models.Mix
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Mix</legend>
<h3>First Properties</h3>
<div>
#Html.TextBoxFor(model => model.obj.Id);
#Html.TextBoxFor(model => model.obj.Name);
#Html.TextBoxFor(model => model.obj.Description);
</div>
<div>
<table>
#for (int i = 0; i < 5; i++)
{
<tr>
<td>
#Html.TextBoxFor(model => model.num[i].Numero)
</td>
<td>
#Html.TextBoxFor(model => model.num[i].Value)
</td>
</tr>
}
</table>
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
But after the submit I get an object Mix null in this ActionResult :
[HttpPost]
public ActionResult Test(Mix obj)
{
return View();
}
Can you explain me how to do that ? May I'm on a wrong way.
Don't consider the design of the form, and I don't know the right type to put to Numbers, Maybe a simple list be enough for that.
The thing I can see is that you are missing the initialization of your model properties in the parameter-less constructor. You should try to update your model code to be:
public class Mix
{
public MainObjet obj { get; set; }
public IEnumerable<Numbers> num { get; set; }
public Mix()
{
obj = new MainObjet();
num = new List<Numbers>();
}
}
As the model binder will instantiate your model, and it will find obj and num to null and will not be able to post the values back.
Hope this helps you.
I have 10 text type input controls, 5 on first tab & 5 on second tab on same page.
Now I have a class as
public partial class TravelerMaster
{
public virtual ICollection<TravelerDetail> TravelerDetails { get; set; }
}
which has a collection as TravelerDetail.
public partial class TravelerDetail
{
public byte PackageTypeId { get; set; }
public int? NoOfPackage { get; set; }
public decimal? Weight { get; set; }
public decimal? PricePerKg { get; set; }
public decimal? PricePerPackage { get; set; }
}
How do i bind the controls such that when I post the data, the TravelerMaster object has two objects in its collection.
Please help me. Thanks in advance. Feel free to ask any query.
You can submit a collection of a particular model like this. By applying an index (this can be anything unique but I've used an integer value for ease) you can submit a collection of the same model to the controller:
Form
<div id="tab1">
#Html.Hidden("data.Index","1")
#Html.TextBox("data[1].weight", "", new { #class = "your-class-names", #id = "data[1].weight" })
...other fields...
</div>
<div id="tab2">
#Html.Hidden("data.Index","2")
#Html.TextBox("data[2].weight", "", new { #class = "your-class-names", #id = "data[2].weight" })
...other fields...
</div>
Controller
[HttpPost]
public ActionResult YourControllerMethod(IEnumerable<TravelerDetail> data)
{
if (data==null || data.Count() == 0) throw new Exception("No Data Added");
....other validation...
foreach(var item in data) {
...database work...
}
}
Now I was looking to make some validation for some checkbox fields in my model.
I want to create a unique rule that would require at least one checkbox to be true (or checked) in each category to make it valid. I have three different categories in this model.
I was told to approach this with enum as stated here.
I've looked into the situation and it seems a little over my head, because you basically utilize C# to customize your own rules.
Now these are the categories as mentioned in the hyperlink above:
//Disabilities
[Display(Name = "Learning Disabilities")]
public bool LD { get; set; }
[Display(Name = "Developmental Disabilities")]
public bool DD { get; set; }
[Display(Name = "AD/HD")]
public bool ADHD { get; set; }
[Display(Name = "Autism")]
public bool Autism { get; set; }
//Age Group
[Display(Name = "Child")]
public bool child { get; set; }
[Display(Name = "Youth")]
public bool youth { get; set; }
[Display(Name = "Adult")]
public bool adult { get; set; }
//Strategy Type
[Display(Name = "Academic")]
public bool academic { get; set; }
[Display(Name = "Behaviour")]
public bool behaviour { get; set; }
[Display(Name = "Communication")]
public bool communication { get; set; }
[Display(Name = "Social")]
public bool social { get; set; }
Now to approach this I was told to use enum:
public enum Age
{
[Display(Name="Child")
Child,
[Display(Name="Youth")
Youth,
[Display(Name="Adult")
Adult
}
^Do I throw this in the model still?
I know this goes into the model:
[Required]
public Age MyAge { get; set; }
After looking at several other examples I know that the above code is incomplete and I would also have to edit my view. As sad as it sounds, my education has not gone this far in programming so I apologize for my lack of understanding.
But if you could point me in the right direction so I can walk this golden brick road that would be much appreciated
Cheers.
Here is the small prototype I did for you with Enums and CheckBoxes and its validation.
Let your ENUM be -
public static class Data
{
public enum BloodGroup
{
[Description("A+")]
APositive,
[Description("B+")]
BPositive
}
}
Then construct your Enum model, which will hold the basic Checkbox properties -
public class EnumModel
{
public Data.BloodGroup BloodGroup { get; set; }
public bool IsSelected { get; set; }
}
Then construct Enum View Model based on Enum model, which basically have List of Enum Models -
public class EnumViewModel
{
public List<EnumModel> CheckBoxItems { get; set; }
}
Then your Controller Index Action, will construct EnumViewModel and will bind it to Index View -
public ActionResult Index()
{
EnumViewModel model = new EnumViewModel();
model.CheckBoxItems = new List<EnumModel>();
model.CheckBoxItems.Add(new EnumModel() { BloodGroup = Data.BloodGroup.APositive, IsSelected = false });
model.CheckBoxItems.Add(new EnumModel() { BloodGroup = Data.BloodGroup.BPositive, IsSelected = false });
return View(model);
}
Index View will display all the checkboxes and will make a POST to Submit action on click of submit button -
#model MVC.Controllers.EnumViewModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#Html.ValidationSummary();
#using (Html.BeginForm("Submit", "Enum", FormMethod.Post))
{
for (int i = 0; i < Model.CheckBoxItems.Count; i++)
{
#Html.LabelFor(m => m.CheckBoxItems[i].BloodGroup);
#Html.CheckBoxFor(m => m.CheckBoxItems[i].IsSelected);
#Html.HiddenFor(m => m.CheckBoxItems[i].BloodGroup);
}
<input type="submit" value="click"/>
}
In the Submit Action We check for the IsSelected properties of the Enum View Model, if there are none, then we return error to Index View.
public ActionResult Submit(EnumViewModel model)
{
if (!model.CheckBoxItems.Where(p => p.IsSelected).Any())
{
ModelState.AddModelError("CheckBoxList", "Please select atleast one!!!");
return View("Index",model);
}
return View();
}
Output -
On Load -
When we do not select anything and submit the form -
I'm having trouble binding data to a collection item's collection (I'm also having trouble wording my problem correctly). Let's just make thing easier on everyone by using an example with psudo models.
Lets say I have the following example models:
public class Month()
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Week> Weeks { get; set; }
}
public class Week()
{
public int ID { get; set; }
public int MonthID { get; set; }
public String Name { get; set; }
public virtual ICollection<Day> Days { get; set; }
}
public class Day()
{
public int ID { get; set; }
public String Name { get; set; }
}
...and an example viewmodel:
public class EditMonthViewModel()
{
public Month Month { get; set; }
public List<Week> Weeks { get; set; }
public List<Day> AllDays { get; set; }
}
The purpose of the Edit Action/View is to enable users to edit a month, the weeks assigned to the month, and add and remove days from weeks of a certain month. A view might help.
#model myProject.ViewModels.EditMonthViewModel
//...
#using (Html.BeginForm())
{
//Edit Month Stuff...
#for(int i = 0; i < Model.Weeks.Count(); i++)
{
<h2>#Model.Weeks[i].Name</h2>
#Html.EditorFor(model => Model.Weeks[i].Name)
//loop through all possible days
//Select only days that are assigned to Week[i]
#for(int d = 0; d < Model.AllDays.Count(); d ++)
{
//This is the focus of this question.
//How do you bind the data here?
<input type="checkbox"
name="I have no idea"
#Html.Raw(Model.Weeks[i].Days.Contains(Model.AllDays[d]) ? "checked" : "") />
}
}
}
Controller Action methods
public ActionResult Edit(int id)
{
var viewModel = new EditMonthViewModel();
viewModel.Month = db.Months.Find(id);
viewModel.Weeks = db.Weeks.Where(w => w.MonthID == id).ToList();
viewModel.AllDays = db.Days.ToList();
}
[HttpPost]
public ActionResult Edit(EditMonthViewModel viewModel)
{
var monthToUpdate = db.Months.Find(viewModel.Month.ID);
//...
if(viewModel.Weeks != null)
{
foreach (var week in viewModel.Weeks)
{
var weekToUpdate = monthToUpdate.Weeks.Single(w => w.ID == week.ID);
//...
/*So, I have a collection of weeks that I can grab,
but how do I know what was selected? My viewModel only has a
list of AllDays, not the days selected for Week[i]
*/
}
}
How can I ensure that when I submit the form the selected days will bind to the week?
It looks like the easiest thing to do is to make it a goal for your form to populate a data structure of the type IEnumerable<DayModel>, where DayModel is defined as:
public class DayModel
{
public int WeekId { get; set; }
public int DayId { get; set; }
public bool IsIncluded { get; set; }
}
You could keep your Razor code as is for the most part, but then when it comes to rendering the checkboxes, you can do something like this:
#{
var modelIdx = 0;
}
// ...
<input type="hidden" name="days[#modelIdx].WeekId" value="#Model.Weeks[i].Id" />
<input type="hidden" name="days[#modelIdx].DayId" value="#Model.AllDays[d].Id" />
<input type="checkbox" name="days[#modelIdx].IsIncluded" value="#(Model.Weeks[i].Days.Contains(Model.AllDays[d]) ? "checked" : "")" />
#{ modelIdx++; }
Then, your controller action you post to could have this signature:
[HttpPost]
public ActionResult Edit(IEnumerable<DayModel> days)
{
//...
}
Something that helps me is to never confuse view models, which should only be used for the model for views (GET actions generally) and non-view models (what we call plain models). Avoid having your POST actions try to bind to view models, and it will simplify your life greatly.
I am sure this is relatively simple, I just keep running into brick walls. I have two entity classes set up like so:
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime CreatedDate { get; set; }
public string Content { get; set; }
public string Tags { get; set; }
public ICollection<Comment> Comments { get; set; }
}
public class Comment
{
public int Id { get; set; }
public string DisplayName { get; set; }
public string Email { get; set; }
public DateTime DateCreated { get; set; }
public string Content { get; set; }
public int PostId { get; set; }
public Post Post { get; set; }
}
And I set up my ViewModel like this:
public class PostCommentViewModel
{
public Post Post { get; set; }
public IQueryable<Comment> Comment { get; set; }
public PostCommentViewModel(int postId)
{
var db = new BlogContext();
Post = db.Posts.First(x => x.Id == postId);
Comment = db.Comments;
}
}
And I have my Controller doing this:
public ActionResult Details(int id = 0)
{
var viewModel = new PostCommentViewModel(id);
return View(viewModel);
}
And then the view looks like this:
#model CodeFirstBlog.ViewModels.PostCommentViewModel
<fieldset>
<legend>PostCommentViewModel</legend>
#Html.DisplayFor(x => x.Post.Title)
<br />
#Html.DisplayFor(x => x.Post.Content)
<br />
#Html.DisplayFor(x => x.Post.CreatedDate)
<hr />
#Html.DisplayFor(x => x.Comment)
</fieldset>
The result IS displaying data, but not quite what I want for the comments.
You see that the comments (There are two of them) and just showing the id property on each one "12"
How can I get it to go into and display the comment details specific to this particular post? I imagine a foreach loop is in order, but i cant figure out how to drill into the Model.Comment property correctly.
I tried this:
#foreach(var item in Model.Comment)
{
#Html.DisplayFor(item.DisplayName)
#Html.DisplayFor(item.Content)
#Html.DisplayFor(item.DateCreated)
}
But the error I get is "The type arguments for method 'System.Web.Mvc.Html.DisplayExtensions.DisplayFor(System.Web.Mvc.HtmlHelper, System.Linq.Expressions.Expression>)' cannot be inferred from the usage. Try specifying the type arguments explicitly."
Not sure what I am supposed to do here..
Loop around the comments model:
#model CodeFirstBlog.ViewModels.PostCommentViewModel
<fieldset>
<legend>PostCommentViewModel</legend>
#Html.DisplayFor(x => x.Post.Title)
<br />
#Html.DisplayFor(x => x.Post.Content)
<br />
#Html.DisplayFor(x => x.Post.CreatedDate)
<hr />
#foreach(var comment in Model.Comment) {
#Html.DisplayFor(x => comment)
}
</fieldset>
public class PostCommentViewModel
{
public Post Post { get; set; }
public IQueryable<Comment> Comment { get; set; }
public PostCommentViewModel(int postId)
{
var db = new BlogContext();
Post = db.Posts.First(x => x.Id == postId);
Comment = Post.Comments;
}
}
And did you make Html helper method for Comment class?
Create a DisplayTemplate for your Comment class and it should work, there is no need to iterate over the collection the view engine does it for you.
As per my comment, this is all you need to do to create a display template for your Comment class:
Create a DisplayTemplates subfolder inside your view folder and inside this create a new partial view called Comment.cshtml. Your template could look like this:
#model CodeFirstBlog.ViewModels.Comment
<div class="comment">
#Html.DisplayFor(m => m.DisplayName)
....
</div>
And that's it! If you need more control over how your comment is displayed then you can simple tweak the template to suit.