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
Related
In my learning NET MAUI app I want to pass a behavior command parameter as string, like this:
Entry x:Name="name" Text="{Binding Name}"
ClearButtonVisibility="WhileEditing">
<Entry.Behaviors>
<toolkit:EventToCommandBehavior
EventName="TextChanged"
Command="{Binding ValidateCommand}"
CommandParameter="'Name'" />
</Entry.Behaviors>
</Entry>
In the code behind I am doing a validation:
public delegate void NotifyWithValidationMessages(Dictionary<string, string?> validationDictionary);
public class BaseViewModel : ObservableValidator
{
public event NotifyWithValidationMessages? ValidationCompleted;
public virtual ICommand ValidateCommand => new RelayCommand<string?>((string? propertyName) =>
{
ClearErrors();
if (!string.IsNullOrWhiteSpace(propertyName))
{
ValidateProperty(propertyName);
}
else
ValidateAllProperties();
var validationMessages = this.GetErrors()
.ToDictionary(k => k.MemberNames.First().ToLower(), v => v.ErrorMessage);
ValidationCompleted?.Invoke(validationMessages);
});
public BaseViewModel() : base()
{}
}
When the propertyName parameter is null, and the ValidateAllProperties() are triggered, everything works fine, but when I have a parameter line "Name", then I get the following error when the ValidateProperty(propertyName) got called :
System.ArgumentException: 'The value for property 'ValidateCommand' must be of type 'System.Windows.Input.ICommand'. (Parameter 'value')'
I am using CommunityToolkit.Mvvm v8.0.0
thnx
ValidateProperty method: Validates a property with a specified name and a given input value.(More info you could refer to ObservableValidator.ValidateProperty(Object, String) Method)
So if you want to Validate a specific property, you could try this:
ValidateProperty(Name,propertyName); // the first Name gets the value of the Name property; propertyName is the parameter you pass from xaml
Here is my complete code:
private string name;
[Required]
[StringLength(255)]
[MinLength(2)]
public string Name
{
get => this.name;
set
{
SetProperty(ref this.name, value);
}
}
public virtual ICommand ValidateCommand => new RelayCommand<string?>((string? propertyName) =>
{
...
if (!string.IsNullOrWhiteSpace(propertyName))
{
ValidateProperty(Name,propertyName); // ***just change this line
}
else
ValidateAllProperties();
...
});
Hope it works for you.
My question is about validation using custom attributes in C#.
I don't quite understand how the validation works. I have declared an attribute with the validation rule in it but when the error should be thrown it is not.
Attribute:
[AttributeUsage(AttributeTargets.Property)]
public class NotNullAttribute : Attribute
{
public bool IsValid(object value)
{
if (value is string && (string)value != "")
{
return false;
}
return true;
}
}
Inside the attribute I check if the property is of type string and if its value is an empty string because that is what I have to check.
The task is to check if a property is a string and if its an empty string then its not valid, otherwise it is.
My Person class:
class Person
{
[NotNull]
public string Name { get; set; }
}
Here I am applying the custom attribute.
Main method:
class Program
{
static void Main(string[] args)
{
Person p1 = new Person();
p1.Name = "";
Console.WriteLine("Validation done");
Console.ReadKey();
}
}
This is where I instantiate the Person class and assign an empty string to the Name property. This is where the error should be thrown I guess.
So my question is why isn't the validation applied? Should I have called the IsValid method from the attribute it self somehow?
I would take some explanation about this, thank you in advance!
The attribute itself is just a "decorator" of the property. If nothing calls it, it will not be automatically executed nor used.
In your case, however, I don't see the point of using an attribute, when you can use property itself:
private string _name = "";
public string Name
{
get
{
return _name;
}
set
{
if ( string.IsNullOrEmpty(value) )
{
//throw or fallback
}
else
{
_name = value;
}
}
}
Doing basic value validation is exactly the job property setters are great for. In case someone uses an invalid value, you can throw an exception, or set a fallback value for example.
If you would still prefer using attributes, you still need to have some code that performs the validation itself. And still, anyone can assign any valid value to the property, unless validation is performed.
For example ASP.NET MVC uses attribute validation during Model Binding - it checks the validation attributes on the bound model class and verifies it before the action method begins executing.
Example of attribute validation
Here is a simple example of how to make your code work with reflection.
First here is a slightly updated version of the validation attribute:
[AttributeUsage(AttributeTargets.Property)]
public class NotNullAttribute : Attribute
{
public bool IsValid(object value)
{
if (!string.IsNullOrEmpty(value as string))
{
return false;
}
return true;
}
}
Your code actually only allowed a null or "" value, which I guess is opposite of what you wanted. This version is valid only when the string is not null and not empty.
Now create a Validate method in your Program class:
private static bool Validate(object model)
{
foreach (var propertyInfo in model.GetType().GetProperties())
{
foreach (var attribute in propertyInfo.GetCustomAttributes(true))
{
var notNullAttribute = attribute as NotNullAttribute;
if (notNullAttribute != null)
{
if (!notNullAttribute.IsValid(propertyInfo.GetValue(model)))
{
return false;
}
}
}
}
return true;
}
This basically gathers all properties of the type of the passed in parameter, checks all attributes of the properties for NotNullAttribute and then executes the attribute's IsValid method against the current value from the model.
Finally here is how you can call it from Main:
static void Main(string[] args)
{
Person p1 = new Person();
p1.Name = "d";
if (Validate(p1))
{
Console.WriteLine("Valid");
}
else
{
Console.WriteLine("Invalid");
}
Console.WriteLine("Validation done");
Console.ReadKey();
}
Now, if you are planning on adding more validation attributes, I would create an interface first:
public interface IValidationAttribute
{
bool IsValid(object value);
}
Then derive all your validation attributes from IValidationAttribute and in Validate method use IValidationAttribute in place of NotNullAttribute. This way the code becomes more future-proof as you can just program against the interface and add new validation attributes anytime.
public class BankAccount
{
public enum AccountType
{
Saving,
Current
}
[Required(ErrorMessage="First Name Required")]
[MaxLength(15,ErrorMessage="First Name should not more than 1`5 character")]
[MinLength(3,ErrorMessage="First Name should be more than 3 character")]
public string AccountHolderFirstName { get; set; }
[Required(ErrorMessage="Last Name Required")]
[MaxLength(15,ErrorMessage="Last Name should not more than 1`5 character")]
[MinLength(3,ErrorMessage="Last Name should be more than 3 character")]
public string AccountHolderLastName { get; set; }
[Required]
[RegularExpression("^[0-9]+$", ErrorMessage = "Only Number allowed in AccountNumber")]
public string AccountNumber { get; set; }
public AccountType AcType { get; set; }
[AccountBalaceCheckAttribute]
public double AccountBalance { get; set; }
}
How to Validate
public class GenericValidator
{
public static bool TryValidate(object obj, out ICollection<ValidationResult> results)
{
var context = new ValidationContext(obj, serviceProvider: null, items: null);
results = new List<ValidationResult>();
return Validator.TryValidateObject(
obj, context, results,
validateAllProperties: true
);
}
}
Example
static void Main(string[] args)
{
var bankAccount = new BankAccount();
ICollection<ValidationResult> lstvalidationResult;
bool valid = GenericValidator.TryValidate(bankAccount, out lstvalidationResult);
if (!valid)
{
foreach (ValidationResult res in lstvalidationResult)
{
Console.WriteLine(res.MemberNames +":"+ res.ErrorMessage);
}
}
Console.ReadLine();
}
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).UpdateSource(); txtPassword.GetBindingExpression(Infrastructure.AttachedProperty.PasswordAssistent.PasswordValue).UpdateSource(); 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/
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.
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;
}
}
}