Forma correcta de vincular un radiobutton mvc3 a un modelo

Tengo una vista que contiene una lista de botones de radio para mis términos y condiciones del sitio.

p.ej

Yes @Html.RadioButtonFor(model => model.TermsAndConditions, "True") No @Html.RadioButtonFor(model => model.TermsAndConditions, "False", new { Checked = "checked" }) 
@Html.ValidationStyledMessageFor(model => model.TermsAndConditions)

Todo está bien si el usuario completa el formulario sin ningún error; sin embargo, si realizo la validación en el servidor y la página se actualiza, pierdo la selección que el usuario realizó para el botón de radio y la radio seleccionada regresa al campo falso predeterminado.

¿Cómo debo vincular el botón de radio para que si un usuario selecciona true este valor se mantenga incluso después de la validación en el servidor?

¡Cualquier sugerencia seria genial!

Para la respuesta corta, debe hacer tres cosas:

  1. Quite el new { Checked = "checked" } del segundo botón de radio. Este valor verificado y codificado anulará toda la magia.
  2. Cuando devuelve su ViewResult de la acción del controlador, dele una instancia de su clase de modelo donde TermsAndConditions sea falso. Esto proporcionará el valor falso predeterminado que necesita para que el botón de opción falso esté preseleccionado para usted.
  3. Use true y false como los valores de sus botones de opción en lugar de "True" y "False" . Esto se debe a que su propiedad es de tipo bool . Estrictamente hablando, casualmente elegiste las representaciones de cadena correctas para true y false , pero el parámetro de valor para el método RadioButtonFor es de tipo object . Lo mejor es pasar el tipo real que desea comparar en lugar de convertirlo en una cadena usted mismo. Más sobre esto a continuación.

Esto es lo que está pasando en profundidad:

El marco de trabajo quiere hacer todo esto por usted automáticamente, pero hizo esas dos primeras cosas incorrectamente lo que le hace tener que luchar con el marco para obtener el comportamiento que desea.

El método RadioButtonFor llama a .ToString() sobre el valor de la propiedad que ha especificado y lo compara con .ToString() del valor que .ToString() al crear el botón de .ToString() . Si son iguales, establece internamente isChecked = true y termina representando checked="checked" en el HTML. Así es como decide qué botón de radio verificar. Simplemente compara el valor del botón de opción con el valor de la propiedad y marca el que coincida.

Puede renderizar botones de opción para casi cualquier propiedad de esta manera y funcionará mágicamente. ¡Las cadenas, ints e incluso los tipos enum funcionan! Cualquier objeto que tenga un método ToString que devuelva una cadena que represente de forma exclusiva el valor del objeto funcionará. Solo tienes que asegurarte de que configuras el valor del botón de opción con un valor que tu propiedad podría tener. La forma más fácil de hacerlo es simplemente pasar el valor en sí mismo, no la representación de cadena del valor. Deje que el marco lo convierta en una cadena para usted.

(Dado que pasó las representaciones de cadena correcta de true y false , esos valores funcionarán siempre que corrija sus dos errores reales, pero sigue siendo prudente pasar los valores reales y no sus cadenas).

Su primer error real fue el código difícil Checked = "checked" para el botón de Checked = "checked" “No”. Esto anulará lo que el marco intenta hacer por usted y los resultados en este botón de radio siempre se verifican.

Obviamente, usted quiere que el botón de opción “No” sea preseleccionado, pero debe hacerlo de una manera que sea compatible con todo lo anterior. Debe dar a la vista una instancia de su clase de modelo donde TermsAndConditions se establezca en falso, y permita que se “vincule” a los botones de opción. Normalmente, una acción de controlador que responde a la solicitud GET inicial de una URL no proporciona a la Vista una instancia de la clase de modelo. Normalmente, solo return View(); . Sin embargo, dado que desea seleccionar un valor predeterminado, debe proporcionarle a la vista una instancia de su modelo que tenga TermsAndConditions establecido en false.

Aquí hay un código fuente que ilustra todo esto:

