48 votes

Comment lier correctement un bouton radio mvc3 à un modèle ?

J'ai une vue qui contient une liste de boutons radios pour mes termes et conditions du site.

par exemple

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

Tout va bien si l'utilisateur remplit le formulaire sans erreur, mais si j'effectue une validation côté serveur et que la page est actualisée, je perds la sélection que l'utilisateur a faite pour le bouton radio et la radio sélectionnée revient au champ faux par défaut.

Comment suis-je censé lier le bouton radio de sorte que si un utilisateur sélectionne true, cette valeur est maintenue même après la validation côté serveur ?

Toute suggestion serait la bienvenue !

95voto

Glazed Points 865

Pour la réponse courte, vous devez faire trois choses :

  1. Retirer le new { Checked = "checked" } à partir du deuxième bouton radio. Cette valeur cochée codée en dur annulera toute la magie.
  2. Lorsque vous renvoyez votre ViewResult à partir de l'action du contrôleur, donnez-lui une instance de votre classe de modèle où TermsAndConditions est false. Cela fournira la valeur false par défaut dont vous avez besoin pour que le bouton radio false soit présélectionné pour vous.
  3. Utilice true y false comme valeurs pour vos boutons radio au lieu de "True" y "False" . Ceci est dû au fait que votre propriété est de type bool . Strictement parlant, vous avez choisi par coïncidence les représentations correctes des chaînes de caractères pour true y false mais le paramètre de valeur pour la méthode RadioButtonFor est de type object . Il est préférable de passer le type réel auquel vous voulez comparer plutôt que de le convertir vous-même en chaîne. Plus d'informations à ce sujet ci-dessous.

Voici ce qui se passe en profondeur :

Le framework veut faire tout cela pour vous automatiquement, mais vous avez fait ces deux premières choses incorrectement, ce qui vous oblige à vous battre avec le framework pour obtenir le comportement que vous voulez.

La méthode RadioButtonFor appelle .ToString() sur la valeur de la propriété que vous avez spécifiée et la compare à la propriété .ToString() de la valeur que vous avez transmise lors de la création du bouton radio. S'ils sont égaux, il définit en interne isChecked = true et finit par rendre checked="checked" dans le HTML. C'est ainsi qu'il décide du bouton radio à vérifier. Il compare simplement la valeur du bouton radio à la valeur de la propriété et vérifie celle qui correspond.

Vous pouvez rendre des boutons radio pour pratiquement n'importe quelle propriété de cette façon et cela fonctionnera comme par magie. Les chaînes de caractères, les ints et même les types d'enum fonctionnent tous ! Tout objet qui possède un ToString qui renvoie une chaîne représentant de manière unique la valeur de l'objet fonctionnera. Vous devez simplement vous assurer que vous paramétrez la valeur du bouton radio à une valeur que votre propriété pourrait avoir. La façon la plus simple de le faire est de passer la valeur elle-même, et non la représentation en chaîne de la valeur. Laissez le framework la convertir en chaîne pour vous.

(Puisque vous avez passé dans les représentations correctes de la chaîne de caractères de true y false alors ces valeurs fonctionneront tant que vous corrigerez vos deux erreurs réelles, mais il est toujours sage de transmettre les valeurs réelles et non leurs chaînes).

Votre première véritable erreur a été de coder en dur Checked = "checked" pour le bouton radio "Non". Cela annule ce que le framework essaie de faire pour vous et fait en sorte que ce bouton radio soit toujours coché.

Il est évident que vous voulez que le bouton radio "Non" soit présélectionné, mais vous devez le faire d'une manière qui soit compatible avec tout ce qui précède. Vous devez donner à la vue une instance de votre classe modèle où TermsAndConditions est défini à false, et la laisser "lier" cette instance aux boutons radio. Normalement, une action de contrôleur qui répond à la requête GET initiale d'une URL ne donne pas du tout à la vue une instance de la classe de modèle. En général, on se contente de return View(); . Cependant, puisque vous voulez qu'une valeur par défaut soit sélectionnée, vous devez fournir à la vue une instance de votre modèle dont la valeur de TermsAndConditions a été fixée à false.

Voici un code source illustrant tout cela :

Une sorte de classe de compte que vous avez probablement déjà. (Le modèle de votre vue) :

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

Quelques méthodes dans votre contrôleur :

//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 vue entière :

