I am using MVVM and I want to use IDataErrorInfo to validate my View.
My current implementation includes Nested objects and different ViewModels. e.g. Business Entity 'Customer' contains Business Entity 'Address'. I am accessing Address directly in my view, like "Customer.Address". To validate changes in Address I would have to implement IDataErrorInfo in Address.
I use Customer or Address in different Views/ViewModels. Usage in different Views/ViewModels lead to different Validation Behavior. Thus, implementing the validation in the Entity itself is insufficient.
Exposing the properties I want to change directly in the ViewModel (creating new Properties that directly set/get the entity) seems to make the ViewModel way too rigid. and quite too large.
I cannot inherit from Base Classes, as some Business Entities already derive from other objects (A fact I cannot change).
The only option I see at the moment is adding an interface to the ViewModel to the Business Entities, and forwarding this[] calls in the Business Entities to that ViewModel Interface.
Is there a best practice on how to validate these nested objects in the ViewModel?
EDIT: One more reason Validation I don't see Validation in the Business Objects as a usable idea is that I need different Business Objects in my ViewModel to validate the View and the data entry.
One way I have done this in the past is to expose a ValidationDelegate on the Model, which allows the ViewModel to attach its own validation code to the model.
Typically I do this because I use the Model
layer as plain data objects, so my Models only validate basic things such as max-length or not-nulls, while any advanced validation not specific to the data model gets done in the ViewModel. This typically includes things such as ensuring an item is unique, or that a user has permission to set a value to a specific range, or even something like your case where the validation only exists for a specific action.
public class CustomerViewModel
{
// Keeping these generic to reduce code here, but it
// should include PropertyChange notification
public AddressModel Address { get; set; }
public CustomerViewModel()
{
Address = new AddressModel();
Address.AddValidationDelegate(ValidateAddress);
}
// Validation Delegate to validate Adderess
private string ValidateAddress(object sender, string propertyName)
{
// Do your ViewModel-specific validation here.
// sender is your AddressModel and propertyName
// is the property on the address getting validated
// For example:
if (propertyName == "Street1" && string.IsNullOrEmpty(Address.Street1))
return "Street1 cannot be empty";
return null;
}
}
Here's the code I usually use for the validation delegate:
#region IDataErrorInfo & Validation Members
#region Validation Delegate
public delegate string ValidationDelegate(
object sender, string propertyName);
private List<ValidationDelegate> _validationDelegates =
new List<ValidationDelegate>();
public void AddValidationDelegate(ValidationDelegate func)
{
_validationDelegates.Add(func);
}
public void RemoveValidationDelegate(ValidationDelegate func)
{
if (_validationDelegates.Contains(func))
_validationDelegates.Remove(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)
{
string s = null;
foreach (var func in _validationDelegates)
{
s = func(this, propertyName);
if (s != null)
return s;
}
return s;
}
#endregion // IDataErrorInfo for binding errors
#endregion // IDataErrorInfo & Validation Members
Usage in different Views/ViewModels lead to different Validation Behavior.
Hence, you have different view models. If you can't inherit these view models from some base view model, use aggregation:
public class Address {}
public class AddressViewModel1 : IDataErrorInfo
{
private readonly Address address;
// other stuff here
}
public class AddressViewModel2 : IDataErrorInfo
{
private readonly Address address;
// other stuff here
}
what about using dependency injection and inject a validationservice to the customer object for each different viewmodel?
but i think implementing idataerrorinfo and all needed properties in your viewmodel would be cleaner, but of course one time more work.