C# MVVM-验证真的必须如此繁琐吗?

C# MVVM-验证真的必须如此繁琐吗?,c#,wpf,validation,mvvm,C#,Wpf,Validation,Mvvm,在我的应用程序中,我有很多表单,大多数表单都有自己的绑定模型!当然,数据验证很重要,但是没有比为所有模型实现IDataErrorInfo,然后为所有属性编写代码来验证它们更好的解决方案了吗 我已经创建了验证帮助程序,删除了很多实际的验证代码,但我还是忍不住觉得我错过了一两个技巧!请允许我补充一点,这是我在其中使用MVVM的第一个应用程序,因此我相信我在这方面还有很多要学习的 编辑: 这是我不喜欢的典型模型的代码(让我解释一下): 有一个switch语句来标识正确的属性,然后为每个属性编写唯一的验

在我的应用程序中,我有很多表单,大多数表单都有自己的绑定模型!当然,数据验证很重要,但是没有比为所有模型实现IDataErrorInfo,然后为所有属性编写代码来验证它们更好的解决方案了吗

我已经创建了验证帮助程序,删除了很多实际的验证代码,但我还是忍不住觉得我错过了一两个技巧!请允许我补充一点,这是我在其中使用MVVM的第一个应用程序,因此我相信我在这方面还有很多要学习的

编辑:

这是我不喜欢的典型模型的代码(让我解释一下):

有一个switch语句来标识正确的属性,然后为每个属性编写唯一的验证函数,这种想法感觉太过分了(不是要做的工作,而是需要的代码量)。也许这是一个优雅的解决方案,但它只是感觉不像一个

注意:我将按照其中一个答案中的建议将我的验证助手转换为扩展(谢谢Sheridan)

解决方案:

因此,根据我已经接受的答案,这是我最初实现使其工作的基本内容(显然,我将改进部分-但我只是想让它首先运行,因为在实现之前,我几乎没有使用lambda表达式或反射的经验)

Validtion字典类(显示主要功能):


忽略我草率的命名约定和位置编码,我很高兴这一切都能正常工作!当然要特别感谢nmclean,也要感谢所有对这个问题做出贡献的人,所有的回答都非常有帮助,但经过一些考虑,我决定采用这种方法

我使用
extension
方法来减少必须编写的验证文本量。如果您不熟悉这些方法,请查看MSDN上的页面,了解
扩展
方法。我有几十个这样的例子,可以验证每种情况。例如:

if (propertyName == "Title" && !Title.ValidateMaximumLength(255)) error = 
    propertyName.GetMaximumLengthError(255);
Validation.cs
类中:

public static bool ValidateMaximumLength(this string input, int characterCount)
{
    return input.IsNullOrEmpty() ? true : input.Length <= characterCount;
}

public static string GetMaximumLengthError(this string input, int characterCount, 
    bool isInputAdjusted)
{
    if (isInputAdjusted) return input.GetMaximumLengthError(characterCount);
    string error = "The {0} field requires a value with a maximum of {1} in it.";
    return string.Format(error, input, characterCount.Pluralize("character"));
}

当然,实现您需要的所有方法需要一段时间,但这样以后您就可以节省大量时间,而且您还可以从所有错误消息的一致性中获益。

我个人喜欢FluentValidation方法

这将使用基于表达式的规则替换开关表,如:

            RuleFor(x => x.Username)
                .Length(3, 8)
                .WithMessage("Must be between 3-8 characters.");

            RuleFor(x => x.Password)
                .Matches(@"^\w*(?=\w*\d)(?=\w*[a-z])(?=\w*[A-Z])\w*$")
                .WithMessage("Must contain lower, upper and numeric chars.");

            RuleFor(x => x.Email)
                .EmailAddress()
                .WithMessage("A valid email address is required.");

            RuleFor(x => x.DateOfBirth)
                .Must(BeAValidDateOfBirth)
                .WithMessage("Must be within 100 years of today.");


关于这一点还有更多的信息——尽管那里的文档主要是基于web MVC的。对于Wpf,也有一些博客帖子,比如你是对的。switch语句太多了。将IDEI(和INotifyDataErrorInfo)逻辑隔离到基类中要容易得多

