El modelo MVC requiere verdadero

85

¿Hay alguna manera de que las anotaciones de datos requieran que una propiedad booleana se establezca en verdadera?

public class MyAwesomeObj{
    public bool ThisMustBeTrue{get;set;}
}
Marty Trenouth
fuente
¿Cuál es exactamente el caso de uso de esto? ¿No podría dejar que la propiedad sea de solo lectura y vuelva a ser verdadera todo el tiempo?
Jan Thomä
1
Es más o menos para decir ... hola amigo, se olvidó de marcar el Acepto ... lo que debería invalidar el modelo.
Marty Trenouth
Creo que esto es algo que le gustaría manejar del lado del cliente.
PsychoCoder
15
@PsychoCoder: Debe manejarse en ambos lados ... no solo en el lado del cliente. Solo estaba buscando para ver si se podía manejar agregando una anotación de datos simple.
Marty Trenouth

Respuestas:

49

Podrías crear tu propio validador:

public class IsTrueAttribute : ValidationAttribute
{
    #region Overrides of ValidationAttribute

    /// <summary>
    /// Determines whether the specified value of the object is valid. 
    /// </summary>
    /// <returns>
    /// true if the specified value is valid; otherwise, false. 
    /// </returns>
    /// <param name="value">The value of the specified validation object on which the <see cref="T:System.ComponentModel.DataAnnotations.ValidationAttribute"/> is declared.
    ///                 </param>
    public override bool IsValid(object value)
    {
        if (value == null) return false;
        if (value.GetType() != typeof(bool)) throw new InvalidOperationException("can only be used on boolean properties.");

        return (bool) value;
    }

    #endregion
}
moribvndvs
fuente
Consideraría mejorar esto con una implementación del lado del cliente; en lugar de usar la validación remota a la que se hace referencia en otras respuestas, use el discreto que se detalla
SamStephens
Esta es una buena (y probada) solución rápida para nosotros. Podemos prescindir de la validación del lado del cliente en la solución de @ dazbradbury (también una buena) porque solo necesitamos esto en una casilla de verificación solitaria en la página anterior de una encuesta.
Seth
return (bool) value == true;esta es una comparación redundante
T-moty
130

Crearía un validador tanto para el servidor como para el cliente. Usando MVC y la validación de formularios discretos, esto se puede lograr simplemente haciendo lo siguiente:

En primer lugar, cree una clase en su proyecto para realizar la validación del lado del servidor así:

public class EnforceTrueAttribute : ValidationAttribute, IClientValidatable
{
    public override bool IsValid(object value)
    {
        if (value == null) return false;
        if (value.GetType() != typeof(bool)) throw new InvalidOperationException("can only be used on boolean properties.");
        return (bool)value == true;
    }

    public override string FormatErrorMessage(string name)
    {
        return "The " + name + " field must be checked in order to continue.";
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        yield return new ModelClientValidationRule
        {
            ErrorMessage = String.IsNullOrEmpty(ErrorMessage) ? FormatErrorMessage(metadata.DisplayName) : ErrorMessage,
            ValidationType = "enforcetrue"
        };
    }
}

Después de esto, anote la propiedad apropiada en su modelo:

[EnforceTrue(ErrorMessage=@"Error Message")]
public bool ThisMustBeTrue{ get; set; }

Y finalmente, habilite la validación del lado del cliente agregando el siguiente script a su Vista:

<script type="text/javascript">
    jQuery.validator.addMethod("enforcetrue", function (value, element, param) {
        return element.checked;
    });
    jQuery.validator.unobtrusive.adapters.addBool("enforcetrue");
</script>

Nota: Ya creamos un método GetClientValidationRulesque empuja nuestra anotación a la vista desde nuestro modelo.

Si usa archivos de recursos para proporcionar el mensaje de error para la internacionalización, elimine la FormatErrorMessagellamada (o simplemente llame a la base) y modifique el GetClientValidationRulesmétodo así:

public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
    string errorMessage = String.Empty;
    if(String.IsNullOrWhiteSpace(ErrorMessage))
    {
        // Check if they supplied an error message resource
        if(ErrorMessageResourceType != null && !String.IsNullOrWhiteSpace(ErrorMessageResourceName))
        {
            var resMan = new ResourceManager(ErrorMessageResourceType.FullName, ErrorMessageResourceType.Assembly);
            errorMessage = resMan.GetString(ErrorMessageResourceName);
        }
    }
    else
    {
        errorMessage = ErrorMessage;
    }

    yield return new ModelClientValidationRule
    {
        ErrorMessage = errorMessage,
        ValidationType = "enforcetrue"
    };
}
Dazbradbury
fuente
3
Gracias por esto, ¡funciona muy bien! Funciona mejor con el método de anulación FormatErrorMessage eliminado, de esa manera funciona la localización de los mensajes de error de los archivos de recursos. Mi uso: [EnforceTrue (ErrorMessageResourceType = typeof (ValidationMessages), ErrorMessageResourceName = "TermsAndConditionsRequired")]
Matt Frear
2
No puedo hacer que la validación del lado del cliente funcione y parece que no puedo decir qué estoy haciendo mal. ¿Dónde debería poner exactamente el javacsript? ¿En la etiqueta de la cabeza? ¿Junto al controlador?
vsdev
Estoy de acuerdo, esta debería ser la respuesta
Simua
1
¡Gran solución que muestra el poder de los atributos de validación personalizados! Aunque recomiendo poner el script en un archivo js referenciado globalmente, no en las vistas, para su reutilización. Además, es mejor manejar todas las formas en que se pueden agregar las cadenas de mensajes: predeterminado si no se proporciona ninguna, o la cadena del mensaje, o desde un archivo de recursos.
jeepwran
1
Gran solución, gracias por publicar. Para aquellos que no pueden hacer que la validación del lado del cliente funcione: debe extender la validación de jQuery antes de que se carguen los controles que validará, así que coloque el script en el encabezado y no en la ventana .onload / $ (document ) .Ready () evento.
Evert
92

Sé que esta es una publicación anterior, pero quería compartir una forma simple del lado del servidor para hacer esto. Usted crea una propiedad pública establecida en true y compara su bool con esa propiedad. Si su bool no está marcado (por defecto es falso), el formulario no se validará.

public bool isTrue
{ get { return true; } }

[Required]
[Display(Name = "I agree to the terms and conditions")]
[Compare("isTrue", ErrorMessage = "Please agree to Terms and Conditions")]
public bool AgreeTerms { get; set; }

Código de la maquinilla de afeitar

@Html.CheckBoxFor(m => Model.AgreeTerms, new { id = "AgreeTerms", @checked = "checked" })
<label asp-for="AgreeTerms" class="control-label"></label>
<a target="_blank" href="/Terms">Read</a>
<br />
@Html.ValidationMessageFor(model => model.AgreeTerms, "", new { @class = "text-danger" })
@Html.HiddenFor(x => Model.isTrue)
fields.cage
fuente
12
+1 por simplicidad. FYI: Tuve que hacer pública la propiedad 'isTrue' para que esto funcione.
Tod Birdsall
Compare no está ahí para mí en MVC4
Michael Rudner Evanchik
Súper solución gran solución
Sreerejith SS
9
Y si agrega un oculto para la propiedad "isTrue", obtiene la validación del lado del cliente
billoreid
2
Molestar esta excelente solución no funcionó para mí. Probado en Mvc 5.2.3.
harvzor
22

Probé varias soluciones, pero ninguna funcionó completamente para mí para obtener la validación del lado del cliente y del servidor. Entonces, lo que hice en mi aplicación MVC 5 para que funcione:

En su ViewModel (para la validación del lado del servidor):

public bool IsTrue => true;

[Required]
[Display(Name = "I agree to the terms and conditions")]
[Compare(nameof(IsTrue), ErrorMessage = "Please agree to Terms and Conditions")]
public bool HasAcceptedTermsAndConditions { get; set; }

En su página de Razor (para la validación del lado del cliente):

<div class="form-group">
   @Html.CheckBoxFor(m => m.HasAcceptedTermsAndConditions)
   @Html.LabelFor(m => m.HasAcceptedTermsAndConditions)
   @Html.ValidationMessageFor(m => m.HasAcceptedTermsAndConditions)

   @Html.Hidden(nameof(Model.IsTrue), "true")
