The problem to solve is to somehow have the validation attributes that have been applied to the C# view models applied in the same manner to view models created in the (Knockout JS based) client.
Doing this by hand is obviously clumsy and error prone, so instead the solution I now have:
- Exposes Web API end points that can be queried by the client to gather meta data
- Has a simple Javascript module that can interpret the response from a meta data serving endpoint call, applying Knockout validation directives (extensions) to a view model
The VS 2015 solution lives in GitHub.
A simple example follows - consider the C# view model below:
public class SimpleViewModel {
[Required]
[StringLength(10)]
public string FirstName { get; set; }
[Required]
[StringLength(20)]
public string Surname { get; set; }
[Required(ErrorMessage = "You must indicate your DOB")]
[Range(1906, 2016)]
public int YearOfBirth { get; set; }
[RegularExpression(@"^[a-z]\d{3}$")]
public string Pin { get; set; }
}
A web API method that can serve meta data on demand (security considerations ignored). It's all interface driven and pluggable, so not limited to the standard MVC data annotations or Knockout validation translation. Server side view model traversal is recursive and collection aware, so arbitrarily complex view models can be interrogated.
public class DynamicValidationController : ApiController {
[HttpGet]
public dynamic MetadataFor(string typeName) {
return new ValidationMetadataGenerator()
.ExamineType(Type.GetType(typeName))
.Generate();
}
}
And finally a very simple client use of the Javascript module. This example HTTP Get's a method that includes the validation meta data along with the view model in a wrapped type, but this need not be the case. The call to vmi.decorate, is the key one, applying as it does the relevant metadata to the ko mapped view model using standard Knockout validation directives.
$.getJSON("/api/ViewModelServing/WrappedSimpleViewModel",
null,
function (response) {
var obj = ko.mapping.fromJS(response.Model);
vmi.decorate({
model: obj,
parsedMetadata: response.ValidationMetadata,
enableLogging: true,
insertedValidatedObservableName: 'validation'
});
ko.validation.init({ insertMessages: false });
ko.applyBindings(obj, $('#koContainer')[0]);
}
);
The object passed to decorate or decorateAsync also allows you to supply a property name (insertedValidatedObservableName) that will be set with a validatedObservable created during metadata interpretation - this is a convenience, meaning that after the example code above executes, calling obj.validation.isValid() will return true or false correctly for the entire view model.
Metadata on the wire looks like this:
No comments:
Post a Comment