Read value of another property in asp.net mvc2 custom attribute? - c#

I have made custom attribute in my asp.net mvc2 project:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class IsUsernameValidAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value == null)
{
return true;
}
var username = value.ToString();
return UserBusiness.IsUsernameValid(username)
// && value of OtherProperty == true;
}
}
for the model:
public class MyClass
{
[IsUsernameValid]
public string UserName { get; set; }
public bool OtherProperty { get; set; }
}
I can get value of UserName, but can I get value of OtherProperty inside custom attribute and use it in return clause and how. Thanks in advance.

The only way to do this is with a class level attribute. This is often used for validating the Password and PasswordConfirmation fields during registration.
Grab some code from there as a starting point.
[AttributeUsage(AttributeTargets.Class)]
public class MatchAttribute : ValidationAttribute
{
public override Boolean IsValid(Object value)
{
Type objectType = value.GetType();
PropertyInfo[] properties = objectType.GetProperties();
...
}
}

Related

Custom Validation Attribute: Comparing one property to another property's inner property

I have a class StarActivityModel, and I want to validate that the inputted value for StarChange, is less than the Client's property of StarCount. To do this I have attempted to create a Custom Validation Attribute but am having trouble getting the StarCount value.
public class StarActivityModel : BaseModel
{
[Display(Name = "App User")]
public Client? Client { get; set; }
[Display(Name = "Star Change")]
public int? StarChange { get; set; }
}
public class Client
{
public virtual int StarCount { get; set; }
}
My attempt at a custom Validation Attribute
[AttributeUsage(AttributeTargets.Property)]
public class ValidStarChangeAttribute : ValidationAttribute
{
private readonly string _comparisonProperty;
public ValidStarChangeAttribute(string testedPropertyName)
{
_comparisonProperty = testedPropertyName;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var propertyInfo = validationContext.ObjectType.GetProperty(_comparisonProperty);
//Compare passed StarCount and Client starcount
if((int) value > //Somehow get client StarCount)
return //Code an error message
return ValidationResult.Success;
}
}
You could search the ValidationContex class for the method and property you need in custom model validation.
I modified your codes and it seems work well
Codes:
public class ValidStarChangeAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
StarActivityModel staractivemodel = (StarActivityModel)validationContext.ObjectInstance;
if (value != null)
{
if((int)value< staractivemodel.Client.StarCount)
{
return ValidationResult.Success;
}
}
return new ValidationResult("error");
}
}
Result:

How to put conditional Required Attribute into class property to work with WEB API?

I just want to put conditional Required Attribute which is work with WEB API
Example
public sealed class EmployeeModel
{
[Required]
public int CategoryId{ get; set; }
public string Email{ get; set; } // If CategoryId == 1 then it is required
}
I am using Model State validation via (ActionFilterAttribute)
You can implement your own ValidationAttribute. Perhaps something like this:
public class RequireWhenCategoryAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var employee = (EmployeeModel) validationContext.ObjectInstance;
if (employee.CategoryId == 1)
return ValidationResult.Success;
var emailStr = value as string;
return string.IsNullOrWhiteSpace(emailStr)
? new ValidationResult("Value is required.")
: ValidationResult.Success;
}
}
public sealed class EmployeeModel
{
[Required]
public int CategoryId { get; set; }
[RequireWhenCategory]
public string Email { get; set; } // If CategoryId == 1 then it is required
}
This is just a sample. It may have casting issues, and I'm not sure this is the best approach to solve this problem.
Here's my 2 cents. It will give you a nice message like "AssigneeId is required for the current AssigneeType value Salesman" It works for enums too.
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class RequiredForAnyAttribute : ValidationAttribute
{
/// <summary>
/// Values of the <see cref="PropertyName"/> that will trigger the validation
/// </summary>
public string[] Values { get; set; }
/// <summary>
/// Independent property name
/// </summary>
public string PropertyName { get; set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var model = validationContext.ObjectInstance;
if (model == null || Values == null)
{
return ValidationResult.Success;
}
var currentValue = model.GetType().GetProperty(PropertyName)?.GetValue(model, null)?.ToString();
if (Values.Contains(currentValue) && value == null)
{
var propertyInfo = validationContext.ObjectType.GetProperty(validationContext.MemberName);
return new ValidationResult($"{propertyInfo.Name} is required for the current {PropertyName} value {currentValue}");
}
return ValidationResult.Success;
}
}
Use it like this
public class SaveModel {
[Required]
public AssigneeType? AssigneeType { get; set; }
[RequiredForAny(Values = new[] { nameof(AssigneeType.Salesman) }, PropertyName = nameof(AssigneeType))]
public Guid? AssigneeId { get; set; }
}

