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.
Related
with reference to this thread in stackoverflow
[Range(typeof(DateTime), "1/2/2004", "3/4/2004",
ErrorMessage = "Value for {0} must be between {1} and {2}")]
public DateTime EventOccurDate{get;set;}
I tried to add some dynamic dates into my model's date range validator as:
private string currdate=DateTime.Now.ToString();
private string futuredate=DateTime.Now.AddMonths(6).ToString();
[Range(typeof(DateTime),currdate,futuredate,
ErrorMessage = "Value for {0} must be between {1} and {2}")]
public DateTime EventOccurDate{get;set;}
But Error Occurs.Is there no way to set dynamic date range validation in MVC?
You cannot use dynamic values in attributes because they are metadata that is generated at compile-time. One possibility to achieve this is to write a custom validation attribute or use Fluent Validation which allows for expressing more complex validation scenarios using a fluent expressions.
Here's an example of how such custom validation attribute might look like:
public class MyValidationAttribute: ValidationAttribute
{
public MyValidationAttribute(int monthsSpan)
{
this.MonthsSpan = monthsSpan;
}
public int MonthsSpan { get; private set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
var date = (DateTime)value;
var now = DateTime.Now;
var futureDate = now.AddMonths(this.MonthsSpan);
if (now <= date && date < futureDate)
{
return null;
}
}
return new ValidationResult(this.FormatErrorMessage(this.ErrorMessage));
}
}
and then decorate your model with it:
[MyValidation(6)]
public DateTime EventOccurDate { get; set; }
I am currently developing a Framework to generate dynamic Views in MVC, the idea is based on this tutorial.
The next step is adding the possibility to generate multiple submit buttons but I cant get it to work. I did some research and found this approach. However, since i want to generate those buttons dynamically, this does not work yet.
What I tried is to modify this attribute code here:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class MultipleButtonAttribute : ActionNameSelectorAttribute
{
public string Name { get; set; }
public string Argument { get; set; }
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
var isValidName = false;
var keyValue = string.Format("{0}:{1}", Name, Argument);
var value = controllerContext.Controller.ValueProvider.GetValue(keyValue);
if (value != null)
{
controllerContext.Controller.ControllerContext.RouteData.Values[Name] = Argument;
isValidName = true;
}
return isValidName;
}
}
While debugging I digged down the ValueProvider and found out that the JQueryFormValueProvider in the Collection of Valueproviders actually contains the Name of the clicked submitbutton.
The button is generated like this:
<input type="submit" name="buttonClick:#Model.Id" value="#Model.Text" />
Unfortunately the ValueProviders do not let me Iterate through the Keys so I dont know how to get the value I circled red in the above screenshot.
It doesnt need to be this approach, all what counts is, to find out which button was clicked.
Ok i found the solution by modifying the Attribute-code like this:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class NameToRouteDataAttribute : ActionNameSelectorAttribute
{
/// <Summary>Name of the Value you want to grab from the control.</Summary>
public string Name { get; set; }
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
ValueProviderCollection providers = controllerContext.Controller.ValueProvider as ValueProviderCollection;
if (providers != null)
{
// We need this value-Provider as it contains the Data from the Form
JQueryFormValueProvider formProvider = providers.OfType<JQueryFormValueProvider>().FirstOrDefault();
if (formProvider != null)
{
// now look for the specified value-prefix.
var kvp = formProvider.GetKeysFromPrefix(Name).FirstOrDefault();
if (kvp.Key != null)
controllerContext.Controller.ControllerContext.RouteData.Values[Name] = kvp.Key;
}
}
return true;
}
}
Contollercode:
[HttpPost]
[NameToRouteDataAttribute(Name = "buttonClick")]
public ActionResult Show(FormViewModel form)
{
string buttonId = RouteData.GetRequiredString("buttonClick");
...
}
Buttoncode
#model MvcForms.Controls.ButtonViewModel
<input type="submit" name="buttonClick.#Model.Id" value="#Model.Text" />
The important thing here is, that the name of the Button contains a dot. This makes the JQueryFormValueProvider.GetKeysFromPrefix work the way I need it to.
I have the following code:
[Serializable]
public class CustomClass
{
public CustomClass()
{
this.Init();
}
public void Init()
{
foreach (PropertyInfo p in this.GetType().GetProperties())
{
DescriptionAttribute da = null;
DefaultValueAttribute dv = null;
foreach (Attribute attr in p.GetCustomAttributes(true))
{
if (attr is DescriptionAttribute)
{
da = (DescriptionAttribute) attr;
}
if (attr is DefaultValueAttribute)
{
dv = (DefaultValueAttribute) attr;
}
}
UInt32 value = 0;
if (da != null && !String.IsNullOrEmpty(da.Description))
{
value = Factory.Instance.SelectByCode(da.Description, 3);
}
if (dv != null && value == 0)
{
value = (UInt32) dv.Value;
}
p.SetValue(this, value, null);
}
}
private UInt32 name;
[Description("name")]
[DefaultValue(41)]
public UInt32 Name
{
get { return this.name; }
set { this.name = value; }
}
(30 more properties)
}
Now the weird thing is: when I try to serialize this class I will get an empty node CustomClass!
<CustomClass />
And when I remove Init from the constructor it works as expected! I will get the full xml representation of the class but ofcourse without values (all with value 0).
<CustomClass>
<Name>0</Name>
...
</CustomClass>
Also, when I comment out the body of Init, I will get the same as above (the one with default values)
I've tried it with a public method, with a Helper class everything, but it does not work. That is, instead of the expected:
<CustomClass>
<Name>15</Name>
...
</CustomClass>
I will get
<CustomClass />
It seems when I use reflection in this class, serialization is not possible.
Or to summarize: when I call Init or when I fill my properties with reflection -> Serialization fails, when I remove this code part -> Serialization works but of course without my values.
Is this true? And does somebody know an alternative for my solution?
It should automatically get something from the database based on the Description and when this returns nothing it falls back to the DefaultValue...
PS1: I am using the XmlSerializer
PS2: When I set a breakpoint before the serialization, I can see that all the properties are filled with the good values (like 71, 72 etc).
Now the weird thing is: when I try to serialize this class I will get an empty node CustomClass!
XmlSerializer uses DefaultValue to decide which values to serialize - if it matches the default value, it doesn't store it. This approach is consistent with similar models such as data-binding / model-binding.
Frankly, I would say that in this case both DefaultValueAttribute and DescriptionAttribute are poor choices. Write your own - perhaps EavInitAttribute - then use something like:
[EavInit(41, "name")]
public uint Name {get;set;}
Note that there are other ways of controlling this conditional serialization - you could write a method like:
public bool ShouldSerializeName() { return true; }
which will also work to convince it to write the value (this is another pattern recognised by various serialization and data-binding APIs) - but frankly this is even more work (it is per-property, and needs to be public, so it makes a mess of the API).
Finally, I would say that hitting the database multiple times (once per property) for every new object construction is very expensive - especially since many of those values are likely to be assigned values in a moment anyway (so looking them up is wasted effort). I would put a lot of thought into making this both "lazy" and "cached" if it was me.
An example of a lazy and "sparse" implementation:
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Xml.Serialization;
static class Program
{
static void Main()
{
var obj = new CustomClass();
Console.WriteLine(obj.Name);
// show it working via XmlSerializer
new XmlSerializer(obj.GetType()).Serialize(Console.Out, obj);
}
}
public class CustomClass : EavBase
{
[EavInit(42, "name")]
public uint Name
{
get { return GetEav(); }
set { SetEav(value); }
}
}
public abstract class EavBase
{
private Dictionary<string, uint> values;
protected uint GetEav([CallerMemberName] string propertyName = null)
{
if (values == null) values = new Dictionary<string, uint>();
uint value;
if (!values.TryGetValue(propertyName, out value))
{
value = 0;
var prop = GetType().GetProperty(propertyName);
if (prop != null)
{
var attrib = (EavInitAttribute)Attribute.GetCustomAttribute(
prop, typeof(EavInitAttribute));
if (attrib != null)
{
value = attrib.DefaultValue;
if (!string.IsNullOrEmpty(attrib.Key))
{
value = LookupDefaultValueFromDatabase(attrib.Key);
}
}
}
values.Add(propertyName, value);
}
return value;
}
protected void SetEav(uint value, [CallerMemberName] string propertyName = null)
{
(values ?? (values = new Dictionary<string, uint>()))[propertyName] = value;
}
private static uint LookupDefaultValueFromDatabase(string key)
{
// TODO: real code here
switch (key)
{
case "name":
return 7;
default:
return 234;
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
protected class EavInitAttribute : Attribute
{
public uint DefaultValue { get; private set; }
public string Key { get; private set; }
public EavInitAttribute(uint defaultValue) : this(defaultValue, "") { }
public EavInitAttribute(string key) : this(0, key) { }
public EavInitAttribute(uint defaultValue, string key)
{
DefaultValue = defaultValue;
Key = key ?? "";
}
}
}
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.
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;
});