Stack OverFlow with Custom validator - c#

I have implemented custom validator as following...
public class RquiredFiledValidation:ValidationRule
{
public string ErrorMessage { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (string.IsNullOrWhiteSpace(value.ToString()))
return new ValidationResult(false, ErrorMessage);
else
return new ValidationResult(true, null);
}
}
And Attached this with a text box as following...
<TextBox x:Name="txtLoging" Grid.Column="1" HorizontalAlignment="Stretch" Validation.ErrorTemplate="{x:Null}" VerticalAlignment="Center" Margin="0,40,30,0">
<Binding Path="Text" ElementName="txtLoging" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<Validate:RquiredFiledValidation ErrorMessage="Please Provide Login Name"></Validate:RquiredFiledValidation>
</Binding.ValidationRules>
</Binding>
</TextBox>
My problem is...
1) When I click directly on login button then the validation doesn't get fired
2) When I put a character in text box validation get fired but produced stack overflow error.

I have solve the first problem from code behind as below txtLoging.GetBindingExpression(TextBox.TextProperty).UpdateS‌​ource(); txtPassword.GetBindingExpression(Infrastructure.AttachedProp‌​erty.PasswordAssiste‌​nt.PasswordValue).Up‌​dateSource(); But how solve the same in MVVM
If you care about the MVVM pattern you should not validate your data using validation rules. Validation rules belong to the view and in an MVVM application the validation logic should be implemented in the view model or the model class.
What you should do is implement the INotifyDataErrorInfo interface: https://msdn.microsoft.com/en-us/library/system.componentmodel.inotifydataerrorinfo%28v=vs.110%29.aspx
Here is an example for you:
public class ViewModel : INotifyDataErrorInfo
{
private string _username;
public string Username
{
get { return _username; }
set
{
_username = value;
ValidateUsername();
}
}
private void ValidateUsername()
{
if (_username == "valid")
{
if (_validationErrors.ContainsKey("Username"))
_validationErrors.Remove(nameof(Username));
}
else if (!_validationErrors.ContainsKey("Username"))
{
_validationErrors.Add("Username", new List<string> { "Invalid username" });
}
RaiseErrorsChanged("Username");
}
private readonly Dictionary<string, ICollection<string>>
_validationErrors = new Dictionary<string, ICollection<string>>();
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
private void RaiseErrorsChanged(string propertyName)
{
if (ErrorsChanged != null)
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
public System.Collections.IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName)
|| !_validationErrors.ContainsKey(propertyName))
return null;
return _validationErrors[propertyName];
}
public bool HasErrors
{
get { return _validationErrors.Count > 0; }
}
}
<TextBox Text="{Binding Username, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}" />
Please refer to the following blog post for more information about the broad picture of how data validation in WPF works and some comprehensive samples on how to implement it.
Data validation in WPF: https://blog.magnusmontin.net/2013/08/26/data-validation-in-wpf/

Related

WPF Binding.ValidationRules based on condition

