Validation of properties that require the values of other properties - c#

So I have checked out this answer ASP:NET MVC 4 dynamic validation of a property depending of the current value of another property and it does not cover the issue I am having.
I am using server side validation. I have a requirement that...
A value is only required if another property is specified
Issue
MVC binds each property and calls each validator on that property as it binds them. If I am dependent on multiple properties being set when I check validationContext.ObjectInstance.[MY_DEPENDENT_PROPERTY] there is a possibility that those dependent properties have not been bound yet.
What I need is a validation attribute that validates after binding - if that even exists.
So here is a simple example to explain my situation (not intended to be executed as it will more than likely be fine since the issue has to do with binding order)
My model
public class Address
{
[Required]
public string ResidentialAddress { get; set; }
public bool PostalIsTheSameAsResidential { get; set; }
// will only be required if PostalIsTheSameAsResidential is false.
// see the static method below and RequiredIfAttribute
[RequiredIf(typeof(Address), nameof(PostalRequiredIfNotSameAsResidential)]
public string PostalAddress { get; set; }
public static bool PostalRequiredIfNotSameAsResidential(Address model)
{
return !model.PostalIsTheSameAsResidential;
}
}
My validator
Essentially what happens here is it calls the static method on the model to see whether it should validate.
public sealed class RequiredIfAttribute : RequiredAttribute
{
private readonly MethodInfo _validationMethod;
public override bool RequiresValidationContext => true;
public RequiredIfAttribute(Type type, string methodName)
{
this._validationMethod = type.GetMethod(methodName);
if (this._validationMethod == null)
{
throw new MethodAccessException($"The validation method '{methodName}' does not exist on type '{type}");
}
}
public override bool IsValid(object value)
{
throw new NotSupportedException();
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ValidationResult result = ValidationResult.Success;
var parameters = this._validationMethod.GetParameters();
var returnType = this._validationMethod.ReturnType;
if (returnType == typeof(bool) && parameters.Length == 1 && parameters[0].ParameterType == validationContext.ObjectType)
{
if ((bool)_validationMethod.Invoke(null, new object[] { validationContext.ObjectInstance }))
{
if (!base.IsValid(value))
{
string[] memberNames;
if (validationContext.MemberName == null)
{
memberNames = null;
}
else
{
memberNames = new string[1];
memberNames[0] = validationContext.MemberName;
}
result = new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName), memberNames);
}
}
return result;
}
var expectedFuncType = typeof(Func<,>).MakeGenericType(validationContext.ObjectType, typeof(bool));
throw new MethodAccessException($"The validation method '{this._validationMethod}' does not have the correct definition. Expected '{expectedFuncType}'");
}
}

So this issue that I was having was that I was inheriting from the RequiredAttribute. Internally MVC handles this attribute differently to everything else.
When the Model Binder is looping through the properties, it gets the RequiredAttributes and executes them at the same time...
// System.Web.Mvc.DefaultModelBinder.SetProperty
....
ModelValidator modelValidator = (from v in ModelValidatorProviders.Providers.GetValidators(modelMetadata, controllerContext)
where v.IsRequired
select v).FirstOrDefault<ModelValidator>();
if (modelValidator != null)
{
foreach (ModelValidationResult current in modelValidator.Validate(bindingContext.Model))
{
bindingContext.ModelState.AddModelError(key, current.Message);
}
}
....
That v.IsRequired actually resolves to a line that tests if the current attribute is a RequiredAttribute and will validate it there, in the current, incomplete model state.
By inheriting from ValidationAttribute it ran the validations after the model had been built and solved my issue.
Thanks to #StephenMuecke for prompting me with this.

Related

Using default IModelBinder within custom binder in Web API 2

