使用WCF RIA和MVVM模式在UI上验证数据
在Silverlight中使用MVVM结合RIA服务构建和验证数据是否有最佳实践或广泛接受的方法?使用WCF RIA和MVVM模式在UI上验证数据
这是我的问题的关键。假设我有一个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实现。
第三种选择是在VM中自己实现INotifyDataErrorInfo和IDataErrorInfo,但是这看起来像是很多管道代码,考虑到使用上述解决方案的容易程度以及稍微“干净” “但是在一天结束的时候更容易一些。
所以我想我的问题是,哪种方法适合在哪种情况下?我错过了更好的方法吗?
如果它是相关的,我正在查看Caliburn.Micro MVVM框架,但我希望看到一般适用的答案。
我使用RIA和Caliburn.Micro,并且非常满意我的客户端验证解决方案。
我所做的是在Screen
(由Caliburn.Micro提供)和我的实际应用程序VM(在您的情况下为EmployeeViewModel
)之间放置ValidationBaseViewModel
。 ValidationBaseViewModel
implements INotifyDataErrorInfo
,这样您所谈论的管道代码只写一次。我然后从(Caliburn.Micro)的重写PropertyChangedBase.NotifyOfPropertyChange
添加/删除/差错通知的经由ValidationBaseViewModel
用下面的代码:
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
类成员是这种类型TEdit
正在由当前VM编辑的一个实例。
与卡利协同程序组合,这允许我做像一些很酷的东西如下:
[Rescue]
public IEnumerable<IResult> Save()
{
if (HasErrors)
{
yield return new GiveFocusByName(PropertyInError);
yield break;
}
...
}
您可以使用分类来扩展您的实体并通过idataerrorinfo添加数据验证。
我在中间层的验证工作很好,我遇到的问题是如何在保持干净的MVVM设计的同时正确连接实体和视图模型。 – 2010-11-21 19:18:48
如果你不想使用外部资源或框架,然后我你可以有一个ViewModelBase
实现INotifyDataErrorInfo
。
该类将有ValidateProperty(string propertyName, object value)
验证特定的属性,Validate()
方法来验证整个对象。在内部使用Validator
类来返回ValidationResult
。
如果您使用反射器,它可以是非常容易通过模仿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));
}
}
这很好,谢谢。 – 2011-04-11 00:25:35