Validating MVC Data Collections - asp.net-mvc

I have a simple Question data model:
public class Question {
int QuestionId { get; set; }
string Text { get; set; }
string Answer { get; set; }
string ValidationMessage { get; set; }
};
Using this class I have built a view Model:
public class QuestionViewModel {
string Introduction { get; set; }
IEnumerable<Question> Questions { get; set; }
};
My Controller the builds the view model (from a data source) and renders the view:
#model QuestionViewModel
#using (Html.BeginForm()) {
if (Model.Questions != null) {
<ol>
#Html.EditorFor(m => Model.Questions)
</ol>
}
#Html.ValidationSummary("Unable to process answers...")
<input type="submit" value="submit" />
}
This view utilises an EditorTemplate:
#model Question
<li>
#Html.HiddenFor(m => m.Questionid)
#Html.TextBoxFor(m => m.Answer)
#Html.ValidationMessageFor(m => m.Answer)
</li>
For now, when the page is posted back, the controller validates the response:
[HttpPost]
public ActionResult Response(QuestionViewModel model) {
if (ModelState.IsValid) {
for (int i = 0; i < model.Questions.Count(); i++) {
Question q = model.Questions[i];
string questionId = String.Format("Questions[{0}]", i);
if (String.IsNullOrWhiteSpace(q.Answer)) {
ModelState.AddModelError(questionId, q.ValidationMessage);
}
}
}
}
The problem I'm having is that most of this works fine - the validates and the Validation Summary shows the correct validation messages. The problem is that I can't get individual field validators to render the error:
<span class="field-validation-valid" data-valmsg-replace="true" data-valmsg-for="Questions[0].StringValue"></span>
As you can see, when I call the ModelState.AddModelError() method, I am currently using key value of the format "Questions[0]", but I have also tried "Questions_0" and various other combinations.
Any help/guidance would be much appreciated.
[Apologies for the overly long post]

I have found the answer - as with so many things, it was obvious once I broke the problem down - the ModelState.AddModelError() just needed a fully qualified key!
Modify the HttpPost Controller as follows:
[HttpPost]
public ActionResult Response(QuestionViewModel model) {
if (ModelState.IsValid) {
for (int i = 0; i < model.Questions.Count(); i++) {
Question q = model.Questions[i];
/*
** The key must specify a fully qualified element name including
** the name of the property value, e.g.
** "Questions[0].Answer"
*/
string questionId = String.Format("Questions[{0}].Answer", i);
if (String.IsNullOrWhiteSpace(q.Answer)) {
ModelState.AddModelError(questionId, q.ValidationMessage);
}
}
}
}

Related

Dropdown list population from ViewModel

