MVC Validate At Least One Checkbox Or a Textbox is Selected - validation

I have a form where either at least one checkbox must be checked OR a textbox is filled in.
I have a ViewModel that populates the CheckboxList and takes the selected values plus the textbox (other) value when required to a SelectedWasteTypes property within the ViewModel. I think my problem is I can't validate against this property as there is no form element on the view that directly relates to it. I've very new to MVC and this one has stumped me.
From the ViewModel
public List<tblWasteTypeWeb> WasteTypeWebs { get; set; }
public string WasteTypeWebOther { get; set; }
public string SelectedWasteTypes { get; set; }
View (segment)
#using (Html.BeginForm("OrderComplete", "Home"))
{
//Lots of other form elements
#for (var i = 0; i < Model.WasteTypeWebs.Count; i++)
{
var wt = Model.WasteTypeWebs[i];
#Html.LabelFor(x => x.WasteTypeWebs[i].WasteTypeWeb, wt.WasteTypeWeb)
#Html.HiddenFor(x => x.WasteTypeWebs[i].WasteTypeWebId)
#Html.HiddenFor(x => x.WasteTypeWebs[i].WasteTypeWeb)
#Html.CheckBoxFor(x => x.WasteTypeWebs[i].WasteTypeWebCb)
}
<br />
<span>
#Html.Label("Other")
#Html.TextBoxFor(x => x.WasteTypeWebOther, new { #class = "form-control input-sm" })
</span>
//More form elements
<input type="submit" value="submit" />
}
Controller Logic (if you can call it that)
[HttpPost]
public ActionResult OrderComplete(OrderViewModel model)
{
var sb = new StringBuilder();
if (model.WasteTypeWebs.Count(x => x.WasteTypeWebCb) != 0)
{
foreach (var cb in model.WasteTypeWebs)
{
if (cb.WasteTypeWebCb)
{
sb.Append(cb.WasteTypeWeb + ", ");
}
}
sb.Remove(sb.ToString().LastIndexOf(",", StringComparison.Ordinal), 1);
}
model.SelectedWasteTypes = sb.ToString();
if (!string.IsNullOrEmpty(model.WasteTypeWebOther))
{
if (!string.IsNullOrEmpty(model.SelectedWasteTypes))
{
model.SelectedWasteTypes = model.SelectedWasteTypes.TrimEnd() + ", " + model.WasteTypeWebOther;
}
else
{
model.SelectedWasteTypes = model.WasteTypeWebOther;
}
}
return View(model);
}
I very much feel I'm up a certain creek... I've thought about using JQuery, but ideally I'd like server side validation to be sure this info is captured (its a legal requirement). However, if this can only be achieved client side, I will live with it.
Any suggestions?

Take a look at the MVC Foolproof Validation Library. It has validation attributes for what you are trying to accomplish: [RequiredIfEmpty] and [RequiredIfNotEmpty]. You can also take a look at my previous SO answer about Conditional Validation.

I would suggest you to implement IValidatableObject in your ViewModel.
Inside Validate( ValidationContext validationContext) method you can check weather your conditions are met. For example:
if(string.IsNullOrWhiteSpace(WasteTypeWebOther))
yield return new ValidationResult("Your validation error here.");

Related

MVC3 Custom Validation error message doesn't display when using ViewModel