I currently have TextBox with a Binding.ValidationRules that work like;
<TextBox>
<Binding Path="MyID" NotifyOnValidationError="True" ValidatesOnDataErrors="True"
Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnSourceUpdated="True"
NotifyOnTargetUpdated="True" Delay="100">
<Binding.ValidationRules>
<local:IDValidator ValidatesOnTargetUpdated="True" table="Items" />
</Binding.ValidationRules>
</Binding>
</TextBox>
And the custom ValidationRule:
public class IDValidator : ValidationRule
{
public string table { get; set; }
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
//Logic
}
}
Problem is under certain conditions I would want IDValidator to be the ValidationRule. Other times I may want say IDValidator2 to be the ValidationRule.
Now I couldn't find a way to accomplish this. So I figured hey why not send another value down to IDValidator and then handle it in the logic of Validate like this:
XMAL update:
<local:IDValidator ValidatesOnTargetUpdated="True" table="Items" testing="{Binding Path=test}" />
IDValidator update:
public string testing { get; set; }
Problem is that doesn't seem to like sending a bind value down. How can I accomplish this?
This is doable, but it is not very simple and has some gotchas that you may not expect. The underlying issue is that dynamic bindings can only be applied on objects that derive from DependencyObject. ValidationRule is not such an object. However, we can add a property to a custom ValidationRule that exposes a class that does derive from DependencyObject. An example will help explain:
public class IDValidator : ValidationRule
{
private IDValidatorRange _range;
public int MinLength { get; set; }
public int MaxLength { get; set; }
public IDValidatorRange Range
{
get { return _range; }
set
{
_range = value;
value?.SetValidator(this);
}
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
// Logic
}
}
Note the IDValidatorRange object returned from the Range property. You will need to create this class using DependencyProperties with a mechanism for updating the IDValidator rule properties. Here is an example of such a class:
public class IDValidatorRange : Freezable
{
public static readonly DependencyProperty MinLengthProperty = DependencyProperty.Register(
"MinLength", typeof (int), typeof (IDValidatorRange), new FrameworkPropertyMetadata(5, OnMinLengthChanged));
public static readonly DependencyProperty MaxLengthProperty = DependencyProperty.Register(
"MaxLength", typeof (int), typeof (IDValidatorRange), new FrameworkPropertyMetadata(10, OnMaxLengthChanged));
public void SetValidator(IDValidator validator)
{
Validator = validator;
if (validator != null)
{
validator.MinLength = MinLength;
validator.MaxLength = MaxLength;
}
}
public int MaxLength
{
get { return (int) GetValue(MaxLengthProperty); }
set { SetValue(MaxLengthProperty, value); }
}
public int MinLength
{
get { return (int) GetValue(MinLengthProperty); }
set { SetValue(MinLengthProperty, value); }
}
private IDValidator Validator { get; set; }
private static void OnMaxLengthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var range = (IDValidatorRange) d;
if (range.Validator != null)
{
range.Validator.MaxLength = (int) e.NewValue;
}
}
private static void OnMinLengthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var range = (IDValidatorRange) d;
if (range.Validator != null)
{
range.Validator.MinLength = (int) e.NewValue;
}
}
protected override Freezable CreateInstanceCore()
{
return new IDValidatorRange();
}
}
You can see that I derived from Freezable instead of its ancestor DependencyObject because we will want to inherit a DataContext for our bindings. DependencyObject does not provide this but Freezable does.
Finally, we can put this all together in XAML like such:
<TextBox>
<TextBox.Resources>
<local:IDValidatorRange x:Key="ValidatorRange"
MaxLength="{Binding MaxLength}"
MinLength="{Binding MinLength}" />
</TextBox.Resources>
<TextBox.Text>
<Binding Delay="100"
Mode="TwoWay"
NotifyOnSourceUpdated="True"
NotifyOnTargetUpdated="True"
NotifyOnValidationError="True"
Path="ID"
UpdateSourceTrigger="PropertyChanged"
ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<local:IDValidator Range="{StaticResource ValidatorRange}" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
One last gotcha here, because validation rules do not hold or inherit a DataContext this will prevent binding from working as expected if you try to declare everything inline with your rule. Instead, declare your bindable rule options as a resource and set the property on your custom rule using a StaticBinding.
This approach is a lot of work and a bit confusing. If your hands are not tied with the data context, I would recommend exploring other options. Using the INotifyDataErrorInfo interface on a view-model may be a more elegant approach to this problem.

WPF MVVM Validation Using IDataErrorInfo

I have created a Person MVVM model which needs to be validated. I am using IDataErrorInfo class and validating the user. But when the screen loads the textboxes are already red/validated indicating that the field needs to be filled out. I believe this is because I bind the PersonViewModel in the InitializeComponent. I tried to use LostFocus for updatetriggers but that did not do anything.
Here is my PersonViewModel:
public class PersonViewModel : IDataErrorInfo
{
private string _firstName;
private string _lastName;
public string LastName { get; set; }
public string Error
{
get { throw new NotImplementedException(); }
}
public string FirstName
{
get { return _firstName; }
set { _firstName = value; }
}
public string this[string columnName]
{
get
{
string validationResult = String.Empty;
switch(columnName)
{
case "FirstName":
validationResult = ValidateFirstName();
break;
case "LastName":
validationResult = ValidateLastName();
break;
default:
throw new ApplicationException("Unknown property being validated on the Product");
}
return validationResult;
}
}
private string ValidateLastName()
{
return String.IsNullOrEmpty(LastName) ? "Last Name cannot be empty" : String.Empty;
}
private string ValidateFirstName()
{
return String.IsNullOrEmpty(FirstName) ? "First Name cannot be empty" : String.Empty;
}
}
Here is the XAML:
<StackPanel>
<TextBlock>First Name</TextBlock>
<TextBox Text="{Binding FirstName, ValidatesOnDataErrors=True, UpdateSourceTrigger=LostFocus}" Background="Gray"></TextBox>
<TextBlock>Last Name</TextBlock>
<TextBox Text="{Binding LastName, ValidatesOnDataErrors=True, UpdateSourceTrigger=LostFocus}" Background="Gray"></TextBox>
</StackPanel>
MainWindow.cs:
public MainWindow()
{
InitializeComponent();
_personViewModel = new PersonViewModel();
this.DataContext = _personViewModel;
}
Am I missing something? I do not want the validation to be fired when the screen loads. I only want it to be fired when the user looses the focus of the textboxes.
Rather than fight the tide of how WPF works by default, consider redefining the UI so that the error display 'fits' the scenario of screen load as well as data entry error. Besides, a user should have some hints on a blank form of what is needed.
Create a method to do your validation, and store the validation results in a dictionary:
private Dictionary<string, string> _validationErrors = new Dictionary<string, string>();
public void Validate(string propertyName)
{
string validationResult = null;
switch(propertyName)
{
case "FirstName":
validationResult = ValidateFirstName();
break;
}
//etc.
}
//Clear dictionary properly here instead (You must also handle when a value becomes valid again)
_validationResults[propertyName] = validationResult;
//Note that in order for WPF to catch this update, you may need to raise the PropertyChanged event if you aren't doing so in the constructor (AFTER validating)
}
Then update your ViewModel to:
Have the indexer return a result from the _validationErrors instead, if present.
Call Validate() in your setters.
Optionally, in Validate(), if the propertyName is null, validate all properties.
WPF will call the indexer to display errors, and since you are returning something, it will think that there are errors. It won't unless you explictly call Validate() with this solution.
EDIT: Please also note that there is now a more efficient way of implementing validation in .NET 4.5 called INotifyDataErrorInfo.