How do you call the default model binder within Web API in a custom IModelBinder? I know MVC has a default binder, but I can't use it with Web API. I just want to use the default Web API binder, and then run some custom logic after that (to avoid re-inventing the wheel).
public class CustomBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
// Get default binding (can't mix Web API and MVC)
var defaultMvcBinder = System.Web.ModelBinding.ModelBinders.Binders.DefaultBinder;
var result = defaultMvcBinder.BindModel(actionContext, bindingContext); // Won't work
if (result == false) return false;
// ... set additional model properties
return true;
}
}
In case others stumble on this question, I had to implement the custom model binder with activation context since there is nothing to re-use from Web API. Here is the solution I am using for my limited scenarios that needed to be supported.
Usage
The implementation below allows me to let any model optionally use JsonProperty for model binding, but if not provided, will default to just the property name. It supports mappings from standard .NET types (string, int, double, etc). Not quite production ready, but it meets my use cases so far.
[ModelBinder(typeof(AttributeModelBinder))]
public class PersonModel
{
[JsonProperty("pid")]
public int PersonId { get; set; }
public string Name { get; set; }
}
This allows the following query string to be mapped in a request:
/api/endpoint?pid=1&name=test
Implementation
First, the solution defines a mapped property to track the source property of the model and the target name to use when setting the value from the value provider.
public class MappedProperty
{
public MappedProperty(PropertyInfo source)
{
this.Info = source;
this.Source = source.Name;
this.Target = source.GetCustomAttribute<JsonPropertyAttribute>()?.PropertyName ?? source.Name;
}
public PropertyInfo Info { get; }
public string Source { get; }
public string Target { get; }
}
Then, a custom model binder is defined to handle the mapping. It caches the reflected model properties to avoid repeating the reflection on subsequent calls. It may not be quite production ready, but initial testing has been promising.
public class AttributeModelBinder : IModelBinder
{
public static object _lock = new object();
private static Dictionary<Type, IEnumerable<MappedProperty>> _mappings = new Dictionary<Type, IEnumerable<MappedProperty>>();
public IEnumerable<MappedProperty> GetMapping(Type type)
{
if (_mappings.TryGetValue(type, out var result)) return result; // Found
lock (_lock)
{
if (_mappings.TryGetValue(type, out result)) return result; // Check again after lock
return (_mappings[type] = type.GetProperties().Select(p => new MappedProperty(p)));
}
}
public object Convert(Type target, string value)
{
try
{
var converter = TypeDescriptor.GetConverter(target);
if (converter != null)
return converter.ConvertFromString(value);
else
return target.IsValueType ? Activator.CreateInstance(target) : null;
}
catch (NotSupportedException)
{
return target.IsValueType ? Activator.CreateInstance(target) : null;
}
}
public void SetValue(object model, MappedProperty p, IValueProvider valueProvider)
{
var value = valueProvider.GetValue(p.Target)?.AttemptedValue;
if (value == null) return;
p.Info.SetValue(model, this.Convert(p.Info.PropertyType, value));
}
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
try
{
var model = Activator.CreateInstance(bindingContext.ModelType);
var mappings = this.GetMapping(bindingContext.ModelType);
foreach (var p in mappings)
this.SetValue(model, p, bindingContext.ValueProvider);
bindingContext.Model = model;
return true;
}
catch (Exception ex)
{
return false;
}
}
}

Customize data annotations CompareAttribute

I need to compare two properties in a class using .net data annotations. One of the two properties should be filled and the other should be blank. How can I override the behavior of the CompareAttribute ? If it is not possible, what's the alternative solution ?
This class works with one issue:
If Property A is set to something and property B already has a value, then property A becomes invalid as expected. Upon Blanking property B, property A should become valid but it won't until I try to modify property A so I trigger the validation again. Is there a way to connect the two together to trigger the validation on both one either one changes ?
class CustomAttribute : ValidationAttribute
{
private readonly string _other;
public CustomAttribute(string other)
{
_other = other;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var property = validationContext.ObjectType.GetProperty(_other);
if (property == null)
{
return new ValidationResult(
string.Format("Unknown property: {0}", _other)
);
}
var otherValue = property.GetValue(validationContext.ObjectInstance, null);
if (!String.IsNullOrEmpty(value.ToString()) && !String.IsNullOrEmpty(otherValue.ToString()))
{
return new ValidationResult("Test");
}
return null;
}
}
For stuff like this I use ExpressiveAnnotations. It has a brilliant RequiredIf attribute:
[RequiredIf("B == null", ErrorMessage = "Either A or B should be filled")]
public string A { get; set; }
[RequiredIf("A == null", ErrorMessage = "Either A or B should be filled")]
public string B { get; set; }
You can extend the CompareAttribute with your own class:
public class CustomCompareAttribute: CompareAttribute {
public CustomCompareAttribute(string otherProperty) : base(otherProperty) {
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
if (OtherProperty == null && value == null) {
return new ValidationResult("Either A or B should be filled");
}
// more checks here ...
}
}

Custom Validation Attribute reading from a form field

