jwaliszko / ExpressiveAnnotations

Annotation-based conditional validation library.
MIT License
351 stars 123 forks source link

logo

ExpressiveAnnotations[annotation-based conditional validation]

Build status Coverage status Release version License

A small .NET and JavaScript library which provides full-stack, annotation-based, conditional validation mechanisms.

Given attributes, powered by expressions engine, allow to forget about imperative way of step-by-step implementation of validation conditions in many cases. Since fields validation requirements are applied as metadata, domain-related code is more condensed.

Table of contents

What is the context behind this work?

There are number of cases where the concept of metadata is used for justified reasons. Attributes are one of the ways to associate complementary information with existing data. Such annotations may also define the correctness of data.

Declarative validation when compared to imperative approach seems to be more convenient in many cases. Clean, compact code - all validation logic defined within the model scope. Simple to write, obvious to read.

RequiredIf vs. AssertThat - where is the difference?

Sample projects + demo

ASP.NET MVC web sample is also hosted online - http://expressiveannotations.net/.

What are a brief examples of usage?

This section presents few exemplary code snippets. Sample projects in the section above contain much more comprehensive set of use cases.

using ExpressiveAnnotations.Attributes;

[RequiredIf("GoAbroad == true")]
public string PassportNumber { get; set; }

Above we are saying, that annotated field is required when condition given in the logical expression is satisfied (passport number is required, if go abroad field has true boolean value).

Simple enough, let's move to another variation:

[AssertThat("ReturnDate >= Today()")]
public DateTime? ReturnDate { get; set; }

By the usage of this attribute type, we are not validating field requirement as before - its value is allowed to be null this time. Nevertheless, if some value is already given, provided restriction needs to be satisfied (return date needs to be greater than or equal to the date returned by Today() built-in function).

As shown below, both types of attributes may be combined (moreover, the same type can be applied multiple times for a single field):

[RequiredIf("Details.Email != null")]
[RequiredIf("Details.Phone != null")]
[AssertThat("AgreeToContact == true")]
public bool? AgreeToContact { get; set; }

Literal translation means, that if either email or phone is provided, you are forced to authorize someone to contact with you (boolean value indicating contact permission has to be true). What is more, we can see that nested properties are supported by the expressions parser.

The complexity of expressions may be arbitrarily increased, e.g. take a brief look at the following construction:

