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

使用自我跟踪实体和 WCF 服务构建的 Silverlight 示例 - 第 3 部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (6投票s)

2011 年 2 月 16 日

CPOL

14分钟阅读

viewsIcon

42617

本系列文章的第三部分,描述了使用自跟踪实体、WCF 服务、WIF、MVVM Light Toolkit、MEF 和 T4 模板创建 Silverlight 业务应用程序。

  • 请访问此 项目站点 获取最新版本和源代码。

文章系列

本文是关于使用自跟踪实体、WCF 服务、WIF、MVVM Light Toolkit、MEF 和 T4 模板开发 Silverlight 业务应用程序的系列文章的第三部分。

目录

引言

在第三部分中,我们将重点关注自跟踪实体的数据验证。数据验证的目的是确保在将任何数据存储到数据库之前对其进行验证。它能在用户进行数据输入任务时提供必要的指导,是任何 Silverlight LOB 应用程序的重要组成部分。在本文的上半部分,我们将讨论在客户端和服务器端添加数据验证的不同方法。之后,我们将深入探讨这些数据验证基础结构是如何实现的。

如何添加验证属性

WCF RIA Services 内置了对成员/字段级别验证、实体级别验证和操作级别验证的支持。验证属性来自 System.ComponentModel.DataAnnotations 命名空间,它们包括:

  • RequiredAttribute:指定数据字段值是必需的
  • StringLengthAttribute:指定数据字段中允许的字符的最小长度和最大长度
  • RegularExpressionAttribute:指定数据字段值必须与指定的正则表达式匹配
  • RangeAttribute:指定数据字段值的数值范围约束
  • CustomValidationAttribute:指定一个自定义验证方法来验证属性或类实例

对于 EF 自动生成的实体类,我们可以像下面这样在元数据类的属性上添加验证属性:

[MetadataTypeAttribute(typeof(User.UserMetadata))]
public partial class User
{
  internal class UserMetadata
  {
    // Metadata classes are not meant to be instantiated.
    protected UserMetadata()
    {
    }
    ......
    [DataMember]
    [Display(Name = "NewPasswordLabel", 
             ResourceType = typeof(IssueVisionResources))]
    [Required(ErrorMessageResourceName = "ValidationErrorRequiredField", 
              ErrorMessageResourceType = typeof(ErrorResources))]
    [RegularExpression("^.*[^a-zA-Z0-9].*$", 
        ErrorMessageResourceName = "ValidationErrorBadPasswordStrength", 
        ErrorMessageResourceType = typeof(ErrorResources))]
    [StringLength(50, MinimumLength = 12, 
        ErrorMessageResourceName = "ValidationErrorBadPasswordLength", 
        ErrorMessageResourceType = typeof(ErrorResources))]
    public string NewPassword { get; set; }
    ......
  }
}

元数据类上的验证属性将用于服务器端验证,然后 WCF RIA Services 在客户端实体类被自动生成时会生成相同的验证属性。这确保了相同的验证逻辑在两侧共享。

不幸的是,元数据类方法不适用于我们的示例,因为我们共享的是自动生成的自跟踪实体类,而元数据类只能存在于服务器端,而不能存在于客户端。我们必须采用不同的方法,使用一个名为“Portable Extensible Metadata”的 VS2010 扩展。它将向我们的 EF 模型添加元数据,并让 IssueVisionModel.tt 为每个自跟踪实体类自动生成所有数据注解属性(属性级别)。

在讨论如何向 EF 模型添加元数据之前,让我们先来介绍一下如何添加仅客户端验证。

添加仅客户端验证

仅客户端验证适用于项目 IssueVision.DataClientExtension 文件夹下定义的仅客户端属性。其中一个例子是 `PasswordResetUser` 类中的 `PasswordConfirmation` 属性。

