780 votes

Comment gérer les boutons d'envoi multiples dans ASP.NET MVC Framework ?

Existe-t-il un moyen simple de gérer plusieurs boutons d'envoi dans un même formulaire ? Par exemple :

<% Html.BeginForm("MyAction", "MyController", FormMethod.Post); %>
<input type="submit" value="Send" />
<input type="submit" value="Cancel" />
<% Html.EndForm(); %>

Avez-vous une idée de la manière de procéder dans ASP.NET Framework Beta ? Tous les exemples que j'ai trouvés sur Google comportent des boutons uniques.

9 votes

À noter à partir de ASP.NET Core il existe des solutions beaucoup plus simples que celles qui sont énumérées ici.

1 votes

667voto

mkozicki Points 1668

Voici une solution propre, basée sur les attributs, pour résoudre le problème des boutons d'envoi multiples, qui s'inspire largement de l'article et des commentaires de l'article suivant Maarten Balliauw .

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class MultipleButtonAttribute : ActionNameSelectorAttribute
{
    public string Name { get; set; }
    public string Argument { get; set; }

    public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
    {
        var isValidName = false;
        var keyValue = string.Format("{0}:{1}", Name, Argument);
        var value = controllerContext.Controller.ValueProvider.GetValue(keyValue);

        if (value != null)
        {
            controllerContext.Controller.ControllerContext.RouteData.Values[Name] = Argument;
            isValidName = true;
        }

        return isValidName;
    }
}

rasoir :

<form action="" method="post">
 <input type="submit" value="Save" name="action:Save" />
 <input type="submit" value="Cancel" name="action:Cancel" />
</form>

et contrôleur :

[HttpPost]
[MultipleButton(Name = "action", Argument = "Save")]
public ActionResult Save(MessageModel mm) { ... }

[HttpPost]
[MultipleButton(Name = "action", Argument = "Cancel")]
public ActionResult Cancel(MessageModel mm) { ... }

Mise à jour : Pages de rasoir semble offrir la même fonctionnalité dès la sortie de la boîte. Pour les nouveaux développements, il peut être préférable.

5 votes

J'ai trouvé que cette solution était le mariage heureux des autres techniques utilisées. Elle fonctionne parfaitement et n'affecte pas la localisation.

0 votes

À quoi sert la ligne "value = new ValueProviderResult(Argument, Argument, null) ;"? Elle semble redondante.

0 votes

J'essaie de mettre en œuvre ce système mais cela ne fonctionne pas. Il semble que l'attribut ne soit jamais activé. Il est directement envoyé à mon contrôleur d'index. Savez-vous pourquoi ?

487voto

Dylan Beattie Points 23222

Donnez un nom à vos boutons d'envoi, puis inspectez la valeur envoyée dans la méthode de votre contrôleur :

<% Html.BeginForm("MyAction", "MyController", FormMethod.Post); %>
<input type="submit" name="submitButton" value="Send" />
<input type="submit" name="submitButton" value="Cancel" />
<% Html.EndForm(); %>

l'affectation à

public class MyController : Controller {
    public ActionResult MyAction(string submitButton) {
        switch(submitButton) {
            case "Send":
                // delegate sending to another controller action
                return(Send());
            case "Cancel":
                // call another action to perform the cancellation
                return(Cancel());
            default:
                // If they've submitted the form without a submitButton, 
                // just return the view again.
                return(View());
        }
    }

    private ActionResult Cancel() {
        // process the cancellation request here.
        return(View("Cancelled"));
    }

    private ActionResult Send() {
        // perform the actual send operation here.
        return(View("SendConfirmed"));
    }

}

EDIT :

Pour étendre cette approche afin de travailler avec des sites localisés, isolez vos messages ailleurs (par exemple, en compilant un fichier de ressources vers une classe de ressources fortement typée).

Ensuite, modifiez le code pour qu'il fonctionne comme suit :