IDataErrorInfo validate before binding occurs

I want to do some simple textbox validation in WPF, but I just realized that IDataErrorInfo relies on raising the PropertyChanged event in order to trigger the validation, which means that the invalid value is applied to my bound object before validation occurs. Is there a way to change this so the validation happens first (and prevents binding on invalid data), or is there another solution that works this way?
Trimmed down code looks like this:
<TextBox>
<TextBox.Text>
<Binding Path="MyProperty" ValidatesOnDataErrors="True" />
</TextBox.Text>
</TextBox>
public class MyViewModel : IDataErrorInfo
{
public string MyProperty
{
get { return _myProperty; }
set
{
if (_myProperty != value)
{
_myProperty = value;
NotifyPropertyChanged(() => MyProperty);
SaveSettings();
}
}
}
public string Error
{
get { return string.Empty; }
}
public string this[string columnName]
{
get
{
if (columnName == "MyProperty")
return "ERROR";
return string.Empty;
}
}
}
The better interface and validation method to use (if using .net 4.5) is INotifyDataErrorInfo. It's main advantage is allowing you to control when and how the validation occurs. One good overview:
http://anthymecaillard.wordpress.com/2012/03/26/wpf-4-5-validation-asynchrone/
I don't think you need to call SaveSettings() method every time property changed. I think it should be called when user click on "Save" button, but not when property changed. However if you still would like to save changes on property changed, you should only do it if there are no validation errors available. For instance:
public class MyViewModel : IDataErrorInfo
{
public string MyProperty
{
get { return _myProperty; }
set
{
if (_myProperty != value)
{
_myProperty = value;
NotifyPropertyChanged(() => MyProperty);
if (string.IsNullOrEmpty(this["MyProperty"]))
{
SaveSettings();
}
}
}
}
public string Error
{
get { return string.Empty; }
}
public string this[string columnName]
{
get
{
if (columnName == "MyProperty")
return "ERROR";
return string.Empty;
}
}
}

how to prevent validation when view and viewModel are initialized and displayed for the first time?

