2012-03-21 4 views

risposta

24

Supponiamo di avere il seguente modello:

[Validator(typeof(MyViewModelValidator))] 
public class MyViewModel 
{ 
    public bool IsChecked { get; set; } 
} 

con il seguente validatore:

public class MyViewModelValidator : AbstractValidator<MyViewModel> 
{ 
    public MyViewModelValidator() 
    { 
     RuleFor(x => x.IsChecked).Equal(true).WithMessage("Please check this checkbox"); 
    } 
} 

e un controller:

public class HomeController : Controller 
{ 
    public ActionResult Index() 
    { 
     return View(); 
    } 

    [HttpPost] 
    public ActionResult Index(MyViewModel model) 
    { 
     return View(model); 
    } 
} 

con una corrispondente vista:

@model MyViewModel 
@using (Html.BeginForm()) 
{ 
    @Html.LabelFor(x => x.IsChecked) 
    @Html.CheckBoxFor(x => x.IsChecked) 
    @Html.ValidationMessageFor(x => x.IsChecked) 
    <button type="submit">OK</button> 
} 

e in Global.asax aver registrato il modello di validazione fornitore validator fluente:

FluentValidationModelValidatorProvider.Configure(); 

Finora abbiamo lato server convalida fino e fine corsa. Quello è buono. Questa è sempre la prima parte che dobbiamo impostare. Ho visto persone che si concentrano troppo sulla convalida del lato client che dimenticano di eseguire la convalida lato server e quando disabiliti javascript (o peggio ancora se inciampi su un utente con cattive intenzioni), beh, le cose brutte accadono. Finora siamo fiduciosi perché sappiamo che anche se qualcosa si fa rovinare sul client, il nostro dominio è protetto con la convalida lato server.


Quindi cerchiamo di ora prendersi cura per la convalida del client. Out of the box FluentValidation.NET supporta la convalida automatica del client per il validatore EqualTo ma durante il confronto con un altro valore di proprietà che è l'equivalente dell'annotazione dati [Compare].

Ma nel nostro caso ci confrontiamo con un valore fisso. Quindi non riusciamo a far uscire la situazione dal lato client. E quando non prendiamo qualcosa fuori dalla scatola, dobbiamo metterlo nella scatola.

Dunque iniziamo definendo un FluentValidationPropertyValidator personalizzato:

public class EqualToValueFluentValidationPropertyValidator : FluentValidationPropertyValidator 
{ 
    public EqualToValueFluentValidationPropertyValidator(ModelMetadata metadata, ControllerContext controllerContext, PropertyRule rule, IPropertyValidator validator) 
     : base(metadata, controllerContext, rule, validator) 
    { 
    } 

    public override IEnumerable<ModelClientValidationRule> GetClientValidationRules() 
    { 
     if (!this.ShouldGenerateClientSideRules()) 
     { 
      yield break; 
     } 
     var validator = (EqualValidator)Validator; 

     var errorMessage = new MessageFormatter() 
      .AppendPropertyName(Rule.GetDisplayName()) 
      .AppendArgument("ValueToCompare", validator.ValueToCompare) 
      .BuildMessage(validator.ErrorMessageSource.GetString()); 

     var rule = new ModelClientValidationRule(); 
     rule.ErrorMessage = errorMessage; 
     rule.ValidationType = "equaltovalue"; 
     rule.ValidationParameters["valuetocompare"] = validator.ValueToCompare; 
     yield return rule; 
    } 
} 

che ci accingiamo a registrare in Application_Start:

FluentValidationModelValidatorProvider.Configure(provider => 
{ 
    provider.AddImplicitRequiredValidator = false; 
    provider.Add(typeof(EqualValidator), (metadata, context, description, validator) => new EqualToValueFluentValidationPropertyValidator(metadata, context, description, validator)); 
}); 

Finora abbiamo associato il nostro FluentValidationPropertyValidator personalizzato con l'EqualValidator.

L'ultima parte è quello di scrivere un adattatore personalizzato:

(function ($) { 
    $.validator.unobtrusive.adapters.add('equaltovalue', ['valuetocompare'], function (options) { 
     options.rules['equaltovalue'] = options.params; 
     if (options.message != null) { 
      options.messages['equaltovalue'] = options.message; 
     } 
    }); 

    $.validator.addMethod('equaltovalue', function (value, element, params) { 
     if ($(element).is(':checkbox')) { 
      if ($(element).is(':checked')) { 
       return value.toLowerCase() === 'true'; 
      } else { 
       return value.toLowerCase() === 'false'; 
      } 
     } 
     return params.valuetocompare.toLowerCase() === value.toLowerCase(); 
    }); 
})(jQuery);  

E questo è praticamente. Tutto ciò che rimane è quello di includere gli script client:

<script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script> 
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script> 
<script src="@Url.Content("~/Scripts/customadapter.js")" type="text/javascript"></script> 
+0

Grazie, è grande. Ma ho dovuto modificare le righe con: [restituire value.toLowerCase() === ...] a: [restituire params.valuetocompare.toLowerCase() === ...] – cryss

+0

Vedere http: // stackoverflow. it/questions/21345078/fluentvalidation-validate-checkbox-and-password-on-the-client-with-equalvalidato per un problema quando si ha un altro ugual validator presente (ad es. conferma password) insieme a una soluzione. – Ted