I am trying to create my own validation attribute IsUnique that checks existing values for given property. I understand IsValid() must be overridden so that custom validation attribute can work. So far I have seen examples with validate attributes that take a string parameters which is then compared with hard coded values inside IsValid() method.
I need IsValid() method to get access to a property and its value to further compare it with values in the database.
This is what I have done so far:
public class IsUnique : ValidationAttribute
{
private string codeno { get; set; }
: base("{0} is already in use")
public IsUnique (string codeno)
{
this.codeno = codeno;
}
public override ValidationResult IsValid(object value,
ValidationContext vContext)
{
if (value != null)
{
MyDBContext db = new MyDBContext();
Student studentsCodeNo =
db.Students.FirstOrDefault(r => r.codeno== (string)value);
if (studentsCodeNo != null)
{
string errorMessage =
FormatErrorMessage(vContext.DisplayName);
return new ValidationResult(errorMessage);
}
}
return ValidationResult.Success;
}
}
As said, the problem is that this version takes parameter. I would like codeno to be read from a user form field, and such value would then be compared against anything in database. I don't know how to read values from the form fields.
Here is code
public class IsUnique : ValidationAttribute{
public override ValidationResult IsValid(object value,
ValidationContext vContext)
{
PropertyInfo property = validationContext.ObjectType.GetProperty("Codeno");
if (property == null)
return new ValidationResult(string.Format("Property '{0}' is undefined","Codeno"));
var fieldValue = property.GetValue(validationContext.ObjectInstance, null);
string codeno= (fieldValue == null ? "" : fieldValue.ToString());
if (!string.IsNullOrEmpty(codeno))
{
MyDBContext db = new MyDBContext();
Student studentsCodeNo =
db.Students.FirstOrDefault(r => r.codeno== codeno);
if (studentsCodeNo != null)
{
string errorMessage =
FormatErrorMessage(vContext.DisplayName);
return new ValidationResult(errorMessage);
}
}
return ValidationResult.Success; }}
There is sort of an out of the box way to do this already
http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.schema.indexattribute(v=vs.113).aspx
[Index(IsUnique=true)]

How do I do Sub Object Custom Validation mvc3