SUMMARY
Question: Why doesn't the custom validation error message show when using a ViewModel.
Answer: The custom validation should be applied to the ViewModel not the Class. See the end of #JaySilk84's answer for example code.
MVC3, project using
jquery-1.7.2.min.js
modernizr-2.5.3.js
jquery-ui-1.8.22.custom.min.js (generated by jQuery.com for the Accordion plugin)
jquery.validate.min.js and
jquery.validate.unobtrusive.min.js
I have validation working in my project for both dataannotations in the View and for ModelState.AddModelError in the Controller so I know I have all the validation code configured properly.
But with custom validation an error is generated in the code but the error message doesn't display.
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{ if (DOB > DateTime.Now.AddYears(-18))
{ yield return new ValidationResult("Must be 18 or over."); } }
Drilling down in debug in the POST action the custom validation causes Model state to fail and the error message is placed in the proper value field but when the model is sent back to the view the error message doesn't display. In the controller I also have ModelState.AddModelError code and its message does display. How is that handled differently as to one would work and not the other? If not that what else would prevent the error message from displaying?
Update 1 :
I'm using a ViewModel to create the model in the view. I stripped out the ViewModel and the error message started displaying, as soon I added the ViewModel back in the message again stopped displaying. Has anyone successfully used a custom validation with a ViewModel? Was there anything you had to do extra to get it to work?
Update 2 :
I created a new MVC3 project with these two simple classes (Agency and Person).
public class Agency : IValidatableObject
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime DOB { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (DOB > DateTime.Now.AddYears(-18)) { yield return new ValidationResult("Must be over 18."); }
}
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
Here's the Controller Code
public ActionResult Create()
{
return View();
}
//
// POST: /Agency/Create
[HttpPost]
public ActionResult Create(Agency agency)
{
if (ModelState.IsValid)
{
db.Agencies.Add(agency);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(agency);
}
//[HttpPost]
//public ActionResult Create(AgencyVM agencyVM)
//{
// if (ModelState.IsValid)
// {
// var agency = agencyVM.Agency;
// db.Agencies.Add(agency);
// db.SaveChanges();
// return RedirectToAction("Index");
// }
// return View(agencyVM);
//}
The View
#model CustValTest.Models.Agency
#*#model CustValTest.Models.AgencyVM*#
#* When using VM (model => model.Name) becomes (model => model.Agency.Name) etc. *#
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Agency</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.DOB)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.DOB)
#Html.ValidationMessageFor(model => model.DOB)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
The ViewModel
public class AgencyVM
{
public Agency Agency { get; set; }
public Person Person { get; set; }
}
When just Agency is presented in the View the validation error displays (DOB under 18). When the ViewModel is presented the error doesn't display. The custom validation always catches the error though and causes ModelState.IsValid to fail and the view to be re-presented. Can anyone replicate this? Any ideas on why and how to fix?
Update 3 :
As a temporary work around I have changed the Validation into a field level one (vs. a model level one) by adding a parameter to the ValidationResult:
if (DOB > DateTime.Now.AddYears(-18)) { yield return new ValidationResult("Must be over 18.", new [] { "DOB" }); }
The problem with this is now the error message is showing up next to the field rather than at the top of the form (which is not good in say an accordion view since the user will be returned to the form with no visible error message). To fix this secondary problem I added this code to the Controller POST action.
ModelState.AddModelError(string.Empty, errMsgInvld);
return View(agencyVM);
}
string errMsgInvld = "There was an entry error, please review the entire form. Invalid entries will be noted in red.";
The question is still unanswered, why doesn't the model level error message show with a ViewModel (see my response to JaySilk84 for more on this)?
The issue is now that your models are nested, the error message is being placed into ModelState under Agency without the .DOB because you didn't specify it in the ValidationResult. The ValidationMessageFor() helper is looking for a key named Agency.DOB (see relevant code below from ValidationMessageFor() helper):
string fullHtmlFieldName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(expression);
FormContext clientValidation = htmlHelper.ViewContext.GetFormContextForClientValidation();
if (!htmlHelper.ViewData.ModelState.ContainsKey(fullHtmlFieldName) && clientValidation == null)
return (MvcHtmlString) null;
GetFullHtmlFieldName() is returning Agency.DOB, not Agency
I think if you add the DOB to the ValidationResult it will work:
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (DOB > DateTime.Now.AddYears(-18)) { yield return new ValidationResult("Must be over 18.", new List<string>() { "DOB" }); }
}
That second parameter to ValidationResult will tell it what key to use in ModelState (By default it will append the parent object which is Agency) so the ModelState will have a key named Agency.DOB which is what your ValidationMessageFor() is looking for.
Edit:
If you don't want field level validation then you don't need the Html.ValidationMessageFor(). You just need the ValidationSummary().
The view is treating AgencyVM as the model. If you want it to validate properly then put the validation at the AgencyVM level and have it validate the child objects. Alternatively you could put validation on the child objects but the parent object (AgencyVM) has to aggregate it to the view. Another thing you can do is keep it as it is and change ValidationSummary(true) to ValidationSummary(false). This will print everything in ModelState to the summary. I think removing the validation from Agency and putting it on AgencyVM might be the best approach:
public class AgencyVM : IValidatableObject
{
public Agency Agency { get; set; }
public Person Person { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Agency.DOB > DateTime.Now.AddYears(-18)) { yield return new ValidationResult("Must be over 18."); }
if (string.IsNullOrEmpty(Agency.Name)) { yield return new ValidationResult("Need a name"); }
}
}

