提交前的MVVM /新实体/必需属性/验证

时间:2010-09-08 00:30:14

标签: mvvm mvvm-light wcf-ria-services

我通过WCF RIA Services Metadata类使用Required属性,其下面是Entity Framework。

我创建了一个新实体,让视图绑定到视图模型。用户查看它一段时间,点击,然后尝试保存它。

此处的方案是用户没有标签或点击具有必填字段属性的字段。

如何在提交数据之前确保所有必填字段都有数据?

Winforms有同样的问题,我用来循环表单中的所有字段以确保所有验证器都已通过。

我是否必须为页面重写该基类 - AGAIN?

在将其发送回服务器之前,是否有办法确保触发所有属性验证?我是否必须使用反射并使用Required属性选取所有字段?

我正在使用实体框架,并且我研究了INotifyDataErrorInfo - 但这是在数据库之旅后使用的(据我理解)。

我知道我不是第一个遇到这种情况的人 - 但在我的研究中,我找不到这种情况的好例子。

2 个答案:

答案 0 :(得分:1)

你看过System.ComponentModel.DataAnnotations中的Validator.ValidateObject吗?我很确定它完全符合你的要求。

答案 1 :(得分:0)

INotifyDataErrorInfo与访问数据库没有任何关系(或通过服务边界调用,这是我认为你的意思)。 INotifyDataErrorInfo是您查看模型可以实现的接口,以便向您的视图报告您的视图模型存在验证错误。连接您的视图模型的验证以使用该界面仍由您自己决定,除非WCF RIA服务免费为您提供,我并不乐观。

我在视图模型中使用[Required]属性只是为UI提供了我的字段所需的提示。我还在我的视图模型中实现了INotifyDataErrorInfo,并确保在视图模型中的任何属性发生更改时调用我的验证方法。当用户执行保存命令时,我还手动调用我的验证方法。

在我的情况下,我正在使用Fluent Validation库来实现我的验证逻辑。我还为需要验证逻辑的任何视图模型构建了一个新的基类。

public class ValidatingViewModelBase<T> : ViewModelBase, IValidatingViewModel, INotifyDataErrorInfo
{
    private readonly IValidator<T> _validator;
    private readonly Dictionary<string, List<ValidationInfo>> _errors;

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public ValidatingViewModelBase() : this(null, null)
    {
    }

    public ValidatingViewModelBase(IValidator<T> validator) : this(validator, null)
    {
    }

    public ValidatingViewModelBase(IValidator<T> validator, IMessenger messenger) : base(messenger)
    {
        _validator = validator;
        _errors = new Dictionary<string, List<ValidationInfo>>();
    }

    public IEnumerable GetErrors(string propertyName)
    {
        if (string.IsNullOrEmpty(propertyName))
            return _errors.Values;

        CreateValidationErrorInfoListForProperty(propertyName);
        return _errors[propertyName];
    }

    public bool HasErrors
    {
        get { return _errors.Count > 0; }
    }

    protected virtual void AddValidationErrorForProperty(string propertyName, ValidationInfo validationInfo)
    {
        CreateValidationErrorInfoListForProperty(propertyName);

        if (!_errors[propertyName].Contains(validationInfo))
        {
            _errors[propertyName].Add(validationInfo);
            RaiseErrorsChanged(propertyName);
        }
    }

    protected virtual void ClearValidationErrorsForProperty(string propertyName)
    {
        CreateValidationErrorInfoListForProperty(propertyName);

        if (_errors[propertyName].Count > 0)
        {
            _errors[propertyName].Clear();
            RaiseErrorsChanged(propertyName);
        }
    }

    protected virtual void ClearAllValidationErrors()
    {
        foreach (var propertyName in _errors.Keys)
            ClearValidationErrorsForProperty(propertyName);
        _errors.Clear();
    }

    private void CreateValidationErrorInfoListForProperty(string propertyName)
    {
        if (!_errors.ContainsKey(propertyName))
            _errors[propertyName] = new List<ValidationInfo>();
    }

    protected void RaiseErrorsChanged(string propertyName)
    {
        var handler = ErrorsChanged;
        if (handler != null)
        {
            handler.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
        }
    }

    protected override void RaisePropertyChanged(string propertyName)
    {
        Validate();
        base.RaisePropertyChanged(propertyName);
    }

    public bool Validate()
    {
        if (_validator == null)
            return true;

        if (this is ILoadAndSaveData && !((ILoadAndSaveData)this).HasLoadedData)
            return true;

        ClearAllValidationErrors();

        var results = _validator.Validate(this);

        if (!results.IsValid)
        {
            foreach (var failure in results.Errors)
            {
                AddValidationErrorForProperty(failure.PropertyName, 
                    new ValidationInfo(failure.ErrorMessage, ValidationType.Error));
            }
        }

        return results.IsValid;
    }

    public void SendValidationMessage()
    {
        var message = _errors.Values.SelectMany(propertyValidations => propertyValidations)
            .Aggregate("Please correct validation errors before saving.\r\n",
                (current, validationInfo) => current + ("\r\n· " + validationInfo.Message));

        MessengerInstance.Send(new ErrorMessage(new ErrorInfo { Message = message, Type = "Validation Error" }));
    }

    public bool ValidateAndSendValidationMessage()
    {
        var isValid = Validate();
        if (!isValid)
        {
            SendValidationMessage();
        }
        return isValid;
    }
}

public interface IValidatingViewModel
{
    bool Validate();
    void SendValidationMessage();
    bool ValidateAndSendValidationMessage();
}

public enum ValidationType { Error, Warning }

public class ValidationInfo
{
    public string Message { get; set; }
    public ValidationType Type { get; set; }

    public ValidationInfo(string message, ValidationType validationType)
    {
        Message = message;
        Type = validationType;
    }

    public override string ToString()
    {
        var result = Message;

        if (Type == ValidationType.Warning)
            result = "Warning: " + result;

        return result;
    }
}

所以我的视图模型继承自这个新的基类。

public class ExampleViewModel : ValidatingViewModelBase<IExampleViewModel>, IExampleViewModel
{
    public ExampleViewModel(IValidator<IExampleViewModel> validator,
        IMessenger messenger)
        : base(validator, messenger)

    {
        SaveCommand = new RelayCommand(SaveCommandExecute);
    }

    public RelayCommand SaveCommand { get; private set; }

    private void SaveCommandExecute()
    {
        if (!ValidateAndSendValidationMessage())
            return;

        // save stuff here
    }
}

我确保为每个视图模型创建一个验证器类。

public class ExampleValidator : AbstractValidator<IExampleViewModel>
{
    public TripInformationValidator()
    {
        // validation logic here
    }
}

希望这有帮助!