C# MVVM-验证真的必须如此繁琐吗?
在我的应用程序中,我有很多表单,大多数表单都有自己的绑定模型!当然,数据验证很重要,但是没有比为所有模型实现IDataErrorInfo,然后为所有属性编写代码来验证它们更好的解决方案了吗 我已经创建了验证帮助程序,删除了很多实际的验证代码,但我还是忍不住觉得我错过了一两个技巧!请允许我补充一点,这是我在其中使用MVVM的第一个应用程序,因此我相信我在这方面还有很多要学习的 编辑: 这是我不喜欢的典型模型的代码(让我解释一下): 有一个switch语句来标识正确的属性,然后为每个属性编写唯一的验证函数,这种想法感觉太过分了(不是要做的工作,而是需要的代码量)。也许这是一个优雅的解决方案,但它只是感觉不像一个 注意:我将按照其中一个答案中的建议将我的验证助手转换为扩展(谢谢Sheridan) 解决方案: 因此,根据我已经接受的答案,这是我最初实现使其工作的基本内容(显然,我将改进部分-但我只是想让它首先运行,因为在实现之前,我几乎没有使用lambda表达式或反射的经验) Validtion字典类(显示主要功能):C# MVVM-验证真的必须如此繁琐吗?,c#,wpf,validation,mvvm,C#,Wpf,Validation,Mvvm,在我的应用程序中,我有很多表单,大多数表单都有自己的绑定模型!当然,数据验证很重要,但是没有比为所有模型实现IDataErrorInfo,然后为所有属性编写代码来验证它们更好的解决方案了吗 我已经创建了验证帮助程序,删除了很多实际的验证代码,但我还是忍不住觉得我错过了一两个技巧!请允许我补充一点,这是我在其中使用MVVM的第一个应用程序,因此我相信我在这方面还有很多要学习的 编辑: 这是我不喜欢的典型模型的代码(让我解释一下): 有一个switch语句来标识正确的属性,然后为每个属性编写唯一的验
忽略我草率的命名约定和位置编码,我很高兴这一切都能正常工作!当然要特别感谢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")}
}