I have an object (header) that has a list of sub-ojects (details) that I want to do custom validation on prior to accepting data. I've tried ModelState.IsValid and TryValidateModel, but it doesn't seem to fire the Validate method on the sub-objects (only the header object).
So on submission I see the validation fire for the header, but not the sub-ojects. Then if I do a TryValidateModel I again see (breakpoint) the validation method get called on the header, but not on the sub-objects.
The annotated validation (must be number, etc...) seems to be working on the sub-objects, just not the custom logic added via the IValidatableObject interface. Any help would be greatly appreciated.
I make an attribute ( [ValidateObject] ) and it will validate the attribute you put on your class, like you would think it suppose to do.
public class Personne
{
[ValidateObject]
public Address Address { get; set; }
//[...]
}
(Address is a custom class.)
It can be used for validating:
Object properties on a model. (like above)
Collection of sub object.
[ValidateObject]
public List<Address> Address { get; set; }
It support multi level of dept, if "Address" had a propertie of type "ZipCode" with the attribute [ValidateObject] it would be validate tho.
Code:
public class ValidateObjectAttribute : ValidationAttribute
{
public ValidateObjectAttribute()
{
}
private ValidationContext ValidationContext { get; set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
this.ValidationContext = validationContext;
var results = new List<ValidationResult>();
try
{
var isIterable = this.IsIterable(value);
if (isIterable)
{
int currentItemPosition = -1;
foreach (var objectToValidate in value as IEnumerable<object>)
{
currentItemPosition++;
var resultTemp = ValidationsForObject(objectToValidate, true, currentItemPosition);
if (resultTemp != null)
results.AddRange(resultTemp);
}
if (results.Count <= 0)
results = null;
}
else
results = ValidationsForObject(value);
if (results != null)
{
//Build a validation result
List<string> memberNames = new List<string>();
results.ForEach(r => memberNames.AddRange(r.MemberNames));
var compositeResultsReturn = new CompositeValidationResult($"Validation for {validationContext.DisplayName} failed!", memberNames.AsEnumerable());
results.ForEach(r => compositeResultsReturn.AddResult(r));
return compositeResultsReturn;
}
}
catch (Exception) { }
return ValidationResult.Success;
}
private List<ValidationResult> ValidationsForObject (object objectToValidate, bool IsIterable = false, int position = -1)
{
var results = new List<ValidationResult>();
var contextTemp = new ValidationContext(objectToValidate, null, null);
var resultsForThisItem = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(objectToValidate, contextTemp, resultsForThisItem, true);
if (isValid)
return null;
foreach (var validationResult in resultsForThisItem)
{
List<string> propNames = new List<string>();// add prefix to properties
foreach (var nameOfProp in validationResult.MemberNames)
{
if (IsIterable)
propNames.Add($"{this.ValidationContext.MemberName}[{position}].{nameOfProp}");
else
propNames.Add($"{this.ValidationContext.MemberName}.{nameOfProp}");
}
var customFormatValidation = new ValidationResult(validationResult.ErrorMessage, propNames);
results.Add(customFormatValidation);
}
return results;
}
private bool IsIterable(object value)
{
////COULD WRITE THIS, but its complicated to debug...
//if (value.GetType().GetInterfaces().Any(
//i => i.IsGenericType &&
//i.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
//{
// // foreach...
//}
Type valueType = value.GetType();
var interfaces = valueType.GetInterfaces();
bool isIterable = false;
foreach (var i in interfaces)
{
var isGeneric = i.IsGenericType;
bool isEnumerable = i.GetGenericTypeDefinition() == typeof(IEnumerable<>);
isIterable = isGeneric && isEnumerable;
if (isIterable)
break;
}
return isIterable;
}
}
public class CompositeValidationResult : ValidationResult
{
private readonly List<ValidationResult> _results = new List<ValidationResult>();
public IEnumerable<ValidationResult> Results
{
get
{
return _results;
}
}
public CompositeValidationResult(string errorMessage) : base(errorMessage)
{
}
public CompositeValidationResult(string errorMessage, IEnumerable<string> memberNames) : base(errorMessage, memberNames)
{
}
protected CompositeValidationResult(ValidationResult validationResult) : base(validationResult)
{
}
public void AddResult(ValidationResult validationResult)
{
_results.Add(validationResult);
}
}
This will work if you model is correctly binded :)
You might want to add the required attribute, making sure the the object is not null itself.
[Required]
Hope it help!
I wonder if you root object has errors preventing child validation.
See Recursive validation using annotations and IValidatableObject
This URL mentions that scenario and also code to force validation on the child from the root
As per the posting triggering off the validation from the root object
public IEnumerable Validate(ValidationContext validationContext)
{
var context = new ValidationContext(this.Details, validationContext.ServiceContainer, validationContext.Items);
var results = new List();
Validator.TryValidateObject(this.Details, context, results);
return results;
}
The TryValidateObject didn't appear to fire custom validation, only data annotations? I went down the path of adding validation of the details to the header validation by doing the following:
foreach (var detail in this.Details)
{
var validationResults = detail.Validate(validationContext);
foreach (var validationResult in validationResults)
{
yield return validationResult;
}
}
This worked in terms of validation, but the UI didn't display the error messages. Even though I have ValidationMessagesFor on the UI.
Validation not working solved here: MVC3 Master-Details Validation not Displaying

CustomValidationAttribute specifed method not being called

I'm using the System.ComponentModel.DataAnnotations.CustomValidationAttribute to validate one of my POCO classes and when I try to unit test it, it's not even calling the validation method.
public class Foo
{
[Required]
public string SomethingRequired { get; set }
[CustomValidation(typeof(Foo), "ValidateBar")]
public int? Bar { get; set; }
public string Fark { get; set; }
public static ValidationResult ValidateBar(int? v, ValidationContext context) {
var foo = context.ObjectInstance as Foo;
if(!v.HasValue && String.IsNullOrWhiteSpace(foo.Fark)) {
return new ValidationResult("Either Bar or Fark must have something in them.");
}
return ValidationResult.Success;
}
}
but when I try to validate it:
var foo = new Foo {
SomethingRequired = "okay"
};
var validationContext = new ValidationContext(foo, null, null);
var validationResults = new List<ValidationResult>();
bool isvalid = Validator.TryValidateObject(foo, validationContext, validationResults);
Assert.IsFalse(isvalid); //FAIL!!! It's valid when it shouldn't be!
It never even steps into the custom validation method. What gives?
Try using the overload that takes a bool that specifies if all properties should be validated. Pass true for the last parameter.
public static bool TryValidateObject(
Object instance,
ValidationContext validationContext,
ICollection<ValidationResult> validationResults,
bool validateAllProperties
)
If you pass false or omit the validateAllProperties, only the RequiredAttribute will be checked.
Here is the MSDN documentation.

Categories