/// <summary>
/// PasswordResetUser class client-side extensions
/// </summary>
public partial class PasswordResetUser
{
  ......
  [Display(Name = "Confirm password")]
  [Required(ErrorMessage = "This field is required.")]
  [CustomValidation(typeof(PasswordResetUser), "CheckPasswordConfirmation")]
  public string PasswordConfirmation
  {
    get { return this._passwordConfirmation; }
    set
    {
      if (_passwordConfirmation != value)
      {
        PropertySetterEntry("PasswordConfirmation");
        _passwordConfirmation = value;
        PropertySetterExit("PasswordConfirmation", value);
        OnPropertyChanged("PasswordConfirmation");
      }
    }
  }
  private string _passwordConfirmation;

  /// <summary>
  /// Custom validation of whether new password and confirm password match
  /// </summary>
  /// <param name="passwordConfirmation"></param>
  /// <param name="validationContext"></param>
  /// <returns></returns>
  public static ValidationResult CheckPasswordConfirmation(
         string passwordConfirmation, ValidationContext validationContext)
  {
    PasswordResetUser currentUser = 
      (PasswordResetUser)validationContext.ObjectInstance;

    if (!string.IsNullOrEmpty(currentUser.ActualPassword) &&
        !string.IsNullOrEmpty(passwordConfirmation) &&
        currentUser.ActualPassword != passwordConfirmation)
    {
      return new ValidationResult("Passwords do not match.", 
             new string[] { "PasswordConfirmation" });
    }

    return ValidationResult.Success;
  }
}

由于 `PasswordConfirmation` 属性仅在客户端定义,因此验证逻辑仅适用于客户端。并且,由于该属性不是由 T4 模板生成的,我们可以直接将数据注解属性添加到属性本身,包括 `CustomValidation` 属性。

通过实体数据模型设计器添加验证

接下来,我们将以 `User` 类的 `NewPassword` 属性为例,逐步介绍如何使用实体数据模型设计器添加任何必要的数据注解属性。以下是 `NewPassword` 属性的验证要求列表:

  • 新密码是一个必需的属性。
  • 密码必须至少包含 12 个字符,最多 50 个字符。
  • 密码需要包含至少一个特殊字符,例如 @ 或 #

首先,打开 IssueVision.edmx 的实体数据模型设计器,选择 `User` 实体中的 `NewPassword` 属性。在属性窗口(如下图所示)中,我们可以将“New Password”指定为其显示名称。

接下来,通过选择“Validations”(上面已突出显示)的集合来打开“Validations Editor”窗口,并为三个验证条件添加架构元数据。

保存对 EDM 文件 IssueVision.edmx 的更改后,T4 模板将自动生成所有自跟踪实体类,并包含我们刚刚添加的新数据注解属性。

通过实体数据模型设计器添加验证元数据的一个限制是,它目前不支持 `CustomValidationAttribute`。对于自定义验证支持,我们必须采用如下所述的另一种方法。

添加实体级别的自定义验证

对于实体级别的自定义验证,我们可以直接采用部分类方法,并将 `CustomValidation` 属性直接添加进去。在我们的示例应用程序中,实体级别的自定义验证逻辑位于 validation 文件夹内。具体来说,在服务器端,它们位于项目 IssueVision.Data.Webvalidation 文件夹下;在客户端,它们位于项目 IssueVision.Datavalidation 文件夹下。下面是 `Issue` 类的一个示例,我们向类本身添加了两个 `CustomValidation` 属性:

/// <summary>
/// Issue class validation logic
/// </summary>
[CustomValidation(typeof(IssueRules), "CheckStatusActive")]
[CustomValidation(typeof(IssueRules), "CheckStatusOpen")]
public partial class Issue
{
    ......
}

相关的自定义验证函数 `CheckStatusActive()` 和 `CheckStatusOpen()` 定义在名为 `IssueRules` 的单独的静态类中。

public static partial class IssueRules
{
  ......
  /// <summary>
  /// When status is Active, assigned-to-user cannot be null.
  /// </summary>
  /// <param name="issue"></param>
  /// <param name="validationContext"></param>
  /// <returns></returns>
  public static ValidationResult CheckStatusActive(Issue issue, 
                ValidationContext validationContext)
  {
    if (issue.StatusID == IssueVisionServiceConstant.ActiveStatusID && 
        issue.AssignedToID == null)
      return new ValidationResult("A user is required when the " + 
             "status is Active. ", 
             new string[] { "StatusID", "AssignedToID" });

    return ValidationResult.Success;
  }

