INotifyDataErrorInfo Interface

Microsoft Silverlight will reach end of support after October 2021. Learn more.

Defines members that data entity classes can implement to provide custom synchronous and asynchronous validation support.

Namespace:  System.ComponentModel
Assembly:  System.Windows (in System.Windows.dll)

Syntax

'Declaration
Public Interface INotifyDataErrorInfo
public interface INotifyDataErrorInfo

The INotifyDataErrorInfo type exposes the following members.

Properties

  Name Description
Public propertySupported by Silverlight for Windows Phone HasErrors Gets a value that indicates whether the entity has validation errors.

Top

Methods

  Name Description
Public methodSupported by Silverlight for Windows Phone GetErrors Gets the validation errors for a specified property or for the entire entity.

Top

Events

  Name Description
Public eventSupported by Silverlight for Windows Phone ErrorsChanged Occurs when the validation errors have changed for a property or for the entire entity.

Top

Remarks

This interface enables data entity classes to implement custom validation rules and expose validation results to the user interface. You typically implement this interface to provide asynchronous validation logic such as server-side validation. This interface also supports custom error objects, multiple errors per property, cross-property errors, and entity-level errors.

Cross-property errors are errors that affect multiple properties. You can associate these errors with one or all of the affected properties, or you can treat them as entity-level errors. Entity-level errors are errors that either affect multiple properties or affect the entire entity without affecting a particular property.

Silverlight 4 also supports the IDataErrorInfo interface, which enables simple validation on the client only. In general, new entity classes for Silverlight should implement INotifyDataErrorInfo for the added flexibility instead of implementing IDataErrorInfo. The IDataErrorInfo support enables you to use many existing entity classes that are written for the full .NET Framework. Other validation options are also available, such as the use of data attributes. For more information, see Data Binding,

INotifyDataErrorInfo Members

The INotifyDataErrorInfo.HasErrors property indicates whether there are currently any validation errors for the entity. The GetErrors method returns an IEnumerable that contains validation errors for the specified property or for the entire entity. You can implement this method to report cross-property errors at the entity level or for each affected property, depending on your reporting needs.

The IEnumerable of errors for a property or entity can change or be replaced as asynchronous validation rules finish processing. Whenever the IEnumerable changes, the ErrorsChanged event must occur. Event handlers can retrieve new HasErrors and GetErrors values to update the UI validation feedback for the affected property or entity.

The validation errors returned by the GetErrors method can be of any type. However, if you implement a custom error type, be sure to override the ToString method to return an error message. Silverlight uses this string in its default error reporting.

Custom error objects are useful when you provide custom error reporting in the user interface. For example, you can create a template for the reporting ToolTip that binds to an ErrorLevel property in order to display warnings in yellow and critical errors in red.

Data Binding Support

The Silverlight binding engine provides built-in support for INotifyDataErrorInfo. To enable this support, simply bind a control to a property of an entity that implements INotifyDataErrorInfo or bind a control to the entity itself. The Binding.ValidatesOnNotifyDataErrors property must also be true, which it is by default.

The binding engine calls GetErrors to retrieve any initial errors for a bound property or entity. If the Binding.Mode property value is not OneTime, the binding engine handles the ErrorsChanged event to monitor for updates. Whenever the errors change for a bound property or entity, the binding engine calls GetErrors to retrieve the updated errors. Note that the binding engine never uses the HasErrors property, although you can use it in custom error reporting.

The binding engine uses the validation results to update the Validation.Errors collection for that binding. First, the binding engine removes any existing errors for the bound property that originate from INotifyDataErrorInfo validation. Then, if the new value is not valid, the binding engine adds a new error for each error that is returned by the GetErrors method.

INotifyDataErrorInfo errors do not interfere with other errors that originate from IDataErrorInfo. However, the binding engine will not expose an INotifyDataErrorInfo error when an exception-based error is active.

Error Reporting

The Validation.Errors collection provides a binding source for error reporting in the user interface. Several controls bind to this collection from their built-in error templates.

To provide custom error reporting, you can replace or modify the existing error templates. Alternately, you can handle the ErrorsChanged event and access the INotifyDataErrorInfo members directly.

