Validate a string to be one of two lengths - asp.net-mvc

Is it possible to use ASP MVC's DataAnnotation to require a string to be one of two lengths? This example obviously doesn't work but I am thinking of something along these lines
[Required]
[DisplayName("Agent ID")]
[StringLength(8) || StringLength(10)]
public string AgentId

You can write your own validation attribute to handle it:
public class UserStringLengthAttribute : ValidationAttribute
{
private int _lenght1;
private int _lenght2;
public UserStringLengthAttribute(int lenght2, int lenght1)
{
_lenght2 = lenght2;
_lenght1 = lenght1;
}
public override bool IsValid(object value)
{
var typedvalue = (string) value;
if (typedvalue.Length != _lenght1 || typedvalue.Length != _lenght2)
{
ErrorMessage = string.Format("Length should be {0} or {1}", _lenght1, _lenght2);
return false;
}
return true;
}
}
And use it:
[Required]
[DisplayName("Agent ID")]
[UserStringLength(8,10)]
public string AgentId

Yes you can do that . Do a custom validator that inherits from StringLength and this will work for both client and server side
public class CustomStringLengthAttribute : StringLengthAttribute
{
private readonly int _firstLength;
private readonly int _secondLength;
public CustomStringLengthAttribute(int firstLength, int secondLength)
: base(firstLength)
{
_firstLength = firstLength;
_secondLength = secondLength;
}
public override bool IsValid(object value)
{
int valueTobeValidate = value.ToString().Length;
if (valueTobeValidate == _firstLength)
{
return base.IsValid(value);
}
if (valueTobeValidate == _secondLength)
{
return true;
}
return base.IsValid(value);
}
}
and register the adapter in the Appplication_start of the Global.asax.cs
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(CustomStringLengthAttribute), typeof(StringLengthAttributeAdapter));

Related

Get custom attribute for parameter when model binding

I've seen a lot of similar posts on this, but haven't found the answer specific to controller parameters.
I've written a custom attribute called AliasAttribute that allows me to define aliases for parameters during model binding. So for example if I have: public JsonResult EmailCheck(string email) on the server and I want the email parameter to be bound to fields named PrimaryEmail or SomeCrazyEmail I can "map" this using the aliasattribute like this: public JsonResult EmailCheck([Alias(Suffix = "Email")]string email).
The problem: In my custom model binder I can't get a hold of the AliasAttribute class applied to the email parameter. It always returns null.
I've seen what the DefaultModelBinder class is doing to get the BindAttribute in reflector and its the same but doesn't work for me.
Question: How do I get this attribute during binding?
AliasModelBinder:
public class AliasModelBinder : DefaultModelBinder
{
public static ICustomTypeDescriptor GetTypeDescriptor(Type type)
{
return new AssociatedMetadataTypeTypeDescriptionProvider(type).GetTypeDescriptor(type);
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = base.BindModel(controllerContext, bindingContext);
var descriptor = GetTypeDescriptor(bindingContext.ModelType);
/*************************/
// this next statement returns null!
/*************************/
AliasAttribute attr = (AliasAttribute)descriptor.GetAttributes()[typeof(AliasAttribute)];
if (attr == null)
return null;
HttpRequestBase request = controllerContext.HttpContext.Request;
foreach (var key in request.Form.AllKeys)
{
if (string.IsNullOrEmpty(attr.Prefix) == false)
{
if (key.StartsWith(attr.Prefix, StringComparison.InvariantCultureIgnoreCase))
{
if (string.IsNullOrEmpty(attr.Suffix) == false)
{
if (key.EndsWith(attr.Suffix, StringComparison.InvariantCultureIgnoreCase))
{
return request.Form.Get(key);
}
}
return request.Form.Get(key);
}
}
else if (string.IsNullOrEmpty(attr.Suffix) == false)
{
if (key.EndsWith(attr.Suffix, StringComparison.InvariantCultureIgnoreCase))
{
return request.Form.Get(key);
}
}
if (attr.HasIncludes)
{
foreach (var include in attr.InlcludeSplit)
{
if (key.Equals(include, StringComparison.InvariantCultureIgnoreCase))
{
return request.Form.Get(include);
}
}
}
}
return null;
}
}
AliasAttribute:
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class AliasAttribute : Attribute
{
private string _include;
private string[] _inlcludeSplit = new string[0];
public string Prefix { get; set; }
public string Suffix { get; set; }
public string Include
{
get
{
return _include;
}
set
{
_include = value;
_inlcludeSplit = SplitString(_include);
}
}
public string[] InlcludeSplit
{
get
{
return _inlcludeSplit;
}
}
public bool HasIncludes { get { return InlcludeSplit.Length > 0; } }
internal static string[] SplitString(string original)
{
if (string.IsNullOrEmpty(original))
{
return new string[0];
}
return (from piece in original.Split(new char[] { ',' })
let trimmed = piece.Trim()
where !string.IsNullOrEmpty(trimmed)
select trimmed).ToArray<string>();
}
}
Usage:
public JsonResult EmailCheck([ModelBinder(typeof(AliasModelBinder)), Alias(Suffix = "Email")]string email)
{
// email will be assigned to any field suffixed with "Email". e.g. PrimaryEmail, SecondaryEmail and so on
}
Gave up on this and then stumbled across the Action Parameter Alias code base that will probably allow me to do this. It's not as flexible as what I started out to write but probably can be modified to allow wild cards.
what I did was make my attribute subclass System.Web.Mvc.CustomModelBinderAttribute which then allows you to return a version of your custom model binder modified with the aliases.
example:
public class AliasAttribute : System.Web.Mvc.CustomModelBinderAttribute
{
public AliasAttribute()
{
}
public AliasAttribute( string alias )
{
Alias = alias;
}
public string Alias { get; set; }
public override IModelBinder GetBinder()
{
var binder = new AliasModelBinder();
if ( !string.IsNullOrEmpty( Alias ) )
binder.Alias = Alias;
return binder;
}
}
which then allows this usage:
public ActionResult Edit( [Alias( "somethingElse" )] string email )
{
// ...
}