First of all, I know this question has been asked many, many times. I've read countless articles and Stack Overflow answers. I've tried to figure this problem out for four days and I think I need help if someone doesn't mind.
I have two databases. The employee database has a field called "DisplayName" -- the second database has a relationship with the first and they work together great. I'm able to call the two databases perfectly in another application.
You can see the in the picture Index Page
that I have a list of people. I want a dropdown below it that lists all display names in the database so employees can add themselves to the list. You'll see a dropdown in the image but it's not populated.
Seems simple. But geez. Part of a problem I'm having is my home controller already has a function to populate the list in the picture so I can't do another on that page. I've tried a lot of suggestions on a lot of sites. I get IEnumerable errors or display reference errors....
Here's my controller (again - it has nothing in it that helps the dropdown):
namespace SeatingChart.Controllers
{
public class HomeController : Controller
{
private ApplicationDbContext db = new ApplicationDbContext();
// GET: Employee
public ActionResult Index()
{
var lists = db.BreakModels
.Include("Employee")
.Include("TimeEntered")
.Include("TimeCleared")
.Include("DisplayName")
.Select(a => new HomeIndexViewModels
{
Employee = a.Employee,
DisplayName = a.EmployeeModels.DisplayName,
TimeEntered = a.TimeEntered,
TimeCleared = a.TimeCleared.Value,
Id = a.EmployeeModels.Id,
});
return View(lists);
}
View:
#model IEnumerable<SeatingChart.Models.HomeIndexViewModels>
#{
Layout = null;
}
#Html.Partial("_Header")
<div class="container_lists">
<div class="container_break col-md-8">
<h5 style="text-align:center">Break List</h5>
<table class="table-bordered col-lg-12">
#if (Model != null)
{
foreach (var item in Model)
{
if (item.TimeCleared == null)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.DisplayName)
</td>
<td>
 BV
</td>
<td>
 #item.TimeEntered.ToString("HH:mm")
</td>
</tr>
}
}
}
</table>
#using (Html.BeginForm())
{
<div class="row site-spaced">
<div class="col-3">
#Html.DropDownList("DisplayName", new SelectList(new List<string>() { "---Dispatcher---" }), new { #class = "required " })
</div>
</div>
<div class="col-3">
<input type="submit" value="Submit" class="site-control" />
</div>
}
</div>
</div>
ViewModel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;
namespace SeatingChart.Models
{
public class HomeIndexViewModels
{
//Break Model
public int BreakId { get; set; }
public int Employee { get; set; }
public DateTime TimeEntered { get; set; }
public DateTime? TimeCleared { get; set; }
//Employee Model
public int Id { get; set; }
public string DisplayName { get; set; }
public string DisplayNames { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool NotActive { get; set; }
public int Force { get; set; }
public string EmployeeList { get; set; }
}
}
I hope this is clear enough. I've tried so many different ways with so much code - the errors are different with everything I've tried.
Thanks in advance for your patience and help!
You can add to your viewmodel
public List<SelectListItem> Employees { get; set; }
Then you can populate this list with controller then in view just call it with:
#Html.DropDownListFor(m => m.Id, Model.Employees, new { #class = "form-control", required = "required" })
Update - how to populate list. Should work (but not tested code).
public List<SelectListItem> GetEmployeeForDropdown(List<HomeIndexViewModels> list)
{
List<SelectListItem> empList = new List<SelectListItem>();
try
{
if (list != null && list.Count > 0)
{
foreach (var item in list)
{
empList.Add(new SelectListItem { Text = item.DisplayName, Value = item.Id.ToString() });
}
}
else
{
empList.Add(new SelectListItem { Text = "No items", Value = string.Empty });
}
}
catch (Exception ex)
{
//handle exceptions here
}
return empList;
}
Edit: Remember to use your model in view!

ASP.NET MVC 5 Html.CheckboxFor only return default value on post

I have read the tutorials and prepared a list of checkboxes for the page. When the form is submitted, the Selected property only get the value false.
Is there something I missed?
The Model
public class SelectStudentModel
{
public int StudentID { get; set; }
public string CardID { get; set; }
public string Name { get; set; }
public bool Selected { get; set;}
}
The ViewModel
public class SelectStudentViewModel
{
public List<SelectStudentModel> VMList;
public SelectStudentViewModel()
{
VMList = SelectStudentModel.GETStudent();
}
}
The View
#using Student.Models
#model SelectStudentViewModel
#using (Html.BeginForm("AddStudent", "SectionStudent", FormMethod.Post, new { #role = "form" }))
{
#{ for (int i = 0; i < Model.VMList.Count(); i++)
{
<tr>
<td>#Html.CheckBoxFor(m => m.VMList[i].Selected)</td>
<td>#Html.DisplayFor(model => model.VMList[i].Name)</td>
</tr>
}
}
<input type="submit" value="submit" />
}#* end form *#
The Controller for posted data
[HttpPost]
public ActionResult AddStudent(SelectStudentViewModel model)
{
foreach (SelectStudentModel m in model.VMList)
{
Console.Write(m.Selected.ToString());
}
return PartialView("StudentSelectForm", model);
}
VMList is a field in your SelectStudentViewModel model. You need to change it to a property (with a getter/setter) so the DefaultModelBinder can set the values
public class SelectStudentViewModel
{
public List<SelectStudentModel> VMList { get; set; } // change
public SelectStudentViewModel()
{
VMList = SelectStudentModel.GETStudent();
}
}
Side note: Suggest you change #Html.DisplayFor(model => model.VMList[i].Name) to #Html.LabelFor(m => m.VMList[i].Selected, Model.MList[i].Name) so that you get a label associated with the checkbox