</div>
Kapé
fuente
1
¡Solución encantadora!
Tobias
3
¡Tenga cuidado con el valor del campo oculto ("verdadero")!
Tobias
10

Solo me gustaría dirigir a las personas al siguiente Fiddle: https://dotnetfiddle.net/JbPh0X

El usuario agregó [Range(typeof(bool), "true", "true", ErrorMessage = "You gotta tick the box!")]a su propiedad booleana que hace que funcione la validación del lado del servidor.

Para que también funcione la validación del lado del cliente, agregaron el siguiente script:

// extend jquery range validator to work for required checkboxes
var defaultRangeValidator = $.validator.methods.range;
$.validator.methods.range = function(value, element, param) {
    if(element.type === 'checkbox') {
        // if it's a checkbox return true if it is checked
        return element.checked;
    } else {
        // otherwise run the default validation function
        return defaultRangeValidator.call(this, value, element, param);
    }
}
Harvzor
fuente
9

Simplemente verifique si su representación de cadena es igual a True:

[RegularExpression("True")]
public bool TermsAndConditions { get; set; }
ta.speot.is
fuente
@JeradRose Está validado muy bien en el servidor. ¿Se refiere a la validación del lado del cliente?
ta.speot.is
3
Confirmado, esto funciona del lado del servidor pero no del lado del cliente
Matt Frear
Pensé que la validación del lado del servidor podría tener una excepción de falta de coincidencia de tipos al intentar comparar un bool con una cadena.
Jerad Rose
RegularExpressionAttributeutiliza internamente Convert.ToStringpara obtener la representación de cadena del valor de la propiedad (que se le entrega como un object).
ta.speot.is
Creo que esta respuesta es más simple que @ fields-cage +1 de mi parte
Aaron Hudon
5

Puede crear su propio atributo o utilizar CustomValidationAttribute .

Así es como usaría CustomValidationAttribute:

[CustomValidation(typeof(BoolValidation), "ValidateBool")]

donde BoolValidation se define como:

public class BoolValidation
{
  public static ValidationResult ValidateBool(bool boolToBeTrue)
  {
    if (boolToBeTrue)
    {
      return ValidationResult.Success;
    }
    else
    {
      return new ValidationResult(
          "Bool must be true.");
    }
  }
Mateo Manela
fuente
5

[Required]atributo significa que requiere cualquier valor, puede ser verdadero o falso. Tendría que usar otra validación para eso.

Sergey Kudriavtsev
fuente
3

Continuando con la publicación de ta.speot.is y el comentario de Jerad Rose:

La publicación dada no funcionará del lado del cliente con una validación discreta. Esto debería funcionar en ambos campos (cliente y servidor):

[RegularExpression("(True|true)")]
public bool TermsAndConditions { get; set; }
lobotomia
fuente
No sé si se trata de un problema de versión más reciente, pero no me funciona con jquery.validate 1.19.2 y jquery.validate.unobtrusive 3.2.11. El problema parece ser que el regexmétodo discreto define primero verifica si la casilla de verificación es opcional antes de validar la expresión regular, lo cual tiene sentido, excepto que jquery.validate parece considerar cualquier casilla de verificación no marcada como opcional. tl; dr Solo ejecuta la expresión regular en las casillas de verificación marcadas. Podemos agregar una corrección para el regex validatormétodo o simplemente crear un validador personalizado.
xr280xr
3

.NET Core MVC: casilla de verificación obligatoria con anotaciones de datos

public class MyModel
{
    [Display(Name = "Confirmation")]
    [Range(typeof(bool), "true", "true", ErrorMessage = "Please check the Confirmation checkbox.")]
    public bool IsConfirmed { get; set; }   
}

<div class="custom-control custom-checkbox col-10">
    <input type="checkbox" asp-for="IsConfirmed" class="custom-control-input" />
    <label class="custom-control-label" for="IsConfirmed">
        "By clicking 'submit', I confirm."
    </label>
    <span asp-validation-for="IsConfirmed" class="text-danger"></span>
</div>

<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>

<script type="text/javascript">
    $(document).ready(function () {
        // extend range validator method to treat checkboxes differently
        var defaultRangeValidator = $.validator.methods.range;
        $.validator.methods.range = function (value, element, param) {
            if (element.type === 'checkbox') {
                // if it's a checkbox return true if it is checked
                return element.checked;
            } else {
                // otherwise run the default validation function
                return defaultRangeValidator.call(this, value, element, param);
            }
        }
    });
</script>
Aung San Myint
fuente
Dar crédito: jasonwatmore.com/post/2013/10/16/…
xr280xr
2

No conozco una forma de realizar anotaciones de datos, pero esto se hace fácilmente en su controlador.

public ActionResult Add(Domain.Something model)
{

    if (!model.MyCheckBox)
        ModelState.AddModelError("MyCheckBox", "You forgot to click accept");

    if (ModelState.IsValid) {
        //'# do your stuff
    }

}

La única otra opción sería construir un validador personalizado para el lado del servidor y un validador remoto para el lado del cliente (la validación remota solo está disponible en MVC3 +)

Chase Florell
fuente
Ya es un poco nuevo cómo verificar la bandera booleana ... quería saber si había una anotación de datos para ello.
Marty Trenouth
2

¿Tiene los elementos adecuados configurados en web.config ?

Eso podría hacer que la validación no funcione.

También puede intentar crear un atributo de validación personalizado (ya que [Required]solo le importa si existe o no, y a usted le importa el valor):

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
sealed public class RequiredTrueAttribute : ValidationAttribute
{
    // Internal field to hold the mask value.
    readonly bool accepted;