Conditional rangevalidator +MVC

In a model of my ASP.NET MVC application I would like validate a textbox as required only if a specific checkbox is checked.
Something like
public bool retired {get, set};
[RangeIf("retired",20,50)]
public int retirementAge {get, set};
How can I do that?
You need to create your custom validation attribute like this:
public class RangeIfAttribute : ValidationAttribute
{
protected RangeAttribute _innerAttribute;
public string DependentProperty { get; set; }
public RangeIfAttribute(string dependentProperty, int minimum, int maximum)
{
_innerAttribute = new RangeAttribute(minimum, maximum);
DependentProperty = dependentProperty;
}
public RangeIfAttribute(string dependentProperty, double minimum, double maximum)
{
_innerAttribute = new RangeAttribute(minimum, maximum);
DependentProperty = dependentProperty;
}
public RangeIfAttribute(string dependentProperty, Type type, string minimum, string maximum)
{
_innerAttribute = new RangeAttribute(type, minimum, maximum);
DependentProperty = dependentProperty;
}
public override string FormatErrorMessage(string name)
{
return _innerAttribute.FormatErrorMessage(name);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// get a reference to the property this validation depends upon
var containerType = validationContext.ObjectInstance.GetType();
var field = containerType.GetProperty(DependentProperty);
if (field != null && field.PropertyType.Equals(typeof(bool)))
{
// get the value of the dependent property
var dependentValue = (bool)(field.GetValue(validationContext.ObjectInstance, null));
// if dependentValue is true...
if (dependentValue)
{
if (!_innerAttribute.IsValid(value))
// validation failed - return an error
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName), new[] { validationContext.MemberName });
}
}
return ValidationResult.Success;
}
}
Then, you can use it in your Model just like in your question.

Semi-Complex View Model Property Validation in ASP.NET MVC 3

