I'm attempting to implement the technique for data validation from Josh Smith's example here: Using a viewmodel to provide meaningful validation...
My code is remarkably similar to the example, except for a few difference, namely I'm using the MVVM-Light toolkit, and my model person class is a partial class that comes from a WCF backend.
Here's an example of the code in question:
First is the Automatically generated version of the class which comes from the WCF:
public partial class Person : BaseObject
{
private string FooField;
public string Foo {
get {
return this.FooField;
}
set {
if ((object.ReferenceEquals(this.FooField, value) != true)) {
this.FooField = value;
this.RaisePropertyChanged("Foo");
}
}
}
I then extend the partial class to implement IDataErrorInfo:
public partial class Person : IDataErrorInfo
{
public string Error
{
get { return null;}
}
public string this[string propertyName]
{
if (propertyName == "Foo")
{
//Do some backend Validation
}
}
}
And lastly I have a viewmodel:
public class PersonViewModel : INotifyProperyChanged, IDataErrorInfo
{
private string _fooString;
private Person _person;
...
public string Foo {
get { return _fooString; }
set
{
if (value == _fooString;)
return;
_fooString = value;
RaisePropertyChanged("Foo");
}
public string this[string propertyName]
{
if (propertyName == "Foo")
{
string msg = Validate(Foo); //Frontend Validation, range, format, etc.
if(msg ! = null)
return msg;
_person.Foo = Foo;
}
}
}
So when I bind to the property in the viewmodel the validation code defined in IDataErrorInfo's indexer gets executed on the view model and my textbox or whatever gets highlighted if my validation fails, as expected. However in my code the Indexer on the MODEL side never gets executed, at all. I can honestly say that I don't see or understand the mechanism that is supposed to invoke it. I have run the example code from Josh Smith's example, and it does work, calling the MV's this[], then if validation passes the Model's this[] hits for additional validation, but I for the life of me can't see how it happens.
I really hope this is something simple I'm overlooking. Thanks for looking at it.
You are missing
return _person[propertyName];
from the the indexer in the ViewModel.
Related
I'm trying to work out an issue I'm having with implementing MVVM in WPF. My Contact class below is my model that's being populated by Entity Framework.
public class Contact : INotifyPropertyChanged
{
public string _firstName;
public string FirstName
{
get
{
return _firstName;
}
set
{
_firstName = value;
OnPropertyChanged("FirstName");
}
}
public string _lastName;
public string LastName
{
get
{
return _lastName;
}
set
{
_lastName = value;
OnPropertyChanged("LastName");
}
}
//INotifyPropertyChanged implementation omitted for brevity
}
Here's my ViewModel:
public class ContactViewModel
{
public Contact MyContact { get; set; }
public string FullName
{
get
{
return MyContact.FirstName + " " + MyContact.LastName;
}
}
}
So I set my View's datasource to an instance of ContactViewModel, and I'm binding two TextBoxes to MyContact.FirstName and MyContact.LastName. I'm binding a TextBlock to FullName. When I change either of my TextBoxes the Full Name TextBlock doesn't update (obviously, I'm not doing an OnPropertyChanged("FullName") anywhere).
The question is, where do I add OnPropertyChanged("FullName")? I don't necessarily want to modify my model because it's being used elsewhere and I don't to tie it to my ViewModel.
Do I need to rethink my architecture?
Do I need to rethink my architecture?
This can be solved with your current architecture. You just need to propagate the call from your Contact object to your viewModel object.
You will need to implement INotifyPropertyChanged in the viewModel to achieve this.
Something like this:
public class ContactViewModel : INotifyPropertyChanged
{
//INotifyPropertyChanged implementation omitted for brevity...
private Contact _myContact;
public Contact MyContact
{
get
{
return _myContact;
}
set
{
_myContact.PropertyChanged -= myHandler;
_myContact = value;
_myContact.PropertyChanged += myHandler;
}
}
public string FullName
{
get
{
return MyContact.FirstName + " " + MyContact.LastName;
}
}
private void myHandler(Object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged("FullName");
}
}
I would also recommend taking a look at MVVM Foundation as this includes a class called PropertyObserver which is designed to make wiring up this sort of thing much easier.
If you want to take the more MVVM pure approach suggested by Big Daddy, you would need to do something like this:
public class ContactViewModel : INotifyPropertyChanged
{
// INotifyPropertyChanged implementation omitted for brevity...
// You will require some way of setting this, either via a property
// or the viewModel constructor...
private Contact _myContact;
public string FirstName
{
get { return _myContact.FirstName; }
set
{
_myContact.FirstName = value;
OnPropertyChanged("FirstName");
OnPropertyChanged("FullName");
}
}
public string LastName
{
get { return _myContact.LastName; }
set
{
_myContact.LastName = value;
OnPropertyChanged("LastName");
OnPropertyChanged("FullName");
}
}
public string FullName
{
get
{
return MyContact.FirstName + " " + MyContact.LastName;
}
}
}
Do I need to rethink my architecture?
Maybe...
It looks to me like you're binding your view's properties to your view-model (ContactViewModel) and your model (Contact). Your view can see your public model's properties, etc. via your view-model - I don't think this is good. It looks like a violation of the Law of Demeter. I'd rather see you use your view-model as a wrapper/façade to your model. This creates more work for sure, but I think it gives you a better design and more flexibility. Your view-model will need to implement INotifyPropertyChanged for this to work.
Here is what I have:
In the view there is a tab control with two tabs (sys1 and sys2) each with the same textboxes that are bound to the properties of their respective entities:
sys1:
<TextBox Text="{Binding sys1.Serial, ValidatesOnExceptions=True, NotifyOnValidationError=True}" />
sys2:
<TextBox Text="{Binding sys2.Serial, ValidatesOnExceptions=True, NotifyOnValidationError=True}" />
Using some form of validation I would like to compare the two values and display an error (red border is fine) if the values don't match.
I've used IDataErrorInfo before, but I'm not sure if this type of validation is possible.
Note: whether or not binding directly to the entity is "correct" is a discussion for another place and time. Just know that this is a team project and our teams standards are to bind to the entity so I can't change that unless I have a good reason. Perhaps if it's not possible validating when bound directly to the entity then I may have a good enough reason to change this.
Thanks
I usually expose a Validation Delegate from my Model that my ViewModel can use to attach business-rule validation to the Models.
For example, the ViewModel containing your objects might look like this:
public ParentViewModel()
{
sys1.AddValidationErrorDelegate(ValidateSerial);
sys2.AddValidationErrorDelegate(ValidateSerial);
}
private string ValidateSerial(object sender, string propertyName)
{
if (propertyName == "Serial")
{
if (sys1.Serial == sys2.Serial)
return "Serial already assigned";
}
return null;
}
The idea is that your Model should only contain raw data, therefore it should only validate raw data. This can include validating things like maximum lengths, required fields, and allowed characters. Business Logic, which includes business rules, should be validated in the ViewModel and this allows that to happen.
The actual implementation of my IDataErrorInfo on the Model class would look like this:
#region IDataErrorInfo & Validation Members
/// <summary>
/// List of Property Names that should be validated
/// </summary>
protected List<string> ValidatedProperties = new List<string>();
#region Validation Delegate
public delegate string ValidationErrorDelegate(object sender, string propertyName);
private List<ValidationErrorDelegate> _validationDelegates = new List<ValidationErrorDelegate>();
public void AddValidationErrorDelegate(ValidationErrorDelegate func)
{
_validationDelegates.Add(func);
}
#endregion // Validation Delegate
#region IDataErrorInfo for binding errors
string IDataErrorInfo.Error { get { return null; } }
string IDataErrorInfo.this[string propertyName]
{
get { return this.GetValidationError(propertyName); }
}
public string GetValidationError(string propertyName)
{
// If user specified properties to validate, check to see if this one exists in the list
if (ValidatedProperties.IndexOf(propertyName) < 0)
{
//Debug.Fail("Unexpected property being validated on " + this.GetType().ToString() + ": " + propertyName);
return null;
}
string s = null;
// If user specified a Validation method to use, Validate property
if (_validationDelegates.Count > 0)
{
foreach (ValidationErrorDelegate func in _validationDelegates)
{
s = func(this, propertyName);
if (s != null)
{
return s;
}
}
}
return s;
}
#endregion // IDataErrorInfo for binding errors
#region IsValid Property
public bool IsValid
{
get
{
return (GetValidationError() == null);
}
}
public string GetValidationError()
{
string error = null;
if (ValidatedProperties != null)
{
foreach (string s in ValidatedProperties)
{
error = GetValidationError(s);
if (error != null)
{
return error;
}
}
}
return error;
}
#endregion // IsValid Property
#endregion // IDataErrorInfo & Validation Members
P.S. I see nothing wrong with binding directly to the Model, especially in smaller applications. It may not be the "MVVM-purist" approach, however it is efficient and a lot less work, so I find it a perfectly valid option.
In the set (mutator) for sys1.Serial1 and sys2.Serial you should be able to get the other's value for a comparison.
I have read a lot of Blog post on WPF Validation and on DataAnnotations. I was wondering if there is a clean way to use DataAnnotations as ValidationRules for my entity.
So instead of having this (Source) :
<Binding Path="Age" Source="{StaticResource ods}" ... >
<Binding.ValidationRules>
<c:AgeRangeRule Min="21" Max="130"/>
</Binding.ValidationRules>
</Binding>
Where you must have your
public class AgeRangeRule : ValidationRule
{...}
I want the WPF Binding to go see the Age property and look for DataAnnotation a bit like this:
[Range(1, 120)]
public int Age
{
get { return _age; }
set
{
_age = value;
RaisePropertyChanged<...>(x => x.Age);
}
}
Any ideas if this is possible ?
The closest approach I found is :
// This loop into all DataAnnotations and return all errors strings
protected string ValidateProperty(object value, string propertyName)
{
var info = this.GetType().GetProperty(propertyName);
IEnumerable<string> errorInfos =
(from va in info.GetCustomAttributes(true).OfType<ValidationAttribute>()
where !va.IsValid(value)
select va.FormatErrorMessage(string.Empty)).ToList();
if (errorInfos.Count() > 0)
{
return errorInfos.FirstOrDefault<string>();
}
return null;
Source
public class PersonEntity : IDataErrorInfo
{
[StringLength(50, MinimumLength = 1, ErrorMessage = "Error Msg.")]
public string Name
{
get { return _name; }
set
{
_name = value;
PropertyChanged("Name");
}
}
public string this[string propertyName]
{
get
{
if (porpertyName == "Name")
return ValidateProperty(this.Name, propertyName);
}
}
}
Source and Source
That way, the DataAnnotation works fine, I got a minimum to do on the XAML ValidatesOnDataErrors="True" and it's a fine workaround of Aaron post with the DataAnnotation.
In your model you could implement IDataErrorInfo and do something like this...
string IDataErrorInfo.this[string columnName]
{
get
{
if (columnName == "Age")
{
if (Age < 0 ||
Age > 120)
{
return "You must be between 1 - 120";
}
}
return null;
}
}
You will also need to notify the binding target of the newly defined behavior.
<TextBox Text="{Binding Age, ValidatesOnDataErrors=True}" />
EDIT:
If you only want to use Data Annotations you can follow this blog post which outlines how to accomplish the task.
UPDATE:
Historical representation of the aforementioned link.
Sounds good Aaron. I'm just into WPF and will study databindings next week at work ;) So cannot completely judge your answer...
But with winforms I have used Validation Application Block from the Entlib and implemented IDataErrorInfo (actually IDXDataErrorInfo because we work with DevExpress controls) on a base entity (business object) and that works pretty fine!
It's a bit more sophisticated than the solution you sketched in this way that you place your validation logic on the object and not in the interface implementation. Making it more OOP and maintainable. At the ID(XD)ataErrorInfo I just call Validation.Validate(this), or even better get the validator for the property that the interface is called for and validate the specific validator. Don't forget to call the [SelfValidation] as well because of validation for combinations of properties ;)
You might be interested in the BookLibrary sample application of the WPF Application Framework (WAF). It uses the DataAnnotations Validation attributes together with WPF Binding.
Recently I've had the same idea using the Data Annotation API to validate EF Code First POCO classes in WPF. Like Philippe's post my solution uses reflection, but all necessary code is included in a generic validator.
internal class ClientValidationRule : GenericValidationRule<Client> { }
internal class GenericValidationRule<T> : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string result = "";
BindingGroup bindingGroup = (BindingGroup)value;
foreach (var item in bindingGroup.Items.OfType<T>()) {
Type type = typeof(T);
foreach (var pi in type.GetProperties()) {
foreach (var attrib in pi.GetCustomAttributes(false)) {
if (attrib is System.ComponentModel.DataAnnotations.ValidationAttribute) {
var validationAttribute = attrib as System.ComponentModel.DataAnnotations.ValidationAttribute;
var val = bindingGroup.GetValue(item, pi.Name);
if (!validationAttribute.IsValid(val)) {
if (result != "")
result += Environment.NewLine;
if (string.IsNullOrEmpty(validationAttribute.ErrorMessage))
result += string.Format("Validation on {0} failed!", pi.Name);
else
result += validationAttribute.ErrorMessage;
}
}
}
}
}
if (result != "")
return new ValidationResult(false, result);
else
return ValidationResult.ValidResult;
}
}
The code above shows a ClientValidatorRule which is derived from the generic GenericValidationRule class. The Client class is my POCO class which will be validated.
public class Client {
public Client() {
this.ID = Guid.NewGuid();
}
[Key, ScaffoldColumn(false)]
public Guid ID { get; set; }
[Display(Name = "Name")]
[Required(ErrorMessage = "You have to provide a name.")]
public string Name { get; set; }
}
Im using DataAnnotation to validate input controls. But ValidatesOnExceptions only works when user type something in textbox and press Tab. (Basically on Lostfocus event).
but if user never enters anything in the textbox and click submit. It does not work. Like ASP.NET Page.IsValid property is there any property or method in Silverlight that i can use, that will validates all the controls on UI?
Taking help from the URL provided by Terence, i have prepared the solution below for you.
This you can use to make sure that all the properties are set before service call.
public class PersonViewModel : EntityBase
{
private readonly RelayCommand saveCommand;
public PersonViewModel(IServiceAgent serviceAgent)
{
saveCommand = new RelayCommand(Save) { IsEnabled = true };
}
public RelayCommand SaveCommand // Binded with SaveButton
{
get { return saveCommand; }
}
public String Name // Binded with NameTextBox
{
get
{
return name;
}
set
{
name = value;
PropertyChangedHandler("Name");
ValidateName("Name", value);
}
}
public Int32 Age // Binded with AgeTextBox
{
get
{
return age;
}
set
{
age = value;
PropertyChangedHandler("Age");
ValidateAge("Age", value);
}
}
private void ValidateName(string propertyName, String value)
{
ClearErrorFromProperty(propertyName);
if (/*SOME CONDITION*/)
AddErrorForProperty(propertyName, "/*NAME ERROR MESSAGE*/");
}
private void ValidateAge(string propertyName, Int32 value)
{
ClearErrorFromProperty(propertyName);
if (/*SOME CONDITION*/)
AddErrorForProperty(propertyName, "/*AGE ERROR MESSAGE*/");
}
public void Save()
{
ValidateName("Name", name);
ValidateAge("Age", age);
if (!HasErrors)
{
//SAVE CALL TO SERVICE
}
}
}
I don't think, that there is a way to validate ALL UserControls which are visible on the page. But I would recommend you to have a look at the INotifyDataErrorInfo. This is, in my opinion, the best way to validate data in silverlight. With the INotifyDataErrorInfo approach you don't have to make changes in the view (like ValidatesOnException, ...) and you are able to validate against a WebService in an easy way (This is not possible with data annotations).
Have a look here: http://mtaulty.com/CommunityServer/blogs/mike_taultys_blog/archive/2009/11/18/silverlight-4-rough-notes-binding-with-inotifydataerrorinfo.aspx
Hope this helps you.
Is there a best practice or widely accepted way of structuring and validating data using MVVM in conjunction with RIA services in Silverlight?
Here's the crux of my problem. Let's say I have an EmployeeView, EmployeeViewModel and some Employee entity. In regular RIA applications I will expose that Employee entity on the view and I get validation "for free", because Entities implement INotifyDataErrorInfo and IDataErrorInfo (correct?).
Now if I want to expose some Employee properties through a ViewModel instead of directly through an Entity then it becomes more complicated. I could expose the bits that I need directly and hook them into the entity on the backend, like this:
private Employee _employee;
public EmployeeViewModel()
{
_employee = new Employee();
}
public string Name
{
get { return _employee.Name; }
set
{
_employee.Name = value;
// fire property change, etc.
}
}
... but I lose the tasty "free" validation of entities. Otherwise, I could expose the entity directly in the view model, like so
private Employee _employee;
public Employee Employee
{
get { return _employee; }
}
public EmployeeViewModel()
{
_employee = new Employee();
}
In this case, the view will bind directly to the Employee entity and find its properties in there, like so:
<StackPanel DataContext="{Binding Employee}">
<TextBox Text="{Binding Name}" />
</StackPanel>
Using this method we get "free" validation, but it's not exactly a clean implementation of MVVM.
A third option would be to implement INotifyDataErrorInfo and IDataErrorInfo myself in the VMs, but this seems like an awful lot of plumbing code, considering how easy it would be for me to use the above solution and have something slightly less "clean" but a heck of a lot easier at the end of the day.
So I guess my question is, which of these approaches are appropriate in which situation? Is there a better approach I am missing?
In case it's relevant I'm looking at the Caliburn.Micro MVVM framework, but I would be keen to see answers that apply generically.
I am using RIA with Caliburn.Micro and am pretty happy with my solution for client side validation.
What I have done is put a ValidationBaseViewModel between Screen (provided by Caliburn.Micro) and my actual application VMs (EmployeeViewModel in your case). ValidationBaseViewModel implements INotifyDataErrorInfo so that plumbing code your talking about is only written once. I then add/remove/notify of errors via ValidationBaseViewModel from an override of the (Caliburn.Micro) PropertyChangedBase.NotifyOfPropertyChange with the following code:
public override void NotifyOfPropertyChange(string property)
{
if (_editing == null)
return;
if (HasErrors)
RemoveErrorFromPropertyAndNotifyErrorChanges(property, 100);
if (_editing.HasValidationErrors)
{
foreach (var validationError in
_editing.ValidationErrors
.Where(error => error.MemberNames.Contains(property)))
{
AddErrorToPropertyAndNotifyErrorChanges(property, new ValidationErrorInfo() { ErrorCode = 100, ErrorMessage = validationError.ErrorMessage });
}
}
base.NotifyOfPropertyChange(property);
}
This is actually in another VM (between ValidationBaseViewModel and EmployeeViewModel) with the following definition:
public abstract class BaseEditViewModel<TEdit> :
ValidationBaseViewModel where TEdit : Entity
where Entity is RIAs System.ServiceModel.DomainServices.Client.Entity and the _editing class member is an instance of this type TEdit which is being edited by the current VM.
In combination with Caliburn coroutines this allows me to do some cool stuff like the following:
[Rescue]
public IEnumerable<IResult> Save()
{
if (HasErrors)
{
yield return new GiveFocusByName(PropertyInError);
yield break;
}
...
}
If you don't want to use external resources or frameworks, then I you could have a ViewModelBase that implement INotifyDataErrorInfo.
That class will have ValidateProperty(string propertyName, object value) to validate a speciic property, and Validate() method to validate the entire object. Internally use the Validator class to return the ValidationResults.
If you use reflector, it can be pretty easy to achieve by mimicking the validation process in the Entity class itself to the ViewModelBase.
Although it's no "free", is still relatively cheap tho.
Here is a sample implementation of IDataErrorInfo. Although not tested, will give you the idea.
public class ViewModelBase : INotifyPropertyChanged, INotifyDataErrorInfo
{
/*
* InotifyPropertyChanged implementation
* Consider using Linq expressions instead of string names
*/
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public IEnumerable GetErrors(string propertyName)
{
if (implValidationErrors == null) return null;
return ImplValidationErros.Where(ve =>
ve.MemberNames.Any(mn => mn == propertyName));
}
public bool HasErrors
{
get
{
return implValidationErrors == null || ImplValidationErros.Any();
}
}
private List<ValidationResult> implValidationErrors;
private List<ValidationResult> ImplValidationErros
{
get
{
return implValidationErrors ??
(implValidationErrors = new List<ValidationResult>());
}
}
private ReadOnlyCollection<ValidationResult> validationErrors;
[Display(AutoGenerateField = false)]
protected ICollection<ValidationResult> ValidationErrors
{
get
{
return validationErrors ??
(validationErrors =
new ReadOnlyCollection<ValidationResult>(ImplValidationErros));
}
}
protected void ValidateProperty(string propertyName, object value)
{
ValidationContext validationContext =
new ValidationContext(this, null, null);
validationContext.MemberName = propertyName;
List<ValidationResult> validationResults =
new List<ValidationResult>();
Validator.TryValidateProperty(
value,
validationContext,
validationResults);
if (!validationResults.Any()) return;
validationResults
.AddRange(ValidationErrors
.Where(ve =>
!ve.MemberNames.All(mn =>
mn == propertyName)));
implValidationErrors = validationResults;
if (ErrorsChanged != null)
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
}
you can use a partial class to extend your entitty and add data validation there via idataerrorinfo.