4

mi piacciono risposta del Darin Dimitrov, ma se si vuole fare in fretta, qui è il mio modo alternativo.

Creare una proprietà aggiuntiva nel modello, ad es.:

public bool ValidationTrue { get; set; } 

e impostarne il valore a true nel contructor del modello.

Usalo in immagine per salvare il valore attraverso le richieste:

@Html.HiddenFor(x => x.ValidationTrue) 

Ora aggiungere una regola di convalida in questo modo:

public class MyViewModelValidator : AbstractValidator<MyViewModel> 
{ 
    public MyViewModelValidator() 
    { 
     RuleFor(x => x.ValidationTrue) 
      .Equal(true); // check it for security reasons, if someone has edited it in the source of the page 

     RuleFor(x => x.HasToBeChecked) 
      .Equal(x => x.ValidationTrue) // HasToBeChecked has to have the same value as ValidationTrue (which is always true) 
      .WithMessage("Required"); 
    } 
} 

che la convalida è supportato dal validatore discreto out-of -la scatola.

1

Sto codificando in ASP.NET MVC5 e il codice Darin produce un errore javascript sulle righe che fanno riferimento a value.ToLowerCase() quando è presente una casella di controllo. Un altro problema è che questo codice invalida il confronto dell'eguaglianza lato client tra due proprietà. Sembra che funzioni solo se confrontato con un valore letterale ... Potrebbe essere stato il suo intento, ma ho bisogno che funzioni per entrambe le situazioni:

Ecco una possibile soluzione, che implica solo due modifiche alla risposta di Darin:

Innanzitutto, ho aggiornato la funzione javascript con quanto segue.

$.validator.addMethod('equaltovalue', function (value, element, params) { 
    if ($(element).is(':checkbox')) { 
     value = $(element).is(':checked') ? "true" : "false"; 
    } 
    return params.valuetocompare.toLowerCase() === value.toLowerCase(); 
}); 

In secondo luogo, ho aggiornato EqualToValueFluentValidationPropertyValidator con il seguente:

public class EqualToValueFluentValidationPropertyValidator : FluentValidationPropertyValidator 
{ 
    EqualValidator EqualValidator 
    { 
     get { return (EqualValidator)Validator; } 
    } 

    public EqualToValueFluentValidationPropertyValidator(ModelMetadata metadata, ControllerContext controllerContext, PropertyRule rule, IPropertyValidator validator) : base(metadata, controllerContext, rule, validator) { 
     ShouldValidate = false; 
    } 

    public override IEnumerable<ModelClientValidationRule> GetClientValidationRules() { 
     if (!ShouldGenerateClientSideRules()) yield break; 

     var propertyToCompare = EqualValidator.MemberToCompare as PropertyInfo; 
     if(propertyToCompare != null) { 
      // If propertyToCompare is not null then we're comparing to another property. 
      // If propertyToCompare is null then we're either comparing against a literal value, a field or a method call. 
      // We only care about property comparisons in this case. 

      var comparisonDisplayName = 
       ValidatorOptions.DisplayNameResolver(Rule.TypeToValidate, propertyToCompare, null) 
       ?? propertyToCompare.Name.SplitPascalCase(); 

      var formatter = new MessageFormatter() 
       .AppendPropertyName(Rule.GetDisplayName()) 
       .AppendArgument("ComparisonValue", comparisonDisplayName); 


      string message = formatter.BuildMessage(EqualValidator.ErrorMessageSource.GetString()); 
      yield return new ModelClientValidationEqualToRule(message, CompareAttribute.FormatPropertyForClientValidation(propertyToCompare.Name)) ; 
     } 
     else 
     { 
      var validator = (EqualValidator)Validator; 

      var errorMessage = new MessageFormatter() 
       .AppendPropertyName(Rule.GetDisplayName()) 
       .AppendArgument("ValueToCompare", validator.ValueToCompare) 
       .BuildMessage(validator.ErrorMessageSource.GetString()); 

      var rule = new ModelClientValidationRule(); 
      rule.ErrorMessage = errorMessage; 
      rule.ValidationType = "equaltovalue"; 
      rule.ValidationParameters["valuetocompare"] = validator.ValueToCompare; 
      yield return rule; 
     } 
    } 
} 

Questo codice è stato copiato dalla classe interna EqualToFluentValidationPropertyValidator nella fonte fluentvalidation, e ho aggiunto la logica di Darin dopo l'altro. Ciò consente alla convalida lato client di funzionare sia per i confronti delle proprietà sia per i confronti di valore ... Non sono sicuro che questo sia un approccio eccezionale poiché stai praticamente ignorando il validatore di uguaglianza incorporato e potrebbe rompersi nelle versioni future di validazione fluente .... ma la risposta di Darin ha lo stesso problema.

Ci potrebbero essere modi migliori per gestirlo. Se qualcuno conosce un modo per includere direttamente la logica dalla classe interna EqualToFluentValidationPropertyValidator, allora mi piacerebbe sentirlo.

2

si basa sulla risposta @cryss

RuleFor(x => x.HasToBeChecked) 
     .Equal(x => true) 
     .WithMessage("Required"); 

non c'è bisogno di utilizzare la proprietà aggiuntiva