I am struggling to complete a server-client validation solution for a semi-complex scenario. I have a core type called DateRange:
public class DateRange {
public DateRange (DateTime? start, DateTime? end) { ... }
public DateTime? Start { get; private set; }
public DateTime? End { get; private set; }
}
I have a view model like:
public class MyViewModel {
public DateRange Period { get; set; }
}
I have a %mvcproject%\Views\Shared\EditorTemplates\DateRange.cshtml like:
#model MyCore.DateRange
#Html.Editor("Start", "Date")
#Html.Editor("End", "Date")
I also have a DateRangeModelBinder to bind the two form inputs into the DateRange property. The problem I'm having is with a DateRangeRequiredAttribute:
public class DateRangeRequired : ValidationAttribute, IClientValidatable,
IMetadataAware
{
private const string DefaultErrorMessage =
"{0} is required.";
public DateRangeRequired(bool endIsRequired = true)
: base(() => DefaultErrorMessage)
{
EndIsRequired = endIsRequired;
}
public bool EndIsRequired { get; set; }
public override bool IsValid(object value)
{
if (value == null)
{
return false;
}
if (!value.GetType().IsAssignableFrom(typeof(DateRange)))
{
throw new ArgumentException("Value is not a DateRange.");
}
var dateRange = value as DateRange;
return (dateRange.Start.HasValue && !EndIsRequired) ||
(dateRange.Start.HasValue && dateRange.End.HasValue && EndIsRequired);
}
public override string FormatErrorMessage(string name)
{
return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name);
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "daterangerequired"
};
rule.ValidationParameters.Add("endisrequired", EndIsRequired.ToString().ToLower());
yield return rule;
}
public void OnMetadataCreated(ModelMetadata metadata)
{
metadata.DataTypeName = "DateRange";
}
}
I can't get it to hook up to the two inputs. It's almost like there needs to be a ValidatorTemplate that pairs with the EditorTemplate because of the split inputs. Any ideas? Let me know if additional clarification is needed.
You haven't shown exactly how your custom DateRangeRequiredAttribute implementation looks like, so let me suggest an example:
public class DateRangeRequiredAttribute : ValidationAttribute, IClientValidatable
{
private readonly string _otherProperty;
public DateRangeRequiredAttribute(string otherProperty)
{
_otherProperty = otherProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var property = validationContext.ObjectType.GetProperty(_otherProperty);
if (property == null)
{
return new ValidationResult(string.Format(CultureInfo.CurrentCulture, "Unknown property {0}", _otherProperty));
}
var otherValue = property.GetValue(validationContext.ObjectInstance, null);
if (!(value is DateTime) || !(otherValue is DateTime))
{
return new ValidationResult(string.Format(CultureInfo.CurrentCulture, "The two properties to compare must be of type DateTime"));
}
if ((DateTime)value >= (DateTime)otherValue)
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
return null;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "daterange"
};
rule.ValidationParameters.Add("other", "*." + _otherProperty);
yield return rule;
}
}
then you could decorate your view model with it:
public class DateRange
{
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:d}")]
[DateRangeRequired("End", ErrorMessage = "Please select a start date before the end date")]
public DateTime? Start { get; set; }
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:d}")]
[Required]
public DateTime? End { get; set; }
}
and finally in the view register the adapter:
jQuery.validator.unobtrusive.adapters.add(
'daterange', ['other'], function (options) {
var getModelPrefix = function (fieldName) {
return fieldName.substr(0, fieldName.lastIndexOf(".") + 1);
};
var appendModelPrefix = function (value, prefix) {
if (value.indexOf('*.') === 0) {
value = value.replace('*.', prefix);
}
return value;
};
var prefix = getModelPrefix(options.element.name),
other = options.params.other,
fullOtherName = appendModelPrefix(other, prefix),
element = $(options.form).find(':input[name="' + fullOtherName + '"]')[0];
options.rules['daterange'] = element;
if (options.message) {
options.messages['daterange'] = options.message;
}
}
);
jQuery.validator.addMethod('daterange', function (value, element, params) {
// TODO: some more advanced date checking could be applied here
// currently it uses the current browser culture setting to perform
// the parsing. If you needed to use the server side culture, this code
// could be adapted respectively
var date = new Date(value);
var otherDate = new Date($(params).val());
return date < otherDate;
}, '');
After reading this pornography, you might consider using FluentValidation.NET which renders this extremely simple validation scenario a couple of lines to implement (which is how such simple validation scenarios should be done). I would strongly recommend you this library. I am using it in all my projects because I am sick of DataAnnotations for validation. They are so pretty limited.