Algún tipo de clase de Cuenta que probablemente ya tengas. (El modelo de su Vista):

 public class Account { public bool TermsAndConditions { get; set; } //other properties here. } 

Algunos métodos en tu controlador:

 //This handles the initial GET request. public ActionResult CreateAccount() { //this default instance will be used to pre-populate the form, making the "No" radio button checked. var account = new Account { TermsAndConditions = false }; return View( account ); } //This handles the POST request. [HttpPost] public ActionResult CreateAccount( Account account ) { if ( account.TermsAndConditions ) { //TODO: Other validation, and create the account. return RedirectToAction( "Welcome" ); } else { ModelState.AddModelError( "TermsAndConditionsAgreement", "You must agree to the Terms and Conditions." ); return View( account ); } } //Something to redirect to. public ActionResult Welcome() { return View(); } 

La vista completa:

 @model Account @{ ViewBag.Title = "Create Account"; } @using ( Html.BeginForm() ) { 
Do you agree to the Terms and Conditions?
@Html.RadioButtonFor( model => model.TermsAndConditions, true, new { id = "TermsAndConditions_true" } )
@Html.RadioButtonFor( model => model.TermsAndConditions, false, new { id = "TermsAndConditions_false" } )
@Html.ValidationMessage( "TermsAndConditionsAgreement" )
}

BONO: notará que agregué una pequeña característica adicional a los botones de opción. En lugar de simplemente usar texto sin formato para las tags de los botones de opción, utilicé el elemento de label HTML con el atributo for establecido en las ID de cada botón de opción. Esto permite a los usuarios hacer clic en la etiqueta para seleccionar el botón de opción en lugar de tener que hacer clic en el botón de opción. Esto es HTML estándar. Para que esto funcione, tuve que establecer identificaciones manuales en los botones de opción; de lo contrario, ambos recibirían la misma ID de “Términos y condiciones”, lo que no funcionaría.

Hay algunas cosas que debe hacer aquí para garantizar que la selección del usuario se mantenga después de la validación del lado del servidor.

a) Enlace la propiedad “marcada” de cada radio a su modelo en la vista, por ejemplo:

 Yes @Html.RadioButtonFor(model => model.TermsAndConditions, "True", model.TermsAndConditions == true ? new { Checked = "checked" } : null) No @Html.RadioButtonFor(model => model.TermsAndConditions, "False", model.TermsAndConditions == false ? new { Checked = "checked" } : null) 

b) Para definir el valor predeterminado inicial cuando la vista se muestra por primera vez, inicialice el modelo devuelto a la vista en la solicitud GET (en la acción del controlador), por ejemplo:

 public ActionResult SomeForm() { return View(new SomeModel { TermsAndConditions = false }); } 

b) Asegúrese de que en su acción de controlador [HttpPost] devuelva el modelo cuando la validación falla, por ejemplo:

 [HttpPost] public ActionResult SomeForm(SomeModel model) { if (!ModelState.IsValid) return View(model); // Do other stuff here } 

De esta forma, cuando se representa la vista en la respuesta después de que falla la validación, tendrá el estado real del modelo que se pasó (manteniendo así la selección del usuario).

No puedo decirlo porque no has mostrado tu código, pero sospecho que si fallas en la validación del lado del servidor, solo estás devolviendo la vista sin procesar. Cuando falla, debe completar la vista con el modelo que se envió, igual que si devolviera cualquier otro error de validación. De lo contrario, obtendrá los valores predeterminados del modelo (que siempre será falso para el registro booleano).

¿Tal vez podrías publicar tu código del lado del servidor?

Aquí estoy ofreciendo otro ejemplo más complejo.

  public enum UserCommunicationOptions { IPreferEmailAndSMS = 1, IPreferEmail = 2, IPreferSMS = 3 } 

Html

 @model UserProfileView // Some other code 
@if (Model.UserCommunicationOption.ToString() == UserCommunicationOptions.IPreferEmailAndSMS.ToString()) { @Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferEmailAndSMS, new { @checked = "checked" }) } else { @Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferEmailAndSMS) }
@if (Model.UserCommunicationOption.ToString() == UserCommunicationOptions.IPreferEmail.ToString()) { @Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferEmail, new { @checked = "checked" }) } else { @Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferEmail) }
@if (Model.UserCommunicationOption.ToString() == UserCommunicationOptions.IPreferSMS.ToString()) { @Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferSMS, new { @checked = "checked" }) } else { @Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferSMS) }
Model [Required(ErrorMessageResourceName = "Communications", ErrorMessageResourceType = typeof(Resource))] [Display(Name = "Communications", ResourceType = typeof(DLMModelEntities.Properties.Resource))] public UserCommunicationOptions UserCommunicationOption { get; set; }

OBTENER

  var client = AppModel.Clients.Single(x => x.Id == clientId); if (Convert.ToBoolean(client.IsEmailMessage) && Convert.ToBoolean(client.IsSMSMessage)) { model.UserCommunicationOption = UserCommunicationOptions.IPreferEmailAndSMS; } else if (Convert.ToBoolean(client.IsEmailMessage)) { model.UserCommunicationOption = UserCommunicationOptions.IPreferEmail; } else if ( Convert.ToBoolean(client.IsSMSMessage)) { model.UserCommunicationOption = UserCommunicationOptions.IPreferSMS; } 

ENVIAR

  [HttpPost] public ActionResult MyProfile(UserProfileView model) { // Some code var client = AppModel.Clients.Single(x => x.Id == clientId); if (model.UserCommunicationOption == UserCommunicationOptions.IPreferEmail) { client.IsSMSMessage = false; client.IsEmailMessage = true; } else if (model.UserCommunicationOption == UserCommunicationOptions.IPreferEmailAndSMS) { client.IsSMSMessage = true; client.IsEmailMessage = true; } else if (model.UserCommunicationOption == UserCommunicationOptions.IPreferSMS) { client.IsSMSMessage = true; client.IsEmailMessage = false; } AppModel.SaveChanges(); //Some code } 

Base de datos

enter image description here

Página web

enter image description here

Tuve un problema similar y resolví el problema estableciendo un valor ViewData en el controlador para hacer un seguimiento de lo que el usuario había seleccionado.