You can also set the Binding.NotifyOnValidationError property to true and handle the FrameworkElement.BindingValidationError event. This event occurs each time an error is added to, or removed from, the Validation.Errors collection for a binding. Some controls, such as ValidationSummary, require this mechanism instead of using the Validation.Errors collection.

The next section describes how to implement INotifyDataErrorInfo using a basic scenario for illustration purposes. For information about implementing more complex validation scenarios, see Implementing Data Validation in Silverlight with INotifyDataErrorInfo.

Examples

The following example code demonstrates the use of INotifyDataErrorInfo to support multiple validation results per property, including critical errors and warnings. Each TextBox displays one result, with errors having priority over warnings. Additionally, a ValidationSummary control displays the complete results.

The IsIdValid and IsNameValid methods validate the Id and Name properties. These methods apply two business rules to each property and update the errors collection based on the results. The methods return false for values that produce critical errors and true for values that are valid or produce warnings only. These return values enable the property setters to avoid committing disallowed values.

The validation methods call the AddError and RemoveError methods to update the errors collection. These methods raise the ErrorsChanged event whenever they modify the errors collection.

This example implements an entity class that has only a few properties and simple business rules to ensure that key concepts are clear. However, the basic structure supports more complex rules.

For example, the INotifyDataErrorInfo implementation automatically supports asynchronous validation. This means that you can add server-side validation by calling Web services in your validation methods and updating the errors collection in callback methods.

You can also add cross-property or entity-level validation by adding appropriate errors to the errors collection. For example, you can add cross-property errors to each affected property. Alternately, you can add entity-level errors and report them in the user interface by updating the ValidationSummary.Errors collection in a BindingValidationError event handler.

This example requires a reference to System.Windows.Controls.Data.Input.dll, which provides the Label, DescriptionViewer, and ValidationSummary controls.

Imports System.ComponentModel

Public Class Product
    Implements INotifyDataErrorInfo

    Private idValue As Integer
    Public Property Id As Integer
        Get
            Return idValue
        End Get
        Set(ByVal value As Integer)
            If IsIdValid(value) AndAlso
                idValue <> value Then idValue = value
        End Set
    End Property

    Private nameValue As String
    Public Property Name As String
        Get
            Return nameValue
        End Get
        Set(ByVal value As String)
            If IsNameValid(value) AndAlso
                nameValue <> value Then nameValue = value
        End Set
    End Property

    ' Validates the Id property, updating the errors collection as needed.
    Public Function IsIdValid(ByVal value As Integer) As Boolean

        Dim isValid = True

        If value < 5 Then
            AddError("Id", ID_ERROR, False)
            isValid = False
        Else
            RemoveError("Id", ID_ERROR)
        End If

        If value < 10 Then
            AddError("Id", ID_WARNING, True)
        Else
            RemoveError("Id", ID_WARNING)
        End If

        Return isValid

    End Function

    ' Validates the Name property, updating the errors collection as needed.
    Public Function IsNameValid(ByVal value As String) As Boolean

        Dim isValid = True

        If value.Contains(" ") Then
            AddError("Name", NAME_ERROR, False)
            isValid = False
        Else
            RemoveError("Name", NAME_ERROR)
        End If

        If (value.Length > 5) Then
            AddError("Name", NAME_WARNING, True)
        Else
            RemoveError("Name", NAME_WARNING)
        End If

        Return isValid

    End Function

    Private errors As New Dictionary(Of String, List(Of String))
    Private ReadOnly ID_ERROR = "Value cannot be less than 5."
    Private ReadOnly ID_WARNING = "Value should not be less than 10."
    Private ReadOnly NAME_ERROR = "Value must not contain any spaces."
    Private ReadOnly NAME_WARNING = "Value should be 5 characters or less."

    ' Adds the specified error to the errors collection if it is not 
    ' already present, inserting it in the first position if isWarning is 
    ' false. Raises the ErrorsChanged event if the collection changes. 
    Public Sub AddError(ByVal propertyName As String, ByVal [error] As String,
                        ByVal isWarning As Boolean)

        If Not errors.ContainsKey(propertyName) Then _
            errors(propertyName) = New List(Of String)()

        If Not errors(propertyName).Contains([error]) Then
            If isWarning Then
                errors(propertyName).Add([error])
            Else
                errors(propertyName).Insert(0, [error])
            End If
            RaiseErrorsChanged(propertyName)
        End If

    End Sub

    ' Removes the specified error from the errors collection if it is
    ' present. Raises the ErrorsChanged event if the collection changes.
    Public Sub RemoveError(ByVal propertyName As String, ByVal [error] As String)

        If errors.ContainsKey(propertyName) AndAlso
            errors(propertyName).Contains([error]) Then

            errors(propertyName).Remove([error])
            If errors(propertyName).Count = 0 Then errors.Remove(propertyName)
            RaiseErrorsChanged(propertyName)

        End If

    End Sub

    Public Sub RaiseErrorsChanged(ByVal propertyName As String)
        RaiseEvent ErrorsChanged(Me,
            New DataErrorsChangedEventArgs(propertyName))
    End Sub

    Public Event ErrorsChanged As EventHandler(Of DataErrorsChangedEventArgs) _
        Implements INotifyDataErrorInfo.ErrorsChanged

    Public Function GetErrors(ByVal propertyName As String) _
        As System.Collections.IEnumerable _
        Implements INotifyDataErrorInfo.GetErrors

        If (String.IsNullOrEmpty(propertyName) OrElse
            Not errors.ContainsKey(propertyName)) Then Return Nothing
        Return errors(propertyName)

    End Function

    Public ReadOnly Property HasErrors As Boolean _
        Implements INotifyDataErrorInfo.HasErrors
        Get
            Return errors.Count > 0
        End Get
    End Property