Data Annotations - How can I replace Range values with Web.Config values in MVC3?

How can I replace the Range values with Web.Config values in MVC3?
[Range(5, 20, ErrorMessage = "Initial Deposit should be between $5.00 and $20.00")
public decimal InitialDeposit { get; set; }
web.config:
<add key="MinBalance" value="5.00"/>
<add key="MaxDeposit" value="20.00"/>
You will need to create a custom attribute inheriting from RangeAttribute and implementing IClientValidatable.
public class ConfigRangeAttribute : RangeAttribute, IClientValidatable
{
public ConfigRangeAttribute(int Int) :
base
(Convert.ToInt32(WebConfigurationManager.AppSettings["IntMin"]),
Convert.ToInt32(WebConfigurationManager.AppSettings["IntMax"])) { }
public ConfigRangeAttribute(double Double) :
base
(Convert.ToDouble(WebConfigurationManager.AppSettings["DoubleMin"]),
Convert.ToDouble(WebConfigurationManager.AppSettings["DoubleMax"]))
{
_double = true;
}
private bool _double = false;
public override string FormatErrorMessage(string name)
{
return String.Format(ErrorMessageString, name, this.Minimum, this.Maximum);
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(this.ErrorMessage),
ValidationType = "range",
};
rule.ValidationParameters.Add("min", this.Minimum);
rule.ValidationParameters.Add("max", this.Maximum);
yield return rule;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null)
return null;
if (String.IsNullOrEmpty(value.ToString()))
return null;
if (_double)
{
var val = Convert.ToDouble(value);
if (val >= Convert.ToDouble(this.Minimum) && val <= Convert.ToDouble(this.Maximum))
return null;
}
else
{
var val = Convert.ToInt32(value);
if (val >= Convert.ToInt32(this.Minimum) && val <= Convert.ToInt32(this.Maximum))
return null;
}
return new ValidationResult(
FormatErrorMessage(this.ErrorMessage)
);
}
}
Example usage:
[ConfigRange(1)]
public int MyInt { get; set; }
[ConfigRange(1.1, ErrorMessage = "This one has gotta be between {1} and {2}!")]
public double MyDouble { get; set; }
The first example will return the default error message, and the second will return your custom error message. Both will use the range values defined in web.config.
You won't be able to do that in the attribute declaration on the property as the values need to be known at compile time. The easiest way that I could see of doing this would be to derive an attribute class from RangeAttribute and set the property values to come from web.config in the derived class. Something like
public class RangeFromConfigurationAttribute : RangeAttribute
{
public RangeFromConfigurationAttribute()
: base(int.Parse(WebConfigurationManager.AppSettings["MinBalance"]), int.Parse(WebConfigurationManager.AppSettings["MaxDeposit"]))
{
}
}
May want to come up with a better name though :)
Thinking out loud here, but ConfigRange attribute dictates that the config must be present for this to work. Can you not write a static class that would read your values from web.config, app.config or whatever you see fit, and then use that static class in existing range attribute?
public static class RangeReader
{
public static double Range1
{
// Replace this with logic to read from config file
get { return 20.0d; }
}
}
Then annotate your property with:
[Range(ConfigReader.Range1, 25.0d)]
I know that static classes are bad and there might well be a good reason for not doing this,but I thought i'll give a go.

attribute dependent on another field