    public bool Accepted
    {
        get { return accepted; }
    }

    public RequiredTrueAttribute(bool accepted)
    {
        this.accepted = accepted;
    }

    public override bool IsValid(object value)
    {
        bool isAccepted = (bool)value;
        return (isAccepted == true);
    }

    public override string FormatErrorMessage(string name)
    {
        return String.Format(CultureInfo.CurrentCulture,
          ErrorMessageString, name, this.Accepted);
    }
}

Entonces, uso:

[RequiredTrue(ErrorMessage="{0} requires acceptance to continue.")]
public bool Agreement {get; set;}

De aqui .

George Stocker
fuente
2

Esto es lo que funcionó para mí. Nada más hizo. MVC 5:

Modelo

public string True
{
  get
  {
    return "true";
  }
}

[Required]
[Compare("True", ErrorMessage = "Please agree to the Acknowlegement")]
public bool Acknowlegement { get; set; }

Ver

  @Html.HiddenFor(m => m.True)
  @Html.EditorFor(model => model.Acknowlegement, new { htmlAttributes = Model.Attributes })
  @Html.ValidationMessageFor(model => model.Acknowlegement, "", new { @class = "text-danger" })

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

Toddmo
fuente
2

Para ASP.NET Core MVC, aquí está la validación de cliente y servidor, basada en la solución de dazbradbury

public class EnforceTrueAttribute : ValidationAttribute, IClientModelValidator
{
    public override bool IsValid(object value)
    {
        if (value == null) return false;
        if (value.GetType() != typeof(bool)) throw new InvalidOperationException("can only be used on boolean properties.");
        return (bool)value;
    }

    public void AddValidation(ClientModelValidationContext context)
    {
        MergeAttribute(context.Attributes, "data-val", "true");
        var errorMessage = ErrorMessage ?? 
            $"The value for field {context.ModelMetadata.GetDisplayName()} must be true.";
        MergeAttribute(context.Attributes, "data-val-enforcetrue", errorMessage);
    }