End Class
using System;
using System.ComponentModel;
using System.Collections.Generic;

namespace INotifyDataErrorInfoExample
{

    public class Product : INotifyDataErrorInfo
    {
        private int idValue;
        public int Id
        {
            get { return idValue; }
            set { if (IsIdValid(value) && idValue != value) idValue = value; }
        }

        private string nameValue;
        public string Name
        {
            get { return nameValue; }
            set { if (IsNameValid(value) && nameValue != value) nameValue = value; }
        }

        // Validates the Id property, updating the errors collection as needed.
        public bool IsIdValid(int value)
        {
            bool isValid = true;

            if (value < 5)
            {
                AddError("Id", ID_ERROR, false);
                isValid = false;
            }
            else RemoveError("Id", ID_ERROR);

            if (value < 10) AddError("Id", ID_WARNING, true);
            else RemoveError("Id", ID_WARNING);

            return isValid;
        }

        // Validates the Name property, updating the errors collection as needed.
        public bool IsNameValid(string value)
        {
            bool isValid = true;

            if (value.Contains(" "))
            {
                AddError("Name", NAME_ERROR, false);
                isValid = false;
            }
            else RemoveError("Name", NAME_ERROR);

            if (value.Length > 5) AddError("Name", NAME_WARNING, true);
            else RemoveError("Name", NAME_WARNING);

            return isValid;
        }

        private Dictionary<String, List<String>> errors =
            new Dictionary<string, List<string>>();
        private const string ID_ERROR = "Value cannot be less than 5.";
        private const string ID_WARNING = "Value should not be less than 10.";
        private const string NAME_ERROR = "Value must not contain any spaces.";
        private const string NAME_WARNING = "Value should be 5 characters or less.";

        // Adds the specified error to the errors collection if it is not 
        // already present, inserting it in the first position if isWarning is 
        // false. Raises the ErrorsChanged event if the collection changes. 
        public void AddError(string propertyName, string error, bool isWarning)
        {
            if (!errors.ContainsKey(propertyName))
                errors[propertyName] = new List<string>();

            if (!errors[propertyName].Contains(error))
            {
                if (isWarning) errors[propertyName].Add(error);
                else errors[propertyName].Insert(0, error);
                RaiseErrorsChanged(propertyName);
            }
        }

