I'm trying to create an attribute that can validate a complex type both on the server and client side. This attribute will be used for required and non required complex types such as the following Address Class
public partial class AddressViewModel
{
[DisplayName("Address 1")]
[MaxLength(100)]
public virtual string Address1 { get; set; }
[DisplayName("Address 2")]
[MaxLength(100)]
public virtual string Address2 { get; set; }
[MaxLength(100)]
public virtual string City { get; set; }
[MaxLength(50)]
public virtual string State { get; set; }
[MaxLength(10)]
[DisplayName("Postal Code")]
public virtual string PostalCode { get; set; }
[MaxLength(2)]
public virtual string Country { get; set; }
}
The problem is that this model could be required sometimes and optional other times. I know that I could simply create another "RequiredAddressViewModel" class that has the "Required" attribute associtated with the properties I deem required. I feel like there could be a reusable solution, such as a ValidationAttribute.
I created the following classes and they work server side, but do not work for client side.
public class AddressIfAttribute : ValidationAttribute, IClientValidatable
{
public string Address1 { get; private set; }
public string Address2 { get; private set; }
public string City { get; private set; }
public string State { get; private set; }
public string PostalCode { get; private set; }
public string Country { get; private set; }
public bool IsRequired { get; private set; }
public AddressIfAttribute(bool isRequired) : base("The field {0} is required.")
{
IsRequired = isRequired;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var address = value as AddressViewModel;
Address1 = address.Address1;
Address2 = address.Address2;
City = address.City;
State = address.State;
Country = address.Country;
PostalCode = address.PostalCode;
var results = new List<ValidationResult>();
var context = new ValidationContext(address, null, null);
Validator.TryValidateObject(address, context, results, true);
if (results.Count == 0 && IsRequired)
{
if (string.IsNullOrEmpty(Address2))
return new ValidationResult(string.Format(ErrorMessageString, validationContext.DisplayName));
}
else if (results.Count != 0)
{
var compositeResults = new CompositeValidationResult(string.Format("Validation for {0} failed!", validationContext.DisplayName));
results.ForEach(compositeResults.AddResult);
return compositeResults;
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
return new[]
{
new ModelClientValidationAddressIfRule(string.Format(ErrorMessageString,metadata.GetDisplayName()), Address1, Address2, City, State, Country, PostalCode,IsRequired)
};
}
}
public class ModelClientValidationAddressIfRule : ModelClientValidationRule
{
public ModelClientValidationAddressIfRule(string errorMessage, object address1, object address2, object city, object state, object country, object postalCode, bool isRequired)
{
ErrorMessage = errorMessage;
ValidationType = "addressif";
ValidationParameters.Add("address1", address1);
ValidationParameters.Add("address2", address2);
ValidationParameters.Add("city", city);
ValidationParameters.Add("state", state);
ValidationParameters.Add("country", country);
ValidationParameters.Add("postalCode", postalCode);
ValidationParameters.Add("isrequired", isRequired.ToString().ToLower());
}
Since the AddressIf attribute is on a complex type the necessary markup isn't added and unobtrusive javascript doesn't validate these fields.
So if I want the rendered HTML to have the proper "data-*" fields, is my only solution to create another RequiredAddressViewModel? At this point, it might be the easiest.