<% Html.BeginForm("MyAction", "MyController", FormMethod.Post); %>
<input type="submit" name="submitButton" value="<%= Html.Encode(Resources.Messages.Send)%>" />
<input type="submit" name="submitButton" value="<%=Html.Encode(Resources.Messages.Cancel)%>" />
<% Html.EndForm(); %>

et votre contrôleur devrait ressembler à ceci :

// Note that the localized resources aren't constants, so 
// we can't use a switch statement.

if (submitButton == Resources.Messages.Send) { 
    // delegate sending to another controller action
    return(Send());

} else if (submitButton == Resources.Messages.Cancel) {
     // call another action to perform the cancellation
     return(Cancel());
}

0 votes

C'est ce que je cherchais ici : stackoverflow.com/questions/649513/ - merci

28 votes

dommage que vous dépendiez du texte affiché sur le bouton, c'est un peu délicat avec une interface utilisateur multi-langue

0 votes

Omu - voir la dernière modification, qui explique (brièvement !) comment faire la même chose avec des ressources de chaînes/messages localisées.

124voto

Trevor de Koekkoek Points 2029

Vous pouvez vérifier le nom dans l'action comme cela a été mentionné, mais vous pouvez vous demander si cela est une bonne conception ou non. Il est bon de prendre en compte la responsabilité de l'action et de ne pas trop coupler cette conception à des aspects de l'interface utilisateur comme le nom des boutons. Envisagez donc d'utiliser deux formulaires et deux actions :

<% Html.BeginForm("Send", "MyController", FormMethod.Post); %>
<input type="submit" name="button" value="Send" />
<% Html.EndForm(); %>

<% Html.BeginForm("Cancel", "MyController", FormMethod.Post); %>
<input type="submit" name="button" value="Cancel" />
<% Html.EndForm(); %>

De même, dans le cas de "Cancel", il s'agit généralement de ne pas traiter le formulaire et d'aller vers une nouvelle URL. Dans ce cas, il n'est pas nécessaire de soumettre le formulaire et il suffit de créer un lien :

<%=Html.ActionLink("Cancel", "List", "MyController") %>

55 votes

Cela convient si vous n'avez pas besoin des mêmes données de formulaire pour chaque bouton d'envoi. Si vous avez besoin de toutes les données dans un formulaire commun, Dylan Beattie est la solution. Existe-t-il un moyen plus élégant de faire cela ?

3 votes

À propos de la présentation visuelle, comment, dans ce cas, faire figurer le bouton "Envoyer" à côté du bouton "Annuler" ?

1 votes

Dylan : Eh bien, pour un bouton d'annulation, vous n'avez pas besoin de soumettre les données du tout et c'est une mauvaise pratique de coupler le contrôleur aux éléments de l'interface utilisateur. Cependant, si vous pouvez créer une "commande" plus ou moins générique, je pense que c'est correct, mais je ne la lierais pas à "submitButton" car c'est le nom d'un élément UI.

107voto

PabloBlamirez Points 969

Eilon suggère que vous pouvez faire comme ceci :

Si vous avez plus d'un bouton, vous pouvez les distinguer en donnant un nom un nom à chaque bouton :

<input type="submit" name="SaveButton" value="Save data" />
<input type="submit" name="CancelButton" value="Cancel and go back to main page" />

Dans la méthode d'action de votre contrôleur, vous pouvez ajouter des paramètres nommés d'après les noms des balises noms des balises d'entrée HTML :

public ActionResult DoSomeStuff(string saveButton, string
cancelButton, ... other parameters ...)
{ ... }

Si une valeur est affichée dans l'un de ces paramètres, cela signifie que ce est celui qui a été cliqué. Le navigateur web n'affichera une valeur pour le un qui a été cliqué. Toutes les autres valeurs seront nulles.

if (saveButton != null) { /* do save logic */ }
if (cancelButton != null) { /* do cancel logic */ }