  /// <summary>
  /// When status is Open, assigned-to-user should be null.
  /// </summary>
  /// <param name="issue"></param>
  /// <param name="validationContext"></param>
  /// <returns></returns>
  public static ValidationResult CheckStatusOpen(Issue issue, 
                ValidationContext validationContext)
  {
    if (issue.StatusID == IssueVisionServiceConstant.OpenStatusID && 
                          issue.AssignedToID != null)
      return new ValidationResult("AssignedTo user is not needed when " + 
             "the status is Open.", 
             new string[] { "StatusID", "AssignedToID" });

    return ValidationResult.Success;
  }
}

从上面的代码片段中,我们可以看到自定义验证函数首先检查指定的条件是否满足,如果一切正常则返回 `ValidationResult.Success`;否则,它们返回一个新的 `ValidationResult` 对象,其中包含错误消息。 `ValidationResult` 对象 的第二个参数是一个字符串数组,包含所有受影响的属性。对于 `CheckStatusActive()` 函数,受影响的属性是 `StatusID` 和 `AssignedToID`。这意味着如果验证失败,这两个属性都会被高亮显示。

添加属性级别的自定义验证

在回顾了如何添加实体级别自定义验证的主题后,让我们来看看如何在属性级别定义自定义验证。属性级别的自定义验证函数也在客户端和服务器端的 validation 文件夹中定义。下面是 `User` 类的 `Email` 属性的一个示例,我们需要确保在用户输入任务期间电子邮件地址是有效的。

/// <summary>
/// User class validation logic
/// </summary>
public partial class User
{
  /// <summary>
  /// Partial method to add all validation actions
  /// defined for this class
  /// </summary>
  partial void InitializeValidationSettings()
  {
  #if SILVERLIGHT
    this.ValidateEntityOnPropertyChanged = false;
  #endif
    AddPropertyValidationAction("Email", ValidateEmail);
  }

  #region "Private Validation Methods"

  /// <summary>
  /// Validation Action:
  /// check whether the email address is valid or not
  /// </summary>
  /// <param name="value"></param>
  private void ValidateEmail(object value)
  {
    string email = value as string;

    // user Email can be null
    if (email == null) return;
    // validate the e-mail format.
    if (Regex.IsMatch(email,
          @"^(?("")("".+?""@)|(([0-9a-zA-Z]((\.(?!\.))|[-!#\$" + 
          @"%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-zA-Z])@))" +
          @"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-zA-Z]" + 
          @"[-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,6}))$") == false)
    {
    #if SILVERLIGHT
      ValidationResult result = new ValidationResult(
         "Invalid email address.", new List<string> { "Email" });
      this.AddError("Email", result);
    #else
      FaultReason faultReason = new FaultReason("Invalid email address.");
      throw new FaultException(faultReason);
    #endif
    }
  }

  #endregion "Private Validation Methods"
}

首先,让我们看一下 `InitializeValidationSettings()` 方法。这是一个部分方法,这意味着如果我们没有自定义验证逻辑,可以选择不实现它。如果我们有属性级别的自定义验证,例如上面的 `User` 实体类,那么 `InitializeValidationSettings()` 方法会被实现,并且应该包含两个主要功能:

  • 我们需要决定是将 `ValidateEntityOnPropertyChanged` 属性设置为 `true` 还是 `false`。当属性发生更改时,如果需要检查任何实体级别的自定义验证,则此属性设置为 `true`。对于 `User` 类,我们可以简单地将其设置为 `false`,因为我们没有任何实体级别的验证函数。
  • 此外,在此部分方法中,我们需要使用 `AddPropertyValidationAction()` 方法添加所有自定义验证函数。此方法 的第一个参数是一个字符串值,即需要验证的属性的名称,第二个参数指向一个自定义验证方法。在上面的代码示例中,自定义验证方法是 `ValidateEmail()`。