Sending new order back to MVC controller

using the JQuery sortable, and trying to send the new order back to my controller, but not having a whole lot of luck. My view is:
using (Ajax.BeginForm("EditTickerOrder", new AjaxOptions { InsertionMode = InsertionMode.Replace, HttpMethod = "POST", }))
{
<div id="editableticker">
#Html.HiddenFor(m => m.ProjectGUID)
<ul id="sortablediv">
#foreach (DGI.CoBRA.Tools.BussinessObjects.CollabLibrary.TickerObjects.Ticker t in Model)
{
<li class="ui-state-default" id="#t.pKeyGuid.ToString()">
<p>#Html.CheckBox(t.pKeyGuid.ToString(), t.Display, new { #class = "activechk" })
<span style="font-weight: bold">
#t.Text
</span>
</p>
</li>
}
</ul>
<input type="submit" value="Save New Ticker Order" />
}
and my controller is:
[HttpPost]
public ActionResult EditTickerOrder(Guid ProjectGUID, List<string> items)
{
TickerCollectionModel TickerData = new TickerCollectionModel();
TickerData.ProjectGUID = ProjectGUID;
TickerData.ListAllBySession(ProjectGUID);
return PartialView("TickerList", TickerData);
}
yet the list<string> items is always null. Any ideas?
You are writing foreach loops, most definitely violating the naming conventions for your form input fields that the default model binder expects for working with collections. If you don't respect the established wire format, you cannot expect the default model binder to be able to rehydrate your models in the POST action.
In fact, why don't you use view models and editor templates? They make everything trivial in ASP.NET MVC.
So let's define a view model that will reflect your view requirements (or at least those shown in your question => you could of course enrich it with additional properties that you want to handle):
public class TickerViewModel
{
public Guid Id { get; set; }
public bool IsDisplay { get; set; }
public string Text { get; set; }
}
public class ProjectViewModel
{
public Guid ProjectGUID { get; set; }
public IEnumerable<TickerViewModel> Tickers { get; set; }
}
and then a controller whose responsibility is to query your DAL layer, retrieve a domain model, map the domain model into the view model we defined for this view and pass the view model to the view. Inversely, the POST action receives a view model from the view, maps the view model back into some domain model, passes the domain model to your DAL layer for processing and renders some view or redirects to a success action:
public class HomeController : Controller
{
public ActionResult Index()
{
// TODO: those values come from a data layer of course
var model = new ProjectViewModel
{
ProjectGUID = Guid.NewGuid(),
Tickers = new[]
{
new TickerViewModel { Id = Guid.NewGuid(), Text = "ticker 1" },
new TickerViewModel { Id = Guid.NewGuid(), Text = "ticker 2" },
new TickerViewModel { Id = Guid.NewGuid(), Text = "ticker 3" },
}
};
return View(model);
}
[HttpPost]
public ActionResult Index(ProjectViewModel model)
{
// Everything will be correctly bound here => map the view model
// back into your domain model and pass the domain model to
// your DAL layer for processing ...
return Content("Thanks for submitting");
}
}
a view (it is worth noting that in this example I have used a standard form instead of AJAX but it is trivial to convert it into an AJAX form):
#model ProjectViewModel
#using (Html.BeginForm())
{
#Html.HiddenFor(m => m.ProjectGUID)
<div id="editableticker">
<ul id="sortablediv">
#Html.EditorFor(x => x.Tickers)
</ul>
</div>
<button type="submit">OK</button>
}
and finally the corresponding editor template which will automatically be rendered for each element of the Tickers collection (~/Views/Home/EditorTemplates/TickerViewModel.cshtml):
#model TickerViewModel
<li class="ui-state-default">
<p>
#Html.CheckBoxFor(x => x.IsDisplay, new { #class = "activechk" })
#Html.LabelFor(x => x.IsDisplay, Model.Text)
#Html.HiddenFor(x => x.Text)
#Html.HiddenFor(x => x.Id)
</p>
</li>

checkbox values back to controller using htmlhelpers

