Validation Using MVVM Light in a Universal Windows App
Asked Answered
W

2

18

After done with setting up MVVM Light in a Universal Windows App application, I have the following structure, and I wonder what is the cleanest way to do validation in 2017 using UWP and mvvmlight to notify users with errors and possibly reset the textbox value when needed. The only trick is that the Textbox is part of a UserControl (cleaned up unnecessary xaml code for clarity) since it will be used multiple times. Also I added DataAnnotations and ValidationResult for demonstration and not to suggest that this is the best way to do it or that it is working in any way so far.

The code works fine as far as binding and adding and removing values

  • ViewModel

    using GalaSoft.MvvmLight;
    using GalaSoft.MvvmLight.Command;
    using GalaSoft.MvvmLight.Views;
    using System;
    using System.ComponentModel.DataAnnotations;
    
    public class ValidationTestViewModel : ViewModelBase
     {
       private int _age;
    
      [Required(ErrorMessage = "Age is required")]
      [Range(1, 100, ErrorMessage = "Age should be between 1 to 100")]
      [CustomValidation(typeof(int), "ValidateAge")]
      public int Age
        {
          get { return _age; }
          set
          {
            if ((value > 1) && (value =< 100))
                _age= value;
          }
        }
    
      public static ValidationResult ValidateAge(object value, ValidationContext validationContext)
       {
          return null;
       }
    }
    
  • View

    <Page
     x:Class="ValidationTest.Views.ValidationTestPage"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:local="using:ValidationTest.Views"
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     DataContext="{Binding ValidationTestPageInstance, Source={StaticResource  Locator}}" 
    xmlns:views="using:ValidationTest.Views">
    
         <views:NumberEdit TextInControl="{Binding Age, Mode=TwoWay}" />
    
    </Page>
    
  • UserControl

    <UserControl
     x:Class="ValidationTest.Views.Number"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:local="using:ValidationTest.Views"
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     x:Name="userControl1">
      <Grid>
    
      <TextBox x:Name="content" Text="{Binding TextInControl, ElementName=userControl1, Mode=TwoWay}"></TextBox>
       </Grid>
    
     </UserControl>
    
  • UserControl Code Behind:

    public partial class NumberEdit : UserControl
    {
       public string TextInControl
          {
            get { return (string)GetValue(TextInControlProperty); }
            set {
                  SetValue(TextInControlProperty, value);
                }
    }
    
    public static readonly DependencyProperty TextInControlProperty =
        DependencyProperty.Register("TextInControl", typeof(string),
                                       typeof(NumberEdit), new PropertyMetadata(null));
    
     }
    
Wenwenceslaus answered 3/2, 2017 at 18:41 Comment(0)
H
2

We usually use IDialogService interface in MVVM Light to notify users with errors, there are ShowError method, ShowMessage method and ShowMessageBox method in IDialogService.

We should be able to new a PropertyMetadata instance with a PropertyChangedCallback value, it will be invoked when the effective property value of a dependency property changes. When it is invoked, we can use the ShowMessage method in it.

For example:

public sealed partial class NumberEdit : UserControl
{
    public NumberEdit()
    {
        this.InitializeComponent();
    }

    public static readonly DependencyProperty TextInControlProperty =
     DependencyProperty.Register("TextInControl", typeof(string),
                                    typeof(NumberEdit), new PropertyMetadata(null, new PropertyChangedCallback(StringChanged)));

    private static void StringChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        int value;
        Int32.TryParse(e.NewValue.ToString(), out value);
        if (0 < value && value < 99)
        {
        }
        else
        {
            var dialog = ServiceLocator.Current.GetInstance<IDialogService>();
            dialog.ShowMessage("Age should be between 1 to 100", "Error mesage");
        }
    }

    public string TextInControl
    {
        get { return (string)GetValue(TextInControlProperty); }
        set
        {
            SetValue(TextInControlProperty, value);
        }
    }
}

Also if you want to reset the TextBox value, you should be able to use RaisePropertyChanged in the Age property. If we do not use RaisePropertyChanged in the Age property, the TextBox value will not change when the Age value has changed.

For more info about the RaisePropertyChanged, please refer INotifyPropertyChanged interface.

For example:

public int Age
{
    get { return _age; }
    set
    {
        if ((value > 1) && (value <= 100))
            _age = value;
        RaisePropertyChanged("Age");
    }
}

Update:

In your Page you should be add to add DataContext in your control.

<Page x:Class="Validation_Using_MVVM_Light_in_a.SecondPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d"
      xmlns:local="using:Validation_Using_MVVM_Light_in_a"
xmlns:VM="using:Validation_Using_MVVM_Light_in_a.ViewModel">

    <Page.Resources>
        <VM:ValidationTestViewModel x:Key="MyViewModel" />
    </Page.Resources>
    <Grid>
        <local:NumberEdit  DataContext="{StaticResource MyViewModel}"  TextInControl="{Binding Age, Mode=TwoWay}" />
    </Grid>
</Page>
Heteronomous answered 7/2, 2017 at 8:57 Comment(3)
Is there any changes need to be done in XAML? Also, the PropertyChangedCallback link is not workingWenwenceslaus
@Wenwenceslaus You should be able to add DataContext to the Page or the Control, also I have updated the code in StringChanged method.Heteronomous
A few concerns about this approach: 1) I found no benefit of using RaisePropertyChanged, removing it does not affect anything 2) Why use StringChanged in Code Behind when all this could be done from the ViewModel since all properties are declared there? 3) Showing a dialog message lacks instant validation since it depends on the user moving the focus away from the Textbox first 4) Even with RaisePropertyChanged, the Textbox value is never reset to the old one even with TwoWay binding mode.Wenwenceslaus
L
0

What you are missing here is a call to Validator.ValidateObject to do the actual validation. This will apply the validation attributes to the data and will also call IValidatableObject.Validate if you have implemented it (you should implement this instead of having ad-hoc functions such as ValidateAge).

Like this:

        // Validate using:
        // 1. ValidationAttributes attached to this validatable's class, and
        // 2. ValidationAttributes attached to the properties of this validatable's class, and 
        // 3. this.Validate( validationContext)
        // 
        // Note, for entities, a NotSupportedException will be thrown by TryValidateObject if any of 
        // the primary key fields are changed. Correspondingly the UI should not allow modifying 
        // primary key fields. 
        ValidationContext validationContext = new ValidationContext(this);
        List<ValidationResult> validationResults = new List<ValidationResult>(64);
        bool isValid = Validator.TryValidateObject( this, validationContext, validationResults, true);
        Debug.Assert(isValid == (validationResults.Count == 0));
Linker answered 7/10, 2017 at 12:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.