From b44d1fc221efe457fcb02317a9c933369a5d4eb0 Mon Sep 17 00:00:00 2001 From: Jaroslaw Waliszko Date: Wed, 12 Aug 2015 18:35:25 +0200 Subject: [PATCH] README + dosc updated. Version up. --- README.md | 209 +++++++++++++----- doc/api/api.chm | Bin 160031 -> 164861 bytes doc/api/api.shfbproj | 19 +- .../Attributes/ValueParserAttribute.cs | 2 +- .../Properties/AssemblyInfo.cs | 4 +- .../ExpressiveAnnotations.MvcWebSample.csproj | 2 +- .../{Content => }/favicon.ico | Bin .../Properties/AssemblyInfo.cs | 4 +- src/expressive.annotations.validate.js | 2 +- 9 files changed, 170 insertions(+), 72 deletions(-) rename src/ExpressiveAnnotations.MvcWebSample/{Content => }/favicon.ico (100%) diff --git a/README.md b/README.md index 2f97954..ecd6de8 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,13 @@ ExpressiveAnnotations is a small .NET and JavaScript library, which provides ann - [Built-in functions](#built-in-functions) - [What about the support of ASP.NET MVC client-side validation?](#what-about-the-support-of-aspnet-mvc-client-side-validation) - [Frequently asked questions](#frequently-asked-questions) - - [What if there is no built-in function I need?](#what-if-there-is-no-built-in-function-i-need) - - [How to cope with dates given in non-standard formats?](#how-to-cope-with-dates-given-in-non-standard-formats) - - [What if `ea` variable is already used by another library?](#what-if-ea-variable-is-already-used-by-another-library) - - [How to control frequency of dependent fields validation?](#how-to-control-frequency-of-dependent-fields-validation) + - [Is it possible to compile all usages of annotations at once?](#is-it-possible-to-compile-all-usages-of-annotations-at-once) (re server-side) + - [What if there is no built-in function I need?](#what-if-there-is-no-built-in-function-i-need) (re client and server-side) + - [How to cope with values of custom types?](#how-to-cope-with-values-of-custom-types) (re client-side) + - [How to cope with dates given in non-standard formats?](#how-to-cope-with-dates-given-in-non-standard-formats) (re client-side) + - [What if `ea` variable is already used by another library?](#what-if-ea-variable-is-already-used-by-another-library) (re client-side) + - [How to control frequency of dependent fields validation?](#how-to-control-frequency-of-dependent-fields-validation) (re client-side) + - [How to boost web console verbosity for debug purposes?](#how-to-boost-web-console-verbosity-for-debug-purposes) (re client-side) - [What if my question is not covered by FAQ section?](#what-if-my-question-is-not-covered-by-faq-section) - [Installation](#installation) - [Contributors](#contributors) @@ -80,11 +83,11 @@ Finally, take a brief look at following construction: public string ReasonForTravel { get; set; } ``` -Restriction above is slightly more complex than its predecessors, but still can be quickly understood (reason for travel has to be provided if you plan to go abroad and: want to visit the same definite country twice, or are between 25 and 55 years old). +Restriction above is slightly more complex than its predecessors, but 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). ###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 the necessarily step-by-step. +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. ```C# @@ -92,6 +95,8 @@ In our case, this concept is materialized by attributes, e.g. 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: @@ -108,6 +113,7 @@ If we choose this way instead of model fields decoration, it has negative impact 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." ###How to construct conditional validation attributes? @@ -161,7 +167,8 @@ Logical expressions should be built according to the syntax defined by grammar, * binary operators: `||`, `&&`, `!`, * relational operators: `==`, `!=`,`<`, `<=`, `>`, `>=`, * arithmetic operators: `+`, `-`, `*`, `/`, -* brackets: `(`, `)`, +* curly brackets: `(`, `)`, +* square brackets: `[`, `]`, * alphanumeric characters with the support of `,`, `.`, `_`, `'` and whitespaces, used to synthesize suitable literals: * null literal: `null`, * integer number literals, e.g. `123`, @@ -172,10 +179,13 @@ Logical expressions should be built according to the syntax defined by grammar, * property names, e.g. `SomeProperty`, * constants, e.g. `SomeType.CONST`, * enum values, e.g. `SomeEnumType.SomeValue`, + * arrays indexing, e.g. `SomeArray[0]`, * function invocations, e.g. `SomeFunction(...)`. Specified expression string is parsed and converted into [expression tree](http://msdn.microsoft.com/en-us/library/bb397951.aspx) structure. A delegate containing compiled version of the lambda expression described by produced expression tree is returned as a result of the parser job. Such delegate is then invoked for specified model object. As a result of expression evaluation, boolean flag is returned, indicating that expression is true or false. +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](#what-about-the-support-of-aspnet-mvc-client-side-validation). Client receives unchanged expression string from server. Such an expression is then evaluated using JavaScript [`eval()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/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 @@ -196,49 +206,49 @@ public string CodeName { get; set; } ``` Toolchain functions available out of the box at server- and client-side: -* `DateTime Now()` - * Gets the current local date and time (client-side returns the number of milliseconds since January 1, 1970, 00:00:00 UTC). -* `DateTime Today()` - * Gets the current date with the time component set to 00:00:00 (client-side returns the number of milliseconds since January 1, 1970, 00:00:00 UTC). -* `DateTime Date(int year, int month, int day)` - * Initializes a new date to a specified year, month (months are 1-based), and day, with the time component set to 00:00:00 (client-side returns the number of milliseconds since January 1, 1970, 00:00:00 UTC). -* `DateTime Date(int year, int month, int day, int hour, int minute, int second)` +* `DateTime Now()` + * Gets the current local date and time (client-side returns the number of milliseconds since January 1, 1970, 00:00:00 UTC). +* `DateTime Today()` + * Gets the current date with the time component set to 00:00:00 (client-side returns the number of milliseconds since January 1, 1970, 00:00:00 UTC). +* `DateTime Date(int year, int month, int day)` + * Initializes a new date to a specified year, month (months are 1-based), and day, with the time component set to 00:00:00 (client-side returns the number of milliseconds since January 1, 1970, 00:00:00 UTC). +* `DateTime Date(int year, int month, int day, int hour, int minute, int second)` * Initializes a new date to a specified year, month (months are 1-based), day, hour, minute, and second (client-side returns the number of milliseconds since January 1, 1970, 00:00:00 UTC). * `TimeSpan TimeSpan(int days, int hours, int minutes, int seconds)` - * Initializes a new time period according to specified days, hours, minutes, and seconds (client-side period is expressed in milliseconds). -* `int Length(str)` - * Gets the number of characters in a specified string (null-safe). -* `string Trim(string str)` - * Removes all leading and trailing white-space characters from a specified string (null-safe). -* `string Concat(string strA, string strB)` - * Concatenates two specified strings (null-safe). -* `string Concat(string strA, string strB, strC)` - * Concatenates three specified strings (null-safe). -* `int CompareOrdinal(string strA, string strB)` - * Compares strings using ordinal sort rules. An integer that indicates the lexical relationship - between the two comparands is returned (null-safe): - * -1 - strA is less than strB, - *  0 - strA and strB are equal, - *  1 - strA is greater than strB. -* `int CompareOrdinalIgnoreCase(string strA, string strB)` - * Compares strings using ordinal sort rules and ignoring the case of the strings being compared (null-safe). -* `bool StartsWith(string str, string prefix)` - * Determines whether the beginning of specified string matches a specified prefix (null-safe). -* `bool StartsWithIgnoreCase(string str, string prefix)` - * Determines whether the beginning of specified string matches a specified prefix, ignoring the case of the strings (null-safe). -* `bool EndsWith(string str, string suffix)` - * Determines whether the end of specified string matches a specified suffix (null-safe). -* `bool EndsWithIgnoreCase(string str, string suffix)` - * Determines whether the end of specified string matches a specified suffix, ignoring the case of the strings (null-safe). -* `bool Contains(string str, string substr)` - * Returns a value indicating whether a specified substring occurs within a specified string (null-safe). -* `bool ContainsIgnoreCase(string str, string substr)` - * Returns a value indicating whether a specified substring occurs within a specified string, ignoring the case of the strings (null-safe). -* `bool IsNullOrWhiteSpace(string str)` - * Indicates whether a specified string is null, empty, or consists only of white-space characters (null-safe). -* `bool IsDigitChain(string str)` - * Indicates whether a specified string represents a sequence of digits (ASCII characters only, null-safe). -* `bool IsNumber(string str)` + * Initializes a new time period according to specified days, hours, minutes, and seconds (client-side period is expressed in milliseconds). +* `int Length(str)` + * Gets the number of characters in a specified string (null-safe). +* `string Trim(string str)` + * Removes all leading and trailing white-space characters from a specified string (null-safe). +* `string Concat(string strA, string strB)` + * Concatenates two specified strings (null-safe). +* `string Concat(string strA, string strB, strC)` + * Concatenates three specified strings (null-safe). +* `int CompareOrdinal(string strA, string strB)` + * Compares strings using ordinal sort rules. An integer that indicates the lexical relationship + between the two comparands is returned (null-safe): + * -1 - strA is less than strB, + *  0 - strA and strB are equal, + *  1 - strA is greater than strB. +* `int CompareOrdinalIgnoreCase(string strA, string strB)` + * Compares strings using ordinal sort rules and ignoring the case of the strings being compared (null-safe). +* `bool StartsWith(string str, string prefix)` + * Determines whether the beginning of specified string matches a specified prefix (null-safe). +* `bool StartsWithIgnoreCase(string str, string prefix)` + * Determines whether the beginning of specified string matches a specified prefix, ignoring the case of the strings (null-safe). +* `bool EndsWith(string str, string suffix)` + * Determines whether the end of specified string matches a specified suffix (null-safe). +* `bool EndsWithIgnoreCase(string str, string suffix)` + * Determines whether the end of specified string matches a specified suffix, ignoring the case of the strings (null-safe). +* `bool Contains(string str, string substr)` + * Returns a value indicating whether a specified substring occurs within a specified string (null-safe). +* `bool ContainsIgnoreCase(string str, string substr)` + * Returns a value indicating whether a specified substring occurs within a specified string, ignoring the case of the strings (null-safe). +* `bool IsNullOrWhiteSpace(string str)` + * Indicates whether a specified string is null, empty, or consists only of white-space characters (null-safe). +* `bool IsDigitChain(string str)` + * Indicates whether a specified string represents a sequence of digits (ASCII characters only, null-safe). +* `bool IsNumber(string str)` * Indicates whether a specified string represents integer or float number (ASCII characters only, null-safe). * `bool IsEmail(string str)` * Indicates whether a specified string represents valid e-mail address (null-safe). @@ -293,6 +303,59 @@ For supplementary reading visit the [installation section](#installation). ###Frequently asked questions +#####Is it possible to compile all usages of annotations at once? + +Yes, a list of types with annotations can be collected and compiled collectively. It can be useful, e.g. during unit tesing phase, when without the necessity of your main application startup, all the compile-time errors (syntax errors, typechecking errors) done to your expressions can be discovered. The following extension is helpful: + +``` +public static IEnumerable CompileExpressiveAttributes(this Type type) +{ + var properties = type.GetProperties() + .Where(p => Attribute.IsDefined(p, typeof (ExpressiveAttribute))); + var attributes = new List(); + foreach (var prop in properties) + { + var attribs = prop.GetCustomAttributes().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 reveals compile-time errors only, you can still can get runtime errors though, e.g.: + +``` +var parser = new Parser(); +parser.AddFunction("CastToBool", obj => (bool) obj); + +parser.Parse("CastToBool(null)"); // compilation succeeds +parser.Parse("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. @@ -316,23 +379,46 @@ class Model ``` 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. +#####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`, `numeric`, `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: + +* at server-side decorate your property with special attribute which gives a hint to client-side, which parser should be chosen for corresponding DOM field value deserialization: + ```C# + class Model + { + [ValueParser('customparser')] + public CustomType SomeField { get; set; } + ``` + +* at client-side register such a parser: + ```JavaScript +