这个自定义验证方法 `ValidateEmail()` 只接受一个参数,即需要验证的属性的值。如果验证失败,该方法 的行为会因是从客户端还是服务器端调用而有所不同。

如果此方法是从客户端调用的,将创建一个带有适当错误消息的新 `ValidationResult` 对象,然后调用 `AddError()` 方法。此 `AddError()` 方法将触发 Silverlight 用户界面来高亮显示验证失败的属性,并显示相应的错误消息。

但是,如果此方法是从服务器端调用的,将抛出一个带有错误消息的 `FaultException`。此异常将被传递回客户端,用户将在此处收到错误通知。

到目前为止,我们已经完成了关于如何添加验证属性的讨论,接下来我们将谈论添加操作级别验证。

如何添加操作级别验证

与添加验证属性相比,添加操作级别验证相对简单。只有两个方法:`TryValidate()` 和 `Validate()`。

在客户端添加验证

`TryValidate()` 方法是用于客户端操作级别验证的方法,它有两种重载形式。第一种不带参数。它遍历所有数据注解属性和所有自定义验证操作。如果任何验证失败,函数返回 `false`;否则返回 `true`。另一种重载带有一个字符串值参数,即需要验证的属性的名称,该方法仅针对指定的属性进行验证。

private void OnLoginCommand(User g)
{
  if (!this._authenticationModel.IsBusy)
  {
    // clear any previous error message
    this.LoginScreenErrorMessage = null;
    // only call SignInAsync when all input are validated
    if (g.TryValidate("Name") && g.TryValidate("Password"))
        this._authenticationModel.SignInAsync(g.Name, g.Password);
  }
}

上面的代码示例来自 `LoginFormViewModel` 类,它显示我们每次调用 `SignInAsync(g.Name, g.Password)` 时都会验证 `Name` 和 `Password` 两个属性。类似地,下面的代码示例来自 `MyProfileViewModel` 类,我们通过调用 `TryValidate()` 来验证 `CurrentUser`,每次调用以保存任何更改。

private void OnSubmitChangeCommand()
{
  try
  {
    if (!_issueVisionModel.IsBusy)
    {
      if (this.CurrentUser != null)
      {
        if (this.CurrentUser.TryValidate())
        {
          // change is not from User Maintenance screen
          this.CurrentUser.IsUserMaintenance = (byte)0;
          this._issueVisionModel.SaveChangesAsync();
        }
      }
    }
  }
  catch (Exception ex)
  {
    // notify user if there is any error
    AppMessages.RaiseErrorMessage.Send(ex);
  }
}

在服务器端添加验证

在服务器端,唯一的操作级别验证方法是 `Validate()`。此方法遍历所有数据注解属性和所有自定义验证操作,如果任何验证失败,它将抛出一个异常,该异常最终将被传递回客户端并在此处处理。以下是项目 IssueVision.ST.Web 中 `PasswordResetService` 类的一个示例。

