I am using the new MVVM Toolkit from WindowsCommunityToolkit for a WPF project on .NET5.
On this project, I have a form to create a customer with data validation.
Consider the following:
Models:
public class Customer : DomainObject
{
public string LastName { get; set; }
public string FirstName { get; set; }
//...
public ObservableCollection<PhoneNumber> PhoneNumbers { get; set; }
}
public class PhoneNumber : DomainObject
{
public string PhoneNumber { get; set; }
public OperatorType PhoneOperator { get; set; }
}
Viewmodel wrapping Customer with validation (ObservableValidator):
public class CustomerViewModel : ObservableValidator
{
private ObservableCollection<PhoneNumberViewModel> phoneNumbers;
public Customer Customer { get; }
public CustomerViewModel(Customer customer = null)
{
if(customer is not null)
Customer = customer;
else
Customer = new Customer();
ErrorsChanged += CustomerViewModel_ErrorsChanged;
PropertyChanged += CustomerViewModel_PropertyChanged;
}
public int Id => Customer.Id;
[Display(ResourceType = typeof(GeneralResources), Name = nameof(GeneralResources.LastName))]
[Required(ErrorMessageResourceType = typeof(ValidationResources), ErrorMessageResourceName = nameof(ValidationResources.Required))]
[RegularExpression(@"^([^d]+)$", ErrorMessageResourceType = typeof(ValidationResources), ErrorMessageResourceName = nameof(ValidationResources.CantContainsNumbers))]
public string LastName
{
get => Customer.LastName;
set => SetProperty(Customer.LastName, value, Customer, (u, n) => u.LastName = n, true);
}
[Display(ResourceType = typeof(GeneralResources), Name = nameof(GeneralResources.FirstName))]
[Required(ErrorMessageResourceType = typeof(ValidationResources), ErrorMessageResourceName = nameof(ValidationResources.Required))]
[RegularExpression(@"^([^d]+)$", ErrorMessageResourceType = typeof(ValidationResources), ErrorMessageResourceName = nameof(ValidationResources.CantContainsNumbers))]
public string FirstName
{
get => Customer.FirstName;
set => SetProperty(Customer.FirstName, value, Customer, (u, n) => u.FirstName = n, true);
}
public ObservableCollection<PhoneNumberViewModel> PhoneNumbers
{
get => PhoneNumbers;
set => SetProperty(ref PhoneNumbers, value);
}
public bool CanCreate => !HasErrors;
public string Errors => string.Join(Environment.NewLine, from ValidationResult e in GetErrors(null) select e.ErrorMessage);
private void CustomerViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != nameof(HasErrors))
OnPropertyChanged(nameof(HasErrors));
}
private void CustomerViewModel_ErrorsChanged(object sender, DataErrorsChangedEventArgs e)
{
OnPropertyChanged(nameof(CanCreate));
OnPropertyChanged(nameof(Errors));
}
}
Viewmodel wrapping PhoneNumber with validation:
public class PhoneNumberViewModel : ObservableValidator
{
public PhoneNumber PhoneNumber { get; }
public PhoneNumberViewModel(PhoneNumber phoneNumber = null)
{
if (phoneNumber is not null)
PhoneNumber = phoneNumber;
else
PhoneNumber = new PhoneNumber();
ErrorsChanged += PhoneNumberViewModel_ErrorsChanged; ;
PropertyChanged += PhoneNumberViewModel_PropertyChanged;
}
public int Id => PhoneNumber.Id;
[Display(ResourceType = typeof(GeneralResources), Name = nameof(GeneralResources.PhoneNumber))]
[Required(ErrorMessageResourceType = typeof(ValidationResources), ErrorMessageResourceName = nameof(ValidationResources.Required))]
[RegularExpression(@"^(?:[0-9]{9,10})$", ErrorMessageResourceType = typeof(ValidationResources), ErrorMessageResourceName = nameof(ValidationResources.Invalid))]
public string PhoneNumber
{
get => PhoneNumber.PhoneNumber;
set => SetProperty(PhoneNumber.PhoneNumber, value, PhoneNumber, (u, n) => u.PhoneNumber = n, true);
}
[Display(ResourceType = typeof(GeneralResources), Name = nameof(GeneralResources.Operator))]
[Required(ErrorMessageResourceType = typeof(ValidationResources), ErrorMessageResourceName = nameof(ValidationResources.Required))]
public OperatorType PhoneOperator
{
get => PhoneNumber.PhoneOperator;
set => SetProperty(PhoneNumber.PhoneOperator, value, PhoneNumber, (u, n) => u.PhoneOperator = n, true);
}
public string Errors => string.Join(Environment.NewLine, from ValidationResult e in GetErrors(null) select e.ErrorMessage);
private void PhoneNumberViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != nameof(HasErrors))
OnPropertyChanged(nameof(HasErrors));
}
private void PhoneNumberViewModel_ErrorsChanged(object sender, DataErrorsChangedEventArgs e)
{
OnPropertyChanged(nameof(Errors));
}
}
I create my customer by calling this Command :
public override async Task ExecuteAsync(object parameter)
{
await _customerDataService.Create(_customersViewModel.CurrentCustomer.Customer);
_customersViewModel.UpdateCustomersList();
_customersViewModel.ResetCustomerOnCreation();
}
And this is my ViewModel where I have my CustomerOnCreation and have a list of all customers :
public class CustomersViewModel : ObservableObject
{
private CustomerViewModel _customerOnCreation;
public CustomerViewModel CustomerOnCreation
{
get => _customerOnCreation;
set => SetProperty(ref _customerOnCreation, value);
}
private ObservableCollection<CustomerViewModel> _customers;
public ObservableCollection<CustomerViewModel> Customers
{
get => _customers;
set => SetProperty(ref _customers, value);
}
public AsyncCommandBase CreateCustomerCommand { get; }
public CustomersViewModel(ICustomerService customerDataService)
{
_customerDataService = customerDataService;
CreateCustomerCommand = new CreateCustomerCommand(customerDataService, this);
}
My problem is that I was creating my customer straight away with the CreateCustomerCommand
because I was using the PhoneNumber
model (ObservableCollection<PhoneNumber>
). But now that I added validation to the PhoneNumber by wrapping it in a viewmodel (ObservableCollection<PhoneNumberViewModel>
) I will need to update it like this :
CustomerOnCreation.Customer.PhoneNumbers = CustomerOnCreation.PhoneNumbers.Select(phoneNumberViewModel => phoneNumberViewModel.PhoneNumber).ToList();
before calling my command. But I am not sure if this is the right way to validate data for one to many relationship when models are wrapped in viewmodels.
I didn't find any examples of multilevel validation.
So my questions are :
- Is my structure right for my goal?
- Should I validate everything once again when calling the create customer command?
- What if I have another level of children model?
question from:
https://stackoverflow.com/questions/65864630/form-with-validation-of-models-wrapped-in-viewmodels-with-one-to-many-relation