实现这一点的一个简单方法是公开一个方法来设置属性的错误,并清除属性的错误。这将很容易实现,尽管您必须为每个属性编写验证代码

public string SomeProperty { get { return _someProperty; }
    set
    {
        _someProperty = value;
        if(string.IsNullOrWhiteSpace(value))
            SetError("SomeProperty", "You must enter a value or something kthx");
        else
            ClearError("SomeProperty");
    } 
其中,在基类中,您保留了一个仅保存这些错误值的字典

protected void SetError(string propertyName, string error)
{
    _errors[propertyName] = error;
{
并按需交付,例如

string IDataErrorInfo.Error
{
    get
    {
        return string.Join(Environment.NewLine, _errors.Values);
    }
}
当您将这种模式与数据注释、一点反射和4.5中的一些特性结合起来以完全避免验证时,它会变得更加强大

有几个使用to的示例。如果设置了属性的名称,并使用反射(如果担心性能,请在第一次调用后将其缓存)获取属性上的任何数据批注,则可以执行所有验证检查并将结果存储在基类中。这会将派生类的属性简化为以下内容:

[NotNullOrWhiteSpace, NotADirtyWord, NotViagraSpam]
public string SomeProperty{ 
    get {return _lol;} 
    set{ _lol = value; PropertyChanged(); } }

这从根本上简化了整个验证流程,只需进行少量工作。

我的设计如下:

new ValidationDictionary() {
    {() => carer_title,
        ValidationHelpers.Required(() => "Please enter the carer's title"),
        ValidationHelpers.LettersOnly(() => "Only letters are valid")}
}
ValidationDictionary是字符串->委托的字典。它重载
Add
以接受一个lambda表达式,该表达式被转换为键的属性名称字符串,以及一个
params
委托数组,该数组被合并为一个值的委托。委托接受一些信息,如属性类型和值,并返回错误消息或
null

在这种情况下,
Required
LettersOnly
是高阶函数,它们生成的委托在无效时返回给定字符串。字符串本身作为委托传递,因此它们可以是动态的


IDataErrorInfo只需在字典中查找属性名称并调用委托以获取错误消息即可实现。

对于共享对象,您不能将验证属性移动到接口中,以便保存一些重复项吗?另一个选项是通过异常验证,当然这还有其他限制。你到底觉得什么很麻烦?我认为在这件事上IDataErrorInfo很文雅。当然,对于一些助手类,我想到了一些带有验证策略的自定义属性。我想展示一下如何实现
IDataErrorInfo
来处理一种类型的“错误/验证工厂/helper”类会很好。数据注释会不会减少一些工作@MatthToMo——没有这么多共享对象,我确实考虑了你的方法,但我觉得它不会有助于事态的这么多,如果有什么使事情更“弥赛亚”-谢谢答复!我喜欢这种方法。玩得很好。这与我正在做的非常相似,但是我还没有将它们作为扩展实现——不过我现在肯定会这么做!我将用一些我非常讨厌并希望减少的代码更新我的问题。Thanks@Gusdor,这是一个简单的
扩展
方法,带有重载,可以根据数字将单数词更改为复数。这并不意味着是一首全唱的歌
public string SomeProperty { get { return _someProperty; }
    set
    {
        _someProperty = value;
        if(string.IsNullOrWhiteSpace(value))
            SetError("SomeProperty", "You must enter a value or something kthx");
        else
            ClearError("SomeProperty");
    } 
protected void SetError(string propertyName, string error)
{
    _errors[propertyName] = error;
{
string IDataErrorInfo.Error
{
    get
    {
        return string.Join(Environment.NewLine, _errors.Values);
    }
}
[NotNullOrWhiteSpace, NotADirtyWord, NotViagraSpam]
public string SomeProperty{ 
    get {return _lol;} 
    set{ _lol = value; PropertyChanged(); } }
new ValidationDictionary() {
    {() => carer_title,
        ValidationHelpers.Required(() => "Please enter the carer's title"),
        ValidationHelpers.LettersOnly(() => "Only letters are valid")}
}