I have the following custom required attribute:
public class RequiredIfAttribute : RequiredAttribute
{
private string _DependentProperty;
private object _TargetValue;
public RequiredIfAttribute(string dependentProperty, object targetValue)
{
this._DependentProperty = dependentProperty;
this._TargetValue = targetValue;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var propertyTestedInfo = validationContext.ObjectType.GetProperty(this._DependentProperty);
if (propertyTestedInfo == null)
{
return new ValidationResult(string.Format("{0} needs to be exist in this object.", this._DependentProperty));
}
var dependendValue = propertyTestedInfo.GetValue(validationContext.ObjectInstance, null);
if (dependendValue == null)
{
return new ValidationResult(string.Format("{0} needs to be populated.", this._DependentProperty));
}
if (dependendValue.Equals(this._TargetValue))
{
var x = validationContext.ObjectType.GetProperty("_Mappings");
var objectInstance = (Dictionary<object, string[]>)x.GetValue(validationContext.ObjectInstance, null);
var isRequiredSatisfied = false;
foreach (var kvp in objectInstance)
{
if (kvp.Key.Equals(this._TargetValue))
{
foreach (var field in kvp.Value)
{
var fieldValue = validationContext.ObjectType.GetProperty(field).GetValue(validationContext.ObjectInstance, null);
if (fieldValue != null && field.Equals(validationContext.MemberName))
{
isRequiredSatisfied = true;
break;
}
}
}
}
if (isRequiredSatisfied)
{
return ValidationResult.Success;
}
else
{
return new ValidationResult(string.Empty);
}
}
else
{
// Must be ignored
return ValidationResult.Success;
}
}
}
What I am trying to achieve with it is that I want to conditionally validate based on a property in a model. It also needs to be generic enough to re-use on more than one model. When a specified property has a specific value (which I specify in the attribute), the custom required validation needs to match on those values. For example in this model:
public class PaymentModel
{
public Dictionary<object, string[]> _Mappings
{
get
{
var mappings = new Dictionary<object, string[]>();
mappings.Add(true, new string[] { "StockID" });
mappings.Add(false, new string[] { "Amount" });
return mappings;
}
}
[Required]
public bool IsBooking { get; set; }
[RequiredIfAttribute("IsBooking", false)]
public decimal? Amount { get; set; }
[RequiredIf("IsBooking", true)]
public int? StockID { get; set; }
public PaymentModel()
{
}
}
If the IsBooking
property is true
, then I want StockId
to be required, but if it is false
, then Amount
should be required.
Currently the solution I have works, but it has 2 problems:
_Mappings
property, which I would like to not have. Does anyone know how I will get around doing it the way I have?_Mappings
property as is, is there any way to use it as a private
access modifier? Currently I can only make my solution work if _Mappings
is public
, because validationContext.ObjectType.GetProperty("_Mappings")
cannot find private
modifiers. (If I want to serialize this model to JSON in a Web API response, then I would ideally not want to send along my validation mappings.)You don't have to use the _Mappings Property, the code below checks if the related Attribute has a value that matches what you specified in the attribute, If there is a match then it checks to see if the Property you are validating has a value.
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var propertyTestedInfo = validationContext.ObjectType.GetProperty(this._DependentProperty);
if (propertyTestedInfo == null)
{
return new ValidationResult(string.Format("{0} needs to be exist in this object.", this._DependentProperty));
}
var dependendValue = propertyTestedInfo.GetValue(validationContext.ObjectInstance, null);
if (dependendValue == null)
{
return new ValidationResult(string.Format("{0} needs to be populated.", this._DependentProperty));
}
if (dependendValue.Equals(this._TargetValue))
{
var fieldValue = validationContext.ObjectType.GetProperty(validationContext.MemberName).GetValue(validationContext.ObjectInstance, null);
if (fieldValue != null)
{
return ValidationResult.Success;
}
else
{
return new ValidationResult(string.Format("{0} cannot be null", validationContext.MemberName));
}
}
else
{
// Must be ignored
return ValidationResult.Success;
}
}