在 Excel 加载项框架中使用属性来验证和导出数据






4.95/5 (12投票s)
演示如何在严肃的应用程序中使用运行时属性来提高可维护性。
摘要
属性可以用于提高可维护性。我将此应用程序转换为使用属性来指定类中每个属性的信息,从而使维护更加容易,并减少了出现错误的几率。这些每个属性的属性用于在使用前修改属性,并确定属性的值是否有效。以前,类中有一个方法返回包含此信息的类集合。
引言
我之前发表了一篇题为《用于验证和导出数据的 Excel 加载项框架》的文章。在发布之前,我对原始实现进行了一些改进,其中许多改进提高了代码的复用性。最终,除了 UI 部分之外,该设计只需要一个定制类。我还想改进指定验证的方式。我最初设计使用匿名方法来指定验证,但这不如我期望的那样可维护。部分问题在于,我不仅必须在类中定义属性,还必须在一个方法中指定属性,该方法包含属性的名称(字符串)和验证。这意味着我必须在两个地方定义一个属性,并伴随着所有相关的维护问题。
我曾怀疑我可以通过使用 Attribute 类做得更好,但还有更重要的问题需要解决,而且我以前从未编程过属性。然后,还有一些设计问题需要解决,并且有可能对应用程序造成严重损坏。
研究
我开始在 Codeproject 网站上搜索文章。我读的第一篇文章是《C# 程序员入门 - 第 21 章》。这篇文章还可以,但有点过于简单,可能是因为它针对的是类而不是类的属性和方法。第二篇文章更有用:《基于属性的业务对象验证方法》,因为它和我正在做的事情有些相似,但我认为他没有充分利用他的属性。
属性设计
所有用于验证的属性类都应继承自一个接口,以便于过滤。 只需要一个方法来执行实际的验证,该方法接受一个类型为 ValueValidator
的参数。这效果很好,因为它包含了验证所需的所有信息,并封装了 Value 字符串,因此当 Value
属性更新时,代码不必担心字符串是不可变的。我想出于多种原因更新 Value
属性:
- 将所有字符转换为特定大小写,
- 允许应用格式,例如仅显示
DateTime
字段的日期。 - 以便可以进行替换。
interface IValueValidationAttribute { string Validate(ValueValidator valueValidator); }
一个继承自此 interface
的属性示例,该属性验证值是否为有效日期
[AttributeUsage(AttributeTargets.Property)] public class IsDateValueValidationAttribute : Attribute, IValueValidationAttribute { private readonly string _returnFormat; public IsDateValueValidationAttribute(string returnFormat = null) { _returnFormat = returnFormat; } public string Validate(ValueValidator valueValidator) { Contract.Requires(valueValidator != null, "value argument is null"); DateTime date; if (!DateTime.TryParse(valueValidator.Value, out date)) return "The value must be a valid date"; if (_returnFormat != null) valueValidator.Value = date.ToString(_returnFormat); return null; } }
可以看出,构造函数接受一个可选参数,即如果 Value
是有效的 DataTime
格式,则返回的格式。然后,Validate 方法负责检查 Value 是否符合要求。如果符合,则返回 null
;否则,返回一个描述错误的 string
。此外,构造函数中的参数用于更新 ValueValidator
的 Value
属性,使其成为指定格式的值的字符串。在示例中使用时,格式指定为短日期,这样在 DataGridView
控件中显示时,只会显示日期,而不会显示时间。
AbstractValidationObject 类
要使用这些属性,一个类需要继承自抽象的 AbstractValidationObject
类。继承的类用于定义将导出为属性的属性,并为这些属性提供定义验证的属性以及标题(即属性名称)是否必须在验证的标题行中。该类中有两个方法。其中一个属性返回 ValueValidator
类实例的集合,每个实例对应一个要从 Excel 工作表中读取的属性。
public IEnumerable<ValueValidator> GetValidators() { return GetType().GetProperties().Select( propertyInfo => new ValueValidator(propertyInfo, this)); }
请注意,每次调用 GetValidators
方法时都会创建一个新的 ValueValidator
类实例。这是因为每个 ValueValidator
都代表 Excel 工作表中的一个单元格,因此代表属性的单个值。
第二个方法对单个 ValueValidator
实例进行验证。
public string Validate(ValueValidator valueValidator) { var propertyInfo = GetType().GetProperty(valueValidator.HeaderText); return propertyInfo.GetCustomAttributes( typeof(IValueValidationAttribute), false). Select(customAttribute => ((IValueValidationAttribute)customAttribute). Validate(valueValidator)). FirstOrDefault(returnResult => returnResult != null); }
如果验证存在问题,则返回值是一个描述错误的字符串。如果 ValueValidator
中的值有效,则返回值是 null
。ValueValidator
提供了属性名称和值。属性名称用于使用反射查找 AbstractValidationObject
中的 PropertyInfo
,然后使用反射通过 PropertyInfo
类的 GetCustomAttributes
方法查找自定义属性。GetCustomAttributes
参数用于过滤所有继承自 IValueValidationAttribute
的属性。幸运的是,顺序得以保持,因此 IValueValidationAttribute
的实例可以按枚举顺序进行处理。这是通过执行返回类上的 Validate
方法来完成的。此 Validate
方法确定 ValueValidator
中的 Value
是否通过单个测试。处理将继续进行,直到第一个 Attribute
实例返回非空字符串,或所有实例都已处理完毕。
ValueValidator 类
ValueValidator
类用于包含有关属性的信息,以及有关包含特定信息的单元格的特定信息。对于 Excel 中的每一行,每个属性都会有一个 ValueValidator
实例。因此,ValueValidator
维护以下信息:
- 标题文本和/或属性名称(可以将设计更改为可选地使用不同的文本作为标题)。这用于在
AbstractValidationObject
类中查找PropertyInfo
,并搜索代表列标题的单元格。还有关于标题是否成功处理工作表所需的信息。 - 与值关联的单元格,用于在必要时重置格式。重置格式也需要保存。有关如何重置 Excel 单元格格式的信息使用
ISaveFormats
接口。 - 用户希望与将被验证的属性关联的值。
- 一个
AbstractValidationObject
实例,用于验证。在设计中,所有ValueValidator
实例都使用相同的AbstractValidationObject
类实例进行验证。
在构造函数中,ValueValidator
接受两个参数:PropertyInfo
参数,用于查找与属性关联的属性,如标题文本;以及 AbstractValidationObject
实例,用于验证。
public ValueValidator(PropertyInfo propertyInfo, AbstractValidationObject abstractValidationObject) { Name = propertyInfo.Name; var alternateTitleAttribute = (AlternateTitleAttribute)propertyInfo. GetCustomAttributes(typeof(AlternateTitleAttribute), false).FirstOrDefault(); HeaderText = alternateTitleAttribute == null ? Name : alternateTitleAttribute.AlternateTitle; Required = propertyInfo.GetCustomAttributes (typeof(RequiredTitleAttribute), false).Any(); _abstractValidationObject = abstractValidationObject; Validate(null, false); }
ValueValidator
中最重要的方法是 Validate
方法,该方法接受 Excel Range
(即工作表单元格)作为参数,以及一个指示验证失败时是否抛出错误的标志。
public void Validate(Range cell, bool throwException = true) { _cell = cell; Value = cell == null ? string.Empty : cell.Value == null ? string.Empty : cell.Value.ToString(); IsValid = true; _errorMessage = _abstractValidationObject.Validate(this); if (!string.IsNullOrEmpty(_errorMessage)) { _errorMessage = string.Format(_errorMessage, HeaderText); IsValid = false; if (throwException) throw new ValidatorException(_errorMessage); } }
此方法负责的功能是:
- 保存 Range 对象的一个实例,该对象代表包含该值的单元格。这很有必要,以便可以对单元格进行格式化,以向用户提供关于验证错误的反馈,并在之后重置格式。
- 将 Value 属性更新为单元格中包含的值。
- 运行
AbstractValidationObject
的Validate
方法,并保存代表错误消息的返回值。还执行: - 根据错误消息进行格式化(如果存在验证标题,则在消息中添加标题文本),并在验证返回值不为 null 时抛出
ValidatorException
。 - 设置或重置
IsValid
属性,以便确定一行是否通过所有验证。
新设计的额外优势
如果您查看原始项目,您会发现我将一个字符串包装在一个类中使其可变。这在验证器中使用,以便可以更新 Value
。使用属性设计,当值更改时,ValueValidator
实例的 Value
属性将被更新。
结论
属性使得自定义此 Excel 代码以支持用于导出数据的通用代码更加简洁易维护。我特别喜欢添加属性非常容易,而这并不会增加自定义导出过程的复杂性。当我在属性中添加支持带空格的标题的功能时,该方法的灵活性得到了体现。这只需要添加一个新的属性,并在 ValueValidator
构造函数的初始化中进行一些更改。我还添加了一个新属性,因为我需要方便地访问属性名称。