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 by commands, and it can also be enabled for staticCommands
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.
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 to the ModelState
object using the AddModelError
extension method on the Context object.
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 or consistency 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 etc.). For example, placing the detection of overlapping appointments in the
Validate
method is not a good idea (unless you are validation an object representing the entire calendar day). 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 DotVVM property path format, e. g. ChildObject/InvalidProperty
or SomeArray/2/InvalidProperty
. The paths here are relative to currently validated object (this
).
There is an extension method called CreateValidationResult<T>
which can produce these expressions from a lambda expression:
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
yield return this.CreateValidationResult<YourViewModel>(validationContext,
"Error message",
t => t.Child.InvalidProperty
);
}
The format of validation paths has changed in DotVVM 4.0 - the validation paths shall be separated by
/
- e. g.ChildObject/InvalidProperty
. In theIValidatableObject
implementation, the paths are relative to the currently validated object. Please see the DotVVM 3.0 version of this page to see the old format of property validation paths.
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, since the method cannot be asynchronous and in some classes (especially in data transfer objects) you don't want to use dependency injection to access various business services.
The request context contains the ModelState
object which holds a list of validation errors. You can add your own errors to this object to report violations of business rules to the users, using the AddModelError
on the viewmodel context object.
Commonly, when you find validation errors, you want to report them to the user and stop processing the request. You can use the Context.FailOnInvalidModelState()
method for this purpose. It will interrupt the execution of the current HTTP request and return 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.
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.
There are more overloads of the AddModelError
method - commonly they accept the viewmodel object and a lambda expression specifying the path to the invalid property.
There is also a method AddRawModelError
which takes the string parameter - we recommend to avoid using this method as it requires specifying the validation paths in DotVVM property path format - e.g. /ChildObject/InvalidProperty
. The path must be absolute (from the root viewmodel of the page).
Note that validation is
staticCommand
methods uses a different model state class, see Validation in staticCommands for more details.
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 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.