I am using Prism+MVVM+C#+WPF for a LoB application.
I've created a ViewModelBase class, which is inherited by all my viewModels.
This base class implements:
IViewModel (one of my base interfaces)
IDataErrorInfo (in order to allow ViewModel validation)
It follows my IDataErrorInfo implementation:
#region IDataErrorInfo members
public string this[string propertyName]
{
get { return this.Validate(propertyName); }
}
public string Error { get; private set; }
#endregion
protected string Validate(string propertyName)
{
this.Error = null;
PropertyInfo property = this.GetType().GetProperty(propertyName);
if (property == null)
throw new ArgumentException("propertyName");
foreach (ValidationAttribute attribute in property.GetCustomAttributes(typeof(ValidationAttribute), true))
{
try
{
object currentValue = property.GetValue(this, null);
attribute.Validate(currentValue, propertyName);
}
catch (ValidationException ex)
{
string errorMessage = (!string.IsNullOrWhiteSpace(attribute.ErrorMessage) ? attribute.ErrorMessage: ex.Message);
if (this.Error == null)
this.Error = errorMessage;
else
this.Error += string.Format("\r\n{0}", errorMessage);
}
}
return this.Error;
}
In a given point of the application, I am building and associating View and ViewModel in this way:
IViewModel viewModel = this.ServiceLocator.GetInstance(typeof(IMyViewModel)) as IMyViewModel;
IView view = this.ServiceLocator.GetInstance(type) as IMyView;
view.ViewModel = viewModel;
this.GlobalRegionManager.Regions[RegionNames.InnerRegion].Add(view);
The problem is occurs when the view starts to read up the viewModel properties. The "this[string propertyName]" is invoked and the validation function gets executed...
In the views, the binding of the properties that need to be validated is defined as:
Text="{Binding SourceFileName, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
Could you advice on how to prevent the initial validation?
Thanks in advance,
Gianluca

CustomValidation attribute doesn't seem to work

I have a simple test page in my Silverlight 4 application in which I'm trying to get a custom validation rule to fire.
I have a TextBox and a Button, and I am showing the validation results in a TextBlock. My view model has a Name property, which is bound the the Text property of the TextBox. I have two validation attributes on the Name property, [Required] and [CustomValidation].
When I hit the Submit button, the Required validator fires correctly, but the breakpoint inside the validation method of my custom validator never gets hit. I can't see why this is, as I think I have followed MS's example very carefully: http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.customvalidationattribute(v=vs.95).aspx
Here is the code for the view model:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using GalaSoft.MvvmLight.Command;
namespace MyProject
{
// custom validation class
public class StartsCapitalValidator
{
public static ValidationResult IsValid(string value)
{
// this code never gets hit
if (value.Length > 0)
{
var valid = (value[0].ToString() == value[0].ToString().ToUpper());
if (!valid)
return new ValidationResult("Name must start with capital letter");
}
return ValidationResult.Success;
}
}
// my view model
public class ValidationTestViewModel : ViewModelBase
{
// the property to be validated
string _name;
[Required]
[CustomValidation(typeof(StartsCapitalValidator), "IsValid")]
public string Name
{
get { return _name; }
set { SetProperty(ref _name, value, () => Name); }
}
string _result;
public string Result
{
get { return _result; }
private set { SetProperty(ref _result, value, () => Result); }
}
public RelayCommand SubmitCommand { get; private set; }
public ValidationTestViewModel()
{
SubmitCommand = new RelayCommand(Submit);
}
void Submit()
{
// perform validation when the user clicks the Submit button
var errors = new List<ValidationResult>();
if (!Validator.TryValidateObject(this, new ValidationContext(this, null, null), errors))
{
// we only ever get here from the Required validation, never from the CustomValidator
Result = String.Format("{0} error(s):\n{1}",
errors.Count,
String.Join("\n", errors.Select(e => e.ErrorMessage)));
}
else
{
Result = "Valid";
}
}
}
}
Here is the view:
<navigation:Page x:Class="Data.Byldr.Application.Views.ValidationTest"
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"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation">
<Grid Width="400">
<StackPanel>
<TextBox Text="{Binding Name, Mode=TwoWay}" />
<Button Command="{Binding SubmitCommand}" Content="Submit" />
<TextBlock Text="{Binding Result}" />
</StackPanel>
</Grid>
</navigation:Page>
Why don't you create your own Validation attribute like this..
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
public class StartsCapital : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var text = value as string;
if(text == null)
return ValidationResult.Success;
if (text.Length > 0)
{
var valid = (text[0].ToString() == text[0].ToString().ToUpper());
if (!valid)
return new ValidationResult("Name must start with capital letter");
}
return ValidationResult.Success;
}
}
And then use it like
// my view model
public class ValidationTestViewModel : ViewModelBase
{
// the property to be validated
string _name;
[Required]
[StartsCapital]
public string Name
{
get { return _name; }
set { SetProperty(ref _name, value, () => Name); }
}
As stated on the MSDN page for that overload of Validator.TryValidateObject ( http://msdn.microsoft.com/en-us/library/dd411803(v=VS.95).aspx ), only the object-level validations are checked with this method, and RequiredAttribute on properties.
To check property-level validations, use the overload that also takes a bool ( http://msdn.microsoft.com/en-us/library/dd411772(v=VS.95).aspx )
So it should be as simple as passing "true" as an extra parameter to TryValidateObject

Categories

Resources