public void ResetPassword(PasswordResetUser user)
{
  // validate the user on the server side first
  user.Validate();

  using (IssueVisionEntities context = new IssueVisionEntities())
  {
    User foundUser = context.Users.FirstOrDefault(n => n.Name == user.Name);

    if (foundUser != null)
    {
      // retrieve password answer hash and salt from database
      string currentPasswordAnswerHash = 
             context.GetPasswordAnswerHash(user.Name).First();
      string currentPasswordAnswerSalt = 
             context.GetPasswordAnswerSalt(user.Name).First();
      // generate password answer hash
      string passwordAnswerHash = 
             HashHelper.ComputeSaltedHash(user.PasswordAnswer,
             currentPasswordAnswerSalt);

      if (string.Equals(user.PasswordQuestion, foundUser.PasswordQuestion, 
          StringComparison.Ordinal) && string.Equals(passwordAnswerHash, 
          currentPasswordAnswerHash, StringComparison.Ordinal))
      {
        // Password answer matches, so save the new user password
        // Re-generate password hash and password salt
        string currentPasswordSalt = HashHelper.CreateRandomSalt();
        string currentPasswordHash = 
          HashHelper.ComputeSaltedHash(user.NewPassword, currentPasswordSalt);

        // re-generate passwordAnswer hash and passwordAnswer salt
        currentPasswordAnswerSalt = HashHelper.CreateRandomSalt();
        currentPasswordAnswerHash = 
          HashHelper.ComputeSaltedHash(user.PasswordAnswer, 
                                       currentPasswordAnswerSalt);

        // save changes
        context.ExecuteFunction("UpdatePasswordHashAndSalt"
            , new ObjectParameter("Name", user.Name)
            , new ObjectParameter("PasswordHash", currentPasswordHash)
            , new ObjectParameter("PasswordSalt", currentPasswordSalt));
        context.ExecuteFunction("UpdatePasswordAnswerHashAndSalt"
            , new ObjectParameter("Name", user.Name)
            , new ObjectParameter("PasswordAnswerHash", currentPasswordAnswerHash)
            , new ObjectParameter("PasswordAnswerSalt", currentPasswordAnswerSalt));
      }
      else
        throw new UnauthorizedAccessException(
                      ErrorResources.PasswordQuestionDoesNotMatch);
    }
    else
      throw new UnauthorizedAccessException(ErrorResources.NoUserFound);
  }
}

在服务器端重复的操作级别验证与在客户端执行的操作完全相同。因此,它永远不应该抛出异常。我们添加此额外步骤的唯一原因是服务器端暴露为 WCF 服务。我们假设调用可能来自任何地方,因此也需要在服务器端进行验证。

到目前为止,我们已经完成了关于属性级别验证、实体级别验证以及操作级别验证的讨论。如果您只对如何使用我们当前的数据验证基础结构添加验证逻辑感兴趣,那么这些信息应该足够了。但是,如果您仍然对该基础结构是如何实现的感兴趣,我们将在接下来进行介绍。

数据验证基础结构

数据验证基础结构主要由两个 T4 模板生成的代码组成:*IssueVisionClientModel.tt* 和 *IssueVisionModel.tt*。客户端 T4 模板 *IssueVisionClientModel.tt* 生成了 `INotifyDataErrorInfo` 接口的实现及其所有相关辅助方法。另一个 T4 模板 *IssueVisionModel.tt* 生成了一组验证辅助方法,包括我们上面已经看到的 `TryValidate()` 和 `Validate()` 方法。与 *IssueVisionClientModel.tt* 生成的代码仅在客户端可用不同,*IssueVisionModel.tt* 生成的代码在客户端和服务器端均可用。

INotifyDataErrorInfo 接口

根据 MSDN 文档的引用,`INotifyDataErrorInfo` 接口“使数据实体类能够实现自定义验证规则并将验证结果公开给用户界面。通常,您会实现此接口来提供异步验证逻辑,例如服务器端验证。此接口还支持自定义错误对象、每个属性的多个错误、跨属性错误和实体级别错误。”它包含一个 `HasErrors` 属性、一个 `GetErrors()` 方法和一个 `ErrorsChanged` 事件,其定义如下所示:

namespace System.ComponentModel
{
  // Defines members that data entity classes can implement to provide custom,
  // asynchronous validation support.
  public interface INotifyDataErrorInfo
  {
    // Gets a value that indicates whether the object has validation errors.
    bool HasErrors { get; }

    // Gets the validation errors for a specified property or for the entire object.
    IEnumerable GetErrors(string propertyName); 

    // Occurs when the validation errors have changed for a property or for the
    // entire object.
    event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
  }

  // Provides data for the INotifyDataErrorInfo.ErrorsChanged event. 
  public sealed class DataErrorsChangedEventArgs : EventArgs
  {
    // Initializes a new instance of the
    // System.ComponentModel.DataErrorsChangedEventArgs class. 
    public DataErrorsChangedEventArgs(string propertyName);

    // Gets the name of the property for which
    // the errors changed, or null or System.String.Empty
    // if the errors affect multiple properties.
    public string PropertyName { get; }
  }
}

