如何在 WPF 中将数据模型与验证分开?
How to separate data model from validations in WPF?
我使用以下代码在 WPF 中实现模型。但问题是它违反了实体原则 [设计模式],因为模型和验证代码都在同一代码中。
帮我把它们分开。
员工模型-
public class EmployeeModel : Model
{
#region Fields
private int employeeId;
private string employeeCode;
private string firstName;
private string lastName;
private DateTime? dateOfJoining;
private DateTime dob;
private string email;
private int? departmentId;
private string departmentName;
private string password;
private string role;
#endregion
#region Public Properties
public int EmployeeId
{
get
{
return employeeId;
}
set
{
if (value != this.employeeId)
{
employeeId = value;
SetPropertyChanged("EmployeeId");
}
}
}
public string EmployeeCode
{
get
{
return employeeCode;
}
set
{
if (value != this.employeeCode)
{
employeeCode = value;
SetPropertyChanged("EmployeeCode");
}
}
}
public DateTime? DateOfJoining
{
get
{
return dateOfJoining;
}
set
{
if (value != this.dateOfJoining)
{
dateOfJoining = Convert.ToDateTime(value);
SetPropertyChanged("DateofJoining");
}
}
}
public string FirstName
{
get
{
return firstName;
}
set
{
if (value != this.firstName)
{
firstName = value;
SetPropertyChanged("FirstName");
}
}
}
public string LastName
{
get
{
return lastName;
}
set
{
if (value != this.lastName)
{
lastName = value;
SetPropertyChanged("LastName");
}
}
}
public string FullName
{
get
{
return string.Join(" ", new[] { firstName, lastName });
}
}
public int? DepartmentId
{
get
{
return departmentId;
}
set
{
if (value != this.departmentId)
{
departmentId = value;
SetPropertyChanged("DepartmentId");
}
}
}
public string DepartmentName
{
get
{
return departmentName;
}
set
{
if (value != this.departmentName)
{
departmentName = value;
SetPropertyChanged("DepartmentName");
}
}
}
public DateTime DOB
{
get
{
return dob;
}
set
{
if (value != this.dob)
{
dob = Convert.ToDateTime(value);
SetPropertyChanged("DateofBirth");
}
}
}
public string Email
{
get
{
return email;
}
set
{
if (value != this.email)
{
email = value;
SetPropertyChanged("Email");
}
}
}
public string Password
{
get
{
return password;
}
set
{
if (value != this.password)
{
password = value;
SetPropertyChanged("Password");
}
}
}
public string Role
{
get
{
return role;
}
set
{
if (value != this.role)
{
role = value;
SetPropertyChanged("Role");
}
}
}
#endregion
#region Private Methods
private bool IsValid(string emailaddress)
{
try
{
MailAddress m = new MailAddress(emailaddress);
return true;
}
catch (Exception)
{
return false;
}
}
#endregion
#region Public Methods
public override string GetErrorForProperty(string propertyName)
{
string retErrorMsg = string.Empty;
switch (propertyName)
{
case "EmployeeCode":
if (EmployeeCode == null || EmployeeCode.Length < 2)
{
retErrorMsg = AppConstants.EmpCodeError;
}
break;
case "FirstName":
if (FirstName == null || FirstName == string.Empty)
{
retErrorMsg = AppConstants.FNameError;
}
break;
case "LastName":
if (LastName == null || LastName == string.Empty)
{
retErrorMsg = AppConstants.LNameError;
}
break;
case "DepartmentId":
if (DepartmentId == null || DepartmentId < 1)
{
retErrorMsg = AppConstants.DepartmentError;
}
break;
case "DOB":
if (DOB.AddYears(60).Date < DateTime.Now.Date || DOB.AddYears(18).Date > DateTime.Now.Date)
{
retErrorMsg = AppConstants.DOBError;
}
break;
case "DateOfJoining":
if (DateOfJoining == null || DateOfJoining > DateTime.Now)
{
retErrorMsg = AppConstants.DOJError;
}
break;
case "Role":
if (!(Role == "A" || Role == "U"))
{
retErrorMsg = AppConstants.RoleError;
}
break;
case "Email":
if (!IsValid(Email))
{
retErrorMsg = AppConstants.EmailError;
}
break;
case "Password":
if ((Password == null || Password.Length < 8))
{
retErrorMsg = AppConstants.PasswordError;
}
break;
}
return retErrorMsg;
}
#endregion
}
基地Class[Model.cs]
public abstract class Model : INotifyPropertyChanged, IDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
public void SetPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public string Error
{
get { return string.Empty; }
}
public string this[string propertyName]
{
get
{
return GetErrorForProperty(propertyName);
}
}
public abstract string GetErrorForProperty(string propertyName);
}
如果您担心 Solid 中的 S(单一职责原则),您应该看看这个答案:
基本上,"The ViewModel single responsibility is to provide the View the information it needs."我个人认为您应该最关心的是 DataModel 如何不了解视图。
这是代码中的一个小示例:
public class EmployeesEditViewModel : INotifyPropertyChanged
{
public string UserInputNewName
{
get
{
return Model.EmployeName;
}
set
{
if (ValidateValue(value))
{
Model.EmployeName = value;
ValidationResult = string.Empty;
OnPropertyChanged("UserInputNewName");
}
else
{
ValidationResult = "Error, name must not be empty.";
}
}
}
private bool ValidateValue(string value)
{
return !string.IsNullOrWhiteSpace(value);
}
private string _ValidationResult;
public string ValidationResult
{
get
{
return _ValidationResult ?? string.Empty;
}
set
{
_ValidationResult = value;
OnPropertyChanged("ValidationResult");
}
}
private EmployeeModel Model { get; set; }
}
public class EmployeeModel
{
public int EmployeeId { get; set; }
public string EmployeName { get; set; }
}
说明:
- 您有一个 EmployeeModel,这是描述员工的真实模型。您的视图(例如带有用户输入字段的 WPF Windows)不知道模型 - 模型和视图之间没有直接联系。
- 视图只知道一个视图模型。假设这是一个允许您修改员工的掩码,那么我们将为此使用 EmployeesEditViewModel。 ViewModel 公开了视图所需的属性,在这个简单的例子中,它只是员工的姓名和验证结果(可以只显示在另一个文本字段中)。当用户输入一个值时,您可以检查它是否有效,然后更新实际模型或告诉用户哪里出了问题。
理想情况下,您可能对模型本身有一些验证逻辑,并且让 ViewModel 仅将此结果转换为用户可以看到的内容。这会将所有责任(如验证)保留在它所属的模型上,但您仍然有一个 ViewModel 可以将其转换并转发给 View。
我使用以下代码在 WPF 中实现模型。但问题是它违反了实体原则 [设计模式],因为模型和验证代码都在同一代码中。 帮我把它们分开。
员工模型-
public class EmployeeModel : Model
{
#region Fields
private int employeeId;
private string employeeCode;
private string firstName;
private string lastName;
private DateTime? dateOfJoining;
private DateTime dob;
private string email;
private int? departmentId;
private string departmentName;
private string password;
private string role;
#endregion
#region Public Properties
public int EmployeeId
{
get
{
return employeeId;
}
set
{
if (value != this.employeeId)
{
employeeId = value;
SetPropertyChanged("EmployeeId");
}
}
}
public string EmployeeCode
{
get
{
return employeeCode;
}
set
{
if (value != this.employeeCode)
{
employeeCode = value;
SetPropertyChanged("EmployeeCode");
}
}
}
public DateTime? DateOfJoining
{
get
{
return dateOfJoining;
}
set
{
if (value != this.dateOfJoining)
{
dateOfJoining = Convert.ToDateTime(value);
SetPropertyChanged("DateofJoining");
}
}
}
public string FirstName
{
get
{
return firstName;
}
set
{
if (value != this.firstName)
{
firstName = value;
SetPropertyChanged("FirstName");
}
}
}
public string LastName
{
get
{
return lastName;
}
set
{
if (value != this.lastName)
{
lastName = value;
SetPropertyChanged("LastName");
}
}
}
public string FullName
{
get
{
return string.Join(" ", new[] { firstName, lastName });
}
}
public int? DepartmentId
{
get
{
return departmentId;
}
set
{
if (value != this.departmentId)
{
departmentId = value;
SetPropertyChanged("DepartmentId");
}
}
}
public string DepartmentName
{
get
{
return departmentName;
}
set
{
if (value != this.departmentName)
{
departmentName = value;
SetPropertyChanged("DepartmentName");
}
}
}
public DateTime DOB
{
get
{
return dob;
}
set
{
if (value != this.dob)
{
dob = Convert.ToDateTime(value);
SetPropertyChanged("DateofBirth");
}
}
}
public string Email
{
get
{
return email;
}
set
{
if (value != this.email)
{
email = value;
SetPropertyChanged("Email");
}
}
}
public string Password
{
get
{
return password;
}
set
{
if (value != this.password)
{
password = value;
SetPropertyChanged("Password");
}
}
}
public string Role
{
get
{
return role;
}
set
{
if (value != this.role)
{
role = value;
SetPropertyChanged("Role");
}
}
}
#endregion
#region Private Methods
private bool IsValid(string emailaddress)
{
try
{
MailAddress m = new MailAddress(emailaddress);
return true;
}
catch (Exception)
{
return false;
}
}
#endregion
#region Public Methods
public override string GetErrorForProperty(string propertyName)
{
string retErrorMsg = string.Empty;
switch (propertyName)
{
case "EmployeeCode":
if (EmployeeCode == null || EmployeeCode.Length < 2)
{
retErrorMsg = AppConstants.EmpCodeError;
}
break;
case "FirstName":
if (FirstName == null || FirstName == string.Empty)
{
retErrorMsg = AppConstants.FNameError;
}
break;
case "LastName":
if (LastName == null || LastName == string.Empty)
{
retErrorMsg = AppConstants.LNameError;
}
break;
case "DepartmentId":
if (DepartmentId == null || DepartmentId < 1)
{
retErrorMsg = AppConstants.DepartmentError;
}
break;
case "DOB":
if (DOB.AddYears(60).Date < DateTime.Now.Date || DOB.AddYears(18).Date > DateTime.Now.Date)
{
retErrorMsg = AppConstants.DOBError;
}
break;
case "DateOfJoining":
if (DateOfJoining == null || DateOfJoining > DateTime.Now)
{
retErrorMsg = AppConstants.DOJError;
}
break;
case "Role":
if (!(Role == "A" || Role == "U"))
{
retErrorMsg = AppConstants.RoleError;
}
break;
case "Email":
if (!IsValid(Email))
{
retErrorMsg = AppConstants.EmailError;
}
break;
case "Password":
if ((Password == null || Password.Length < 8))
{
retErrorMsg = AppConstants.PasswordError;
}
break;
}
return retErrorMsg;
}
#endregion
}
基地Class[Model.cs]
public abstract class Model : INotifyPropertyChanged, IDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
public void SetPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public string Error
{
get { return string.Empty; }
}
public string this[string propertyName]
{
get
{
return GetErrorForProperty(propertyName);
}
}
public abstract string GetErrorForProperty(string propertyName);
}
如果您担心 Solid 中的 S(单一职责原则),您应该看看这个答案:
基本上,"The ViewModel single responsibility is to provide the View the information it needs."我个人认为您应该最关心的是 DataModel 如何不了解视图。
这是代码中的一个小示例:
public class EmployeesEditViewModel : INotifyPropertyChanged
{
public string UserInputNewName
{
get
{
return Model.EmployeName;
}
set
{
if (ValidateValue(value))
{
Model.EmployeName = value;
ValidationResult = string.Empty;
OnPropertyChanged("UserInputNewName");
}
else
{
ValidationResult = "Error, name must not be empty.";
}
}
}
private bool ValidateValue(string value)
{
return !string.IsNullOrWhiteSpace(value);
}
private string _ValidationResult;
public string ValidationResult
{
get
{
return _ValidationResult ?? string.Empty;
}
set
{
_ValidationResult = value;
OnPropertyChanged("ValidationResult");
}
}
private EmployeeModel Model { get; set; }
}
public class EmployeeModel
{
public int EmployeeId { get; set; }
public string EmployeName { get; set; }
}
说明:
- 您有一个 EmployeeModel,这是描述员工的真实模型。您的视图(例如带有用户输入字段的 WPF Windows)不知道模型 - 模型和视图之间没有直接联系。
- 视图只知道一个视图模型。假设这是一个允许您修改员工的掩码,那么我们将为此使用 EmployeesEditViewModel。 ViewModel 公开了视图所需的属性,在这个简单的例子中,它只是员工的姓名和验证结果(可以只显示在另一个文本字段中)。当用户输入一个值时,您可以检查它是否有效,然后更新实际模型或告诉用户哪里出了问题。
理想情况下,您可能对模型本身有一些验证逻辑,并且让 ViewModel 仅将此结果转换为用户可以看到的内容。这会将所有责任(如验证)保留在它所属的模型上,但您仍然有一个 ViewModel 可以将其转换并转发给 View。