@model Account
@{
    ViewBag.Title = "Create Account";
}
@using ( Html.BeginForm() )
{
    <div>
        <span>Do you agree to the Terms and Conditions?</span>
        <br />
        @Html.RadioButtonFor( model => model.TermsAndConditions, true, new { id = "TermsAndConditions_true" } )
        <label for="TermsAndConditions_true">Yes</label>
        <br />
        @Html.RadioButtonFor( model => model.TermsAndConditions, false, new { id = "TermsAndConditions_false" } )
        <label for="TermsAndConditions_false">No</label>
        <br />
        @Html.ValidationMessage( "TermsAndConditionsAgreement" )
    </div>
    <div>
        <input id="CreateAccount" type="submit" name="submit" value="Create Account" />
    </div>
}

BONUS : Vous remarquerez que j'ai ajouté une petite fonctionnalité supplémentaire aux boutons radio. Plutôt que d'utiliser du texte brut pour les étiquettes des boutons radio, j'ai utilisé le langage HTML label avec l'élément for défini sur les ID de chaque bouton radio. Cela permet aux utilisateurs de cliquer sur l'étiquette pour sélectionner le bouton radio au lieu de devoir cliquer sur le bouton radio lui-même. Il s'agit d'un code HTML standard. Pour que cela fonctionne, j'ai dû définir des ID manuels sur les boutons radio, sinon ils auraient tous deux reçu le même ID, à savoir "TermsAndConditions", ce qui n'aurait pas fonctionné.

15voto

Michael Points 363

Il y a plusieurs choses que vous devez faire ici afin de vous assurer que la sélection de l'utilisateur est maintenue après la validation côté serveur.

a) Liez la propriété "checked" de chaque radio à votre modèle dans la vue, par exemple :

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) Pour définir la valeur initiale par défaut lors du premier affichage de la vue, initialiser le modèle renvoyé à la vue dans la requête GET (dans l'action du contrôleur), par exemple :

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

b) Assurez-vous que dans votre action de contrôleur [HttpPost], vous renvoyez le modèle lorsque la validation échoue, par exemple :

[HttpPost]
public ActionResult SomeForm(SomeModel model)
{
    if (!ModelState.IsValid)
        return View(model);

    // Do other stuff here
}

De cette façon, lorsque la vue est rendue dans la réponse après l'échec de la validation, elle aura l'état réel du modèle qui a été transmis (maintenant ainsi la sélection de l'utilisateur).

4voto

Ben M Points 41

Je ne peux pas vraiment dire puisque vous n'avez pas montré votre code, mais je soupçonne que si vous échouez sur la validation côté serveur, vous renvoyez simplement la vue brute. Lorsque cela échoue, vous devez remplir la vue avec le modèle qui a été soumis, comme si vous retourniez toute autre erreur de validation. Sinon, vous obtiendrez les valeurs par défaut du modèle (qui sera toujours false pour le booléen d'enregistrement).

Vous pourriez peut-être afficher votre code côté serveur ?

3voto

Clark Kent Points 5845

Je vous propose ici un autre exemple plus complexe.

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

Html

@model   UserProfileView

// Some other code

 <div class="form-group">
                    <label class="col-lg-2 control-label">Communication</label>
                    <div class="col-lg-10">
                        <div class="  col-xs-">
                            @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)
                            }
                            <label class="  control-label" for="@Model.UserCommunicationOption">I Prefer Email And SMS</label>
                        </div>
                        <div class=" col-xs-">
                            @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)
                            }
                            <label class=" control-label" for="@Model.UserCommunicationOption">I Prefer Email</label>
                        </div>
                        <div class="  col-xs-">
                            @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)
                            }

                            <label class=" control-label" for="@Model.UserCommunicationOption">@DLMModelEntities.Properties.Resource.IPreferSMS</label>
                        </div>
                    </div>
                </div>

Model

   [Required(ErrorMessageResourceName = "Communications", ErrorMessageResourceType = typeof(Resource))]
        [Display(Name = "Communications", ResourceType = typeof(DLMModelEntities.Properties.Resource))]
        public UserCommunicationOptions UserCommunicationOption { get; set; }

GET

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

POST

  [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 données

enter image description here

Page web

enter image description here

1voto

Shawn Points 19

J'ai eu un problème similaire et j'ai résolu le problème en définissant une valeur ViewData dans le contrôleur pour garder la trace de ce que l'utilisateur avait sélectionné.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X