Purpose

ScriptAnnotations is a set of client components designed to allow MVVM frameworks, like Knockoutjs, to leverage DataAnnotations for unobtrusive JQuery / MVC style validation. These components install as a nuget package and contain the following components.

  • JasonSoft.ScriptAnnotations.dll – A managed .NET assembly that contains a class which inherits from the Newtonsoft.JsonConverter and is responsible for transforming your serialized Json to include meta-data provided by any data annotations your class may contain. ScriptAnnotations provides out-of-the-box support for all of the .Net Framework supported data annotations and exports several public interfaces that work with Managed Extensibility Framework to allow you add support for any of your custom DataAnnotations.
  • HtmlHelperExtensions – A class that nuget installs into a directory called Extensions in your web projects root and contains an extension method for the MVC HttpHelper class that allows you to render new instances of serialized types when the page initially loads. This is meant be a performance optimization that prevents you from needing to make a separate Ajax call just to load empty object instances.
  • scriptAnnotations.Knockout.js – A JavaScript file placed into your web application’s Scripts directory and extends Knockout.js with a function called fromAnnotatedObject. This function is similar to ko.mapping.fromJS() in that it converts a Json string into a JavaScript object with fields decorated with ko.observable() but with two important differences:
    • It ONLY wraps the fields in ko.observable that you decorated with DataAnnotations on the server side.
    • It automatically extends each observable field with knockout validation rules that corespond to those defined in your DataAnnotations.
  • App_Start.ScriptAnnotations class – ScriptAnnotations uses the WebActivator component to provide boot strapping functions when  your app starts. The boot strapping preforms two functions 1) It adds the AnnotatedJsonConverter to  GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.Converters and 2) it registers scriptAnnotations.Knockout.js with your knockout script Bundle. If you prefer to handle all of your boot strapping in your Global.asax Application_Start method, please feel free to move these lines to the appropriate static class, delete this file, and remove the reference to WebActivator.

Installation

  • Requirements
    • .NET Framework 4.5 or greater
    • MVC 4.0 or greater
    • System.Web.Optimization
    • Newtonsoft.Json 6.0.2 or greater (installs with package)
  • Assumptions - You are using an MVVM framework such as Knockoutjs, are leveraging script bundling,  are using the Knockout validation extension, and have a script bundle called ~/bundles/knockout.
  • From the package manager prompt simply run PM> Install-Package ScriptAnnotations with your web app selected as as the default project

How it works

ScriptAnnotations works by overriding the default Newtonsoft.Json serialization mechanism to turn your annotated properties into complex Json types that contain two properties: jExtend and jValue. jValue contains the original value assigned to your property and jExtend is an array containing objects which your MVVM framework can use to extend or decorate values with the meta-data contained in your DataAnnotation. For example as new instance of the following class:

public class Person
{
[Required]
public string FirstName { get; set; }

public string MiddleName { get; set; }

[Required]
[RegularExpression("[A-Za-z\\-]{8,50}")]
public string LastName { get; set; }

[EmailAddress]
[Required]
public string Email { get; set; }

[Required]
[MinLength(8)]
public string Password { get; set; }

[Compare("Password", ErrorMessage = "Password and confirmation do not match")]
[Required]
public string PasswordConfirmation { get; set; }

}

Will be serialized as:

{"FirstName": {"jValue":"","jExtend":[{"required":true}]}, "MiddleName": null,
"LastName": {"jValue":"","jExtend":[{"required":true},{"pattern":"[A-Za-z\-]{8,50}"}]},
"Email": {"jValue":"","jExtend":[{"email":true},{"required":true}]},
"Password": {"jValue":"","jExtend":[{"minLength":8},{"required":true}]},
"PasswordConfirmation": {"jValue":"","jExtend":[{"equal":"Password","message":'Password and confirmation do not match'},{"required":true}]} };

Usage

To see ScriptAnnotations in action, take a look at the sample web app included with the source code

ScriptAnnotations natively supports the following .NET Data Annotations:

  • Required
  • MinLength
  • MaxLength
  • Range   
  • RegularExpression
  • EmailAddress
  • Compare

In addition, ScriptAnnotations is fully extensible so you can add support for custom data annotations.

Extensibility

To add support for custom DataAnnotations simply crate a class that implements the IValidatorStrategy interface and implement the WriteExtender method, which tells ScriptAnnotations how to render your annotation to Json. ScriptAnnotations uses the Managed Extensibility Framework to locate and load validator strategies so you will need to decorate your strategy class with the following attributes and make certain that your class is part of an assembly that is built to the same output directory as your web application.

[Export(typeof(IValidatorStrategy))]
[ExportMetadata("ValidationAttribute", "MinLengthAttribute")]

The ExportMetadata attribute requires two parameters. The first parameter is a marker so MEF can distinguish between components and is the same value, “ValidationAttribute”, for all strategy classes. The second parameter is the type name of your custom Data Annotation.

 

The following is an example of the required validator strategy included with ScriptAnnotations:

[Export(typeof(IValidatorStrategy))][ExportMetadata("ValidationAttribute", "RequiredAttribute")]
class RequiredValidatorStrategy : IValidatorStrategy
{

public string WriteExtender(JToken jToken, IList<CustomAttributeTypedArgument> ctorArgs, IList<CustomAttributeNamedArgument> namedArgs)
{
return string.Format("{{\"required\":true{0}}}",namedArgs.ErrorMessageOrEmptyString());
}
}

static class CustomAttributeNamedArgumentExtensions
{
public static string ErrorMessageOrEmptyString(this IList<CustomAttributeNamedArgument> argList)
{
if (argList.All(a => a.MemberName != "ErrorMessage"))
{
return string.Empty;
}

return string.Format(",\"message\":'{0}'", argList.Single(a => a.MemberName == "ErrorMessage").TypedValue.Value);
}
}

To boost performance, all validator strategies are lazy initialized so that only the strategies needed to serialize your class are actually instantiated.

Future

Future releases will include support for additional JavaScript frameworks such as Backbone.js, Ember, and Breeze.

Support

For support please contact me though my Linkedin profile

Last edited Apr 19, 2014 at 3:38 AM by JCBowers, version 9