How to access viewmodel's property value in custom validation attribute to alter messages?

The viewmodel has many string properties like Sample as below. My requirement is to show different validation messages depending on a bool flag in my viewmodel. That flag is IsProposer property as mentioned below:
[SampleAttribute(true, "bla prop", "foo add driver")]
public string Sample { get; set; }
public bool IsProposer { get; set; }
I thought to create a validation attribute so that I can just place it on all my string properties (required validation). And then depending on the value of that boolean flag, I will pass the msg accordingly. My custom validation attribute is as follows:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
public class SampleAttribute : RequiredAttribute
{
protected string ProposerErrorMessage { get; set; }
protected string AdditionalDriverErrorMessage { get; set; }
protected bool IsProposer { get; set; }
public SampleAttribute(bool isProposer, string propmsg, string adddrivermsg)
{
ProposerErrorMessage = propmsg;
IsProposer = isProposer;
AdditionalDriverErrorMessage = adddrivermsg;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (IsValid(value))
{
return ValidationResult.Success;
}
else
{
return new ValidationResult(IsProposer ? ProposerErrorMessage : AdditionalDriverErrorMessage);
}
}
}
Now the issue is, as you can see I am just passing true as first parameter for the attribute. Here, I need to pass the Isproposer property's value from the viewmodel instance so that I can then act accordingly. How can I access it?
I solved my problem by creating a attribute like this:
/// <summary>
/// This validation attribute is an extension to RequiredAttribute that can be used to choose either of the two
/// validation messages depending on a property in the context of same model.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
public class RequiredExtensionAttribute : RequiredAttribute
{
private string _errorMessageIfTruthy;
private string _errorMessageIfFalsy;
private string _dependentProperty;
public RequiredExtensionAttribute(string dependentproperty, string errorMessageIfTruthy, string errorMessageIfFalsy)
{
_errorMessageIfTruthy = errorMessageIfTruthy;
_dependentProperty = dependentproperty;
_errorMessageIfFalsy = errorMessageIfFalsy;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var propertyTestedInfo = validationContext.ObjectType.GetProperty(this._dependentProperty);
if (propertyTestedInfo == null)
{
return new ValidationResult(string.Format("unknown property {0}", this._dependentProperty));
}
var propertyTestedValue = propertyTestedInfo.GetValue(validationContext.ObjectInstance, null);
if (IsValid(value))
{
return ValidationResult.Success;
}
else
{
return new ValidationResult((bool)propertyTestedValue ? _errorMessageIfTruthy : _errorMessageIfFalsy);
}
}
}
This can now be used in models like:
[RequiredExtensionAttribute("IsProposerViewModel", "Please select your employment status.", "Please select this driver's employment status")]
public string EmploymentStatus { get; set; }
public bool IsProposerViewModel { get; set; }
-where the first parameter for attribute is the IsProposerViewModel, the dependent value.

ASP.NET MVC: Custom Validation by DataAnnotation