如果您需要有关 `INotifyDataErrorInfo` 接口的详细说明,白皮书“使用 INotifyDataErrorInfo 在 Silverlight 中实现数据验证”是一个很好的参考来源。在此,我将仅简要介绍它在我们的示例中的实现。

为了实现 `INotifyDataErrorInfo`,我们使用了一个泛型 `Dictionary` 对象属性,名为 `ValidationErrors`。此对象表示属性名称(作为键)及其对应的 `ValidationResult` 对象列表的集合。

protected Dictionary<string, List<ValidationResult>> ValidationErrors
{
  get
  {
    if (_validationErrors == null)
    {
      _validationErrors = new Dictionary<string, List<ValidationResult>>();
    }
    return _validationErrors;
  }
}

private Dictionary<string, List<ValidationResult>> _validationErrors;

在 `ValidationErrors` 属性的帮助下,其余代码很容易理解:`HasErrors` 属性返回 `ValidationErrors` 是否包含元素,`GetErrors()` 方法返回与传入属性名称匹配的 `ValidationResult` 列表。

/// <summary>
/// Gets a value indicating whether there are known errors or not.
/// </summary>
bool INotifyDataErrorInfo.HasErrors
{
  get
  {
    return this.ValidationErrors.Keys.Count != 0;
  }
}

/// <summary>
/// Gets the currently known errors
/// for the provided property name. Use String.Empty/null
/// to retrieve entity-level errors.
/// </summary>
IEnumerable INotifyDataErrorInfo.GetErrors(string propertyName)
{
  if (propertyName == null)
  {
    propertyName = string.Empty;
  }

  if (this.ValidationErrors.ContainsKey(propertyName))
  {
    return this.ValidationErrors[propertyName];
  }
  return null;
}

INotifyDataErrorInfo 辅助方法

虽然 `INotifyDataErrorInfo` 使数据实体类能够实现自定义验证规则并将验证结果公开给用户界面,但它不知道如何与示例应用程序的其余部分进行通信。这就是为什么我们还通过 T4 模板 *IssueVisionClientModel.tt* 创建了一组辅助方法。以下是辅助方法的列表:

  • AddError(string propertyName, ValidationResult validationResult):为提供的属性名称,或为实体(如果属性名称为 `String.Empty`/`null`)将新错误添加到泛型 `Dictionary` 对象 `ValidationErrors` 中。每次添加新错误时,该方法还会触发 `ErrorsChanged` 事件,以便用户界面收到更改的通知。
  • ClearErrors(string propertyName):从泛型 `Dictionary` 对象 `ValidationErrors` 中移除针对提供的属性名称的错误,或为实体(如果属性名称为 `String.Empty`/`null`)移除错误。当 `ValidationErrors` 对象发生任何更改时,此方法还会通过 `ErrorsChanged` 事件通知用户界面。
  • ClearErrors():移除泛型 `Dictionary` 对象 `ValidationErrors` 中所有属性名称的错误。
  • ValidateEntityOnPropertyChanged:指示是否必须在实体属性更改时应用顶级验证规则。此属性通常在 `InitializeValidationSettings()` 方法中设置,如上所述。
  • PropertySetterEntry(string propertyName):通过调用 `ClearErrors()` 来移除所提供属性名称的任何已知错误。
  • PropertySetterExit(string propertyName, object propertyValue):验证所提供属性名称的任何已知错误。

请注意,最后两个方法 `PropertySetterEntry()` 和 `PropertySetterExit()` 是部分方法。它们仅在客户端实现,不在服务器端实现。对于自跟踪实体类的任何基本属性,在设置新值之前和之后都会立即调用这两个方法,如下所示:

[DataMember]
[Required()]
public string Value
{
  get { return _value; }
  set
  {
    if (_value != value)
    {
      ChangeTracker.RecordOriginalValue("Value", _value);
      PropertySetterEntry("Value");
      _value = value;
      PropertySetterExit("Value", value);
      OnPropertyChanged("Value");
    }
  }
}
private string _value;

