Create a complex type model validation attribute with server and client validation
Asked Answered
E

0

3

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 associated 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.

Extramundane answered 9/12, 2016 at 16:28 Comment(2)
No, it is not possible to get client side validation with you implementation. You could include bool IsRequired property in your view model and then apply a foolproof [RequiredIfTrue("IsRequired")] or similar attribute to each property of the view modelMange
Alternatively, if its not required, don't generate any inputs for that model and it will be null in the POST method and no validation will be performed - I assume this model is a property of a parent model (side note: view models should not be partial classes)Mange

© 2022 - 2024 — McMap. All rights reserved.