Displaying subitems on mvc 3 form?

(warning newbie)
I am using codefirst entity framework (with MVC3) and trying to display a list of steps and its associated questions. Not sure why my syntax is throwing an exception "the ObjectContext instance has been disposed and can no longer be used for operations that require a connection.":
#model IEnumerable<COPSGMIS.Models.Step>
#{
ViewBag.Title = "Questionaire";
}
<h2>Questionaire</h2>
#using (Html.BeginForm("Questionaire", "Question", FormMethod.Post, new { id = "SignupForm" }))
{
<div>
#foreach (var item in Model)
{
<fieldset class="wizard">
<legend class="wizard">#Html.DisplayFor(modelItem => item.Title)</legend>
#foreach (var question in item.Questions)
//*** item.Questions is throwing an exception ****
{
<label for="question">#Html.DisplayFor(modelItem => question.QuestionText)</label>
<input id="question" type="text" />
}
</fieldset>
}
<p>
<input id="SaveAccount" type="button" value="Save" />
</p>
</div>
}
My model:
public int StepID { get; set; }
public int ReviewID { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public int StepOrder { get; set; }
public virtual ICollection<Question> Questions { get; set; }
My controller:
var steps = from b in db.Steps
orderby b.StepOrder
select b;
return View(steps.ToList());
When item.Questions is access, EF will try to hit the database to grab the questions for that item. If the context has been disposed, it will fail. Since you'll be looping through the questions in your view, I'd suggest adding them to your initial query using "Include".
var steps = from b in db.Steps.Include(s => s.Questions)
orderby b.StepOrder
select b;
return View(steps.ToList());

How to pass an entire ViewModel back to the controller

I have a ViewModel that contains two objects:
public class LookUpViewModel
{
public Searchable Searchable { get; set; }
public AddToSearchable AddToSearchable { get; set; }
}
The two contained models look something like this:
public class Searchable
{
[Key]
public int SearchableId { get; set; }
public virtual ICollection<AddToSearchable> AddedData { get; set; }
}
public class AddToSearchable
{
[Key]
public int AddToSearchableId { get; set;}
[Required]
public int SearchableId { get; set; }
[Required]
public String Data { get; set; }
[Required]
public virtual Searchable Searchable { get; set; }
}
I have a view that uses my LookUpViewModel and receives input to search for a SearchableId. If the Searchable object is found, a LookUpViewModel object is created and passed to the View. The view then displays editor fields for AddToSearchable.Data. Once submitted, I want the LookUpViewModel to be passed to an action method to handle all the back-end code. The only problem is, the LookUpViewModel passed to my action method contains a null reference to Searchable and a valid reference to AddToSearchable.. i.e. I'm missing half of my data.
Here's an example of what my view looks like:
#model HearingAidTrackingSystem.ViewModels.LookUpViewModel
#using (Html.BeginForm("LookUp", "Controller", "idStr", FormMethod.Post))
{
<input type="text" name="idStr" id="idStr"/>
<input type="submit" value="Search" />
}
#if (Model.Searchable != null && Model.AddToSearchable != null)
{
using (Html.BeginForm("AddMyStuff", "Controller"))
{
Html.HiddenFor(model => model.Searchable.SearchableId);
Html.HiddenFor(model => model.Searchable.AddedData);
Html.HiddenFor(model => model.AddToSearchable.AddToSearchableId);
Html.HiddenFor(model => model.AddToSearchable.SearchableId);
Html.HiddenFor(model => model.AddToSearchable.Searchable);
<div class="editor-field">
#Html.EditorFor(model => model.AddToSearchable.Data)
#Html.ValidationMessageFor(model => model.AddToSearchable.Data);
</div>
<input type="submit" value="Submit" />
}
}
and here are my action methods:
public ActionResult LookUp(LookUpViewModel vm)
{
return View(vm);
}
[HttpPost]
public ActionResult LookUp(string idStr)
{
int id = /*code to parse string to int goes here*/;
Searchable searchable = dal.GetById(id);
LookUpViewModel vm = new LookUpViewModel { Searchable = searchable,
AddToSearchable = new AddToSearchable() };
//When breakpoint is set, vm contains valid references
return View(vm);
}
[HttpPost]
public ActionResult AddMyStuff(LookUpViewModel vm)
{
//**Problem lies here**
//Do backend stuff
}
Sorry for the lengthy post. I tried my best to keep it simple. Any suggestions you may have.. fire away.
Two methods to fix it:
You can add to do HiddenFor() for all properties of Model.Searchable.
You can use serialization to transfer your Model.Searchable into text presentation and repair it from serialized form in controller.
Update: The problem is: You need to use #Html.HiddenFor(), not Html.HiddenFor();.

Too many data in my matrix implementation >> slow and unable to submit so much data

I need a kind of matrix to edit values. Here is my result so far:
So I have a very big kind of table (composed of several textboxes). I still need to add caption text in heading but this is just a test at this time.
I have 2 problems with my solution:
When I focus on numbers in the matrix to change it, it takes time to focus on these textboxes.
When I click submit, I got an error because there are too many elements to return.
I try with less data and it works better: quicker changing focus in different textboxes and the submit works just fine.
Here are my controller actions:
public ActionResult Index()
{
var dto = _requestServiceClient.GetMatrices();
var vm = new List<MatrixVM>();
var viewModel = new MatrixIndexViewModel();
Mapper.Map(dto, vm);
viewModel.Matrix = vm;
return View(viewModel);
}
[HttpPost]
public ActionResult Index(MatrixIndexViewModel viewModel)
{
return View(viewModel);
}
Here is the model I use:
public class MatrixIndexViewModel
{
public List<MatrixVM> Matrix { get; set; }
}
public class MatrixVM
{
public int MatrixID { get; set; }
public int OriginStopID { get; set; }
public string OriginStopText { get; set; }
public int DestinationStopID { get; set; }
public string DestinationStopText { get; set; }
public int NumberOfDays { get; set; }
}
Here is my view:
#model PLATON.WebUI.Areas.Admin.ViewModels.Matrix.MatrixIndexViewModel
#using PLATON.WebUI.App_LocalResources
#{
Layout = "~/Areas/Admin/Views/Shared/_Layout.Admin.cshtml";
ViewBag.Title = UserResource.Matrix;
double nbrStops = Math.Sqrt(Model.Matrix.Count());
Html.EnableClientValidation(false);
}
#using (Html.BeginForm())
{
for (int count = 0; count < Model.Matrix.Count(); count++)
{
if (count % nbrStops == 0)
{
// Displaying heading text for each lines
#Html.DisplayFor(x => x.Matrix[count].OriginStopText)
}
#Html.HiddenFor(x => x.Matrix[count].MatrixID)
#Html.HiddenFor(x => x.Matrix[count].OriginStopID)
#Html.HiddenFor(x => x.Matrix[count].DestinationStopID)
#Html.TextBoxFor(x => x.Matrix[count].NumberOfDays, new { style = "width:13px" })
if (count % nbrStops == nbrStops - 1)
{
// Proceed next line
#:<br />
}
}
<div class="submit_block">
<input type="submit" class="btn primary" value="Enregistrer" />
</div>
}
Do you have an idea of a better implementation? Maybe a bettier idea is to have a "read-only" matrix and be able to click on elements to edit it in a jquery dialog. What do you think?
Thanks anyway.
I think I would go with a <table> to show the data and on the onclick/onmouseenter of every cell I will show a Textbox to accept the input.
You can think of opening a popup so you can post a value at the time to the controller or you can substitute the cell text with a TextBox and log only the values changed in a list of input hidden.

Resources