0

projet actuel:FluentValidation - Validation des « fuites » dans d'autres modèles répliquées

  • ASP.NET 4.5.2
  • MVC 5
  • Validation Courant

je peut avoir un très étrange question ici. Il se peut que je subisse une «fuite de validation», la validation sur un sous-modèle (étant supposé être complètement rempli) est «fuyante» dans le même modèle importé une deuxième et une troisième fois, mais pas doivent être remplis. J'ai donc ce formulaire d'inscription, qui se produit après l'enregistrement. Sans remplir le formulaire, un utilisateur ne peut pas accéder au site. Essentiellement, une page de profil qui est essentielle pour le bon fonctionnement des internes du site. Une classe d'utilisateur, appelée Recruteur, a besoin de trois "Adresses": pour l'entreprise, pour les contacter (si leur bureau est différent) et pour la facturation (dans le cas où l'emplacement de facturation est différent). Seule l'adresse de la société est requise, en ce sens qu'elle nécessite une adresse entrée manuellement. Si le second et le troisième sont les mêmes que le premier, l'utilisateur peut les signaler spécifiquement via des booléens contrôlés par le commutateur Bootstrap, et les deuxième et troisième sous-modèles peuvent être soumis comme vides. La validation ne doit se produire sur le premier sous-modèle si l'une des deux booléens (« cette même adresse que l'adresse principale ») sont mis à vrai (ce qui est important!)

Ma solution originale (que j'ai abandonné depuis) est d'avoir le sous-modèle d'adresse refléter exactement le modèle DB, en ce qu'il a non seulement l'adresse physique, mais aussi le numéro de téléphone, eMail, etc., etc. Je l'ai abandonné parce que je rencontrais des problèmes de validation Modèle d'adresse, en ce sens que les deux sous-modèles secondaires ont été validés même si mes validateurs n'étaient supposés être déclenchés que lorsque les adresses étaient différentes (j'ai utilisé un Bootstrap Switch pour permettre à ces champs d'adresses d'être masqués décochez les champs d'adresse si les deuxième et troisième adresses étaient vraiment différentes).

J'ai depuis reconçu le sous-modèle Adresse pour inclure seulement l'adresse physique: Rue, ville, état, etc. Malheureusement, je suis encore en train de rencontrer le problème, en ayant les deuxième et troisième sous-formulaires car la validation est lancée à vide, même si l'utilisateur les laisse dans la configuration par défaut (cette adresse est la même que l'adresse principale - ne validez pas).

Mon modèle Adresse:

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 
} 

Comme vous pouvez le voir, je fournirai des sections pour la prise de décision: si un utilisateur est du Canada ou aux États-Unis, ils obtiennent une liste déroulante personnalisée pour Province/État et tout le monde obtient des champs de texte Pays et Province simples.

Mon modèle recruteur:

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 choses là-bas, désolé pour la décharge de données. Probablement que la première moitié est importante.

Ma validation Adresse:

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."); 
     }); 
    } 
} 

Et maintenant ma validation de recruteur:

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."); 
    } 
} 

La façon dont je vois la validation, le .SetValidator() pour le contact et les modèles d'adresse de facturation ne devrait le feu lorsque la Les indicateurs booléens sont définis sur false, mais ils apparaissent pour déclencher à chaque fois, quel que soit le drapeau booléen. Comme dans, le formulaire est retourné par le serveur (déclenchement côté serveur) avec les adresses de contact et de facturation marqués comme requis pour être rempli. Et c'est pas ce que je veux! Le modèle d'adresse n'est décoré par aucun attribut de validation, donc la seule chose à laquelle je peux penser est que la validation .SetValidator() pour MailingAddress "fuit" dans les modèles Contact et Facturation.

Je l'ai confirmé en désactivant explicitement les appels de validation pour Contact et Facturation dans la validation du recruteur (le .SetValidator()) - et le formulaire peut être soumis avec succès avec des sous-modèles vides lorsque je fais cela. Le problème est que, si le booléen est mis à False pour l'un ou l'autre, je dois être capable de le valider pour m'assurer que l'adresse est correcte et complète.

Comment puis-je surmonter cela?


Pensée fou:

Je viens de réaliser que ce problème pourrait être me frapper de chaque extrémité:

  • Parce que les modèles importés utilisent les mêmes noms de champs, le validateur pour le mailingAddress AddressModel capture les autres noms de champs dans les autres modèles. Parce que les différentes formes utilisent le même validateur, il est déjà chargé via mailingAddress, et en tant que tel, il est déjà chargé et prêt à valider une fois que contactAddress et billingAddress ont été traitées. Ils sont donc pris dans la validation même si leurs propres validateurs ne sont pas explicitement chargés.

Ou peut-être que je reçois un coup des deux extrémités à la fois. J'aimerais une opinion d'expert ici.


Mise à jour:

À la réflexion, je ne pense pas que ce soit est « pensée folle » qui est la question ici, parce que quand je supprimer explicitement le sous-modèle .SetValidator() validateur attribution entièrement à partir de la validation principale de Recruiter, le problème de validation disparaît. Les sous-modèles d'adresse de contact et de facturation vides peuvent être soumis et traités avec succès. Si l'une de mes deux pensées ci-dessus était en jeu, cela n'arriverait pas.

Le problème est que si elles ne sont pas vides, do besoin d'être validé! Alors, pourquoi le booléen n'est-il pas correctement utilisé pour déterminer s'il faut ou non attribuer une validation à ces sous-formulaires?Pourquoi l'inclusion d'un validateur de sous-modèle, même si est supposé être déclenchée uniquement par une valeur booléenne false, ce qui entraîne la validation des sous-modèles?

+0

Qu'en est-il au lieu de l'article lorsque, utilisez une normale si et seulement la validation de la configuration si elle passe? –

Répondre

0

Vous pouvez essayer avec:

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

Désolé, je n'ai pas inclus cet exemple dans mon post original, mais * était * ma première version. Et non, la validation a encore été lancée sur 'contactAddress' même lorsque' ContactSameAsAddress' était 'true'. –

+0

Alors qu'en est-il de l'ancien C# avec une clause IF? –

+0

Cela ne fonctionne pas dans un validateur; seuls les termes explicites de FluentValidation fonctionnent ici. J'ai essayé ça aussi. –