Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
177 views
in Technique[技术] by (71.8m points)

c# - Form with validation of models wrapped in ViewModels with One To Many Relation

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)
Waitting for answers

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...