#ExpressiveAnnotations - annotation-based conditional validation
Notice: This document describes latest implementation. For previous version < 2.0 take a look at EA1 branch.
ExpressiveAnnotations is a small .NET and JavaScript library, which provides annotation-based conditional validation mechanisms. Given implementations of RequiredIf and AssertThat attributes allows to forget about imperative way of step-by-step verification of validation conditions in many cases. This in turn results in less amount of code which is also more compacted, since fields validation requirements are applied as metadata, just in the place of such fields declaration.
###RequiredIf vs AssertThat attributes?
RequiredIf indicates that annotated field is required, when given condition is fulfilled. AssertThat on the other hand indicates, that non-empty annotated field is considered as valid, when given condition is fulfilled.
###What are brief examples of usage?
For sample usages go to demo project.
- Simplest:
[RequiredIf("GoAbroad == true")]
public string PassportNumber { get; set; }
Here we are saying that annotated field is required when dependent field has appropriate value (passport number is required, if go abroad option is selected). Simple enough, let's move to another variation:
[RequiredIf("ContactDetails.Email != null")]
public bool AgreeToContact { get; set; }
This one means, that if email is non-empty, boolean value indicating contact permission has to be true. What is more, we can see here that nested properties are supported by the mechanism.
[AssertThat("ReturnDate >= Today")]
public DateTime? ReturnDate { get; set; }
Here return date needs to be greater than or equal to the date given in target value. This time we are not validating field requirement as before. Now attribute puts restriction on field, which needs to be satisfied for such field to be considered as valid (restriction verification is executed for non-empty field).
- More complex:
[RequiredIf("GoAbroad == true " +
"&& (" +
"(NextCountry != 'Other' && NextCountry == Country) " +
"|| (Age > 24 && Age <= 55)" +
")")]
public string ReasonForTravel { get; set; }
Here we are saying that annotated field is required when computed result of given logical expression is true. How such an expression should be understood?
###How to construct conditional validation attributes? #####Signatures:
RequiredIfAttribute([string Expression], ...) - Validation attribute which indicates that annotated field is
required when computed result of given logical expression is
true.
AssertThatAttribute([string Expression], ...) - Validation attribute, executed for non-empty annotated field,
which indicates that assertion given in logical expression
has to be satisfied, for such field to be considered as valid.
Expression - Gets or sets the logical expression based on which requirement condition is computed.
#####Implementation:
/* EBNF GRAMMAR:
*
* expression => or-exp
* or-exp => and-exp [ "||" or-exp ]
* and-exp => not-exp [ "&&" and-exp ]
* not-exp => [ "!" ] rel-exp
* rel-exp => val [ rel-op val ]
* rel-op => "==" | "!=" | ">" | ">=" | "<" | "<="
* val => "null" | int | float | bool | string | prop | "(" or-exp ")"
*/
###What is the context behind this implementation?
Declarative validation, when compared to imperative approach, seems to be more convenient in many cases. Clean, compact code - all validation logic can be defined within the model metadata.
###What is the difference between declarative and imperative programming?
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 the step-by-step.
In our example it is more about metadata, e.g.
[RequiredIf("GoAbroad == true " +
"&& (" +
"(NextCountry != 'Other' && NextCountry == Country) " +
"|| (Age > 24 && Age <= 55)" +
")")]
public string ReasonForTravel { get; set; }
With imperative programming, you define the control flow of the computation which needs to be done. You tell the compiler what you want, 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 go abroad, why do you
want to visit the same country twice?");
return View("Home", model);
}
###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:
- Add ExpressiveAnnotations.dll and ExpressiveAnnotations.MvcUnobtrusiveValidatorProvider.dll reference libraries to your projest,
- In
Global.asax
register required validators (IClientValidatable
interface is not directly implemented by the attribute, to avoid coupling ofExpressionAnnotations
assembly withSystem.Web.Mvc
dependency):
protected void Application_Start()
{
DataAnnotationsModelValidatorProvider.RegisterAdapter(
typeof (RequiredIfAttribute), typeof (RequiredIfValidator));
DataAnnotationsModelValidatorProvider.RegisterAdapter(
typeof(AssertThatAttribute), typeof(AssertThatValidator));
- Include expressive.annotations.validate.js scripts in your page (do not forget standard jQuery validation scripts):
<script src="/Scripts/jquery.validate.js"></script>
<script src="/Scripts/jquery.validate.unobtrusive.js"></script>
...
<script src="/Scripts/expressive.annotations.validate.js"></script>
Alternatively, using the NuGet Package Manager Console (currently only previous version is published):
###PM> Install-Package ExpressiveAnnotations