Swetha Bindu
Swetha Bindu

Reputation: 649

How to validate one field related to another's value in ASP .NET MVC 3

I had two fields some thing like phone number and mobile number. Some thing like..

    [Required]
    public string Phone { get; set; }

    [Required]
    public string Mobile{ get; set; }

But user can enter data in either one of it. One is mandatory. How to handle them i.e how to disable the required field validator for one field when user enter data in another field and viceversa. In which event i have to handle it in javascript and what are the scripts i need to add for this. Can anyone please help to find the solution...

Upvotes: 21

Views: 37820

Answers (3)

John Lord
John Lord

Reputation: 2175

Since nobody else suggested it, I'm going to tell you a different way to do this that we use.

If you create a notmapped field of a custom data type (in my example, a pair of gps points), you can put the validator on that and you don't even need to use reflection to get all the values.

    [NotMapped]
    [DCGps]
    public GPS EntryPoint
    {
        get
        {
            return new GPS(EntryPointLat, EntryPointLon);
        }
    }

and the class, a standard getter/setter

   public class GPS
{
    public decimal? lat { get; set; }
    public decimal? lon { get; set; }
    public GPS(decimal? lat, decimal? lon)
    {
        this.lat = lat;
        this.lon = lon;
    }
}

and now the validator:

    public class DCGps : DCValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (!(value is GPS)) {
            return new ValidationResult("DCGps:  This annotation only works with fields with the data type GPS.");
        }
        //value stored in the field.
        //these come through as zero or emptry string.  Normalize to ""
        string lonValue = ((GPS)value).lonstring == "0" ? "" : ((GPS)value).lonstring;
        string latValue = ((GPS)value).latstring == "0" ? "" : ((GPS)value).latstring;

        //place validation code here.  You have access to both values.  
        //If you have a ton of values to validate, you can do them all at once this way.
    }
}

Upvotes: 0

jwaliszko
jwaliszko

Reputation: 17064

I know this question is not so hot, because it was asked relatively long time ago, nevertheless I'm going to share with a slightly different idea of solving such an issue. I decided to implement mechanism which provides conditional attributes to calculate validation results based on other properties values and relations between them, which are defined in logical expressions.

Your problem can be defined and automatically solved by the usage of following annotations:

[RequiredIf("Mobile == null",
    ErrorMessage = "At least email or phone should be provided.")]
public string Phone{ get; set; }

[RequiredIf("Phone == null",
    ErrorMessage = "At least email or phone should be provided.")]
public string Mobile { get; set; }

If you feel it would be useful for your purposes, more information about ExpressiveAnnotations library can be found here. Client side validation is also supported out of the box.

Upvotes: 12

Darin Dimitrov
Darin Dimitrov

Reputation: 1038720

One possibility is to write a custom validation attribute:

public class RequiredIfOtherFieldIsNullAttribute : ValidationAttribute, IClientValidatable
{
    private readonly string _otherProperty;
    public RequiredIfOtherFieldIsNullAttribute(string otherProperty)
    {
        _otherProperty = otherProperty;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var property = validationContext.ObjectType.GetProperty(_otherProperty);
        if (property == null)
        {
            return new ValidationResult(string.Format(
                CultureInfo.CurrentCulture, 
                "Unknown property {0}", 
                new[] { _otherProperty }
            ));
        }
        var otherPropertyValue = property.GetValue(validationContext.ObjectInstance, null);

        if (otherPropertyValue == null || otherPropertyValue as string == string.Empty)
        {
            if (value == null || value as string == string.Empty)
            {
                return new ValidationResult(string.Format(
                    CultureInfo.CurrentCulture,
                    FormatErrorMessage(validationContext.DisplayName),
                    new[] { _otherProperty }
                ));
            }
        }

        return null;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule
        {
            ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
            ValidationType = "requiredif",
        };
        rule.ValidationParameters.Add("other", _otherProperty);
        yield return rule;
    }
}

which you would apply to one of the properties of your view model:

public class MyViewModel
{
    [RequiredIfOtherFieldIsNull("Mobile")]
    public string Phone { get; set; }

    public string Mobile { get; set; }
}

then you could have a controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View(new MyViewModel());
    }

    [HttpPost]
    public ActionResult Index(MyViewModel model)
    {
        return View(model);
    }
}

and finally a view in which you will register an adapter to wire the client side validation for this custom rule:

@model MyViewModel

<script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
<script type="text/javascript">
    jQuery.validator.unobtrusive.adapters.add(
        'requiredif', ['other'], function (options) {

            var getModelPrefix = function (fieldName) {
                return fieldName.substr(0, fieldName.lastIndexOf('.') + 1);
            }

            var appendModelPrefix = function (value, prefix) {
                if (value.indexOf('*.') === 0) {
                    value = value.replace('*.', prefix);
                }
                return value;
            }

            var prefix = getModelPrefix(options.element.name),
                other = options.params.other,
                fullOtherName = appendModelPrefix(other, prefix),
                element = $(options.form).find(':input[name="' + fullOtherName + '"]')[0];

            options.rules['requiredif'] = element;
            if (options.message) {
                options.messages['requiredif'] = options.message;
            }
        }
    );

    jQuery.validator.addMethod('requiredif', function (value, element, params) {
        var otherValue = $(params).val();
        if (otherValue != null && otherValue != '') {
            return true;
        }
        return value != null && value != '';
    }, '');
</script>

@using (Html.BeginForm())
{
    <div>
        @Html.LabelFor(x => x.Phone)
        @Html.EditorFor(x => x.Phone)
        @Html.ValidationMessageFor(x => x.Phone)
    </div>

    <div>
        @Html.LabelFor(x => x.Mobile)
        @Html.EditorFor(x => x.Mobile)
        @Html.ValidationMessageFor(x => x.Mobile)
    </div>

    <button type="submit">OK</button>
}

Pretty sick stuff for something so extremely easy as validation rule that we encounter in our everyday lives. I don't know what the designers of ASP.NET MVC have been thinking when they decided to pick a declarative approach for validation instead of imperative.

Anyway, that's why I use FluentValidation.NET instead of data annotations to perform validations on my models. Implementing such simple validation scenarios is implemented in a way that it should be - simple.

Upvotes: 51

Related Questions