I have a Model with 4 properties which are of type string. I know you can validate the length of a single property by using the StringLength annotation. However I want to validate the length of the 4 properties combined.
What is the MVC way to do this with data annotation?
I'm asking this because I'm new to MVC and want to do it the correct way before making my own solution.
You could write a custom validation attribute:
public class CombinedMinLengthAttribute: ValidationAttribute
{
public CombinedMinLengthAttribute(int minLength, params string[] propertyNames)
{
this.PropertyNames = propertyNames;
this.MinLength = minLength;
}
public string[] PropertyNames { get; private set; }
public int MinLength { 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<string>();
var totalLength = values.Sum(x => x.Length) + Convert.ToString(value).Length;
if (totalLength < this.MinLength)
{
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
return null;
}
}
and then you might have a view model and decorate one of its properties with it:
public class MyViewModel
{
[CombinedMinLength(20, "Bar", "Baz", ErrorMessage = "The combined minimum length of the Foo, Bar and Baz properties should be longer than 20")]
public string Foo { get; set; }
public string Bar { get; set; }
public string Baz { get; set; }
}
Self validated model
Your model should implement an interface IValidatableObject. Put your validation code in Validate method:
public class MyModel : IValidatableObject
{
public string Title { get; set; }
public string Description { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Title == null)
yield return new ValidationResult("*", new [] { nameof(Title) });
if (Description == null)
yield return new ValidationResult("*", new [] { nameof(Description) });
}
}
Please notice: this is a server-side validation. It doesn't work on client-side. You validation will be performed only after form submission.
ExpressiveAnnotations gives you such a possibility:
[Required]
[AssertThat("Length(FieldA) + Length(FieldB) + Length(FieldC) + Length(FieldD) > 50")]
public string FieldA { get; set; }
To improve Darin's answer, it can be bit shorter:
public class UniqueFileName : ValidationAttribute
{
private readonly NewsService _newsService = new NewsService();
public override bool IsValid(object value)
{
if (value == null) { return false; }
var file = (HttpPostedFile) value;
return _newsService.IsFileNameUnique(file.FileName);
}
}
Model:
[UniqueFileName(ErrorMessage = "This file name is not unique.")]
Do note that an error message is required, otherwise the error will be empty.
Background:
Model validations are required for ensuring that the received data we receive is valid and correct so that we can do the further processing with this data. We can validate a model in an action method. The built-in validation attributes are Compare, Range, RegularExpression, Required, StringLength. However we may have scenarios wherein we required validation attributes other than the built-in ones.
Custom Validation Attributes
public class EmployeeModel
{
[Required]
[UniqueEmailAddress]
public string EmailAddress {get;set;}
public string FirstName {get;set;}
public string LastName {get;set;}
public int OrganizationId {get;set;}
}
To create a custom validation attribute, you will have to derive this class from ValidationAttribute.
public class UniqueEmailAddress : ValidationAttribute
{
private IEmployeeRepository _employeeRepository;
[Inject]
public IEmployeeRepository EmployeeRepository
{
get { return _employeeRepository; }
set
{
_employeeRepository = value;
}
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
var model = (EmployeeModel)validationContext.ObjectInstance;
if(model.Field1 == null){
return new ValidationResult("Field1 is null");
}
if(model.Field2 == null){
return new ValidationResult("Field2 is null");
}
if(model.Field3 == null){
return new ValidationResult("Field3 is null");
}
return ValidationResult.Success;
}
}
Hope this helps. Cheers !
References
Code Project - Custom Validation Attribute in ASP.NET MVC3
Haacked - ASP.NET MVC 2 Custom Validation
A bit late to answer, but for who is searching.
You can easily do this by using an extra property with the data annotation:
public string foo { get; set; }
public string bar { get; set; }
[MinLength(20, ErrorMessage = "too short")]
public string foobar
{
get
{
return foo + bar;
}
}
That's all that is too it really. If you really want to display in a specific place the validation error as well, you can add this in your view:
#Html.ValidationMessage("foobar", "your combined text is too short")
doing this in the view can come in handy if you want to do localization.
Hope this helps!

ASP MVC: Custom Validation Attribute