I have the below code in my VIEW, and thereafter a submit button. I do have many of these checkboxes in my view, so that the user can click on as many as he wishes.
#Html.CheckBox("code" + l_code, false, new { Value = #item.expertiseCode })
In my controller i have the fll., which is the HTTPPost method
public ActionResult RegisterSP(RegisterModel model, FormCollection collection)
However, when debugging, i see that ALL the checkboxes are being passed back to the controller, and not just the ones that were clicked on. I just want the ones that were clicked on and ignore the rest for i need to add these to the DB. Also, the checbox values passed in contains TRUE/FALSE. Because of this the false value is also being added to the DB. If i use the below method (not using the htmlHelper), i dont have the above problem. But i wud like to use the htmlHelper:
<input type="checkbox" name="code#(l_code)" value="#item.expertiseCode" />
IF you have a collection of Checkboxes, Create a ViewModel like this
public class ExpertiseCodeViewModel
{
public string Name { set;get;}
public int ExpertiseId { set;get;}
public bool IsSelected { set;get;}
}
Now In your main ViewModel, Add a collection of this as a property
public class UserViewModel
{
public List<ExpertiseCodeViewModel > Expertises{ set; get; }
public UserViewModel()
{
if(this.Expertises==null)
this.Expertises=new List<ExpertiseCodeViewModel>();
}
}
And in your create an editor template called ExpertiseCodeViewModel
#model ExpertiseCodeViewModel
#Html.CheckBoxFor(x => x.IsSelected)
#Html.LabelFor(x => x.IsSelected, Model.Name)
#Html.HiddenFor(x => x.ExpertiseId )
Include this in your Main View
#model UserViewModel
#using (Html.BeginForm())
{
//other elements
#Html.EditorFor(m=>m.Expertises)
<input type="submit" value="Save" />
}
In your HTTPPost Action method,
[HttpPost]
public ActionResult Save(UserViewModel model)
{
List<int> items=new List<int>();
foreach (ExpertiseCodeViewModel objItem in model.Expertises)
{
if (objPVM.IsSelected)
{
//you can get the selected item id here
items.Add(objItem.ExpertiseId);
}
}
}
Try
#Html.CheckBox("code" + l_code, false, new { #value = item.expertiseCode })
or
string name = "code" + l_code;
#Html.CheckBox(name, false, new { #value = item.expertiseCode })

MVC 3 Get result from partial view into model

