I am using the custom RequiredIf attribute created by Simon Ince in my MVC app.

I have a viewmodel which is passed to the view like this:

<!-- language: lang-cs -->
public class HistoryViewModel
{
    public Contact ContactModel { get; set; }
    public Account AccountModel { get; set; }
    public Person PersonModel { get; set; }
 }

I have a bunch of models with all the props in them (i.e. contact.cs, account.cs, person.cs)

<!-- language: lang-cs -->
public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public bool IsAdult { get; set; }        
    [RequiredIf("IsAdult", "Yes", Errormessage="Please leave a comment")]
    public string Comments { get; set; }
 }
 

The RequiredIf validation does not seem to be working when passed through the viewmodel. It works fine if I move the properties into the viewmodel directly. All other [Required]` attributes work from the model, through the viewmodel.

So do I need to move all the properties into the viewmodel, that need the RequiredIf on them? Or is there another way around this?

The Problem

If you look at the html emitted by your razor view, you'll see what's at issue here.

Without a viewmodel, we should generate the following code (cleaned up a little):

<!-- language: lang-html --> <pre><code>&lt;input type=&quot;checkbox&quot; name=&quot;IsAdult&quot; id=&quot;<b><i>IsAdult</i></b>&quot; /&gt; &lt;input type=&quot;text&quot; name=&quot;Comments&quot; id=&quot;Comments&quot; data-val-requiredif-dependprop=&quot;<b><i>IsAdult</i></b>&quot; data-val-requiredif-value=&quot;Yes&quot; data-val-requiredif=&quot;Please leave a comment&quot; /&gt; </code></pre>

With a viewmodel, we get this:

<!-- language: lang-html --> <pre><code>&lt;input type=&quot;checkbox&quot; name=&quot;PersonModel.IsAdult&quot; id=&quot;<b><i>PersonModel_IsAdult</i></b>&quot; /&gt; &lt;input type=&quot;text&quot; name=&quot;PersonModel.Comments&quot; id=&quot;PersonModel_Comments&quot; data-val-requiredif-dependprop=&quot;<b><i>IsAdult</i></b>&quot; data-val-requiredif-value=&quot;Yes&quot; data-val-requiredif=&quot;Please leave a comment&quot; /&gt; </code></pre>

Whenever a property is nested inside another property, MVC will build a stack of prefixes to generate unique ID's and names. You can see in the first case, IsAdult is enough to identify the field, however once it has been nested, the id has changed. In unobtrusive validation (rules stored in attributes), everything we need to possibly know about how to validate an element needs to get sent along in the data-attributes, including how to locate other properties.

The Solution

The data attribute on the class should never know or care about what context it is being called under, so it will continue to blindly point out the relative attribute on which it depends:

<!-- language: lang-cs -->
public bool IsAdult { get; set; }
[RequiredIf("IsAdult", "Yes", Errormessage="Please leave a comment")]
public string Comments { get; set; }

So we'll have to establish the context either on the server or on the client.

On the Server - Nope!

As part of your public class RequiredIfAttribute : ValidationAttribute, IClientValidatable, you'll have a method that emits client side validation rules that looks something like this:

public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
    ModelClientValidationRule requiredIfRule = new ModelClientValidationRule();
    requiredIfRule.ErrorMessage = ErrorMessageString;
    requiredIfRule.ValidationType = "requiredif";
    requiredIfRule.ValidationParameters.Add("dependprop", this._propertyName);
    requiredIfRule.ValidationParameters.Add("value", Json.Encode(this._value));     

    yield return requiredIfRule;
}

The temptation here would be to navigate the viewContext.ViewData.TemplateInfo to return the GetFullHtmlFieldId, but from what I can tell, this information isn't available yet.

Client - wonky but working:

On the client, we'll wire up the adapter with a method that looks something like this:

<!-- language: lang-js -->
$.validator.unobtrusive.adapters.add('requiredif', ['dependprop', 'value'], function (options) {
    options.rules["requiredif"] = { 
        id: '#' + options.params['dependprop'],
        value: JSON.parse(options.params.value) 
    };
    options.messages['requiredif'] = options.message;
});

Notice this still just takes the plain old property name and assumes it can be used as the ID to locate the object.

By making some reasonable assumptions, we can build the full dependency property id. It should always be the case that the requiredif calling element is within the same scope as the property we're identify (that's how we were able to find it on the server via reflection).

So we'll grab that context from the sender, remove that property's name, and append our own like this inside of $.validator.unobtrusive.adapters.add:

<!-- language: lang-js -->
var curId = options.element.id;               // get full id      i.e. ViewModel_Comments
var context = curId.replace(/[^_]+$/, "");    // remove last prop i.e. ViewModel_
var targetProp = options.params['dependprop'] // target name      i.e. IsAdult
var targetId = '#' + context + targetProp;    // build target ID  i.e. #ViewModel_IsAdult

options.rules["requiredif"] = {
    id: targetId,
    value: JSON.parse(options.params.value)
};

That should help find the right client side property - then write whatever other conditions you need to meet.