I'm trying to write my own Custom Validation attribute but I'm having some problems.
The attribute I'm trying to write is that when a user logs in, the password will be compared against the confirmation password.
namespace Data.Attributes
{
public class ComparePassword : ValidationAttribute
{
public string PasswordToCompareWith { get; set; }
public override bool IsValid(object value)
{
if (PasswordToCompareWith == (string)value)
{
return true;
}
return false;
}
}
Now my problem is when i'm trying to set the attribute like this in the model file:
[Required]
[ComparePassword(PasswordToCompareWith=ConfirmPassword)]
public string Password { get; set; }
[Required]
public string ConfirmPassword { get; set; }
}
I get the following error:
Error 1 An object reference is required for the non-static field, method,
or property 'Project.Data.Models.GebruikerRegistreerModel.ConfirmPassword.get'
It seems that VS is not accepting the confirmpassword in the PasswordToCompareWith=ConfirmPassword part.
What am I doing wrong?
According to this link http://devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-1 there is an special validation attribute now in MVC3:
public class RegisterModel
{
// skipped
[Required]
[ValidatePasswordLength]
[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 do not match.")]
public string ConfirmPassword { get; set; }
}
CompareAttribute is a new, very useful validator that is not actually
part of
System.ComponentModel.DataAnnotations,
but has been added to the
System.Web.Mvc DLL by the team. Whilst
not particularly well named (the only
comparison it makes is to check for
equality, so perhaps EqualTo would be
more obvious), it is easy to see from
the usage that this validator checks
that the value of one property equals
the value of another property. You can
see from the code, that the attribute
takes in a string property which is
the name of the other property that
you are comparing. The classic usage
of this type of validator is what we
are using it for here: password
confirmation.
Sorry to disappoint you but handling such a simple case like yours using Data Annotations could be a pain. You may take a look at this post.
FoolProof http://foolproof.codeplex.com/ seems to be the best solution.
public class SignUpViewModel
{
[Required]
public string Password { get; set; }
[EqualTo("Password", ErrorMessage="Passwords do not match.")]
public string RetypePassword { get; set; }
}
It is better than suggested PropertiesMustMatchAttribute as it adds the validation error for the "RetypePassword' instead of the global model level as PropertiesMustMatchAttribute does.
I don't know why this is made out to be such a big deal, just do this:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
public class ComparePassword: ValidationAttribute
{
public ComparePassword()
: base("Passwords must match.") { }
protected override ValidationResult IsValid (object value, ValidationContext validationContext)
{
if (value == null) return new ValidationResult("A password is required.");
// Make sure you change YourRegistrationModel to whatever the actual name is
if ((validationContext.ObjectType.Name != "YourRegistrationModel")
return new ValidationResult("This attribute is being used incorrectly.");
if (((YourRegistrationModel)validationContext.ObjectInstance).ConfirmPassword != value.ToString())
return new ValidationResult("Passwords must match.");
return ValidationResult.Success;
}
}
Now all you need to do is add [ComparePassword] to your password property, nothing to pass... simple and fairly clean
You can't pass a reference type to an attribute unless you do some rather lame reflection code.
In this situation, I would think creating a custom model binder would be a better idea and then checking the Password and ComparePassword at that point.
you need a STATIC method in your case:
EXAMPLE:
public static ValidationResult ValidateFrequency( double frequency, ValidationContext context )
{
if( context == null )
{
return ( ValidationResult.Success );
}
}
just as an example:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Web.Mvc;
using System.Web.Security;
namespace GDNET.Web.Mvc.Validation
{
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class ValidatePasswordLengthAttribute : ValidationAttribute, IClientValidatable
{
private const string defaultErrorMessage = "'{0}' must be at least {1} characters long.";
private readonly int minRequiredPasswordLength = Membership.Provider.MinRequiredPasswordLength;
public ValidatePasswordLengthAttribute()
: base(defaultErrorMessage)
{
}
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, minRequiredPasswordLength);
}
public override bool IsValid(object value)
{
string valueAsString = value as string;
return (valueAsString != null && valueAsString.Length >= minRequiredPasswordLength);
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
return new[]
{
new ModelClientValidationStringLengthRule(FormatErrorMessage(metadata.GetDisplayName()), minRequiredPasswordLength, int.MaxValue)
};
}
}
}
source: https://code.google.com/p/gdnetprojects/source/browse/trunk/Experiments/Common/GDNET.Web.Mvc/Validation/ValidatePasswordLengthAttribute.cs?r=69

Categories

Resources