Validation overview
DotVVM supports the Model validation mechanism known from other ASP.NET technologies, like MVC, Web API or Web Forms.
The validation is triggered commands.
To use validation in DotVVM, you need to decide three things:
What is validated: By default, every command triggers the validation for the entire viewmodel. You can change the validation target to some child object in the viewmodel, so only a part of the viewmodel will be validated for the particular command. You can even disable the validation for particular controls entirely.
How does it look like: When validation errors are found, they need to be indicated to the user. We have several validation controls which can display error messages, apply CSS classes on invalid form fields, and so on.
Define validation rules: You need to specify the validation rules for properties, or even objects in the viewmodel. How to specify the rules is described in the rest of this chapter.
Validation in static commands is currently not supported, but the work on this feature has already been started, and it will be available in the future releases of DotVVM.
Define validation rules
There are three ways how you can define the validation rules. You can use validation attributes to define rules for individual viewmodel properties, you can implement your own validation logic by implementing the IValidatableObject
interface on objects in the viewmodel, and you can add errors in the ModelState
object directly.
Validation attributes
You can validate the viewmodel properties by applying the validation attributes from the System.ComponentModel.DataAnnotations
namespace.
This is useful when you need to check the format of individual properties.
The most commonly used attributes are:
Required
- value must not be emptyEmailAddress
- value must be in a format of an e-mail addressRange
- value must be a number from a specified rangeRegularExpression
- value must match the specified regular expressionCompare
- value must be the same as in another property
Most attributes can specify either the ErrorMessage
argument directly, or use the ErrorMessageResourceType
and ErrorMessageResourceName
arguments to provide localized error messages from a resource file.
[Required]
public string NewTaskTitle { get; set; }
[Required(ErrorMessage = "The password is required!")]
public string Password { get; set; }
[Required(ErrorMessageResourceType = typeof(MyResourceFile), ErrorMessageResourceName = "PasswordLabel")]
public string Password { get; set; }
You can even use custom validation attributes (by creating a class which implements the IValidationAttribute
interface).
Please note that is not easy to use dependency injection in validation attributes (they are static), so it may be difficult to validate business rules (which may need to look in the database). For example, creating a validation attribute which checks the uniqueness of the e-mail address in the database, is not a good idea. Use the ModelState to report violations of business rules.
IValidatableObject
In order to validate consistency of an entire object, any class can implement the IValidatableObject
interface and use its Validate
method to return a list of validation errors:
using DotVVM.Framework.ViewModel.Validation;
public class AppointmentData : IValidatableObject
{
[Required]
public DateTime BeginDate { get; set; }
[Required]
public DateTime EndDate { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (BeginDate >= EndDate)
{
yield return new ValidationResult(
"The begin date of the appointment must be lower than the end date.",
new[] { nameof(BeginDate), nameof(EndDate) } // one (or more) expressions indicating which properties are invalid
);
}
}
}
This is helpful to provide formal validation rules that verify the state of the object.
Please note that this method doesn't make it easy for dependency injection, so it may be difficult to validate business rules (which may need to look in the database). For example, placing the detection of overlapping appointments in the
Validate
method is not a good idea. Use the ModelState to report violations of business rules.
Report errors for child objects
If you need to validate properties in child objects, we recommend to implement IValidatableObject
in the child objects. DotVVM calls the Validate
method recursively on the entire viewmodel.
If this is not possible and you need to return validation errors for properties in child objects, you need to return the property path in a Knockout observable form, e. g. ChildObject().InvalidProperty
or SomeArray()[2]().InvalidProperty
. This will probably change in DotVVM 4.0 so use it only if there is no other way. There is an extension method called CreateValidationResult<T>
which can produce these expressions from a lambda expression:
yield return this.CreateValidationResult<YourViewModel>( // YourViewModel must implement IDotvvmViewModel
"Error message",
t => t.Child.InvalidProperty
);
ModelState
By default, the validation is triggered automatically on all postbacks. When all validation attributes and IValidatableObject
rules pass, the command method is invoked. You can perform additional validation checks in the command method itself and report additional validation errors to the user.
This is commonly used to perform validations of business rules which often require access to the database or other resources, e. g. to make sure that an e-mail address is not registered yet. It would be difficult to do such checks in validation attributes, or in the IValidatableObject
implementation.
The request context contains the ModelState
object which holds a list of validation errors. You can add your own errors to the collection to report violations of business rules to the users.
If you need to report the errors to the user, you can use the Context.FailOnInvalidModelState()
which interrupts execution of the current HTTP request and returns the ModelState
errors to the user. The browser will show the validator controls, same as for the validation errors provided by attributes or IValidatableObject
.
In the following sample, we validate the EmailAddress
property using the validation attributes. When these checks pass, the Subscribe
method is invoked.
If the registration of the user fails (we use a custom exception to indicate the cause of the problem), we add a validation error to the Context.ModelState.Errors
collection and return the validation errors to the user. You can use a useful extension method AddModelError
.
public class RegisterViewModel : DotvvmViewModelBase
{
[Required]
[EmailAddress]
public string EmailAddress { get; set; }
public void Subscribe()
{
...
try
{
subscriptionService.RegisterEmailAddress(EmailAddress);
}
catch (EmailAddressAlreadyRegisteredException)
{
// add the error to the list of validation errors
this.AddModelError(v => v.EmailAddress, "The e-mail address is already registered!");
// interrupt request execution and report the validation errors
Context.FailOnInvalidModelState();
}
...
}
}
You can see that the business rule itself is handled in the business layer of the application (i. e. in some subscriptionService
). The business layer throws an exception which is then caught in the viewmodel and interpreted as a validation error.
Since this pattern is quite common in DotVVM applications, you can use filters to transform business layer exceptions to model state errors globally.
Disable validation
You can also disable validation on a part of the page or on a specific control, by using Validation.Enabled="false"
. You often need to do this e. g. for delete and cancel buttons where the values in the form don't need to be valid.
Also, you might need this e.g. on the Changed
event of TextBox
when you need to pre-fill some values for the user, but the form may not be complete yet, and thus it is not valid at such time.
<dot:TextBox Text="{value: Address}"
Changed="{command: GetGpsLocationForAddress()}"
Validation.Enabled="false" />
<!-- There are additional fields in the form which are required, but we need the command to be executed even if these fields are empty. -->
If you turn on the debug mode in the configuration, a red notification in the top right corner of the screen appears if the postback was canceled because of validation errors. This helps you to discover validation errors when you don't have validator controls on the page yet.