[RequiredIf(@"GoAbroad == true
              && (
                     (NextCountry != 'Other' && NextCountry == Country)
                     || (Age > 24 && Age <= 55)
                 )")]
public string ReasonForTravel { get; set; }

Restriction above, despite being more specific than its predecessors, still can be quickly understood (reason for travel has to be provided if you plan to go abroad and, either want to visit the same definite country twice, or are between 25 and 55).

Conditional operations are supported (ternary operator defined). You can imply various assertions on specific field based on certain condition (or nested conditions), e.g.

[AssertThat("Switch == 'ON' ? Voltage1 == Voltage2 : true")]
public int Voltage1 { get; set; }

Here, when switch is ON, voltages must be equal - otherwise everything is OK. You could express the same statement without conditional operator, i.e.

[AssertThat("Switch == 'ON' && (Voltage1 == Voltage2) || (Switch != 'ON')")]

but it is less verbose.

Declarative vs. imperative programming - what is it about?

With declarative programming you write logic that expresses what you want, but not necessarily how to achieve it. You declare your desired results, but not step-by-step.

In our case, this concept is materialized by attributes, e.g.

[RequiredIf("GoAbroad == true && NextCountry != 'Other' && NextCountry == Country",
    ErrorMessage = "If you plan to travel abroad, why visit the same country twice?")]
public string ReasonForTravel { get; set; }

Here, we're saying "ensure the field is required according to given condition".

With imperative programming you define the control flow of the computation which needs to be done. You tell the compiler what you want, exactly step by step.

If we choose this way instead of model fields decoration, it has negative impact on the complexity of the code. Logic responsible for validation is now implemented somewhere else in our application, e.g. inside controllers actions instead of model class itself:

    if (!model.GoAbroad)
        return View("Success");
    if (model.NextCountry == "Other")
        return View("Success");
    if (model.NextCountry != model.Country)
        return View("Success");

    ModelState.AddModelError("ReasonForTravel",
        "If you plan to travel abroad, why visit the same country twice?");
    return View("Home", model);
}

Here instead, we're saying "if condition is met, return some view; otherwise, add error message to state container, return other view".

EA expressions specification

Grammar definition

Expressions handled by EA parser must comply with the following grammar:

exp         => cond-exp
cond-exp    => l-or-exp ['?' exp ':' exp]       // right associative (right recursive)
l-or-exp    => l-and-exp  ('||' l-and-exp)*     // left associative (non-recursive alternative to left recursion)
l-and-exp   => b-and-exp ('&&' b-and-exp)*
b-or-exp    => xor-exp ('|' xor-exp)*
xor-exp     => b-and-exp ('^' b-and-exp)*
b-and-exp   => eq-exp ('&' eq-exp)*
eq-exp      => rel-exp (('==' | '!=') rel-exp)*
rel-exp     => shift-exp (('>' | '>=' | '<' | '<=') shift-exp)*
shift-exp   => add-exp (('<<' | '>>') add-exp)*
add-exp     => mul-exp (('+' | '-') mul-exp)*
mul-exp     => unary-exp (('*' | '/' | '%')  unary-exp)*
unary-exp   => ('+' | '-' | '!' | '~') unary-exp | primary-exp
primary-exp => null-lit | bool-lit | num-lit | string-lit | arr-access | id-access | '(' exp ')'

arr-access  => arr-lit |
               arr-lit '[' exp ']' ('[' exp ']' | '.' identifier)*

id-access   => identifier |
               identifier ('[' exp ']' | '.' identifier)* |
               func-call ('[' exp ']' | '.' identifier)*

func-call   => identifier '(' [exp-list] ')'

null-lit    => 'null'
bool-lit    => 'true' | 'false'
num-lit     => int-lit | float-lit
int-lit     => dec-lit | bin-lit | hex-lit
array-lit   => '[' [exp-list] ']'

exp-list    => exp (',' exp)*

Terminals are expressed in quotes. Each nonterminal is defined by a rule in the grammar except for dec-lit, bin-lit, hex-lit, float-lit, string-lit and identifier, which are assumed to be implicitly defined (identifier specifies names of functions, properties, constants and enums).

Expressions are built of Unicode letters and numbers (i.e. [L*] and [N*] categories respectively) with the usage of the following components:

Where syntax meets semantics

EA expressions syntax is defined by the grammar shown above.

Valid expressions must follow not only lexical and syntax level rules, but must have appropriate semantics as well.

Grammars, as already mentioned, naturally define the language syntax - but not only. Grammars in general contribute to the semantics a little, by defining the parse tree - thus denoting the precedence and associativity of operators. This being said, it is not just about the legal string being produced - the structure of parse tree corresponds to the order, in which various parts of the expression are to be evaluated.

Such contribution to semantics is not enough though. Grammar says nothing about the types of operands. For the expressions to have valid semantics, all the requirements on the types of operands must be realized as well.

Prior to execution of an operation, type checks and eventual type conversions are made. The result type, e.g. of a valid binary operation is, most of the time but not always, the same as the most general of the input types. When a binary operation is used then a generalization of operands is performed, to make the two operands the same (most general) type, before the operation is done. The order of generalization, from most general to most specific, is briefly described as follows: string (generalization done using the current locale) -> double -> int. Also nullable types are more general than their non-nullable counterparts.

Operators precedence and associativity

The following table lists the precedence and associativity of operators (listed top to bottom, in descending precedence):

Precedence Operator Description Associativity
1 ()
Function call (postfix) Left to right
[]
Subscript (postfix)
. Member access (postfix)
2 + - Unary plus and minus Right to left
! ~ Logical NOT and bitwise NOT (one's complement)
3 * / % Multiplication, division, and remainder Left to right
4 + - Addition and subtraction
5 << >> Bitwise left shift and right shift
6 < <= Relational operators < and ≤ respectively
> >= Relational operators > and ≥ respectively
7 == != Equality operators = and ≠ respectively
8 & Bitwise AND
9 ^ Bitwise XOR (exclusive OR)
10 | Bitwise OR (inclusive OR)
11 && Logical AND
12 || Logical OR
13 ?: Ternary conditional Right to left
Built-in functions (methods ready to be used by expressions)

As already noted, there is an option to reinforce expressions with functions, e.g.

[AssertThat("StartsWith(CodeName, 'abc.') || EndsWith(CodeName, '.xyz')")]
public string CodeName { get; set; }

Toolchain functions available out of the box at server- and client-side:

How to construct conditional validation attributes?

Signatures description
RequiredIfAttribute(
    string expression,
    [bool AllowEmptyStrings],
    [int Priority]
    [string ErrorMessage]    ...) /* Validation attribute, executed for null-only annotated
                                   * field, which indicates that such a field is required
                                   * to be non-null, when computed result of given logical
                                   * expression is true. */
AssertThatAttribute(
    string expression,
    [int Priority]
    [string ErrorMessage]    ...) /* Validation attribute, executed for non-null annotated
                                   * field, which indicates that assertion given in logical
                                   * expression has to be satisfied, for such a field to be
                                   * considered as valid. */
expression        - The logical expression based on which specified condition is computed.
AllowEmptyStrings - Gets or sets a flag indicating whether the attribute should allow empty
                    or whitespace strings. False by default.
Priority          - Gets or sets the hint, available for any concerned external components,
                    indicating the order in which this attribute should be executed among
                    others of its kind. Value is optional and not set by default, which 
                    means that execution order is undefined.
ErrorMessage      - Gets or sets an explicit error message string. A difference to default
                    behavior is awareness of new format items, i.e. {fieldPath[:indicator]}.
                    Given in curly brackets, can be used to extract values of specified
                    fields, e.g. {field}, {field.field}, within current model context or
                    display names of such fields, e.g. {field:n}. Braces can be escaped by
                    double-braces, i.e. to output a { use {{ and to output a } use }}. The
                    same logic works for messages provided in resources.

Note above covers almost exhaustively what is actually needed to work with EA. Nevertheless, the full API documentation, generated with Sandcastle (with the support of SHFB), can be downloaded (in the form of compiled HTML help file) from here (includes only C# API, no JavaScript part there).

Implementation details outline

Implementation core is based on expressions parser, which runs on the grammar shown above.

Firstly, at the lexical analysis stage, character stream of the expression is converted into token stream (whitespaces ignored, characters grouped into tokens and associated with position in the text). Next, at the syntax analysis level, abstract syntax tree is constructed according to the rules defined by the grammar. While the tree is being built, also the 3rd stage, mainly semantic analysis, is being performed. This stage is directly related to operands type checking (and eventual type conversions according to type generalization rules, when incompatible types are detected).

Based on valid expression string expression tree structure is finally being built. A delegate containing compiled version of the lambda expression (defined by the expression tree) is returned as a result of the parsersing mechanism. Such delegate can be then invoked for specified model object.

When expression is provided to the attribute, it should be of a boolean type. The result of its evaluation indicates whether the assertion or requirement condition is satisfied or not.

For the sake of performance optimization, expressions provided to attributes are compiled only once. Such compiled lambdas are then cached inside attributes instances and invoked for any subsequent validation requests without recompilation.

When working with ASP.NET MVC stack, unobtrusive client-side validation mechanism is additionally available. Client receives unchanged expression string from server. Such an expression is then evaluated using JavaScript eval() method within the context of reflected model object. Such a model, analogously to the server-side one, is basically deserialized DOM form (with some type-safety assurances and registered toolchain methods).

Traps (discrepancies between server- and client-side expressions evaluation)

Because client-side handles expressions in its unchanged form (as provided to attribute), attention is needed when dealing with null keyword - there are discrepancies between EA parser (mostly follows C# rules) and JavaScript, e.g.

What about the support of ASP.NET MVC client-side validation?

Client-side validation is fully supported. Enable it for your web project within the next few steps:

  1. Reference both assemblies to your project: core ExpressiveAnnotations.dll (defines validation attributes driven by expressions) and subsidiary ExpressiveAnnotations.MvcUnobtrusive.dll (defines model validators for ASP.NET MVC).
  2. In Global.asax register required validators (IClientValidatable interface is not directly implemented by the attributes, to avoid coupling of ExpressionAnnotations assembly with System.Web.Mvc dependency):

    using ExpressiveAnnotations.Attributes;
    using ExpressiveAnnotations.MvcUnobtrusive.Validators;
    
    protected void Application_Start()
    {
        DataAnnotationsModelValidatorProvider.RegisterAdapter(
            typeof (RequiredIfAttribute), typeof (RequiredIfValidator));
        DataAnnotationsModelValidatorProvider.RegisterAdapter(
            typeof (AssertThatAttribute), typeof (AssertThatValidator));

    Alternatively, use predefined ExpressiveAnnotationsModelValidatorProvider (recommended):

    using ExpressiveAnnotations.MvcUnobtrusive.Providers;
    
    protected void Application_Start()
    {
        ModelValidatorProviders.Providers.Remove(
            ModelValidatorProviders.Providers
                .FirstOrDefault(x => x is DataAnnotationsModelValidatorProvider));
        ModelValidatorProviders.Providers.Add(
            new ExpressiveAnnotationsModelValidatorProvider());

    Despite the fact this provider automatically registers adapters for expressive validation attributes, it additionally respects their processing priorities when validation is performed (i.e. the Priority property actually means something in practice).

  3. Include expressive.annotations.validate.js script (makes client-side validation to work out of the box) in your page. It should be included in bundle below jQuery validation files:

    <script src="https://github.com/jwaliszko/ExpressiveAnnotations/raw/master/Scripts/jquery.validate.js"></script>
    <script src="https://github.com/jwaliszko/ExpressiveAnnotations/raw/master/Scripts/jquery.validate.unobtrusive.js"></script>
    ...
    <script src="https://github.com/jwaliszko/ExpressiveAnnotations/raw/master/Scripts/expressive.annotations.validate.js"></script>

For supplementary reading visit the installation section.

Frequently asked questions

Is it possible to compile all usages of annotations at once?

Yes, a complete list of types with annotations can be retrieved and compiled collectively. It can be useful, e.g. during unit testing phase, when without the necessity of your main application startup, all the compile-time errors (syntax errors, type checking errors) done to your expressions can be discovered. The following extension is helpful:

public static IEnumerable<ExpressiveAttribute> CompileExpressiveAttributes(this Type type)
{
    var properties = type.GetProperties()
        .Where(p => Attribute.IsDefined(p, typeof (ExpressiveAttribute)));
    var attributes = new List<ExpressiveAttribute>();
    foreach (var prop in properties)
    {
        var attribs = prop.GetCustomAttributes<ExpressiveAttribute>().ToList();
        attribs.ForEach(x => x.Compile(prop.DeclaringType));
        attributes.AddRange(attribs);
    }
    return attributes;
}

with the succeeding usage manner:

// compile all expressions for specified model:
var compiled = typeof (SomeModel).CompileExpressiveAttributes().ToList();

// ... or for current assembly:
compiled = Assembly.GetExecutingAssembly().GetTypes()
    .SelectMany(t => t.CompileExpressiveAttributes()).ToList();

// ... or for all assemblies within current domain:
compiled = AppDomain.CurrentDomain.GetAssemblies()
    .SelectMany(a => a.GetTypes()
        .SelectMany(t => t.CompileExpressiveAttributes())).ToList();

Notice that such compiled lambdas will be cached inside attributes instances stored in compiled list. That means that subsequent compilation requests:

compiled.ForEach(x => x.Compile(typeof (SomeModel));

do nothing (due to optimization purposes), unless invoked with enabled recompilation switch:

compiled.ForEach(x => x.Compile(typeof (SomeModel), force: true);

Finally, this solution reveals compile-time errors only, you can still can get runtime errors though, e.g.:

var parser = new Parser();
parser.AddFunction<object, bool>("CastToBool", obj => (bool) obj);

parser.Parse<object>("CastToBool(null)"); // compilation succeeds
parser.Parse<object>("CastToBool(null)").Invoke(null); // invocation fails (type casting err)
What if there is no built-in function I need?

Create it yourself. Any custom function defined within the model class scope at server-side is automatically recognized and can be used inside expressions, e.g.

class Model
{
    public bool IsBloodType(string group)
    {
        return Regex.IsMatch(group, @"^(A|B|AB|0)[\+-]$");
    }

    [AssertThat("IsBloodType(BloodType)")] // method known here (context aware expressions)
    public string BloodType { get; set; }

If client-side validation is needed as well, function of the same signature (name and the number of parameters) must be available there. JavaScript corresponding implementation should be registered by the following instruction:

<script>
    ea.addMethod('IsBloodType', function(group) {
        return /^(A|B|AB|0)[\+-]$/.test(group);
    });

Many signatures can be defined for a single function name. Types are not taken under consideration as a differentiating factor though. Methods overloading is based on the number of arguments only. Functions with the same name and exact number of arguments are considered as ambiguous. The next issue important here is the fact that custom methods take precedence over built-in ones. If exact signatures are provided built-in methods are simply overridden by new definitions.

Can I have custom utility-like functions outside of my models?

Sure, provide your own methods provider, or extend existing global one, i.e.

  • extend existing provider:

    protected void Application_Start()
    {
        Toolchain.Instance.AddFunction<int[], int>("ArrayLength", array => array.Length);
  • define new provider:

    public class CustomFunctionsProvider : IFunctionsProvider
    {
        public IDictionary<string, IList<LambdaExpression>> GetFunctions()
        {
            return new Dictionary<string, IList<LambdaExpression>>
            {
                {"ArrayLength", new LambdaExpression[] {(Expression<Func<int[], int>>) (array => array.Length)}}
            };
        }
    }
    
    protected void Application_Start()
    {
        Toolchain.Instance.Recharge(new CustomFunctionsProvider());
How to cope with values of custom types?

If you need to handle value string extracted from DOM field in any non built-in way, you can redefine given type-detection logic. The default mechanism recognizes and handles automatically types identified as: timespan, datetime, enumeration, number, string, bool and guid. If non of them is matched for a particular field, JSON deserialization is invoked. You can provide your own deserializers though. The process is as follows:

Finally, there is a possibility to override built-in conversion globally. In this case, use the type name to register your value parser - all fields of such a type will be intercepted by it, e.g.

<script>
    ea.addValueParser('typename', function(value) {
        return ... // handle specified type (numeric, datetime, etc.) parsing on your own
    });

If you redefine default mechanism, you can still have the ValueParser annotation on any fields you consider exceptional - annotation gives the highest parsing priority.

How to cope with dates given in non-standard formats?

When values of DOM elements are extracted, they are converted to appropriate types. For fields containing date strings, JavaScript Date.parse() method is used by default. As noted in MDN, the input parameter is:

A string representing an RFC 2822 or ISO 8601 date (other formats may be used, but results may be unexpected)

When some non-standard format needs to be handled, simply override the default behavior and provide your own implementation. E.g. when dealing with UK format dd/mm/yyyy, solution is:

class Model
{
    [ValueParser('ukdateparser')]
    public DateTime SomeField { get; set; }
<script>
    ea.addValueParser('ukdateparser', function(value) {
        var arr = value.split('/');
        var date = new Date(arr[2], arr[1] - 1, arr[0]);
        return date.getTime(); // return msecs since January 1, 1970, 00:00:00 UTC
    });
What if "ea" variable is already used by another library?

Use noConflict() method. In case of naming collision return control of the ea variable back to its origins. Old references of ea are saved during ExpressiveAnnotations initialization - noConflict() simply restores them:

<script src="https://github.com/jwaliszko/ExpressiveAnnotations/raw/master/another.js"></script>
<script src="https://github.com/jwaliszko/ExpressiveAnnotations/raw/master/expressive.annotations.validate.js"></script>
<script>
    var expann = ea.noConflict(); // relinquish EA's control of the `ea` variable
    expann.addMethod... // do something with ExpressiveAnnotations
    ea... // do something with original ea variable
How to control frequency of dependent fields validation?

When a field value is modified, validation results for some other fields, directly dependent on currenty modified one, may be affected. To control the frequency of when dependent fields validation is triggered, change default ea.settings.dependencyTriggers settings. It is a string containing one or more DOM field event types (such as change, keyup or custom event names), associated with currently modified field, for which fields directly dependent on are validated. Multiple event types can be bound at once by including each one separated by a space.

Default value is 'change keyup' (for more information check eventType parameter of jQuery bind() method). If you want to turn this feature off entirely, set it to empty string, null or undefined (validation will be fired on form submit attempt only).

<script>
    ea.settings.dependencyTriggers = 'change'; // mute some excessive activity if you wish,
                                               // or turn it off entirely (set to undefined)

Alternatively, to enforce re-binding of already attached validation handlers, use following construction:

<script>
    ea.settings.apply({
        dependencyTriggers: 'new set of events'
    });
Can I increase web console verbosity for debug purposes?

If you need more insightful overview of what client-side script is doing, enable verbose logging:

<script>
    ea.settings.debug = true; // output debug messages to the web console
                              // (should be disabled for release code not to introduce redundant overhead)

Warnings and errors are always flushed to the console, no matter this feature is enabled or not.

I prefer enum values to be rendered as numbers, is it allowed?

There is a possibility to setup of how enumeration values are internally processed: as integral numerics or string identifiers - see enumsAsNumbers settings flag in the script.

This setting should be consistent with the way of how input fields values are stored in HTML, e.g. @Html.EditorFor(m => m.EnumValue) renders to string identifier by default, in contrast to @Html.EditorFor("EnumValue", (int)Model.EnumValue) statement, where value is explicitly casted to int. The flag setup should reflect that behavior, to have the internal JS model context deserialized accordingly.

How to access one method from another?

Through this. You may want to turn the registerAllMethods option on. When enabled, EA attempts to register all of the methods within the model context, unless naming conflict with field identifier occurs - registration of such a particular method is then skipped.

<script>
    ea.settings.registerAllMethods = true;

    ea.addMethod('MethodA', function() {
        ...
    });
    ea.addMethod('MethodB', function() {
        return this.MethodA();
    });
How to access current control from inside a method?

Simply access it through the __meta__ property of this scope, e.g.

<script>
    ea.addMethod('Method', function() {
        var elem = this.__meta__.element;
        ...
    });

Alternatively, you can pass the field name to the method and then look for it, e.g.

[AssertThat("Method('SomeField')")]
public string SomeField { get; set; }
<script>
    ea.addMethod('Method', function(name) {
        var field = $(':input[name="' + name + '"]')
        ...
    });
How to fetch field's value or its display name in error message?

Notice that {{ is treated as the escaped bracket character.

Is there any event raised when validation is done?

Each element validated by EA triggers one or more eavalid events. Attach to this event type in the following manner:

<script>
    $('form').find('input, select, textarea').on('eavalid', function(e, type, valid, expr, cond, index) {
        console.log('event triggered by ' + e.currentTarget.name);
    });

The parameters are as follows:

*where the last two parameters are available only for 'requiredif' type of validation.

Can I decorate conditionally required fields with asterisks?

EA does not provide any built-in mechanisms to manipulate DOM. Nevertheless, the asterisk decoration (or any similar effect) can be relatively easily achieved with the support of a supplementary code you could write for yourself. It will be based on the eavalid events handling. Such an event type is triggered by EA when a field validation is performed. Please take a look at the snippet below (or run web sample to see it working):

<script>
    ea.settings.optimize = false; // if flag is on, requirement expression is not needlessly evaluated for non-empty fields
                                  // otherwise, it is evaluated and such an evaluation result is provided to the eavalid event

    $(document).ready(function() {
        var selector = 'input, select, textarea';

        $('form').find(selector).on('eavalid', function(e, type, valid, expr, cond, idx) { // verify asterisk visibility based on computed condition
            if (type === 'requiredif' && cond !== undefined) {
                if (idx === 0) { // if first of its kind...
                    e.currentTarget.eacond = false; // ...reset asterisk visibility flag
                }
                e.currentTarget.eacond |= cond; // multiple requiredif attributes can be applied to a single field - remember if any condition is true

                if (e.currentTarget.eacond) {
                    $(e.currentTarget).closest('li').find('.asterisk').show(); // show asterisk for required fields (no matter if valid or not)
                }
                else {
                    $(e.currentTarget).closest('li').find('.asterisk').hide();
                }
            }
        });

        $('form').find(selector).each(function() { // apply asterisks for required or conditionally required fields
            if ($(this).attr('data-val-required') !== undefined // make sure implicit required attributes for value types are disabled i.e. DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false
                || $(this).attr('data-val-requiredif') !== undefined) {
                $(this).parent('li').prepend('<span class="asterisk">*</span>');
            }
        });

        $('form').find(selector).each(function() { // run validation for every field to verify asterisks visibility
            if ($(this).attr('data-val-requiredif') !== undefined) {
                $(this).valid();
                $(this).removeClass('input-validation-error');
                $(this).parent().find('.field-validation-error').empty();
            }
        });
    });

For the needs of this example, the code above makes assumptions:

How to handle validation based on parent model field?

In this case you need to have a back reference to your container, i.e.

public class TParent
{
    public TParent()
    {
        Child.Parent = this;
    }

    public TChild Child { get; set; }
    public bool Flag { get; set; }
}

public class TChild
{     
    public TParent Parent { get; set; } // back-reference to parent

    [RequiredIf("Parent.Flag")]
    public string IsRequired { get; set; }
}

This is all you need for server-side. Unfortunately, out-of-the-box client-side support of back-references to the parent context is not implemented. EA at client-side is straightforward and designed to handle nested properties when explicitly provided to the view, without abilities to infer any relationships.

There still exists a way to handle it though. When using HTML helpers at the client-side, the more nested property, the more prefixed name. When reference to container is used in the expression, what client-side script actually does, it looks for such a property within the current context, i.e. within the fields of names annotated with the current prefix. EA internally has no idea that such a property refers to a field from a container (most likely already existing form field with different prefix).

In this exemplary case above, the Flag property used in the expression Parent.Flag != null, makes client-side EA to look for analogic input field within current scope, i.e. Child.Parent.Flag. The "Child." prefix corresponds with what HTML helper prepends to names of nested properties from the Child object.

The solution is to provide such a field, EA expects to find, i.e.

@Html.HiddenFor(m => m.Child.Parent.Flag) // hidden mock

Next, make the mocked field clone the behavior of the original (from parent) one:

<script type="text/javascript">

    function Mimic(src, dest) {
        $(src).on(ea.settings.dependencyTriggers,
            function(e) {
                $(dest).val($(this).val());
                $(dest).trigger(e.type); // trigger the change for dependency validation
            });
    }

    $(document).ready(function() {
        Mimic('input[name="@Html.NameFor(m => m.Flag)"]',
              'input[name="@Html.NameFor(m => m.Child.Parent.Flag)"]');
    });

</script>

Please note, that this slightly hacky workaround is only needed for such a specific cases, where back-references are required to be handled by client-side EA logic.

RequiredIf attribute is not working, what is wrong?

Make sure RequiredIf is applied to a field which accepts null values.

In the other words, it is redundant to apply this attribute to a field of non-nullable value type, like e.g. int, which is a struct representing integral numeric type, DateTime, etc. Because the value of such a type is always non-null, requirement demand is constantly fulfilled. Instead, for value types use their nullable forms, e.g. int?, DateTime?, etc.

[RequiredIf("true")] // no effect...
public int Value { get; set; } // ...unless int? is used
[RequiredIf("true")] // no effect...
public DateTime Value { get; set; } // ...unless DateTime? is used
Few fields seem to be bypassed during validation, any clues?

Most likely these input fields are hidden, and :hidden fields validation is off by default.

Such a fields are ignored by default by jquery-validation plugin, and EA follows analogic rules. If this is true, try to enable validation for hidden fields (empty-out the ignore filter), i.e.:

<script>
    $(function() { // DOM needs to be ready...
        var validator = $('form').validate(); // ...for the form to be found
        validator.settings.ignore = ''; // enable validation for ':hidden' fields
    });
Client-side validation doesn't work, how to troubleshoot it?
Is there a possibility to perform asynchronous validation?

Currently not. Although there is an ongoing work on async-work branch, created especially for asynchronous-related ideas. If you feel you'd like to contribute, either by providing better solution, review code or just test what is currently there, your help is always highly appreciated.

What if my question is not covered by FAQ section?

If you're searching for an answer to some other problem, not covered by this document, try to browse through already posted issues labelled by question tag, or possibly have a look at Stack Overflow.

Installation instructions

Simplest way is using the NuGet Package Manager Console:

Contributors

GitHub Users

Special thanks to Szymon Małczak

License

Copyright (c) 2014 Jarosław Waliszko

Licensed MIT: http://opensource.org/licenses/MIT