每当在客户端设置 `Value` 属性时,调用 `PropertySetterEntry("Value")` 将清除任何已知错误。在为 `Value` 属性分配新值后,`PropertySetterExit("Value", value)` 将触发数据验证逻辑。另一方面,如果 `Value` 属性是在服务器端设置的,则不会执行数据验证,因为 `PropertySetterEntry()` 和 `PropertySetterExit()` 都没有实现。

验证和辅助方法

与 `INotifyDataErrorInfo` 接口实现和相关辅助方法不同,由 T4 模板 *IssueVisionModel.tt* 生成的验证和辅助方法集在客户端和服务器端均可用。我们可以将属性和方法分组到以下列表中:

  • 基本验证方法 `Validate(string propertyName, object value)`
  • 客户端方法 `TryValidate()` 和服务器端方法 `Validate()`
  • 部分方法定义 `PropertySetterEntry(string propertyName)`、`PropertySetterExit(string propertyName, object propertyValue)` 和 `InitializeValidationSettings()`
  • 属性 `ValidationActions` 和方法 `AddPropertyValidationAction(string propertyName, Action validationAction)`

    基本验证方法 `Validate(string propertyName, object value)` 遍历指定属性名称的所有相关数据注解属性以及所有相关的自定义验证操作。如果 `propertyName` 为 `String.Empty`/`null`,则表示实体级别的验证。此方法主要由客户端部分方法 `PropertySetterExit()` 使用,如下所示:

    /// <summary>
    /// Validate for any known errors for the provided property name
    /// </summary>
    /// <param name="propertyName">Propery name
    ///           or String.Empty/null for top-level errors</param>
    /// <param name="propertyValue">Property value</param>
    partial void PropertySetterExit(string propertyName, object propertyValue)
    {
      if (IsDeserializing)
      {
        return;
      }
    
      if (this.ValidateEntityOnPropertyChanged)
      {
        this.Validate(string.Empty, this);
      }
      else
      {
        this.Validate(propertyName, propertyValue);
      }
    }

    对于客户端操作级别验证方法 `TryValidate()` 和服务器端操作级别验证方法 `Validate()`,我们已经在上面介绍了它们的用法,现在将继续介绍列表中的下一项,即三个部分方法的声明。部分方法 `PropertySetterEntry()` 和 `PropertySetterExit()` 仅在客户端实现,并且始终成对用于实体类中的基本属性设置器定义。另一个部分方法 `InitializeValidationSettings()` 也在客户端实现,并且仅在我们需要为该实体类进行自定义验证逻辑时才需要实现它。

    受保护属性 `ValidationActions` 是一个泛型 `Dictionary` 对象,它保存属性名称(作为键)及其对应的 `Action` 对象列表的集合。而 `AddPropertyValidationAction(string propertyName, Action validationAction)` 是一个辅助方法,用于将其与匹配属性名称的自定义验证操作添加到 `ValidationActions` 属性中。让我们回顾一下 `Issue` 类中的以下代码片段,了解 `InitializeValidationSettings()` 和 `AddPropertyValidationAction()` 方法是如何一起使用的。

    /// <summary>
    /// Partial method to add all validation actions
    /// defined for this class
    /// </summary>
    partial void InitializeValidationSettings()
    {
    #if SILVERLIGHT
      // there are top-level validation actions defined for this class
      // so, we need to enable this property.
      this.ValidateEntityOnPropertyChanged = true;
    #endif
      AddPropertyValidationAction(string.Empty, ValidateStatusResolved);
    }

    下一步

    我们已经完成了关于如何使用我们增强的自跟踪实体生成器添加数据验证的讨论,以及关于数据验证基础结构实现的讨论。在最后一篇中,我们将继续介绍使用 WIF 进行身份验证和授权以及其他剩余的感兴趣的主题。希望本文对您有所帮助,请在下方评分和/或留下反馈。谢谢!

    历史

    • 2011 年 2 月 16 日 - 初始发布。
    • 2011年3月 - 更新以修复多个错误,包括内存泄漏问题。
    © . All rights reserved.