使用 WCF RIA 和 MVVM 模式验证 UI 上的数据
-
25-09-2019 - |
题
是否存在使用 MVVM 与 Silverlight 中的 RIA 服务结合构建和验证数据的最佳实践或广泛接受的方法?
这是我的问题的症结所在。假设我有一个 EmployeeView、EmployeeViewModel 和一些 Employee 实体。在常规 RIA 应用程序中,我将在视图上公开 Employee 实体,并且“免费”获得验证,因为实体实现了 INotifyDataErrorInfo 和 IDataErrorInfo(正确吗?)。
现在,如果我想通过 ViewModel 而不是直接通过实体公开一些 Employee 属性,那么它会变得更加复杂。我可以直接公开我需要的位并将它们挂接到后端的实体中,如下所示:
private Employee _employee;
public EmployeeViewModel()
{
_employee = new Employee();
}
public string Name
{
get { return _employee.Name; }
set
{
_employee.Name = value;
// fire property change, etc.
}
}
...但我失去了实体的美味“免费”验证。否则,我可以直接在视图模型中公开实体,如下所示
private Employee _employee;
public Employee Employee
{
get { return _employee; }
}
public EmployeeViewModel()
{
_employee = new Employee();
}
在这种情况下,视图将直接绑定到 Employee 实体并在其中找到其属性,如下所示:
<StackPanel DataContext="{Binding Employee}">
<TextBox Text="{Binding Name}" />
</StackPanel>
使用这种方法我们可以获得“免费”验证,但它并不完全是 MVVM 的干净实现。
第三种选择是在虚拟机中自己实现 INotifyDataErrorInfo 和 IDataErrorInfo ,但这看起来像是大量的管道代码,考虑到我使用上述解决方案是多么容易,并且有一些稍微不太“干净”但很糟糕的东西归根结底,事情变得容易多了。
所以我想我的问题是,这些方法中哪些适合哪种情况?我缺少更好的方法吗?
如果它是相关的,我正在查看 Caliburn.Micro MVVM 框架,但我很想看到普遍适用的答案。
解决方案
我使用与Caliburn.Micro RIA和我与我的客户端验证的解决方案非常满意。
我所做的是把ValidationBaseViewModel
(由Caliburn.Micro提供)和我的实际应用程序的虚拟机(在你的情况Screen
)之间的EmployeeViewModel
。 ValidationBaseViewModel
工具INotifyDataErrorInfo
所以你谈论的管道代码只能写一次。
ValidationBaseViewModel
的用下面的代码的重写添加/删除/错误的通知经由PropertyChangedBase.NotifyOfPropertyChange
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);
}
这实际上是在另一VM(ValidationBaseViewModel和EmployeeViewModel之间)具有以下定义:
public abstract class BaseEditViewModel<TEdit> :
ValidationBaseViewModel where TEdit : Entity
其中Entity
是RIA的System.ServiceModel.DomainServices.Client.Entity
和_editing
类部件是由当前的VM编辑的这种类型TEdit
的一个实例。
在与卡利组合协同程序这允许我做一些凉的东西等的情况如下:
[Rescue]
public IEnumerable<IResult> Save()
{
if (HasErrors)
{
yield return new GiveFocusByName(PropertyInError);
yield break;
}
...
}
其他提示
如果您不想使用外部资源或框架,那么我可以有一个 ViewModelBase
实施 INotifyDataErrorInfo
.
那个班级会有 ValidateProperty(string propertyName, object value)
验证特定属性,以及 Validate()
方法来验证整个对象。内部使用 Validator
类返回 ValidationResult
s。
如果使用反光板的话,可以 挺容易 通过模仿验证过程来实现 Entity
类本身 ViewModelBase
.
虽然它不是“免费”,但仍然相对便宜。
这是一个示例实现 IDataErrorInfo
. 。虽然没有经过测试,但会给你这个想法。
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));
}
}
可以使用一个局部类来扩展你的实体和添加数据验证那里通过IDataErrorInfo的。