FluentValidation - 验证“泄漏”到其他复制模型

时间:2016-08-09 20:33:21

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

当前项目:

  • ASP.NET 4.5.2
  • MVC 5
  • 流利验证

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

所以我有这个注册表格,在注册后发生。如果没有填写表单,用户就无法进入该站点。基本上,一个配置文件页面对于站点内部的正确操作至关重要。

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

我原来的解决方案(我已经放弃了)是让地址子模型准确地反映数据库模型,因为它不仅有物理地址,还有电话号码,电子邮件等等。我放弃了它,因为我遇到了整个地址模型的验证问题,因为即使我的验证器仅在地址不同时才被触发,我仍然会对两个辅助子模型进行验证(我使用了一个Bootstrap Switch来允许这些地址字段作为组隐藏,因此如果第二个和第三个地址真的不同,用户可以取消隐藏地址字段。)

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

我的地址模型:

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;
  }
}

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

我的地址验证:

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时触发,但它们出现以激活每一次,无论布尔标志如何。就像在,服务器返回表单(服务器端触发),联系人和计费地址被标记为需要填写。这就是我想要的东西!

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

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

我怎样才能克服这个?

疯狂思维:

我刚才意识到这个问题可能会从任何一端打击我:

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

或者我可能会立即从两端受到攻击。我想在这里有专家意见。

更新

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

问题是 - 如果这些不是空的,我需要对它们进行验证!那么为什么布尔值没有被正确地用于确定是否为这些子表单分配验证?为什么包含子模型验证器,即使假设仅由false布尔值触发,导致子模型得到验证?

1 个答案:

答案 0 :(得分:0)

您可以尝试:

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