4.2 preview - validation in staticCommands, and more

We have just released the 4.2 preview4 version with a number of new features and many more bug fixes and stability improvements. The larger features are validation in statiCommands, custom primitive types, support for System.Diagnostics.Metrics, support for ko.options.deferUpdates, and WebForms adapters.

In this post I’d like to show how to use the validation in staticCommands. Information about the other features will come, and we’ll describe them in release notes of the 4.2 stable (for now, it can be found on github)

If you don’t know about it, staticCommand is essentially a lightweight alternative to a command binding - it only transfers the method arguments and result, thus skips initialization of the page and the full view model. However, it comes with some limitations - one of them was the inability to send validation errors in the response.

Manual validation

DotVVM validation essentially comes in two flavors - either you rely on DataAnnotations attributes like [RegularExpression("abc")] or you explicitly add the validation errors into Context.ModelState. We call the first option Automatic while second one is Manual.

In staticCommand, IDotvvmRequestContext is rarely used, so we decided to not use Context.ModelState and instead create another class for collecting validation errors - StaticCommandModelState. Another difference is that there is no Validation.Target nor root view model, so the validation errors are attached onto method arguments.

[AllowStaticCommand(StaticCommandValidation.Manual)]
public static string DoSomething(MyViewModel vm, string name)
{
    var modelState = new StaticCommandModelState();
    if (vm.AProperty > 10)
        modelState.AddArgumentError(() => vm.AProperty, "Must be less than 10.");
    if (string.IsNullOrWhiteSpace(name))
        modelState.AddArgumentError(() => name, "Please specify a name.");
    modelState.FailOnInvalidModelState();
    // ...
}

As you can see in the example, adding errors is done using the AddArgumentError function. After the validation is done, we call FailOnInvalidModelState to interrupt the request if the data contain errors. Note that if the lambda-based API would be too restrictive, you can also use AddRawArgumentError which allows manually specifying the validation path. Or, you can even use AddRawError to add errors into any property in the view model, not only on the data received through method arguments.

After staticCommand invocation, DotVVM automatically maps the argument errors onto the page viewmodel. Note that in some cases it might not be possible - for example if I’d call the method using {staticCommand: DoSomething(_this, "")}, I’d get the error on the empty string constant. In such cases, the request will fail with HTTP 500.

If you are wondering why even the manual validation has to be enabled, it’s because DotVVM must clear all existing validation errors on the page before running the validation. StaticCommands are however often run without user interaction - either periodically, or as a response to a WebSocket message. In these cases, clearing the errors would be a very unpleasant breaking change in v4.2, so we decided to make the validation opt-in.

Automatic validation

The automatic validation is simpler to use. When we use StaticCommandValidation.Automatic instead of Manual, DotVVM will check all ValidationAttributes on the view model and arguments, before invoking the method.

The above example can be thus simplified to

[AllowStaticCommand(StaticCommandValidation.Automatic)]
public static string DoSomething(MyViewModel vm, [Required] string name)
{
}

...

public class MyViewModel {
    [Range(0, 10)]
    public int AProperty { get; set; }
}

That’s all needed. Please keep in mind that staticCommand validation is not directed by Validation.Target and Validation.Enabled properties in the dothtml markup. DotVVM would have no server-side way of checking whether it’s enabled when the staticCommand is being executed.

Let us know in the comments what do you think about it or if you encounter any problems.

Standard command bindings also validate these attributes client-side, but it’s not currently implemented for staticCommands. We do plan to add this feature, but we think it’s perfectly usable without it, so no promises it’s make it to the 4.2 release.