I'm sure this is easy, but maybe I haven't searched well ...
I want to know how to get results from a partial view back to the model and/or controller.
If the user enters a FirstName, Gender (from drop down) and Grade (from drop down), I only find then FirstName and Gender in the model. I want to know how to get the Grade from the drop down in the partial view all the way back into the model, so I can see it in the controller.
Please look for this question in the controller code:
What do I need to do to get the GradeLevel from the partial class to be here: <<<<<
Note: this is not the exact code. There may be small, insignificant typo's.
EDIT: Apparently you can't add a long comment, so I will add here:
Thank you, Tom and Mystere Man. Tom got me thinking as to why it doesn't work. I didn't think through the model binding. With the design I proposed, the HTML gets rendered and the Grade drop down has this id: "Grade". The property on the model I want to bind to is: "GradeLevelID". If I change the helper in the partial view to be #Html.DropDownList("GradeLevelID" ... it works perfectly.
But that is not a good solution. My idea was to abstract the partial view from the main view. Hard coding the name blows that! I did work up a slightly improved solution. In the main view, I change the #Html.Partial statement to pass the model property name to the partial. Like such:
#Html.Partial("GradeDropDown", (SelectList)Model.GradeSelectList, new ViewDataDictionary { { "modelPropertyName", "GradeLevelID" } })
Then I could change the partial view to say
#model System.Web.Mvc.SelectList
#Html.DropDownList((string)ViewData["modelPropertyName"], Model)
But that also seems like a goofy way to approach things. Thanks for the help. I'll look at EditorTemplates.
Here is my model:
public class RegisterModel{
public MemberRegistration MemberRegistration{
get{
if (HttpContext.Current.Session["MemberRegistration"] == null){
return null;
}
return (MemberRegistration)HttpContext.Current.Session["MemberRegistration"];
}
set{
HttpContext.Current.Session["MemberRegistration"] = value;
}
}
public string FirstName{
get{
return MemberRegistration.FirstName;
}
set{
MemberRegistration.FirstName = value;
}
}
public SelectList GenderSelectList{
get{
List<object> tempList = new List<object>();
tempList.Add(new { Value = "", Text = "" });
tempList.Add(new { Value = "M", Text = "Male" });
tempList.Add(new { Value = "F", Text = "Female" });
return new SelectList(tempList, "value", "text", MemberRegistration.Gender);
}
}
[Required(ErrorMessage = "Gender is required")]
public string Gender{
get{
return MemberRegistration.MemberPerson.Gender;
}
set{
MemberRegistration.MemberPerson.Gender = value;
}
}
public SelectList GradeLevelSelectList{
get{
List<object> tempList = new List<object>();
tempList.Add(new { Value = "", Text = "" });
tempList.Add(new { Value = "1", Text = "1st" });
tempList.Add(new { Value = "2", Text = "2nd" });
tempList.Add(new { Value = "3", Text = "3rd" });
tempList.Add(new { Value = "4", Text = "4th" });
return new SelectList(tempList, "value", "text", MemberRegistration.GradeLevel);
}
}
[Required(ErrorMessage = "Grade is required")]
public Int32 GradeLevel{
get{
return MemberRegistration.GradeLevel;
}
set{
MemberRegistration.GradeLevel = value;
}
}
}
Here is my main view:
#model RegisterModel
#using (Html.BeginForm())
{
<p class="DataPrompt">
<span class="BasicLabel">First Name:</span>
<br />
#Html.EditorFor(x => x.FirstName)
</p>
<p class="DataPrompt">
<span class="BasicLabel">Gender:</span>
<br />
#Html.DropDownListFor(x => x.Gender, Model.GenderSelectList)
</p>
<p class="DataPrompt">
<span class="BasicLabel">Grade:</span><span class="Required">*</span>
<br />
#Html.Partial("GradeDropDown", (SelectList)Model.GradeLevelSelectList)
</p>
<p class="DataPrompt">
<input type="submit" name="button" value="Next" />
</p>
}
Here is my partial view (named "GradeDropDown"):
#model System.Web.Mvc.SelectList
#Html.DropDownList("Grade", Model)
Here is my controller:
[HttpPost]
public ActionResult PlayerInfo(RegisterModel model)
{
string FirstName = model.Registration.FirstName;
string Gender = model.Registration.Gender;
>>>>> What do I need to do to get the GradeLevel from the partial class to be here: <<<<<
Int32 GradeLevel = model.Registration.GradeLevel;
return RedirectToAction("Waivers");
}
I don't even know why you are using a partial view. All you're doing is using one helper method, you could replace the partial view with the helper method in the view and it would be less code.
Second, you should be using Html.DropDownListFor() instead of Html.DropDownList(), then it will correctly name the html controls for you.
Just do this:
<p class="DataPrompt">
<span class="BasicLabel">Grade:</span><span class="Required">*</span>
<br />
#Html.DropDownListFor(m => m.GradeLevel, (SelectList)Model.GradeLevelSelectList)
</p>
try this to get the correct naming for the elements when they get posted.
On your main view
#Html.Partial("GradeDropDown", Model) //Pass the Model to the partial view
Here is your partial view (named "GradeDropDown"):
#model RegisterModel
#Html.DropDownList("Grade", (SelectList)Model.GradeLevelSelectList)

How to handle Dropdownlist in mvc asp .net

this is my Model1 class
namespace chetan.Models
{
public class Model1
{
public string selectedItem { get; set; }
public IEnumerable<SelectListItem> items { get; set; }
}
}
this is my controller class
public class HomeController : Controller
{
private rikuEntities rk = new rikuEntities();
public ActionResult Index()
{
var model = new Model1
{
items = new[]
{
new SelectListItem { Value = "Theory", Text = "Theory" },
new SelectListItem { Value = "Appliance", Text = "Appliance" },
new SelectListItem { Value = "Lab", Text = "Lab" }
}
}; return View(model);
}
public ActionResult viewToController(Model1 m)
{
string getSelectedName = m.selectedItem;
return Content(getSelectedName);
}
}
this is my view...
#using (Html.BeginForm("viewToController", "Home"))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>emp</legend>
<div class="editor-field">
#Html.DropDownListFor(x => x.selectedItem,
new SelectList(Model.items, "Value", "Text"))
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
i want to add a drop downlist and i want to use selected value in viewToController action of homeController. and there is also one error in View page is "an expression tree may not contain dynamic operation" in (x=>x.selectedItem). Please solve my problem .
I don't understnad what you exactly need. You want to dynamicly add items to the drop down from the database?
I'm big fan of jQuery. You can do everything what you want with HTML using jQuery. So if you are looking how to automaticly add items to the drop down, take look at this: How do I add options to a DropDownList using jQuery?

Resources