In a model of my ASP.NET MVC application I would like validate a textbox as required only if a specific checkbox is checked.
Something like
public bool retired {get, set};
[RequiredIf("retired",true)]
public string retirementAge {get, set};
How can I do that?
Thank you.
Take a look at this: http://blogs.msdn.com/b/simonince/archive/2010/06/04/conditional-validation-in-mvc.aspx
I've modded the code somewhat to suit my needs. Perhaps you benefit from those changes as well.
public class RequiredIfAttribute : ValidationAttribute
{
private RequiredAttribute innerAttribute = new RequiredAttribute();
public string DependentUpon { get; set; }
public object Value { get; set; }
public RequiredIfAttribute(string dependentUpon, object value)
{
this.DependentUpon = dependentUpon;
this.Value = value;
}
public RequiredIfAttribute(string dependentUpon)
{
this.DependentUpon = dependentUpon;
this.Value = null;
}
public override bool IsValid(object value)
{
return innerAttribute.IsValid(value);
}
}
public class RequiredIfValidator : DataAnnotationsModelValidator<RequiredIfAttribute>
{
public RequiredIfValidator(ModelMetadata metadata, ControllerContext context, RequiredIfAttribute attribute)
: base(metadata, context, attribute)
{ }
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
// no client validation - I might well blog about this soon!
return base.GetClientValidationRules();
}
public override IEnumerable<ModelValidationResult> Validate(object container)
{
// get a reference to the property this validation depends upon
var field = Metadata.ContainerType.GetProperty(Attribute.DependentUpon);
if (field != null)
{
// get the value of the dependent property
var value = field.GetValue(container, null);
// compare the value against the target value
if ((value != null && Attribute.Value == null) || (value != null && value.Equals(Attribute.Value)))
{
// match => means we should try validating this field
if (!Attribute.IsValid(Metadata.Model))
// validation failed - return an error
yield return new ModelValidationResult { Message = ErrorMessage };
}
}
}
}
Then use it:
public DateTime? DeptDateTime { get; set; }
[RequiredIf("DeptDateTime")]
public string DeptAirline { get; set; }
Just use the Foolproof validation library that is available on Codeplex:
https://foolproof.codeplex.com/
It supports, amongst others, the following "requiredif" validation attributes / decorations:
[RequiredIf]
[RequiredIfNot]
[RequiredIfTrue]
[RequiredIfFalse]
[RequiredIfEmpty]
[RequiredIfNotEmpty]
[RequiredIfRegExMatch]
[RequiredIfNotRegExMatch]
To get started is easy:
Download the package from the provided link
Add a reference to the included .dll file
Import the included javascript files
Ensure that your views references the included javascript files from within its HTML for unobtrusive javascript and jquery validation.
Using NuGet Package Manager I intstalled this: https://github.com/jwaliszko/ExpressiveAnnotations
And this is my Model:
using ExpressiveAnnotations.Attributes;
public bool HasReferenceToNotIncludedFile { get; set; }
[RequiredIf("HasReferenceToNotIncludedFile == true", ErrorMessage = "RelevantAuditOpinionNumbers are required.")]
public string RelevantAuditOpinionNumbers { get; set; }
I guarantee you this will work!
I have not seen anything out of the box that would allow you to do this.
I've created a class for you to use, it's a bit rough and definitely not flexible.. but I think it may solve your current problem. Or at least put you on the right track.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
namespace System.ComponentModel.DataAnnotations
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class RequiredIfAttribute : ValidationAttribute
{
private const string _defaultErrorMessage = "'{0}' is required";
private readonly object _typeId = new object();
private string _requiredProperty;
private string _targetProperty;
private bool _targetPropertyCondition;
public RequiredIfAttribute(string requiredProperty, string targetProperty, bool targetPropertyCondition)
: base(_defaultErrorMessage)
{
this._requiredProperty = requiredProperty;
this._targetProperty = targetProperty;
this._targetPropertyCondition = targetPropertyCondition;
}
public override object TypeId
{
get
{
return _typeId;
}
}
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString, _requiredProperty, _targetProperty, _targetPropertyCondition);
}
public override bool IsValid(object value)
{
bool result = false;
bool propertyRequired = false; // Flag to check if the required property is required.
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
string requiredPropertyValue = (string) properties.Find(_requiredProperty, true).GetValue(value);
bool targetPropertyValue = (bool) properties.Find(_targetProperty, true).GetValue(value);
if (targetPropertyValue == _targetPropertyCondition)
{
propertyRequired = true;
}
if (propertyRequired)
{
//check the required property value is not null
if (requiredPropertyValue != null)
{
result = true;
}
}
else
{
//property is not required
result = true;
}
return result;
}
}
}
Above your Model class, you should just need to add:
[RequiredIf("retirementAge", "retired", true)]
public class MyModel
In your View
<%= Html.ValidationSummary() %>
Should show the error message whenever the retired property is true and the required property is empty.
Hope this helps.
Try my custom validation attribute:
[ConditionalRequired("retired==true")]
public string retirementAge {get, set};
It supports multiple conditions.

Resources