Not used to ask questions here but there is a first time for everything.
I'm currently working on a web project in ASP.NET MVC and I am stuck at the very first form...
What I'm trying to do is to display a group of checkboxes and require at least one of them to be checked. Fortunately, I already found several answers on the matter and it works... only on the server side.
But I can't figure out why it is not working on the client side! Although all my other fields custom rules apply well on client side!
I'm still rather new to this tech so maybe I am missing something here...
I've been on it for days now. I hope someone here will be kind enough to help me.
Below is my code around the checkboxes :
View
<div class="col-sm-3">
<label>Documents:<br/>(select at least one)*</label><br/>
#Html.ValidationMessageFor(m => m.AllDocumentsChecked, "", new #class = "text-danger" })
</div>
<div class="col-sm-3">
<div class="form-check">
#Html.CheckBoxFor(m => m.AllDocumentsChecked, new { #class = "form-check-input", #id = "AllDocumentsChecked", #name = "outputFiles" })
<label for="select_all" class="form-check-label">All Documents</label>
</div>
<div class="form-check">
#Html.CheckBoxFor(m => m.Doc1Checked, new { #class = "form-check-input check", #id = "Doc1Checked", #name = "outputFiles" })
<label for="file_doc1" class="form-check-label">Doc1</label>
</div>
<div class="form-check">
#Html.CheckBoxFor(m => m.Doc2Checked, new { #class = "form-check-input check", #id = "Doc2Checked", #name = "outputFiles" })
<label for="file_doc2" class="form-check-label">Doc2</label>
</div>
</div>
<div class="col-sm-3">
<div class="form-check">
#Html.CheckBoxFor(m => m.Doc3Checked, new { #class = "form-check-input check", #id = "Doc3Checked", #name = "outputFiles" })
<label for="file_doc3" class="form-check-label">Doc3</label>
</div>
<div class="form-check">
#Html.CheckBoxFor(m => m.Doc4Checked, new { #class = "form-check-input check", #id = "Doc4Checked", #name = "outputFiles" })
<label for="file_doc4" class="form-check-label">Doc4</label>
</div>
<div class="form-check">
#Html.CheckBoxFor(m => m.Doc5Checked, new { #class = "form-check-input check", #id = "Doc5Checked", #name = "outputFiles" })
<label for="file_doc5" class="form-check-label">Doc5</label>
</div>
</div>
<div class="col-sm-3">
<div class="form-check">
#Html.CheckBoxFor(m => m.Doc6Checked, new { #class = "form-check-input check", #id = "Doc6Checked", #name = "outputFiles" })
<label for="file_doc6" class="form-check-label">Doc6</label>
</div>
</div>
Model
[Display(Name = "All Documents")]
[RequireAtLeastOneOfGroup("Documents")]
public bool AllDocumentsChecked { get; set; }
[Display(Name = "Doc1")]
[RequireAtLeastOneOfGroup("Documents")]
public bool Doc1Checked { get; set; }
[Display(Name = "Doc2")]
[RequireAtLeastOneOfGroup("Documents")]
public bool Doc2Checked { get; set; }
[Display(Name = "Doc3")]
[RequireAtLeastOneOfGroup("Documents")]
public bool Doc3Checked { get; set; }
[Display(Name = "Doc4")]
[RequireAtLeastOneOfGroup("Documents")]
public bool Doc4Checked { get; set; }
[Display(Name = "Doc5")]
[RequireAtLeastOneOfGroup("Documents")]
public bool Doc5Checked { get; set; }
[Display(Name = "Doc6")]
[RequireAtLeastOneOfGroup("Documents")]
public bool Doc6Checked { get; set; }
RequireAtLeastOneFromGroupAttribute class
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class RequireAtLeastOneOfGroupAttribute : ValidationAttribute, IClientValidatable
{
public string GroupName { get; private set; }
public RequireAtLeastOneOfGroupAttribute(string groupName)
{
ErrorMessage = string.Format("You must select at least one value from this group", groupName);
GroupName = groupName;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
foreach (var property in GetGroupProperties(validationContext.ObjectType))
{
var propertyValue = (bool)property.GetValue(validationContext.ObjectInstance, null);
if (propertyValue)
{
// at least one property is true in this group => the model is valid
return ValidationResult.Success;
}
}
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
private IEnumerable<PropertyInfo> GetGroupProperties(Type type)
{
return
from property in type.GetProperties()
where property.PropertyType == typeof(bool)
let attributes = property.GetCustomAttributes(typeof(RequireAtLeastOneOfGroupAttribute), false).OfType<RequireAtLeastOneOfGroupAttribute>()
where attributes.Count() > 0
from attribute in attributes
where attribute.GroupName == GroupName
select property;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var groupProperties = GetGroupProperties(metadata.ContainerType).Select(p => p.Name);
var rule = new ModelClientValidationRule
{
ErrorMessage = this.ErrorMessage
};
rule.ValidationType = string.Format("group", GroupName.ToLower());
rule.ValidationParameters.Add("group", string.Join(",", groupProperties));
//rule.ValidationParameters["propertynames"] = string.Join(",", groupProperties);
yield return rule;
}
}
require_at_least_one_from_group javascript
$.validator.unobtrusive.adapters.add('atleastone', ['propertynames'],
function (options) {
options.rules['group'] = { propertynames: options.params.propertynames.split(',') };
options.messages['group'] = options.message;
});
$.validator.addMethod('group',
function (value, element, params) {
var properties = params.properties.split(',');
var isValid = false;
for (var i = 0; i < properties.length; i++) {
var property = properties[i];
if ($('#' + property).is(':checked')) {
isValid = true;
break;
}
}
return isValid;
}, '');
EDIT
Example of html tagging for a checkbox
Below the html resulting from the helpers above. Still can't see what is wrong.
<input class="form-check-input check" data-val="true" data-val-atleastone="You must select at least one value from this group"
data-val-atleastone-group="AllDocumentsChecked,Doc1Checked,Doc2Checked,
Doc3Checked,Doc4Checked,Doc5Checked,Doc6Checked" data-val-required="The Doc1 field is required."
id="Doc1Checked" name="Doc1Checked" value="true" type="checkbox">
<input name="Doc1Checked" value="false" type="hidden">
Thanks by advance!
Related
I have a scenario where I have a form where I would like administrators to modify their exam / survey details, I am trying to update my database table with the following code.
However the code does not save my changes for the "if" part in my controller, and does not throw any error, and will just redirect me to the next page which is "EditExam2".
I am trying to update the "InformationSheetText" and "InformationConsentForm" fields.
I know the query works as the the "else" part of my code in my controller works when adding a new row into the database.
My View
#model
AppXamApplication.Models
InformationSheetViewModel
#{
ViewBag.Title = "InformationSheet";
}
<!DOCTYPE html>
<html>
<body>
<h2>InformationSheet</h2>
<h3>Survey ID: #ViewBag.CurrentExamID</h3>
#using (Html.BeginForm("InformationSheet", "ExamAdmin", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<h4>Create Information and Consent Sheet.</h4>
<hr />
#Html.ValidationSummary("", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.ImageURL, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
<input type="file" name="ImageFile" />
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.InformationSheetText, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.EditorFor(m => m.InformationSheetText, new { #class = "form-control", #rows = 4, #style = "resize: none;" })
</div>
</div>
<div class="form-group">
<div class="col-md-10">
#Html.CheckBoxFor(m => m.Check_InformationSheet, new { #disabled = "disabled", #checked = true })
#Html.LabelFor(m => m.Check_InformationSheet, new { })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.InformationConsentForm, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.EditorFor(m => m.InformationConsentForm, new { #class = "form-control", #rows = 4, #style = "resize: none;" })
</div>
</div>
<div class="form-group">
<div class="col-md-10">
#Html.CheckBoxFor(m => m.Check_InformationConsentForm1, new { #disabled = "disabled", #checked = true })
#Html.LabelFor(m => m.Check_InformationConsentForm1, new { })
</div>
<div class="col-md-10">
#Html.CheckBoxFor(m => m.Check_InformationConsentForm2, new { #disabled = "disabled", #checked = true })
#Html.LabelFor(m => m.Check_InformationConsentForm2, new { })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Create Exam" />
</div>
</div>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
My model
public class InformationSheetViewModel
{
public string ExamID { get; set; }
[Display(Name = "Choose Image To Display")]
public string ImageURL { get; set; }
[Display(Name = "Enter your Information Sheet")]
public string InformationSheetText { get; set; }
[Display(Name = "Enter your Consent Form")]
public string InformationConsentForm { get; set; }
public HttpPostedFileBase ImageFile { get; set; }
[Display(Name = "I had read and understood the information sheet")]
public bool Check_InformationSheet { get; set; }
[Display(Name = "I consent and agree to the information consent form")]
public bool Check_InformationConsentForm1 { get; set; }
[Display(Name = "I have read, agree and consent to the information and conditions")]
public bool Check_InformationConsentForm2 { get; set; }
}
My Controller
[HttpGet]
public ActionResult InformationSheet(string id)
{
if (ModelState.IsValid)
{
ViewBag.CurrentExamID = id;
using (var ctx = new AppXamApplicationEntities())
{
var query = ctx.InformationConsentAndSheets.Where(x => x.ExamID.Equals(id)).Select(x => new InformationSheetViewModel()
{
ExamID = id,
InformationSheetText = x.InformationSheetText,
InformationConsentForm = x.InformationSheetText
}).FirstOrDefault();
return View(query);
}
}
return View();
}
[HttpPost]
[Authorize(Roles = "ExamAdmin")]
[ValidateAntiForgeryToken]
public ActionResult InformationSheet(string id, InformationSheetViewModel model)
{
using (var ctx = new AppXamApplicationEntities())
{
InformationConsentAndSheet query = ctx.InformationConsentAndSheets.Where(x => x.ExamID.Equals(id)).FirstOrDefault();
if (query != null)
{
//To insert picture into database as well as folder
string fileName = Path.GetFileNameWithoutExtension(model.ImageFile.FileName);
string extension = Path.GetExtension(model.ImageFile.FileName);
fileName = fileName + DateTime.Now.ToString("yymmssfff") + extension;
model.ImageURL = "~/Image/" + fileName;
fileName = Path.Combine(Server.MapPath("~/Image/"), fileName);
model.ImageFile.SaveAs(fileName);
query = new InformationConsentAndSheet()
{
ExamID = id,
ImageURL = model.ImageURL,
InformationSheetText = model.InformationSheetText,
InformationConsentForm = model.InformationConsentForm
};
ctx.SaveChanges();
}
else
{
//To insert picture into database as well as folder
string fileName = Path.GetFileNameWithoutExtension(model.ImageFile.FileName);
string extension = Path.GetExtension(model.ImageFile.FileName);
fileName = fileName + DateTime.Now.ToString("yymmssfff") + extension;
model.ImageURL = "~/Image/" + fileName;
fileName = Path.Combine(Server.MapPath("~/Image/"), fileName);
model.ImageFile.SaveAs(fileName);
query = new InformationConsentAndSheet()
{
ExamID = id,
ImageURL = model.ImageURL,
InformationConsentForm = model.InformationConsentForm,
InformationSheetText = model.InformationSheetText
};
ctx.InformationConsentAndSheets.Add(query);
ctx.SaveChanges();
}
return RedirectToAction("EditExam2");
}
}
I am very perplexed to what is wrong with my code, any form of help will be very appreciated as I am extremely new to MVC in general.
First of all you need to send your Id of survey when you want to edit. It can be done easly by route parameters in your form.
#using (Html.BeginForm("InformationSheet", "ExamAdmin", new { id = ViewBag.CurrentExamID }, FormMethod.Post, new { enctype = "multipart/form-data" }))
Your code for edit existing item is a little bit wrong. You need to, well, modify existing InformationConsentAndSheet and not creating new.
InformationConsentAndSheet query = ctx.InformationConsentAndSheets.Where(x => x.ExamID.Equals(id)).FirstOrDefault();
if (query != null)
{
// work with files
query.ExamID = id;
query.ImageURL = model.ImageURL;
query.InformationSheetText = model.InformationSheetText;
query.InformationConsentForm = model.InformationConsentForm;
ctx.Entry(query).State = EntityState.Modified;
ctx.SaveChanges();
}
Before submiting changes you need to specify that query have been edited and needs to be saved.
Hope its helps.
I have UI where i am showing 3 checkboxes and each refer to different property of model class. i am using jquery unobtrusive validation just by mvc data annotation. i want when user submit form then user has to select one checkbox otherwise client side error message will display and form will not be submitted.
i can do it by jquery but i want to do it by mvc data annotation.
see my model class
public class Customer
{
[Required]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Required]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Display(Name = "Mail to me")]
public bool SelfSend { get; set; }
[Display(Name = "3rd party")]
public bool thirdParty { get; set; }
[Display(Name = "Others")]
public bool Others { get; set; }
}
Controller
[ValidateAntiForgeryToken()]
[HttpPost]
public ActionResult Index(Customer customer)
{
if (customer.Others == false || customer.SelfSend == false || customer.thirdParty == false)
ModelState.AddModelError("Error", "Must select one option");
return View();
}
with the below code i can validate any checkboxes is selected or not from server side code and add model error which show error at client side.
but i want to do validation by client side using normal data annotation.
see my razor code
<div class="row">
<div class="col-md-8">
<section id="testform">
#using (Html.BeginForm("Index", "Customers", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<h4>Enter customer info.</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.FirstName, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.FirstName, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.FirstName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.LastName, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.LastName, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.LastName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<div class="checkbox">
#Html.CheckBoxFor(m => m.SelfSend)
#Html.LabelFor(m => m.SelfSend)
</div>
</div>
<div class="col-md-offset-2 col-md-10">
<div class="checkbox">
#Html.CheckBoxFor(m => m.thirdParty)
#Html.LabelFor(m => m.thirdParty)
</div>
</div>
<div class="col-md-offset-2 col-md-10">
<div class="checkbox">
#Html.CheckBoxFor(m => m.Others)
#Html.LabelFor(m => m.Others)
</div>
</div>
<div class="col-md-offset-2 col-md-10">
#Html.ValidationMessage("Error", "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
}
</section>
</div>
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
You can try to write a customer model validation attribute.
add CheckBoxAuthAttribute in your one of three validation property.
There is a method protected virtual ValidationResult IsValid(object value, ValidationContext validationContext) in you can override inValidationAttribute.
public class CheckBoxAuthAttribute : ValidationAttribute
{
public CheckBoxAuthAttribute(params string[] propertyNames)
{
this.PropertyNames = propertyNames;
}
public string[] PropertyNames { get; private set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var properties = this.PropertyNames.Select(validationContext.ObjectType.GetProperty);
var values = properties
.Select(p => p.GetValue(validationContext.ObjectInstance, null))
.OfType<bool>();
if (values.Contains(true) || (bool)value == true)
{
return null;
}
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
}
public class Customer
{
[Required]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Required]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Display(Name = "Mail to me")]
[CheckBoxAuth("thirdParty", "Others", ErrorMessage = "Must select one option"))]
public bool SelfSend { get; set; }
[Display(Name = "3rd party")]
public bool thirdParty { get; set; }
[Display(Name = "Others")]
public bool Others { get; set; }
}
Since you want one of 3 possible options to be selected, then use radio buttons and bind to a property with a required attribute.
Start by defining a view model
public class CustomerVM
{
[Required]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Required]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
public int? Mailing { get; set; } // see notes below
}
And in the view
#model CustomerVM
....
#using (Html.BeginForm())
{
....
<label>
#Html.RadioButtonFor(m => m.Mailing, 1, new { id = ""})
<span>Mail to me</span>
</label>
<label>
#Html.RadioButtonFor(m => m.Mailing, 2, new { id = ""})
<span>3rd party</span>
</label>
.... // ditto for "Others"
#Html.ValidationMessageFor(m => m.Mailing)
....
}
and the POST method will be
[HttpPost]
public ActionResult Index(CustomerVM model)
{
if(!ModelState.IsValid)
{
return View(model);
}
.... // map to instance of data model, save and redirect
}
Note that if these options are unlikely to change, it would be more appropriate to make the property an enum rather than an int, for example
public enum Mailing
{
[Display(Name = "Mail to me")]
SelfSend = 1,
[Display(Name = "3rd party")]
ThirdParty = 2,
[Display(Name = "Others")]
Others = 3
}
public class CustomerVM
{
....
[Required]
public Mailing? Mailing { get; set; }
}
and the view code would be
#Html.RadioButtonFor(m => m.Mailing, Mailing.SelfSend, new { id = ""})
I want validate a property using data annotation based on the value of another property.
I have a people model--
public class People
{
[DisplayName("Your Name")]
[Required]
public string Name { get; set; }
[Required]
public string Gender { get; set; }
[DisplayName("Your Age")]
[Required]
// Another Attribute here for my custom validation
public int Age { get; set; }
}
And Index :
public ActionResult Index()
{
IList<SelectListItem> types = new List<SelectListItem>();
types.Add(new SelectListItem() { Text = "Male", Value = "M" });
types.Add(new SelectListItem() { Text = "Female", Value = "F" });
ViewBag.ItemTypes = types;
return View();
}
'Gender' is bound to a drop down list and based on the selected Gender I want to validate 'Age' property.
If Male is selected then the Age range must be between 22 and 60.
If Female is selected then the Age range must be between 18 and 58.
There must be a custom validation attribute for this but I am not able to figure it out.
My View:
#using (Html.BeginForm("Index", "Test", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>TimeRecord</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Gender, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.Gender, (IEnumerable<SelectListItem>)ViewBag.ItemTypes, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Name, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Name, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Age, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Age, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Age, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
You can create a custom validation attribute as it works well for validating individual properties (individual property-level validation), check the below code snippet.
namespace SO.Models
{
using Helpers;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
public class Person
{
// code omitted for brevity
[DisplayName("Your Age")]
[Required]
[PersonAge(minMaleAge: 22, maxMaleAge: 60, minFemaleAge: 18, maxFemaleAge: 58)]
public int Age { get; set; }
}
}
namespace SO.Helpers
{
using Models;
using System.ComponentModel.DataAnnotations;
public class PersonAgeAttribute : ValidationAttribute
{
private const string _validationMessage = "Age should be between {0} and {1}";
private int _minMaleAge;
private int _maxMaleAge;
private int _minFemaleAge;
private int _maxFemaleAge;
public PersonAgeAttribute(int minMaleAge, int maxMaleAge, int minFemaleAge, int maxFemaleAge)
{
_minMaleAge = minMaleAge;
_maxMaleAge = maxMaleAge;
_minFemaleAge = minFemaleAge;
_maxFemaleAge = maxFemaleAge;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Person person = (Person)validationContext.ObjectInstance;
if (person.Gender == "M" && (person.Age < _minMaleAge || person.Age > _maxMaleAge))
{
return new ValidationResult(GetErrorMessage(person.Gender));
}
else if (person.Gender == "F" && (person.Age < _minFemaleAge || person.Age > _maxFemaleAge))
{
return new ValidationResult(GetErrorMessage(person.Gender));
}
return ValidationResult.Success;
}
private string GetErrorMessage(string gender)
{
if (gender == "M")
{
return string.Format(_validationMessage, _minMaleAge, _maxMaleAge);
}
else
{
return string.Format(_validationMessage, _minFemaleAge, _maxFemaleAge);
}
}
}
}
Alternatively IValidatableObject interface can be implemented to do class-level validation by implementing its Validate method. For more information check Class-Level Model Validation
Based on your requirement as per my understanding the solution may be like this.
For server side validation create your own custom side validation attribute. To enable client side validation inherit 'IClientValidatable'.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
public class AgeForGenderAttribute : ValidationAttribute, IClientValidatable
{
private string _PropertyToCompare { get; set; }
private string CurrentPropertyValue { get; set; }
public int _MinAgeForMale { get; set; }
public int _MaxAgeForMale { get; set; }
public int _MinAgeForFemale { get; set; }
public int _MaxAgeForFemale { get; set; }
string relevantErrorMessage { get; set; }
public AgeForGenderAttribute(string PropertyToCompare, int MinAgeForMale, int MaxAgeForMale, int MinAgeForFemale, int MaxAgeForFemale, string validationMessage)
{
this._PropertyToCompare = PropertyToCompare;
this._MinAgeForMale = MinAgeForMale;
this._MaxAgeForMale = MaxAgeForMale;
this._MinAgeForFemale = MinAgeForFemale;
this._MaxAgeForFemale = MaxAgeForFemale;
ErrorMessage = validationMessage;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var comparePropertyInfo = validationContext.ObjectType.GetProperty(_PropertyToCompare);
object dependentPropertyValue = comparePropertyInfo.GetValue(validationContext.ObjectInstance, null);
CurrentPropertyValue = dependentPropertyValue.ToString();
var currentValue = (int)value;
if (dependentPropertyValue != null)
{
switch (CurrentPropertyValue)
{
case "M":
if (currentValue < _MinAgeForMale || currentValue > _MaxAgeForMale)
{
ErrorMessage = string.Format(ErrorMessage, _MinAgeForMale, _MaxAgeForMale);
return new ValidationResult(ErrorMessage);
}
break;
case "F":
if (currentValue < _MinAgeForFemale || currentValue > _MaxAgeForFemale)
{
ErrorMessage = string.Format(ErrorMessage, _MinAgeForMale, _MaxAgeForMale);
return new ValidationResult(ErrorMessage);
}
break;
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rules = new ModelClientValidationRule
{
ValidationType = "conditionalrange",
ErrorMessage = ErrorMessage
};
rules.ValidationParameters.Add("condiotionalpropertyname", _PropertyToCompare);
rules.ValidationParameters.Add("minageformale", _MinAgeForMale);
rules.ValidationParameters.Add("maxageformale", _MaxAgeForMale);
rules.ValidationParameters.Add("minageforfemale", _MinAgeForFemale);
rules.ValidationParameters.Add("maxageforfemale", _MaxAgeForFemale);
yield return rules;
}
}
Your js:
$(function () {
$.validator.addMethod("conditionalrange", function (value, element, params) {
var condiotionalpropertyname = $('#' + params.condiotionalpropertyname).val();
var retVal = true;
switch (condiotionalpropertyname) {
case 'M':
if (value < params.minageformale || value > params.maxageformale) {
retVal = false;
}
break;
case "F":
if (value < params.minageforfemale || value > params.maxageforfemale) {
retVal = false;
}
break;
}
return retVal;
});
$.validator.unobtrusive.adapters.add("conditionalrange", ['condiotionalpropertyname', 'minageformale', 'maxageformale', 'minageforfemale', 'maxageforfemale'],
function (options) {
options.rules['conditionalrange'] = {
condiotionalpropertyname: options.params.condiotionalpropertyname,
minageformale: options.params.minageformale,
maxageformale: options.params.maxageformale,
minageforfemale: options.params.minageforfemale,
maxageforfemale: options.params.maxageforfemale
};
options.messages['conditionalrange'] = options.message;
}
);
}(jQuery));
It will work exactly as per your requirement.Make sure that you have included the all relevant js.
Property:
[DisplayName("Your Age")]
[Required]
[AgeForGender("Gender", 18, 45, 22, 60, "Age should be between {0} and {1}")]
public int Age { get; set; }
i have the below jquery unobtrusive code which is not firing.
$.validator.unobtrusive.adapters.add('customvalidation', ['productname'], function (options) {
options.rules['customvalidation'] = { productname: options.params.productname };
});
$.validator.addMethod("customvalidation", function (value, element, param) {
alert(param.productname);
return false;
});
but the above code suppose to show alert i guess when pressing button to submit my form.
here is my full code
Model and view model
public class Product
{
public int ID { set; get; }
public string Name { set; get; }
}
public class Hobby
{
public string Name { get; set; }
public bool IsSelected { get; set; }
}
public class SampleViewModel
{
[Display(Name = "Products")]
public List<Product> Products { set; get; }
//[AtleastOne(ErrorMessage = "Select at least one checkbox.")]
public List<Hobby> Hobbies { get; set; }
[Required(ErrorMessage = "Select any Product")]
public int SelectedProductId { set; get; }
[Required(ErrorMessage = "Select Male or Female")]
public string Gender { get; set; }
public bool? IsAdult { get; set; }
public int? Age { get; set; }
[ConditionalAttribute(SelectedProductID = "SelectedProductId", Products = "Products", Hobbies = "Hobbies",IsAdult="IsAdult",Age="Age")]
public string ErrorMsg { get; set; }
}
Custom server side validation
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class ConditionalAttribute : ValidationAttribute , IClientValidatable
{
public string SelectedProductID = "", Products = "", Hobbies="";
public string IsAdult = "";
public string Age ="";
string _productname = "";
bool _hashobby = false;
bool _isadult = false;
int _age = 0;
public ConditionalAttribute() { }
public ConditionalAttribute(string SelectedProductId, string Products, string Hobbies, string IsAdult, string Age)
{
this.SelectedProductID = SelectedProductId;
this.Products = Products;
this.Hobbies = Hobbies;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
//getting selected product
Product oProduct = null;
ValidationResult validationResult = ValidationResult.Success;
var containerType = validationContext.ObjectInstance.GetType();
var SelectedProductID = containerType.GetProperty(this.SelectedProductID);
Int32 selectedproduct = (Int32)SelectedProductID.GetValue(validationContext.ObjectInstance, null);
var ProductList = containerType.GetProperty(this.Products);
List<Product> oProducts = (List<Product>)ProductList.GetValue(validationContext.ObjectInstance, null);
oProduct = oProducts.Where(e => e.ID == selectedproduct).FirstOrDefault();
_productname = oProduct.Name;
if (_productname != "iPod")
{
var field2 = containerType.GetProperty(this.Hobbies);
List<Hobby> hobbies = (List<Hobby>)field2.GetValue(validationContext.ObjectInstance, null);
foreach (var hobby in hobbies)
{
if (hobby.IsSelected)
{
_hashobby = true;
break;
}
//return ValidationResult.Success;
}
if (!_hashobby)
{
this.ErrorMessage = "Select Any Hobbie's checkbox";
return new ValidationResult(ErrorMessage);
//return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
var PropIsAdult = containerType.GetProperty(this.IsAdult);
if (PropIsAdult.GetValue(validationContext.ObjectInstance, null) != null)
{
_isadult = (bool)PropIsAdult.GetValue(validationContext.ObjectInstance, null);
if (_isadult)
{
var PropAge = containerType.GetProperty(this.Age);
if (PropAge.GetValue(validationContext.ObjectInstance, null) != null)
{
_age = (Int32)PropAge.GetValue(validationContext.ObjectInstance, null);
if (_age != null && _age <= 0)
{
this.ErrorMessage = "Age is compulsory for adult";
return new ValidationResult(ErrorMessage);
}
}
else
{
this.ErrorMessage = "Age is compulsory for adult";
return new ValidationResult(ErrorMessage);
}
}
}
return ValidationResult.Success;
}
// Implement IClientValidatable for client side Validation
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "customvalidation",
};
rule.ValidationParameters.Add("productname", _productname);
yield return rule;
}
}
My view code
#model AuthTest.Models.SampleViewModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm("Index", "TestVal", FormMethod.Post, new { name = "TestVal" }))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>DateValTest</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Products, htmlAttributes: new { #class = "control-label col-md-2", style = "padding-top:0px;" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.SelectedProductId, new SelectList(Model.Products, "ID", "Name"), "-- Select Product--")
#Html.ValidationMessageFor(model => model.SelectedProductId, "", new { #class = "text-danger" })
#for (int i = 0; i < Model.Products.Count(); i++)
{
<div>
#Html.HiddenFor(model => Model.Products[i].Name)
#Html.HiddenFor(model => Model.Products[i].ID)
</div>
}
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<b>Gender</b><br />
<label>
<span>Male</span> #Html.RadioButtonFor(model => model.Gender, "Male", new { style = "width:20px;" })
<span>Female</span>#Html.RadioButtonFor(model => model.Gender, "Female", new { style = "width:20px;" })
</label>
<label>
</label>
#Html.ValidationMessageFor(model => model.Gender, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10 input-validation-error">
<b>Hobbies</b><br />
#for (int x = 0; x < Model.Hobbies.Count(); x++)
{
#Html.CheckBoxFor(p => p.Hobbies[x].IsSelected, new { #class = "hobbycls", id = "Hobbies" }) #:
#Html.LabelFor(p => p.Hobbies[x].IsSelected, Model.Hobbies[x].Name) #:
#Html.HiddenFor(p => p.Hobbies[x].Name)
}
<span id="Hobbies-error" class="field-validation-error">
<span>Select any hobbies.</span>
</span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<b>Is Adult</b><br />
<label>
<span>Yes</span> #Html.RadioButtonFor(model => model.IsAdult, "true", new { style = "width:20px;" })
<span>No</span>#Html.RadioButtonFor(model => model.IsAdult, "false", new { style = "width:20px;" })
</label>
</div>
<div class="col-md-offset-2 col-md-10">
<label>
Enter Age #Html.TextBoxFor(model => model.Age)
</label>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<label>
#Html.HiddenFor(model => model.ErrorMsg)
#Html.ValidationMessageFor(model => model.ErrorMsg, "", new { #class = "text-danger" })
</label>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Submit" class="btn btn-default" />
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
#if (ViewBag.IsPostBack != null && ViewBag.IsPostBack)
{
<text>
<b>Your Selected Product ID :</b> #ViewBag.ProductID<br />
<b>Your Selected Product Name :</b> #ViewBag.ProductName<br />
<b>Gender :</b> #ViewBag.Gender<br />
<b>Hobbies :</b> #ViewBag.Hobbies <br />
<b>Is Adult :</b> #ViewBag.IsAdult <br />
<b>Age :</b> #ViewBag.Age <br />
</text>
}
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
<script type="text/javascript">
//$.validator.unobtrusive.adapters.add('customvalidation', ['productname', 'hashobby', 'isadult', 'age'], function (options) {
$.validator.unobtrusive.adapters.add('customvalidation', ['productname'], function (options) {
options.rules['customvalidation'] = { productname: options.params.productname };
});
$.validator.addMethod("customvalidation", function (value, element, param) {
alert(param.productname);
return false;
});
</script>
}
This question already has answers here:
Model Binding to a List MVC 4
(3 answers)
Closed 9 years ago.
UPDATE: The solution was to use an EditorTemplate. See solution below:
I want to pass a model to/from a controller which let's me set name, and set the value on an undetermined roles (as checkboxes). When I examine the postback, I get a value for Name in model, but Roles is null. How can I tell which checkboxes were checked?
Model:
public class MyModel
{
public string Name { get; set; }
public IEnumerable<RoleItem> Roles { get; set; }
}
public class RoleItem
{
public String Name { get; set; }
public String Id { get; set; }
public bool Selected { get; set; }
public RoleItem(String id, String name, bool selected = false)
{
this.Name = name;
this.Id = id;
this.Selected = selected;
}
}
Razor:
#model WebApplication1.Controllers.MyModel
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary()
#Html.TextBoxFor(m=>m.Name)
foreach (var m in Model.Roles)
{
<div>
#Html.Label(m.Id, m.Name)
#Html.CheckBox(m.Id, m.Selected, new { id = #m.Id })
</div>
}
<input type="submit"/>
}
GOAL: To allow any Administrator to add new users to the Asp identity tables and assign them roles that are defined in a list using checkboxes.
Model:
public class RegisterViewModel
{
[Display(Name = "Name")]
public string FullName { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
public List<RoleItem> Roles { get; set; }
}
public class RoleItem
{
public String Name { get; set; }
public String Id { get; set; }
public bool IsMember { get; set; }
}
Controller (GET): This reads all of the roles in the database and transforms them to a list of RoleItems. This will prepend the character "r" onto the id field as some browsers have a problem with an id starting with a number. We want to make sure the "Users" group is checked by default, so we find this value in the list and set the IsMember property to true. We check the request to see if the page was redirected here from a successful POST (see below)
// GET: /Account/AddUser
[Authorize(Roles = "Administrators")]
public ActionResult AddUser()
{
var rolesDb = new ApplicationDbContext(); //Users, Administrators, Developers, etc
ViewBag.AddSuccess = Request["added"]=="1" ? true : false;
var roleItems = rolesDb.Roles.Select(r => new RoleItem() { Id = "r" + r.Id, Name = r.Name, IsMember = false }).ToList(); //add an r to get around a browser bug
var users = roleItems.FirstOrDefault(r => r.Name == "Users"); //Get the row that has the Users value and set IsMember=true
if (users != null)
users.IsMember = true;
var m = new RegisterViewModel() {Roles = roleItems};
return View(m);
}
View: Pretty standard stuff. Note #Html.EditorFor(x => x.Roles) at the bottom, which uses an editor template (follows)
#model cherry.Models.RegisterViewModel
#{
ViewBag.Title = "AddUser";
}
<h2>#ViewBag.Title.</h2>
#if (Convert.ToBoolean(ViewBag.AddSuccess))
{
<text>User added!</text>
}
#using (Html.BeginForm("AddUser", "Account", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<h4>Create a new account.</h4>
<hr />
#Html.ValidationSummary()
<div class="form-group">
#Html.LabelFor(m => m.EmailAddress, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.EmailAddress, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.FullName, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.FullName, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Password, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.PasswordFor(m => m.Password, new { value = Model.Password, #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.ConfirmPassword, new {#class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.PasswordFor(m => m.ConfirmPassword, new {value = Model.ConfirmPassword, #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Register" />
</div>
</div>
#Html.EditorFor(x => x.Roles)
}
EditorTemplate:
You MUST give the template the same name as the object you are creating the template for. You must also put this object in a folder called EditorTemplates below the view you are designing this for, or you can put the folder inside the shared folder.
Views\Account\EditorTemplates\RoleItem.cshtml
#model cherry.Models.RoleItem
<div>
#Html.CheckBoxFor(x => x.IsMember)
#Html.LabelFor(x => x.IsMember, Model.Name)
#Html.HiddenFor(x => x.Name)
</div>