        // Removes the specified error from the errors collection if it is
        // present. Raises the ErrorsChanged event if the collection changes.
        public void RemoveError(string propertyName, string error)
        {
            if (errors.ContainsKey(propertyName) &&
                errors[propertyName].Contains(error))
            {
                errors[propertyName].Remove(error);
                if (errors[propertyName].Count == 0) errors.Remove(propertyName);
                RaiseErrorsChanged(propertyName);
            }
        }

        public void RaiseErrorsChanged(string propertyName)
        {
            if (ErrorsChanged != null)
                ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
        }

        #region INotifyDataErrorInfo Members

        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        public System.Collections.IEnumerable GetErrors(string propertyName)
        {
            if (String.IsNullOrEmpty(propertyName) || 
                !errors.ContainsKey(propertyName)) return null;
            return errors[propertyName];
        }

        public bool HasErrors
        {
            get { return errors.Count > 0; }
        }

        #endregion
    }
}
<UserControl x:Class="INotifyDataErrorInfoExample.MainPage"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:sdk="https://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

  <Grid x:Name="LayoutRoot" Background="White" Margin="10">

    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>

    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="Auto"/>
      <ColumnDefinition Width="200"/>
      <ColumnDefinition Width="Auto"/>
    </Grid.ColumnDefinitions>

    <TextBlock Grid.Column="1" Margin="3" Text="Product" FontWeight="Bold"/>

    <sdk:Label Grid.Row="1" Target="{Binding ElementName=IdTextBox}" 
      Margin="3" HorizontalAlignment="Right"/>
    <TextBox x:Name="IdTextBox" KeyDown="TextBox_KeyDown"  
      Grid.Column="1" Grid.Row="1" Margin="3" MaxLength="5" 
      Text="{Binding Id, Mode=TwoWay, ValidatesOnExceptions=True,
      NotifyOnValidationError=True}"/>
    <sdk:DescriptionViewer Grid.Column="2" Grid.Row="1" 
      Description="Id must be greater than 4 and should be greater than 9."/>

    <sdk:Label Grid.Row="2" Target="{Binding ElementName=NameTextBox}" 
      Margin="3" HorizontalAlignment="Right"/>
    <TextBox x:Name="NameTextBox" KeyDown="TextBox_KeyDown"  
      Grid.Column="1" Grid.Row="2" Margin="3" MaxLength="10" 
      Text="{Binding Name, Mode=TwoWay, NotifyOnValidationError=True}"/>
    <sdk:DescriptionViewer Grid.Column="2" Grid.Row="2" Description=
      "Name must not contain spaces and should be 5 characters or less."/>

    <sdk:ValidationSummary Grid.Row="3" Grid.ColumnSpan="2" Margin="3"/>

  </Grid>
</UserControl>
Partial Public Class MainPage
    Inherits UserControl

    Public Sub New()
        InitializeComponent()
        LayoutRoot.DataContext = New Product() With {.Id = 10, .Name = "food"}
    End Sub

    ' Commits text box values when the user presses ENTER. This makes it 
    ' easier to experiment with different values in the text boxes.
    Private Sub TextBox_KeyDown(ByVal sender As System.Object,
                                ByVal e As System.Windows.Input.KeyEventArgs)
        If e.Key = System.Windows.Input.Key.Enter Then CType(sender, TextBox) _
            .GetBindingExpression(TextBox.TextProperty).UpdateSource()
    End Sub

End Class
using System.Windows.Controls;

namespace INotifyDataErrorInfoExample
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            LayoutRoot.DataContext = new Product() { Id = 10, Name = "food" };
        }

        // Commits text box values when the user presses ENTER. This makes it 
        // easier to experiment with different values in the text boxes.
        private void TextBox_KeyDown(object sender, 
            System.Windows.Input.KeyEventArgs e)
        {
            if (e.Key == System.Windows.Input.Key.Enter) (sender as TextBox)
                .GetBindingExpression(TextBox.TextProperty).UpdateSource();
        }
    }
}

Version Information

Silverlight

Supported in: 5, 4

Silverlight for Windows Phone

Supported in: Windows Phone OS 7.1

Platforms

For a list of the operating systems and browsers that are supported by Silverlight, see Supported Operating Systems and Browsers.