3

I have two separate types:

public class Person
{
    public string Name { get; set; }
    public bool IsActive { get; set; }

    public Contact ContactDetails { get; set; }
}

public class Contact
{
    [RequiredIfActive]
    public string Email { get; set; }
}

What I need is to perform conditional declarative validation of internal model field, based on some state of the parent model field - in this particular example an Email has to be filled, if IsActive option is enabled.

I do not want to reorganize these models taxonomy, while in the same time I need to use attribute-based approach. It seems that from within an attribute there is no access to the validation context of the parent model. How to reach or inject it there?

public class RequiredIfActiveAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, 
                                                ValidationContext validationContext)
    {
        /* validationContext.ObjectInstance gives access to the current 
           Contact type, but is there any way of accessing Person type? */

Edit:

I know how conditional validation can be implemented using Fluent Validation, but I'm NOT asking about that (I don't need support regarding Fluent Validation). I'd like to know however, if exists any way to access parent model from inside System.ComponentModel.DataAnnotations.ValidationAttribute.

jwaliszko
  • 16,942
  • 22
  • 92
  • 158
  • 1
    Fluent Validation is much better than default MVC validation. I switched to fluent validation 3 months ago, after using default mvc validation for 4 years. – Softlion Sep 07 '13 at 20:40
  • @Softlion: This question is mainly my curiosity, I'm not trying to validate anything. I'm not trying to simplify my life in this particular context either. That's why Fluent Validation suggestions do not target my question at any level. – jwaliszko Sep 09 '13 at 17:54

2 Answers2

1

My Suggestion

Go to Tools => Library Package Manager => Package Manager Console and install Fluent Validation.

enter image description here

Action Methods

[HttpGet]
public ActionResult Index()
{
    var model = new Person
    {
        Name = "PKKG",
        IsActive = true,
        ContactDetails = new Contact { Email = "PKKG@stackoverflow.com" }
    };
    return View(model);
}
[HttpPost]
public ActionResult Index(Person p)
{
    return View(p);
}

Fluent Validation Rules

public class MyPersonModelValidator : AbstractValidator<Person>
{
    public MyPersonModelValidator()
    {
        RuleFor(x => x.ContactDetails.Email)
            .EmailAddress()
            .WithMessage("Please enter valid email address")
            .NotNull().When(i => i.IsActive)
            .WithMessage("Please enter email");
    }
}

View Models

[Validator(typeof(MyPersonModelValidator))]
public class Person
{
    [Display(Name = "Name")]
    public string Name { get; set; }

    [Display(Name = "IsActive")]
    public bool IsActive { get; set; }

    public Contact ContactDetails { get; set; }
}

public class Contact
{
    [Display(Name = "Email")]
    public string Email { get; set; }
}

View

@{
    var actionURL = Url.Action("Action", "Controller", new { area = "AreaName" },
                           Request.Url.Scheme);
}
@using (Html.BeginForm("Action", "Controller", FormMethod.Post, 
                                                    new { @action = actionURL }))
    @Html.EditorFor(i => i.Name);
    @Html.ValidationMessageFor(i => i.Name);

    @Html.EditorFor(i => i.IsActive);
    @Html.ValidationMessageFor(i => i.IsActive);

    @Html.EditorFor(i => i.ContactDetails.Email);
    @Html.ValidationMessageFor(i => i.ContactDetails.Email);
    <button type="submit">
        OK</button>
}
Imad Alazani
  • 6,688
  • 7
  • 36
  • 58
  • I appreciate your effort but, as it has been clearly annotated in the question itself (and in the comments, which are now removed), Fluent Validation is not the solution I'm looking for in my particular case. – jwaliszko Sep 07 '13 at 18:05
1

This cannot be done via an attribute on Contact.Email since, as you have already discovered, the parent Person is not available from the attribute context at runtime. To enable this scenario via a validation attribute, the attribute must decorate the Person class. You have two choices for this with System.ComponentModel.DataAnnotations attributes: CustomValidationAttribute or a custom ValidationAttribute subclass that targets Person.

Here's what the two classes might look like when using CustomValidationAttribute:

[CustomValidation(typeof(Person), "ValidateContactEmail")]
public class Person
{
    public string Name { get; set; }
    public bool IsActive { get; set; }
    public Contact ContactDetails { get; set; }

    public static ValidationResult ValidateContactEmail(Person person, ValidationContext context)
    {
        var result = ValidationResult.Success;
        if (person.IsActive)
        {
            if ((person.ContactDetails == null) || string.IsNullOrEmpty(person.ContactDetails.Email))
            {
                result = new ValidationResult("An e-mail address must be provided for an active person.", new string[] { "ContactDetails.Email" });
            }
        }

        return result;
    }
}

public class Contact
{
    public string Email { get; set; }
}
Nicole Calinoiu
  • 20,843
  • 2
  • 44
  • 49