J'aime cette méthode car elle ne repose pas sur la propriété valeur des boutons d'envoi, qui est plus susceptible de changer que les noms attribués, et ne nécessite pas l'activation de javascript.

Voir : http://forums.asp.net/p/1369617/2865166.aspx#2865166

2 votes

Si quelqu'un tombe sur cette vieille question, c'est la réponse la plus propre si vous ne voulez pas utiliser les éléments HTML5 <button>. Si le HTML5 ne vous dérange pas, utilisez <button>avec l'attribut value.

0 votes

Comment faire si l'on crée un appel ajax sur ce formulaire. Il semble que form.serialize() ne récupère pas le nom du bouton submit

0 votes

@Kugel a raison, c'est toujours la réponse la plus propre. Merci

46voto

Andrey Shchekin Points 7740

Je viens d'écrire un billet à ce sujet : Boutons d'envoi multiples avec ASP.NET MVC :

En gros, au lieu d'utiliser ActionMethodSelectorAttribute J'utilise ActionNameSelectorAttribute ce qui me permet de prétendre que le nom de l'action est celui que je veux. Heureusement, ActionNameSelectorAttribute ne me fait pas seulement spécifier le nom de l'action, mais je peux choisir si l'action actuelle correspond à la demande.

Voilà donc ma classe (d'ailleurs je n'aime pas trop le nom) :

public class HttpParamActionAttribute : ActionNameSelectorAttribute {
    public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo) {
        if (actionName.Equals(methodInfo.Name, StringComparison.InvariantCultureIgnoreCase))
            return true;

        if (!actionName.Equals("Action", StringComparison.InvariantCultureIgnoreCase))
            return false;

        var request = controllerContext.RequestContext.HttpContext.Request;
        return request[methodInfo.Name] != null;
    }
} 

Pour l'utiliser, il suffit de définir un formulaire comme ceci :

<% using (Html.BeginForm("Action", "Post")) { %>
  <!— …form fields… -->
  <input type="submit" name="saveDraft" value="Save Draft" />
  <input type="submit" name="publish" value="Publish" />
<% } %> 

et contrôleur avec deux méthodes

public class PostController : Controller {
    [HttpParamAction]
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult SaveDraft(…) {
        //…
    }

    [HttpParamAction]
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Publish(…) {
        //…
    } 
}

Comme vous le voyez, l'attribut ne nécessite pas de spécifier quoi que ce soit. De plus, les noms des boutons sont directement traduits en noms de méthodes. En outre (je n'ai pas essayé), ces boutons devraient également fonctionner comme des actions normales, de sorte que vous pouvez poster à l'un d'eux directement.

1 votes

Magnifique ! Je pense que c'est la solution la plus élégante. Elle élimine le valeur de la submit Ce qui est idéal puisqu'il s'agit d'un attribut de l'interface utilisateur pure qui ne devrait avoir aucune incidence sur le flux de contrôle. Au lieu de cela, l'unique name de chaque submit se traduit directement par une méthode d'action discrète sur votre contrôleur.

0 votes

+1 Pour moi, c'est de loin la meilleure solution à ce problème. Depuis que je l'ai implémentée, je remarque que beaucoup de trafic passe par l'attribut HttpParamActionAttribut mais comparé à toutes les autres choses qu'Asp.Net MVC doit faire pendant le traitement d'une requête, c'est totalement acceptable. La seule chose que je dois faire est de mettre une 'Action' vide dans mon contrôleur pour éviter que Resharper ne me prévienne que l'action 'Action' n'existe pas. Merci beaucoup !

0 votes

J'ai examiné toutes les solutions et je suis également d'accord pour dire qu'il s'agit d'une solution simple et élégante. Elle est géniale parce qu'il n'y a pas d'instructions conditionnelles et qu'elle est robuste car elle permet de définir une nouvelle action de contrôleur lorsque vous avez un nouveau bouton. J'ai appelé ma classe MultiButtonActionHandler pour info ;-)

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