【问题标题】:FluentValidation - Validation “leakage” into other replicated modelsFluentValidation - 验证“泄漏”到其他复制模型中
【发布时间】:2016-08-09 20:33:21
【问题描述】:

当前项目:

  • ASP.NET 4.5.2
  • MVC 5
  • 流畅的验证

我在这里可能有一个非常奇怪的问题。我可能会遇到“验证泄漏”,即对一个子模型( 应该完全填写)的验证“泄漏”到第二次和第三次导入的同一模型中,但是它在哪里不需要必须填写。

所以我有这个注册表单,它是在注册后生成的。如果不填写表格,用户将无法进入该网站。从本质上讲,这是一个对网站内部正常运行至关重要的个人资料页面。

称为招聘人员的一类用户需要三个“地址”:对于公司,对于他们作为联系人(如果他们的办公室不同),以及对于帐单(如果帐单地点不同)。只有公司地址是必需的,因为它需要手动输入地址。如果第二个和第三个与第一个相同,用户可以通过Bootstrap Switch控制的布尔值来专门标记它们,第二个和第三个子模型可以提交为空。仅当两个布尔值(“此地址与主地址相同”)中的任何一个设置为 True(这很重要!)时,才需要在第一个子模型上进行验证。

我最初的解决方案(后来我放弃了)是让 Address 子模型准确地反映 DB 模型,因为它不仅有物理地址,还有电话号码、电子邮件等。我放弃了它,因为我遇到了整个地址模型的验证问题,因为两个辅助子模型不断得到验证,即使我的验证器只应该在地址不同时被触发(我使用引导开关来允许这些地址字段作为组隐藏,因此如果第二个和第三个地址确实不同,用户可以取消隐藏地址字段。

此后,我重新设计了地址子模型,包含物理地址:街道、城市、州等。不幸的是,我仍然遇到问题,因为有第二个和第三个子表单为空时会对其进行验证,即使用户将它们留在默认配置中(此地址与主地址相同 - 不验证)。

我的地址模型:

public class CreateRecruiterAddressModel {
  [DisplayName("Address")]
  public string Address1 { get; set; }
  [DisplayName("P.O. Box, Suite, etc.")]
  public string Address2 { get; set; }
  [DisplayName("City")]
  public string City { get; set; }
  #region For US and Canada
  [DisplayName("Country")]
  public Guid CountryId { get; set; }
  #endregion
  #region For US
  [DisplayName("State")]
  public Guid? StateId { get; set; }
  [DisplayName("Zip Code")]
  [DataType(DataType.PostalCode)]
  public string ZipCode { get; set; }
  #endregion
  #region for Canada
  [DisplayName("Province")]
  public Guid? ProvinceId { get; set; }
  [DisplayName("Postal Code")]
  [DataType(DataType.PostalCode)]
  public string PostalCode { get; set; }
  #endregion
  #region For everyone else
  [DisplayName("Province")]
  public string ProvinceName { get; set; }
  [DisplayName("Country")]
  public string CountryName { get; set; }
  [DisplayName("Postal Code")]
  [DataType(DataType.PostalCode)]
  public string Postal { get; set; }
  #endregion
}

如您所见,我提供了决策部分:如果用户来自加拿大或美国,他们会获得省/州的自定义下拉菜单,而其他任何人都会获得简单的国家和省文本字段。

我的招聘人员模型:

public class CreateRecruiterProfileViewModel {
  [DisplayName("Company Name")]
  public string CompanyName { get; set; }

  public CreateRecruiterAddressModel mailingAddress { get; set; }

  [DisplayName("Phone Number")]
  [DataType(DataType.PhoneNumber)]
  public string MailingPhone { get; set; }
  [DisplayName("Fax Number")]
  [DataType(DataType.PhoneNumber)]
  public string MailingFax { get; set; }

  [DisplayName("Contact Name")]
  public string ContactName { get; set; }
  [DisplayName("Is your contact address at this company the same as the company’s mailing address, above?")]
  public bool ContactSameAsAddress { get; set; }

  public CreateRecruiterAddressModel contactAddress { get; set; }

  [DisplayName("Phone Number")]
  [DataType(DataType.PhoneNumber)]
  public string ContactPhone { get; set; }
  [DisplayName("Extension")]
  public short? ContactExtension { get; set; }
  [DisplayName("eMail:")]
  public string ContacteMail { get; set; }

  [DisplayName("Name on the credit card")]
  public string BillingName { get; set; }
  [DisplayName("Is the billing address for this account the same as the company’s mailing address, above?")]
  public bool BillingSameAsAddress { get; set; }

  public CreateRecruiterAddressModel billingAddress { get; set; }

  [DisplayName("Phone Number")]
  [DataType(DataType.PhoneNumber)]
  public string BillingPhone { get; set; }
  [DisplayName("Extension")]
  public short? BillingExtension { get; set; }
  [DisplayName("eMail:")]
  public string BillingeMail { get; set; }

  [DisplayName("Website")]
  public string Website { get; set; }
  [DisplayName("Industry")]
  public string IndustryId { get; set; }
  [DisplayName("Number of Employees:")]
  public string NumberEmployees { get; set; }
  [DisplayName("Operating Since:")]
  [DataType(DataType.Date)]
  public DateTime? OperatingSince { get; set; }
  [DisplayName("Operating Revenue:")]
  public string OperatingRevenue { get; set; }


  public Guid CountryNU {
    get { return Settings.Default.CountryNU; }
  }
  public Guid CountryCA {
    get { return Settings.Default.CountryCA; }
  }
  public Guid CountryUS {
    get { return Settings.Default.CountryUS; }
  }
  private IEnumerable<SelectListItem> _CountryList;
  public IEnumerable<SelectListItem> CountryList {
    get { return SelectLists.CountryList(); }
    set { _CountryList = value; }
  }
  private IEnumerable<SelectListItem> _StateList;
  public IEnumerable<SelectListItem> StateList {
    get { return SelectLists.ProvinceList(Settings.Default.CountryUS); } // Add US Guid 
    set { _StateList = value; }
  }
  private IEnumerable<SelectListItem> _ProvinceList;
  public IEnumerable<SelectListItem> ProvinceList {
    get { return SelectLists.ProvinceList(Settings.Default.CountryCA); } // Add CA Guid 
    set { _ProvinceList = value; }
  }
  private IEnumerable<SelectListItem> _IndustryList;
  public IEnumerable<SelectListItem> IndustryList {
    get { return SelectLists.IndustryList(); }
    set { _IndustryList = value; }
  }

  public CreateRecruiterProfileViewModel() {
    ContactSameAsAddress = true;
    BillingSameAsAddress = true;
  }
}

那里有很多东西,很抱歉数据转储。可能只有前半部分很重要。

我的地址验证:

public class CreateRecruiterAddressValidator : AbstractValidator<CreateRecruiterAddressModel> {
  public CreateRecruiterAddressValidator() {
    RuleFor(x => x.Address1)
      .NotEmpty().WithMessage("Please provide the current address.")
      .Length(6, 128).WithMessage("Addresses should be between 6 and 128 characters long.");
    RuleFor(x => x.City)
      .NotEmpty().WithMessage("Please provide the current city.")
      .Length(2, 64).WithMessage("City names should be between 2 and 64 characters long.");
    RuleFor(x => x.CountryId)
      .NotEmpty().WithMessage("Please choose the current country.");
    When(x => x.CountryId == Settings.Default.CountryCA,
      () => {
        RuleFor(x => x.ProvinceId)
          .NotEmpty().WithMessage("Please choose the current province.");
        RuleFor(x => x.PostalCode)
          .NotEmpty().WithMessage("Please enter a valid postal code.")
          .Length(7, 7).WithMessage("Postal code must be in the form of &#8220;X1X-1X1&#8221;.")
          .Matches(@"^([ABCEGHJKLMNPRSTVXY]\d[ABCEGHJKLMNPRSTVWXYZ])-(\d[ABCEGHJKLMNPRSTVWXYZ]\d)$").WithMessage("Postal code must be Canada-valid, in the form of &#8220;X1X-1X1&#8221;");
      });
    When(x => x.CountryId == Settings.Default.CountryUS,
      () => {
        RuleFor(x => x.StateId)
          .NotEmpty().WithMessage("Please choose the current state.");
        RuleFor(x => x.ZipCode)
          .NotEmpty().WithMessage("Please enter a valid zip code.")
          .Length(5, 10).WithMessage("Zip code must be in the form of &#8220;12345&#8221 or &#8220;12345-6789&#8221;.")
          .Matches(@"^\d{5}(?:[-\s]\d{4})?$").WithMessage("Zip code must be US-valid, in the form of &#8220;12345&#8221 or &#8220;12345-6789&#8221;.");
      });
    When(x => x.CountryId == Settings.Default.CountryNU,
      () => {
        RuleFor(x => x.CountryName)
          .NotEmpty().WithMessage("Please provide a valid country name.")
          .Matches(@"[a-zA-Z\u00C0-\u01FF- ]+").WithMessage("Please enter only letters and spaces, no numbers or special characters.")
          .Length(2, 64).WithMessage("Country names should be between 2 and 64 characters long.");
        RuleFor(x => x.ProvinceName)
          .NotEmpty().WithMessage("Please provide a valid province name.")
          .Matches(@"[a-zA-Z\u00C0-\u01FF- ]+").WithMessage("Please enter only letters and spaces, no numbers or special characters.")
          .Length(2, 64).WithMessage("Province names should be between 2 and 64 characters long.");
        RuleFor(x => x.Postal)
          .NotEmpty().WithMessage("Please provide a valid postal code.")
          .Length(3, 10).WithMessage("Postal code must be between 3 and 10 digits long, and valid for the country of residence.");
      });
  }
}

现在是我的招聘人员验证:

public class CreateRecruiterProfileValidator : AbstractValidator<CreateRecruiterProfileViewModel> {
  public CreateRecruiterProfileValidator() {
    RuleFor(x => x.CompanyName)
      .NotEmpty().WithMessage("Please provide a valid company name.")
      .Length(2, 64).WithMessage("Company names should be between 2 and 64 characters long.");

    RuleFor(x => x.mailingAddress)
  .SetValidator(new CreateRecruiterAddressValidator()); // This is the validator I think screws up the bottom two

    RuleFor(x => x.ContactName)
      .NotEmpty().WithMessage("Please provide a valid contact name.")
      .Length(2, 64).WithMessage("Contact names should be between 2 and 64 characters long.");

    RuleFor(x => x.contactAddress)
      .SetValidator(new CreateRecruiterAddressValidator())
      .When(x => x.ContactSameAsAddress == false); // Problem one

    RuleFor(x => x.ContactPhone)
      .NotEmpty().WithMessage("Please enter a valid 10-digit phone number.")
      .Length(12, 12).WithMessage("Phone number must be in the form of &#8220;123-456-7890&#8221;")
      .Matches(@"^\d{3}-\d{3}-\d{4}$").WithMessage("Phone number must be a valid 10-digit phone number with dashes, in the form of &#8220;123-456-7890&#8221;");
    RuleFor(x => x.ContacteMail)
      .NotEmpty().WithMessage("Please enter a valid eMail Address..")
      .EmailAddress().WithMessage("Please provide a valid eMail address.");
    RuleFor(x => x.BillingName)
      .NotEmpty().WithMessage("Please provide a valid billing name.")
      .Length(2, 64).WithMessage("Billing names should be between 2 and 64 characters long.");

    RuleFor(x => x.billingAddress)
      .SetValidator(new CreateRecruiterAddressValidator())
      .When(x => x.BillingSameAsAddress == false); // Problem two

    RuleFor(x => x.BillingPhone)
      .NotEmpty().WithMessage("Please enter a valid 10-digit phone number.")
      .Length(12, 12).WithMessage("Phone number must be in the form of &#8220;123-456-7890&#8221;")
      .Matches(@"^\d{3}-\d{3}-\d{4}$").WithMessage("Phone number must be a valid 10-digit phone number with dashes, in the form of &#8220;123-456-7890&#8221;");
    RuleFor(x => x.BillingeMail)
      .NotEmpty().WithMessage("Please enter a valid eMail Address..")
      .EmailAddress().WithMessage("Please provide a valid eMail address.");
    When(x => !string.IsNullOrEmpty(x.Website),
      () => {
        RuleFor(x => x.Website)
          .Length(12, 64).WithMessage("A URL should be in the form of “http://www.domain.com/”")
          .Matches(@"^http[s]?:\/\/").WithMessage("A URL should begin with “http://” or “https://”");
      });
    RuleFor(x => x.IndustryId)
      .NotEmpty().WithMessage("Please choose the closest appropriate industry.");
  }
}

我看到验证的方式是,联系人和帐单地址模型的 .SetValidator() 应该只在布尔标志设置为 false 时触发,但它们似乎会触发 每一个时间,与布尔标志无关。例如,服务器正在返回表单(服务器端触发),其中联系人和帐单地址被标记为需要填写。这不是我想要的!

Address 模型没有被任何 Validation 属性修饰,所以我唯一能想到的是 MailingAddress 的 .SetValidator() 验证“泄漏”到 Contact 和 Billing 模型中。

我已通过在招聘人员的验证(.SetValidator())中明确禁用联系人和计费的验证调用来确认这一点 - 当我这样做时,可以使用空子模型成功提交表单。问题是,如果其中任何一个的布尔值设置为 False,我需要能够验证它们以确保地址正确且完整。

我该如何克服这个问题?


疯狂的想法:

我刚刚意识到这个问题可能会从两端打击我:

  • 由于导入的模型使用相同的字段名称,mailingAddress AddressModel 的验证器正在捕获其他模型中的其他字段名称。
  • 因为不同的表单使用相同的验证器,它已经通过mailingAddress 加载,因此它已经加载并准备好在contactAddressbillingAddress 得到处理后进行验证。因此,即使他们自己的验证器没有被显式加载,他们也会陷入验证中。

或者我可能同时受到两端的打击。我希望在这里获得专家意见。


更新:

再想一想,我认为这里的问题不是“疯狂的想法”,因为当我明确删除完全从主要招聘人员验证分配的子模型 .SetValidator() 验证器时,验证问题就消失了.可以成功提交和处理空的联系人和帐单地址子模型。如果我上面的两个想法中的任何一个在起作用,这都不会发生。

问题是 - 如果这些不为空,我确实需要验证它们!那么为什么没有正确使用布尔值来确定是否为这些子表单分配验证呢?为什么包含任何子模型验证器,即使它假定仅由 false 布尔值触发,从而导致子模型得到验证?

【问题讨论】:

  • 怎么样而不是 when 子句,使用普通的 if 并且仅在它通过时设置验证?

标签: asp.net asp.net-mvc asp.net-mvc-5 fluentvalidation fluentvalidation-2.0


【解决方案1】:

你可以试试:

When(x => x.ContactSameAsAddress == false, () => { 
  RuleFor(x => x.contactAddress)
  .SetValidator(new CreateRecruiterAddressValidator())
});

【讨论】:

  • 对不起,我没有在我的原始帖子中包含那个例子,但那是我的第一个版本。不,即使 ContactSameAsAddresstrue,验证仍然会在 contactAddress 上触发。
  • 那么带有 IF 子句的老式纯 C# 呢?
  • 这在验证器中不起作用;只有明确的 FluentValidation 术语在那里有效。我也试过了。
猜你喜欢
  • 2016-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多