    private void MergeAttribute(IDictionary<string, string> attributes,
        string key,
        string value)
    {
        if (attributes.ContainsKey(key))
        {
            return;
        }
        attributes.Add(key, value);
    }
}

Y luego sobre el cliente:

$.validator.addMethod("enforcetrue", function (value, element, param) {
    return element.checked;
});

$.validator.unobtrusive.adapters.addBool("enforcetrue");

Entonces el uso es:

[EnforceTrue(ErrorMessage = "Please tick the checkbox")]
public bool IsAccepted { get; set; }
Mate
fuente
1

Intenté usar la respuesta de fields.cage y no funcionó del todo para mí, pero algo más simple sí lo hizo, y no estoy seguro exactamente por qué (¿una versión diferente de Razor, tal vez?), Pero todo lo que tenía que hacer era esto:

[Required]
[Range(typeof(bool), "true", "true", ErrorMessage = "Agreement required.")]
[Display(Name = "By clicking here, I agree that my firstborn child will etc etc...")]
public bool Agreement1Checked { get; set; }

Y en el archivo .cshtml:

@Html.CheckBoxFor(m => m.Agreement1Checked)
@Html.LabelFor(m => m.Agreement1Checked)
@Html.ValidationMessageFor(m => m.Agreement1Checked)
Dronz
fuente
Esto no funciona del lado del cliente para mí. Por alguna razón, el parámetro pasado al método de regla jquery.validate es [NaN, NaN]donde debería estar[true, true]
xr280xr
@ xr280xr ¿Incluso cuando el usuario ha marcado la casilla de verificación?
Dronz
0

Creo que la mejor manera de manejar esto es simplemente verificar en su controlador si la casilla es verdadera; de lo contrario, simplemente agregue un error a su modelo y haga que vuelva a mostrar su vista.

Como se indicó anteriormente, lo único que hace [Obligatorio] es asegurarse de que haya un valor y, en su caso, si no se marca, seguirá siendo falso.

samack
fuente
0

Echa un vistazo a la validación infalible aquí . Puede descargarlo / instalarlo a través de Nuget.

Es una gran biblioteca pequeña para este tipo de cosas.

DavidWainwright
fuente
Ehhhh ... Sin embargo, los atributos de validación predeterminados funcionan bastante bien.
Pangamma
0
/// <summary> 
///  Summary : -CheckBox for or input type check required validation is not working the root cause and solution as follows
///
///  Problem :
///  The key to this problem lies in interpretation of jQuery validation 'required' rule. I digged a little and find a specific code inside a jquery.validate.unobtrusive.js file:
///  adapters.add("required", function (options) {
///  if (options.element.tagName.toUpperCase() !== "INPUT" || options.element.type.toUpperCase() !== "CHECKBOX") {
///    setValidationValues(options, "required", true);
///    }
///   });
///   
///  Fix: (Jquery script fix at page level added in to check box required area)
///  jQuery.validator.unobtrusive.adapters.add("brequired", function (options) {
///   if (options.element.tagName.toUpperCase() == "INPUT" && options.element.type.toUpperCase() == "CHECKBOX") {
///              options.rules["required"] = true;
///   if (options.message) {
///                   options.messages["required"] = options.message;
///                       }
///  Fix : (C# Code for MVC validation)
///  You can see it inherits from common RequiredAttribute. Moreover it implements IClientValidateable. This is to make assure that rule will be propagated to client side (jQuery validation) as well.
///  
///  Annotation example :
///   [BooleanRequired]
///   public bool iAgree { get; set' }
/// </summary>


public class BooleanRequired : RequiredAttribute, IClientValidatable
{

    public BooleanRequired()
    {
    }

    public override bool IsValid(object value)
    {
        return value != null && (bool)value == true;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        return new ModelClientValidationRule[] { new ModelClientValidationRule() { ValidationType = "brequired", ErrorMessage = this.ErrorMessage } };
    }
}
dhandapani harikrishnan
fuente
Si bien este enlace puede responder a la pregunta, es mejor incluir las partes esenciales de la respuesta aquí y proporcionar el enlace como referencia. Las respuestas de solo enlace pueden dejar de ser válidas si cambia la página enlazada.
Ravi Dhoriya ツ
Funciona Consulte este enlace con la razón por la que falla en la validación- itmeze.com/2010/12/06/…
dhandapani harikrishnan
Hoy funciona. ¿Puede estar seguro de que seguirá funcionando en 5 o 10 años después? Estas bases de datos de preguntas y respuestas también se crean para futuros usuarios
Eliyahu