65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (12投票s)

2012 年 9 月 21 日

CPOL

7分钟阅读

viewsIcon

44894

downloadIcon

290

演示如何在严肃的应用程序中使用运行时属性来提高可维护性。

摘要

属性可以用于提高可维护性。我将此应用程序转换为使用属性来指定类中每个属性的信息,从而使维护更加容易,并减少了出现错误的几率。这些每个属性的属性用于在使用前修改属性,并确定属性的值是否有效。以前,类中有一个方法返回包含此信息的类集合。

引言

我之前发表了一篇题为《用于验证和导出数据的 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。此外,构造函数中的参数用于更新 ValueValidatorValue 属性,使其成为指定格式的值的字符串。在示例中使用时,格式指定为短日期,这样在 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 中的值有效,则返回值是 nullValueValidator 提供了属性名称和值。属性名称用于使用反射查找 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 属性更新为单元格中包含的值。
  • 运行 AbstractValidationObjectValidate 方法,并保存代表错误消息的返回值。还执行:
  • 根据错误消息进行格式化(如果存在验证标题,则在消息中添加标题文本),并在验证返回值不为 null 时抛出 ValidatorException
  • 设置或重置 IsValid 属性,以便确定一行是否通过所有验证。

新设计的额外优势

如果您查看原始项目,您会发现我将一个字符串包装在一个类中使其可变。这在验证器中使用,以便可以更新 Value。使用属性设计,当值更改时,ValueValidator 实例的 Value 属性将被更新。

结论

属性使得自定义此 Excel 代码以支持用于导出数据的通用代码更加简洁易维护。我特别喜欢添加属性非常容易,而这并不会增加自定义导出过程的复杂性。当我在属性中添加支持带空格的标题的功能时,该方法的灵活性得到了体现。这只需要添加一个新的属性,并在 ValueValidator 构造函数的初始化中进行一些更改。我还添加了一个新属性,因为我需要方便地访问属性名称。

© . All rights reserved.