I am using CompareAttribute in MVC3 and its working fine. But I want to use case insensitive classCode. Is there any way to get that working
Thanks in Advance
[CompareAttribute("ClassCode", ErrorMessageResourceName = "ClassCode_DontMatch", ErrorMessageResourceType = typeof(Resources.Class))]
public string ConfirmClassCode {get; set; }
A little late to the party, but here is an implementation I just wrote that also includes support for client-side validation using the IClientValidatable interface. You could use Darin Dimitrov's answer as a starting point as well, I just already had some of this.
Server-Side Validation:
//Create your custom validation attribute
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class CompareStrings : ValidationAttribute, IClientValidatable
{
private const string _defaultErrorMessage = "{0} must match {1}";
public string OtherPropertyName { get; set; }
public bool IgnoreCase { get; set; }
public CompareStrings(string otherPropertyName)
: base(_defaultErrorMessage)
{
if (String.IsNullOrWhiteSpace(otherPropertyName)) throw new ArgumentNullException("OtherPropertyName must be set.");
OtherPropertyName = otherPropertyName;
}
public override string FormatErrorMessage(string name)
{
return String.Format(ErrorMessage, name, OtherPropertyName);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
string otherPropVal = validationContext.ObjectInstance.GetType().GetProperty(OtherPropertyName).GetValue(validationContext.ObjectInstance, null) as string;
//Convert nulls to empty strings and trim spaces off the result
string valString = (value as string ?? String.Empty).Trim();
string otherPropValString = (otherPropVal ?? String.Empty).Trim();
bool isMatch = String.Compare(valString, otherPropValString, IgnoreCase) == 0;
if (isMatch)
return ValidationResult.Success;
else
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
Client-Side Validation
//...continuation of CompareStrings class
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
return new[] { new ModelClientValidationCompareStringsRule(FormatErrorMessage(metadata.GetDisplayName()), OtherPropertyName, IgnoreCase) };
}
}
Define ModelClientValidationCompareStringsRule which is used (above) to pass the attribute's properties to the client-side script.
public class ModelClientValidationCompareStringsRule : ModelClientValidationRule
{
public ModelClientValidationCompareStringsRule(string errorMessage, string otherProperty, bool ignoreCase)
{
ErrorMessage = errorMessage; //The error message to display when invalid. Note we used FormatErrorMessage above to ensure this matches the server-side result.
ValidationType = "comparestrings"; //Choose a unique name for your validator on the client side. This doesn't map to anything on the server side.
ValidationParameters.Add("otherprop", otherProperty); //Pass the name of the property to compare to
ValidationParameters.Add("ignorecase", ignoreCase.ToString().ToLower()); //And whether to ignore casing
}
}
Javascript:
(function ($) {
//Add an adapter for our validator. This maps the data from the ModelClientValidationCompareStringsRule
//we defined above, to the validation plugin. Make sure to use the same name as we chose for the ValidationType property ("comparestrings")
$.validator.unobtrusive.adapters.add("comparestrings", ["otherprop", "ignorecase"],
function (options) {
options.rules["comparestrings"] = {
otherPropName: options.params.otherprop,
ignoreCase: options.params.ignorecase == "true"
};
options.messages["comparestrings"] = options.message;
});
//Add the method, again using the "comparestrings" name, that actually performs the client-side validation to the page's validator
$.validator.addMethod("comparestrings", function (value, element, params) {
//element is the element we are validating and value is its value
//Get the MVC-generated prefix of element
//(E.G. "MyViewModel_" from id="MyViewModel_CompareEmail"
var modelPrefix = getModelIDPrefix($(element).prop("id"));
//otherPropName is just the name of the property but we need to find
//its associated element to get its value. So concatenate element's
//modelPrefix with the other property name to get the full MVC-generated ID. If your elements use your own, overridden IDs, you'd have to make some modifications to allow this code to find them (e.g. finding by the name attribute)
var $otherPropElem = $("#" + modelPrefix + params.otherPropName);
var otherPropValue = getElemValue($otherPropElem);
//Note: Logic for comparing strings needs to match what it does on the server side
//Trim values
value = $.trim(value);
otherPropValue = $.trim(otherPropValue);
//If ignoring case, lower both values
if (params.ignoreCase) {
value = value.toLowerCase();
otherPropValue = otherPropValue.toLowerCase();
}
//compare the values
var isMatch = value == otherPropValue;
return isMatch;
});
function getElemValue(element){
var value;
var $elem = $(element);
//Probably wouldn't use checkboxes or radio buttons with
//comparestrings, but this method can be used for other validators too
if($elem.is(":checkbox") || $elem.is(":radio") == "radio")
value = $elem.prop("checked") ? "true" : "false";
else
value = $elem.val();
return value;
}
//Gets the MVC-generated prefix for a field by returning the given string
//up to and including the last underscore character
function getModelIDPrefix(fieldID) {
return fieldID.substr(0, fieldID.lastIndexOf("_") + 1);
}
}(jQuery));
Usage is standard:
public string EmailAddress { get; set; }
[CompareStrings("EmailAddress", ErrorMessage = "The email addresses do not match", IgnoreCase=true)]
public string EmailAddressConfirm { get; set; }
This plugs into the Unobtrusive Validation framework, so you need to already have that installed and working. At the time of writing I am on Microsoft.jQuery.Unobtrusive.Validation v 3.0.0.
You could write a custom attribute that will perform the case insensitive comparison:
public class CaseInsensitiveCompareAttribute : System.Web.Mvc.CompareAttribute
{
public CaseInsensitiveCompareAttribute(string otherProperty)
: base(otherProperty)
{ }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var property = validationContext.ObjectType.GetProperty(this.OtherProperty);
if (property == null)
{
return new ValidationResult(string.Format(CultureInfo.CurrentCulture, "Unknown property {0}", this.OtherProperty));
}
var otherValue = property.GetValue(validationContext.ObjectInstance, null) as string;
if (string.Equals(value as string, otherValue, StringComparison.OrdinalIgnoreCase))
{
return null;
}
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
}
and then decorate your view model property with it:
[CaseInsensitiveCompare("ClassCode", ErrorMessageResourceName = "ClassCode_DontMatch", ErrorMessageResourceType = typeof(Resources.Class))]
public string ConfirmClassCode { get; set; }
For client side validation, put this code below in document ready-
jQuery.validator.addMethod("ignoredCaseEqualTo", function (value, element, param) {
return this.optional(element) || value.toLowerCase() === $(param).val().toLowerCase();
}, "__Your Validation message___");
$("#EmailAddress").rules("add", {
ignoredCaseEqualTo: "#EmailAddressConfirm"
});
this code adds new validation rule of case insensitive comparison.
Might not be an optimum way, but this will do your job for client side validation.
Related
I am using MVC5, and I have a ViewModel for my View, which contains a simple form with the following fields:
MinFirstNameLength
FirstName
MinLastNameLength
LastName
Now, I wish to apply a validation rule on FirstName, based on the value of MinFirstNameLength, and similarly for LastName using MinLastNameLength. I want to do this on the client-side too.
So, I used MVC's unobtrusive client side validation feature. I made a custom validation attribute, implementing the IClientValidatable interface. The GetClientValidationRules method looks like this:
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
string ErrorMessage = ErrorMessageString;
ModelClientValidationRule NameMinLengthRule = new ModelClientValidationRule();
NameMinLengthRule.ErrorMessage = ErrorMessage;
NameMinLengthRule.ValidationType = "nameminlength";
NameMinLengthRule.ValidationParameters.Add("minlengthpropname", MinLengthPropName);
yield return NameMinLengthRule;
}
This validation attribute is applied on the FirstName and LastName properties like this:
[NameMinLength("FirstNameMinLength",ErrorMessage = "First Name must be at least {0} characters"]
public string FirstName { get; set; }
[NameMinLength("LastNameMinLength",ErrorMessage = "Last Name must be at least {0} characters"]
public string LastName { get; set; }
Also, I have the client side validation functions in a .js file elsewhere, which looks like this:
$.validator.addMethod("nameminlength",
function (
value,
element,
params
) {
return value.length >= parseInt($(params).val());
});
$.validator.unobtrusive.adapters.add("nameminlength", ["minlengthpropname"], function (options) {
var paramField = "#" + options.params.minlengthpropname;
options.rules["nameminlength"] = paramField;
var errormessage = options.message;
options.messages["nameminlength"] = errormessage;
});
My question is, how do I assign the value in the textbox for MinFirstNameLength to the placeholder {0} in my error message for FirstName, so that the error message reads First Name must be at least [value] characters?
I tried adding modifying my $.validator.unobtrusive.adapters.add method as follows:
$.validator.unobtrusive.adapters.add("nameminlength", ["minlengthpropname"], function (options) {
var paramField = "#" + options.params.minlengthpropname;
options.rules["nameminlength"] = paramField;
var errormessage = options.message;
options.messages["nameminlength"] = errormessage;
$(paramField).blur(function () {
// change error message in the 'data-val-nameminlength' attribute of the HTML element on which validation is applied
var newErrorMessage = options.messages["nameminlength"].replace("{0}", $(paramField).val());
$(options.element).attr("data-val-nameminlength", newErrorMessage);
// change error message inside the error message span generated for displaying this error
$(options.element).siblings(".field-validation-valid").html(newErrorMessage);
});
});
But both these tricks didn't work. The HTML markup did change to modify the error appropriately, but this change wasn't reflected in the page.
Currently, the error message I see is:
First Name must be at least #FirstNameMinLength characters
The placeholder is replaced automatically by the id of the control which is used as a parameter in the validation rule.
Where does this error message come from? How do I modify it at runtime?
I think you should just format your message on server side in the IsValid method of your NameMinLength validation attribute. Here's a small example on how to do it.
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var conditionalPropertyInfo = validationContext.ObjectType.GetProperty(this.MinLengthPropName);
var conditionalPropertyValue = conditionalPropertyInfo.GetValue(validationContext.ObjectInstance, null);
this.ErrorMessage = string.Format(this.ErrorMessage, conditionalPropertyValue.ToString())
// YOUR OTHER CODE HERE
}
This should replace the placeholder with the correct value from MinLengthPropName property. In this case the error message will be formatted before moving it to client side. So no additional logic required for this.
EDIT:
Hm, just thought that you might want to validate based on user input which is really wierd. Actually the fact that you might have different min length restrictions for the same field doesn't make sense to me but as long as it' not based on user input it is much more secure.
UPDATED WITH CLIENT SIDE SOLUTION:
I did a simple test with a similar attribute and it works for me
Edit Model:
[MinCustomLength("MinLenthDestURI", "Dest URI must be at least {0} characters")]
public string DestinationURI { get; set; }
public int MinLenthDestURI { get; set; }
Attribute code:
public class MinCustomLengthAttribute : ValidationAttribute, IClientValidatable
{
private String PropertyName { get; set; }
public MinCustomLengthAttribute(String propertyName, String errormessage)
{
this.PropertyName = propertyName;
this.ErrorMessage = errormessage;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
// Just for test server side validation always returns Success
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var modelClientValidationRule = new ModelClientValidationRule
{
ValidationType = "mincustomlength",
ErrorMessage = this.ErrorMessage
};
modelClientValidationRule.ValidationParameters.Add("prop", PropertyName);
yield return modelClientValidationRule;
}
}
Client side code:
$.validator.addMethod("mincustomlength", function (value, element, params) {
var conditionalId = $.validator.getId(element, params.prop);
var minLength = parseInt($("#" + conditionalId).val());
if (value.length < minLength) {
var message = $(element).attr('data-val-mincustomlength');
$.validator.messages.mincustomlength = $.format(message, minLength);
return false;
}
return true;
});
$.validator.unobtrusive.adapters.add('mincustomlength', ['prop'], function (options) {
options.rules['mincustomlength'] = options.params;
if (options.message != null) {
$.validator.messages.mincustomlength = options.message;
}
});
This will replace {0} with a value from MinLenthDestURI textbox when validation is done.
Hope it helps!
I want to create a custom validation attribute that calls other validation attributes.
For example I want to create an attribute called PasswordValidationAttribute. I want it to decorate the property it is defined on with RequiredAttribute, RegularExpressionAttribute and StringLengthAttribute with various parameters defined (such as the regular expression for a password and a maximum and minimum string length).
I'm struggling on where to begin, ascertain how much work is involved and determine if it is at all possible. Once this attribute is applied to a property I would like it to process the ValidationMessageFor HtmlHelper correctly and do a serverside call. I'm hoping I don't need to redefine them (otherwise it will be too much work).
For .net 4 it could look like:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class MyValidationAttribute : ValidationAttribute
{
private readonly bool isRequired;
public string Regex { get; set; }
public int StringLength { get; set; }
public MyValidationAttribute(bool isRequired)
{
this.isRequired = isRequired;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var composedAttributes = ConstructAttributes().ToArray();
if (composedAttributes.Length == 0) return ValidationResult.Success;
var errorMsgBuilder = new StringBuilder();
foreach (var attribute in composedAttributes)
{
var valRes = attribute.GetValidationResult(value, validationContext);
if (valRes != null && !string.IsNullOrWhiteSpace(valRes.ErrorMessage))
errorMsgBuilder.AppendLine(valRes.ErrorMessage);
}
var msg = errorMsgBuilder.ToString();
if (string.IsNullOrWhiteSpace(msg))
return ValidationResult.Success;
return new ValidationResult(msg);
}
private IEnumerable<ValidationAttribute> ConstructAttributes()
{
if (isRequired)
yield return new RequiredAttribute();
if (Regex != null)
yield return new RegularExpressionAttribute(Regex);
if (StringLength > 0)
yield return new StringLengthAttribute(StringLength);
}
}
Usage:
[MyValidationAttribute(true, Regex = "[a-z]*", StringLength = 3)]
public string Name { get; set; }
In .net 3.5 there is a limitation, that you cannot dynamically construct the message value from underlying attributes (at least I was not able get to through it). You can set only one message per whole attribute.
Everything changed is inside method IsValid.
public override bool IsValid(object value)
{
var composedAttributes = ConstructAttributes().ToArray();
if (composedAttributes.Length == 0) return true;
return composedAttributes.All(a => a.IsValid(value));
}
Note to ErrorMessage:
Return value of IsValid method of ValidationAttribute in .net 3.5 is not ValidationResult but bool. When I tried to set the ErrorMessage, I got the error that value can be set only once.
For a project at work I'm trying to create a process that lets a user dynamically create a form that other users could then fill out the values for. I'm having trouble figuring out how to go about getting this to play nice with the built in model binding and validation with ASP MVC 3, though.
Our view model is set up something like this. Please note that I've over simplified the example code:
public class Form
{
public FieldValue[] FieldValues { get; set; }
}
public class Field
{
public bool IsRequired { get; set; }
}
public class FieldValue
{
public Field Field { get; set; }
public string Value { get; set; }
}
And our view looks something like:
#model Form
#using (Html.BeginForm("Create", "Form", FormMethod.Post))
{
#for(var i = 0; i < Model.Fields.Count(); i++)
{
#Html.TextBoxFor(_ => #Model.Fields[i].Value)
}
<input type="submit" value="Save" name="Submit" />
}
I was hoping that we'd be able to create a custom ModelValidatorProvider or ModelMetadataProvider class that would be able to analyze a FieldValue instance, determine if its Field.IsRequired property is true, and then add a RequiredFieldValidator to that specific instance's validators. I'm having no luck with this, though. It seems that with ModelValidatorProvider(and ModelMetadataProvider) you can't access the parent container's value(ie: GetValidators() will be called for FieldValue.Value, but there's no way from there to get the FieldValue object).
Things I've tried:
In the ModelValidatorProvider, I've tried using
ControllerContext.Controller.ViewData.Model, but that doesn't work if
you have nested types. If I'm trying to figure out the validators
Form.FieldValues[3], I have no idea which FieldValue to use.
I tried using a custom ModelMetadata that tries to use the internal
modelAccessor's Target property to get the parent, but this also
doesn't work if you have a nested type. Somewhere internal to MVC, an
expression like the one in my example will result in the Target being
the Model's type(Form), not FieldValue. So I get the same problem as
above where I have no idea what instance of FieldValue to compare
against.
A class-level validation attribute that I could put on the FieldValue
class itself, but this only gets called during server validation. I
need client-side validation, too.
Is what I'm trying to do even possible in MVC? Or is there something I'm missing entirely?
One possibility is to use a custom validation attribute.
But before getting into the implementation I would like to point out a potential flaw in your scenario. The IsRequired property is part of your model. This means that when the form is submitted its value must be known so that we conditionally apply the required rule to the corresponding property. But for this value to be known when the form is submitted this means that it must be either part of the form (as a hidden or standard input field) or must be retrieved from somewhere (datastore, ...). The problem with the first approach is obvious => hidden field means that the user can set whatever value he likes, so it's no longer a real validation because it is the user that decides which field is required.
This warning being said, let's suppose that you trust your users and decide to take the hidden field approach for storing the IsRequired value. Let's see how a sample implementation:
Model:
public class Form
{
public FieldValue[] Fields { get; set; }
}
public class FieldValue
{
public Field Field { get; set; }
[ConditionalRequired("Field")]
public string Value { get; set; }
}
public class Field
{
public bool IsRequired { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new Form
{
Fields = new[]
{
new FieldValue { Field = new Field { IsRequired = true }, Value = "" },
new FieldValue { Field = new Field { IsRequired = true }, Value = "" },
new FieldValue { Field = new Field { IsRequired = false }, Value = "value 3" },
}
};
return View(model);
}
[HttpPost]
public ActionResult Index(Form model)
{
return View(model);
}
}
View:
#model Form
#using (Html.BeginForm())
{
#Html.EditorFor(x => x.Fields)
<input type="submit" value="Save" name="Submit" />
}
ConditionalRequiredAttribute:
public class ConditionalRequiredAttribute : ValidationAttribute, IClientValidatable
{
private RequiredAttribute _innerAttribute = new RequiredAttribute();
private readonly string _fieldProperty;
public ConditionalRequiredAttribute(string fieldProperty)
{
_fieldProperty = fieldProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var containerType = validationContext.ObjectInstance.GetType();
var field = containerType.GetProperty(_fieldProperty);
if (field == null)
{
return new ValidationResult(string.Format("Unknown property {0}", _fieldProperty));
}
var fieldValue = (Field)field.GetValue(validationContext.ObjectInstance, null);
if (fieldValue == null)
{
return new ValidationResult(string.Format("The property {0} was null", _fieldProperty));
}
if (fieldValue.IsRequired && !_innerAttribute.IsValid(value))
{
return new ValidationResult(this.ErrorMessage, new[] { validationContext.MemberName });
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "conditionalrequired",
};
rule.ValidationParameters.Add("iserquiredproperty", _fieldProperty + ".IsRequired");
yield return rule;
}
}
Associated unobtrusive adapter:
(function ($) {
$.validator.unobtrusive.adapters.add('conditionalrequired', ['iserquiredproperty'], function (options) {
options.rules['conditionalrequired'] = options.params;
if (options.message) {
options.messages['conditionalrequired'] = options.message;
}
});
$.validator.addMethod('conditionalrequired', function (value, element, parameters) {
var name = $(element).attr('name'),
prefix = name.substr(0, name.lastIndexOf('.') + 1),
isRequiredFiledName = prefix + parameters.iserquiredproperty,
requiredElement = $(':hidden[name="' + isRequiredFiledName + '"]'),
isRequired = requiredElement.val().toLowerCase() === 'true';
if (!isRequired) {
return true;
}
return value && value !== '';
});
})(jQuery);
I am trying to write a custom validation attribute that will conditionally require fields based on boolean properties of the model.
I have my attribute implementing IClientValidatable. I have the name of the property to check, but I dont know how to get the client id of the target property.
public IEnumerable<ModelClientValidationRule>
GetClientValidationRules(ModelMetadata metadata,
ControllerContext context)
{
var clientTarget = ?????;
var rule = new ModelClientValidationRule()
{
ErrorMessage =
FormatErrorMessage(metadata.DisplayName ?? metadata.PropertyName),
ValidationType = "requiredif"
};
rule.ValidationParameters["target"] = clientTarget;
yield return rule;
}
The javascript:
$.validator.addMethod("requiredif", function (value, element, target)
{
//check on value of target
});
$.validator.unobtrusive.adapters.addSingleVal("requiredif", "target");
How can I get the client id of the target property so that the client side javascript can check on the value?
I took Nathan's excellent answer, added some comments, and wrapped it in an extension method named GetHtmlId, so that now I can use code like this to get the HTML ID of any other element on the same page:
public virtual IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule {
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "requiredif",
};
// Find the value on the control we depend on...
string depProp = this.GetHtmlId(metadata, context, this.DependentPropertyName);
rule.ValidationParameters.Add("dependentproperty", depProp);
yield return rule;
}
And here's the extension method:
using System;
using System.Collections.Generic;
using System.Web.Mvc;
namespace sbs.Lib.Web.ValidationAttributes
{
public static class IClientValidatableExtensions
{
/// <summary> Returns the HTML ID of the specified view model property. </summary>
/// <remarks> Based on: http://stackoverflow.com/a/21018963/1637105 </remarks>
/// <param name="metadata"> The model metadata. </param>
/// <param name="viewContext"> The view context. </param>
/// <param name="propertyName"> The name of the view model property whose HTML ID is to be returned. </param>
public static string GetHtmlId(this IClientValidatable me,
ModelMetadata metadata, ControllerContext context,
string propertyName)
{
var viewContext = context as ViewContext;
if (viewContext == null || viewContext.ViewData.TemplateInfo.HtmlFieldPrefix == string.Empty) {
return propertyName;
} else {
// This is tricky. The "Field ID" returned by GetFullHtmlFieldId is the HTML ID
// attribute created by the MVC view engine for the property whose validator is
// being set up by the caller of this routine. This code removes the property
// name from the Field ID, then inserts the specified property name.
// Of course, this only works for elements on the same page as the caller of
// this routine!
string fieldId = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId("");
fieldId = fieldId.Remove(fieldId.LastIndexOf("_"));
return fieldId + "_" + propertyName;
}
}
}
}
This has only been tested with MVC5, but I suspect it hasn't changed from MVC3.
This is kind of ugly, but it seems to work. There are two assumptions:
The MVC Framework actually always passes in a ViewContext for the ControllerContext argument of GetClientValidationRules(....). In all my testing this has been the case, but I can't guarantee this 100%.
The other property is on the same model class level (not a sub property of a complex type for instance)
If both of these assumptions hold true, then the following seems to work:
var viewContext = (ViewContext)context;
if(ViewData.TemplateInfo.HtmlFieldPrefix != string.Empty)
{
string fieldId = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId("");
fieldId = fieldId.Remove(fieldId.LastIndexOf("_"));
fieldId = fieldId + "_" + BooleanPropertyName
}
else
{
string fieldId = BooleanPropertyName
}
This works for me, might need a bit of tweaking, but you can get the idea:
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "requiredif",
};
string depProp = BuildDependentPropertyId(metadata, context as ViewContext);
// find the value on the control we depend on;
// if it's a bool, format it javascript style
// (the default is True or False!)
string targetValue = (this.TargetValue ?? "").ToString();
if (this.TargetValue != null && this.TargetValue.GetType() == typeof(bool))
targetValue = targetValue.ToLower();
rule.ValidationParameters.Add("dependentproperty", depProp);
rule.ValidationParameters.Add("targetvalue", targetValue);
yield return rule;
}
private string BuildDependentPropertyId(ModelMetadata metadata, ViewContext viewContext)
{
return QualifyFieldId(metadata, this.DependentProperty, viewContext);
}
Attribute the property like:
[RequiredIf("SelectedPeriod", "DateRange", ErrorMessageResourceName = "FromDateRequired", ErrorMessageResourceType = typeof(Common))]
public DateTime? StartDate { get; set; }
//dependent property
public string SelectedPeriod { get; set; }
And this is how to get the field id:
protected string QualifyFieldId(ModelMetadata metadata, string fieldId, ViewContext viewContext)
{
// build the ID of the property
string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(fieldId);
var thisField = metadata.PropertyName + "_";
if (depProp.StartsWith(thisField))
// strip it off again
depProp = depProp.Substring(thisField.Length);
else if (null != metadata.ContainerType && !string.IsNullOrEmpty(metadata.ContainerType.Name))
{
depProp = metadata.ContainerType.Name + "_" + fieldId;
}
return depProp;
}
Have a look at this article. it discusses DataAnnotation validation on server side and it also demonstrates how to hook these attributes on client side by implementing IClientVaildatable and writing some jquery on client side.
Simple problem here (I think).
I have a form with a checkbox at the bottom where the user must agree to the terms and conditions. If the user doesn't check the box, I'd like an error message to be displayed in my validation summary along with the other form errors.
I added this to my view model:
[Required]
[Range(1, 1, ErrorMessage = "You must agree to the Terms and Conditions")]
public bool AgreeTerms { get; set; }
But that didn't work.
Is there an easy way to force a value to be true with data annotations?
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using System.Web.Mvc;
namespace Checked.Entitites
{
public class BooleanRequiredAttribute : ValidationAttribute, IClientValidatable
{
public override bool IsValid(object value)
{
return value != null && (bool)value == true;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
//return new ModelClientValidationRule[] { new ModelClientValidationRule() { ValidationType = "booleanrequired", ErrorMessage = this.ErrorMessage } };
yield return new ModelClientValidationRule()
{
ValidationType = "booleanrequired",
ErrorMessage = this.ErrorMessageString
};
}
}
}
There's actually a way to make it work with DataAnnotations. The following way:
[Required]
[Range(typeof(bool), "true", "true")]
public bool AcceptTerms { get; set; }
You can write a custom validation attribute which has already been mentioned. You will need to write custom javascript to enable the unobtrusive validation functionality to pick it up if you are doing client side validation. e.g. if you are using jQuery:
// extend jquery unobtrusive validation
(function ($) {
// add the validator for the boolean attribute
$.validator.addMethod(
"booleanrequired",
function (value, element, params) {
// value: the value entered into the input
// element: the element being validated
// params: the parameters specified in the unobtrusive adapter
// do your validation here an return true or false
});
// you then need to hook the custom validation attribute into the MS unobtrusive validators
$.validator.unobtrusive.adapters.add(
"booleanrequired", // adapter name
["booleanrequired"], // the names for the properties on the object that will be passed to the validator method
function(options) {
// set the properties for the validator method
options.rules["booleanRequired"] = options.params;
// set the message to output if validation fails
options.messages["booleanRequired] = options.message;
});
} (jQuery));
Another way (which is a bit of a hack and I don't like it) is to have a property on your model that is always set to true, then use the CompareAttribute to compare the value of your *AgreeTerms * attribute. Simple yes but I don't like it :)
ASP.Net Core 3.1
I know this is a very old question but for asp.net core the IClientValidatable does not exist and i wanted a solution that works with jQuery Unobtrusive Validation as well as on server validation so with the help of this SO question Link i made a small modification that works with boolean field like checkboxes.
Attribute Code
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class MustBeTrueAttribute : ValidationAttribute, IClientModelValidator
{
public void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val", "true");
var errorMsg = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
MergeAttribute(context.Attributes, "data-val-mustbetrue", errorMsg);
}
public override bool IsValid(object value)
{
return value != null && (bool)value == true;
}
private bool MergeAttribute(
IDictionary<string, string> attributes,
string key,
string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
}
Model
[Display(Name = "Privacy policy")]
[MustBeTrue(ErrorMessage = "Please accept our privacy policy!")]
public bool PrivacyPolicy { get; set; }
Client Side Code
$.validator.addMethod("mustbetrue",
function (value, element, parameters) {
return element.checked;
});
$.validator.unobtrusive.adapters.add("mustbetrue", [], function (options) {
options.rules.mustbetrue = {};
options.messages["mustbetrue"] = options.message;
});