Comment gérez-vous plusieurs boutons d'envoi dans ASP.NET MVC Framework?


734

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

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

Une idée de la façon de procéder dans ASP.NET Framework Beta? Tous les exemples que j'ai recherchés sur Google contiennent des boutons uniques.


6
Il convient de mentionner qu'à partir d' ASP.NET Core, il existe des solutions beaucoup plus faciles que celles répertoriées ici.
Steven Jeuris

Réponses:


629

Voici une solution basée sur des attributs pour la plupart du temps, propre au problème des boutons d'envoi multiples, basée principalement sur le message et les commentaires de 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: les pages Razor cherchent à fournir la même fonctionnalité prête à l'emploi. Pour un nouveau développement, il peut être préférable.


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

17
Excellente solution !! Beaucoup plus utiles que ces réponses les mieux notées
Sasha

11
Un problème avec cette approche est que si vous essayez return View(viewmodel)dans le cas où votre modèle contient des erreurs, il tentera de renvoyer une vue appelée Sendou selon le nom de votre argument.
Chaussure du

15
@Shoe - vient de trouver une chose similaire. Assurez-vous de spécifier explicitement le nom de la vue à renvoyer si vous utilisez cette méthode:return View("Index", viewModel)
ajbeaven

4
juste une information, nous devons ajouter system.Reflection pour MethodInfo
Vignesh Subramanian

469

Donnez un nom à vos boutons d'envoi, puis inspectez la valeur soumise 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(); %>

publication sur

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

}

ÉDITER:

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

Modifiez ensuite le code pour qu'il fonctionne comme:

<% 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());
}

28
dommage que vous dépendiez du texte affiché sur le bouton, c'est un peu délicat avec une interface utilisateur multilingue
Omu

3
Switch / case ne fonctionne qu'avec des constantes, la version localisée ne peut donc pas utiliser switch / case. Vous devez passer à if else ou à une autre méthode d'envoi.
mlibby

10
vous devez utiliser un <button type = "submit" au lieu de <input type, car la valeur d'un <button type n'est pas le texte;). Ensuite, vous pouvez avoir quelque chose comme ceci: <button name = "mySubmitButton" type = "submit" value = "keyValue"> YourButtonText </button>
J4N

4
Comment cela fonctionnerait-il en passant le modèle à l'action au lieu de simplement la valeur de soumission?
bizzehdee

2
Attention à ne pas nommer vos boutons "action" si vous utilisez jQuery. Il provoque un conflit au sein de la bibliothèque qui rompt l'URL de l'action.
HotN

121

Vous pouvez vérifier le nom dans l'action comme cela a été mentionné, mais vous pouvez vous demander si c'est une bonne conception. C'est une bonne idée de considérer la responsabilité de l'action et de ne pas trop coupler cette conception aux aspects de l'interface utilisateur comme les noms des boutons. Pensez donc à utiliser 2 formulaires et 2 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 plus, dans le cas de "Annuler", vous ne traitez généralement pas le formulaire et vous allez vers une nouvelle URL. Dans ce cas, vous n'avez pas du tout à soumettre le formulaire et vous avez juste besoin d'un lien:

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

53
C'est correct lorsque 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 sous une forme commune, Dylan Beattie est la solution. Existe-t-il une manière plus élégante de procéder?
zidane

3
Concernant la présentation visuelle, comment dans ce cas le bouton "Envoyer" à côté du bouton "Annuler"?
Kris-I

1
Dylan: Eh bien, pour un bouton d'annulation, vous n'avez pas du tout besoin de soumettre les données 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 le lierais pas à "submitButton" car c'est le nom d'un élément d'interface utilisateur.
Trevor de Koekkoek

1
@Kris: vous pouvez positionner vos boutons avec CSS et ils peuvent toujours résider dans 2 sections de formulaire différentes.
Trevor de Koekkoek

8
sérieusement? cela ne sent-il personne que moi?!

99

Eilon suggère que vous puissiez le faire comme ceci:

Si vous avez plusieurs boutons, vous pouvez les distinguer en donnant 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 votre méthode d'action de contrôleur, vous pouvez ajouter des paramètres nommés d'après les noms des balises d'entrée HTML:

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

Si une valeur est publiée dans l'un de ces paramètres, cela signifie que c'est le bouton qui a été cliqué. Le navigateur Web ne poster une valeur pour le un bouton qui a obtenu 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 dépend pas de la propriété value des boutons de soumission qui est plus susceptible de changer que les noms attribués et ne nécessite pas d'activer javascript

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


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

Comment feriez-vous si vous créez un appel ajax sur ce formulaire. Il semble que form.serialize () ne ramasse pas le nom du bouton de soumission.
mko

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

45

Je viens d'écrire un article à ce sujet: plusieurs boutons de soumission avec ASP.NET MVC :

Fondamentalement, 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 qu'il soit. Heureusement, ActionNameSelectorAttributecela ne me fait pas seulement spécifier le nom de l'action, mais je peux choisir si l'action actuelle correspond à la demande.

Il y a donc ma classe (btw 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 utiliser, définissez simplement un formulaire comme celui-ci:

<% 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 vous oblige pas à spécifier quoi que ce soit. De plus, le nom des boutons est traduit directement en noms de méthode. De plus (je n'ai pas essayé), ces actions devraient également fonctionner comme des actions normales, vous pouvez donc les publier directement.


1
Beau! Je pense que c'est la solution la plus élégante. Il élimine la valeur de la submitbalise de la considération, ce qui est idéal car il s'agit d'un attribut d'interface utilisateur pur qui ne devrait pas avoir d'incidence sur le flux de contrôle. Au lieu de cela, l' nameattribut unique de chaque submitbalise se traduit directement en une méthode d'action discrète sur votre contrôleur.
Kirk Woll

+1 Pour moi, c'est de loin la meilleure solution à ce problème. Depuis que je l'ai implémenté, je remarque que beaucoup de trafic passe par HttpParamActionAttribut mais par rapport à toutes les autres choses que Asp.Net MVC doit faire lors du traitement d'une demande, c'est totalement acceptable. Pour ne pirater que je dois faire est de mettre une «Action» vide nommée dans mon contrôleur pour empêcher Resharper de m'avertir que l'action «Action» n'existe pas. Merci beaucoup!
Samuel

J'ai passé en revue toutes les solutions et suis également d'accord pour dire que c'est une belle solution simple et élégante. Excellent bc il n'y a pas d'instructions conditionnelles et robuste où vous pouvez définir une nouvelle action de contrôleur lorsque vous avez un nouveau bouton. Appelé ma classe MultiButtonActionHandler FYI ;-)
ejhost

36

c'est court et suite:

Jeroen Dop a répondu

<input type="submit" name="submitbutton1" value="submit1" />
<input type="submit" name="submitbutton2" value="submit2" />

et faire comme ça dans le code derrière

 if( Request.Form["submitbutton1"] != null)
{
    // Code for function 1
}
else if(Request.Form["submitButton2"] != null )
{
       // code for function 2
}

Bonne chance.


Impressionnant. exactement ce que je faisais dans les formulaires Web. Cheers buddy
djack109

Beaucoup plus simple que la meilleure réponse! Merci!
pabben

35

Je suggère aux parties intéressées d' examiner la solution de Maarten Balliauw . Je pense que c'est très élégant.

Dans le cas où le lien disparaît, il utilise l' MultiButtonattribut appliqué à une action du contrôleur pour indiquer à quel clic de bouton cette action doit se rapporter.


C'est la solution que nous utilisons maintenant et c'est très soigné. Est-ce que c'est MVC 2 seulement?
Simon Keep

C'est beau! Je n'avais jamais vu ça avant! Bien que je convienne que vous souhaitiez peut-être repenser toute solution qui utilise plusieurs soumissions pour n'utiliser qu'un seul bouton, je suis dans un endroit où je suis paralysé et je dois le faire. Cette réponse aurait dû gagner!
Rikon

C'est une excellente solution. Très propre
Arnej65

J'ai essayé cette approche et je n'ai pas pu la faire fonctionner dans MVC3. Une variante du vote-getter n ° 1 a fonctionné pour moi.
Scott Lawrence

Court et doux .. mais pas pour mvc 3+
Piotr Kula

21

Vous devriez pouvoir nommer les boutons et leur donner une valeur; mappez ensuite ce nom comme argument à l'action. Vous pouvez également utiliser 2 liens d'action distincts ou 2 formulaires.


De loin la solution la plus propre et la plus simple que j'ai vue.
Jason Evans

13

Vous pourriez écrire:

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

Et puis dans la page, vérifiez si le nom == "Envoyer" ou le nom == "Annuler" ...


1
Bien que cela fonctionne, mais je pense que c'est une mauvaise pratique d'avoir deux éléments avec le même nom.
Péter

1
Ce n'est pas nécessairement faux. Cela dépend de la façon dont vous utilisez les entrées. Vous pouvez avoir plusieurs éléments avec le même nom et vous attendre à recevoir plusieurs données (c'est ainsi que fonctionnent les boutons radio et les cases à cocher). Mais oui, si vous utilisez cette méthode, c'est parce que vous la faites "mal" ... C'est pourquoi je mets "Vous pourriez" mais pas "Vous devriez": P
Ironicnet

12

Quelque chose que je n'aime pas dans ActionSelectName, c'est que IsValidName est appelé pour chaque méthode d'action dans le contrôleur; Je ne sais pas pourquoi cela fonctionne de cette façon. J'aime une solution où chaque bouton a un nom différent en fonction de ce qu'il fait, mais je n'aime pas le fait que vous devez avoir autant de paramètres dans la méthode d'action que de boutons dans le formulaire. J'ai créé une énumération pour tous les types de boutons:

public enum ButtonType
{
    Submit,
    Cancel,
    Delete
}

Au lieu d'ActionSelectName, j'utilise un ActionFilter:

public class MultipleButtonsEnumAttribute : ActionFilterAttribute
{
    public Type EnumType { get; set; }

    public MultipleButtonsEnumAttribute(Type enumType)
    {
        EnumType = enumType;
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        foreach (var key in filterContext.HttpContext.Request.Form.AllKeys)
        {
            if (Enum.IsDefined(EnumType, key))
            {
                var pDesc = filterContext.ActionDescriptor.GetParameters()
                    .FirstOrDefault(x => x.ParameterType == EnumType);
                filterContext.ActionParameters[pDesc.ParameterName] = Enum.Parse(EnumType, key);
                break;
            }
        }
    }
}

Le filtre trouvera le nom du bouton dans les données du formulaire et si le nom du bouton correspond à l'un des types de bouton définis dans l'énumération, il trouvera le paramètre ButtonType parmi les paramètres d'action:

[MultipleButtonsEnumAttribute(typeof(ButtonType))]
public ActionResult Manage(ButtonType buttonPressed, ManageViewModel model)
{
    if (button == ButtonType.Cancel)
    {
        return RedirectToAction("Index", "Home");
    }
    //and so on
    return View(model)
}

puis dans les vues, je peux utiliser:

<input type="submit" value="Button Cancel" name="@ButtonType.Cancel" />
<input type="submit" value="Button Submit" name="@ButtonType.Submit" />

11

Voici ce qui me convient le mieux:

<input type="submit" value="Delete" name="onDelete" />
<input type="submit" value="Save" name="onSave" />


public ActionResult Practice(MyModel model, string onSave, string onDelete)
{
    if (onDelete != null)
    {
        // Delete the object
        ...
        return EmptyResult();
    }

    // Save the object
    ...
    return EmptyResult();
}

J'obtiens une valeur nulle pour onDelete et onSave dans la méthode du contrôleur. Est-ce que tu sais pourquoi?
Diganta Kumar

L'un ou l'autre ne sera pas nul si vous cliquez sur le bouton correspondant. Sur quel bouton cliquez-vous pour obtenir la valeur nulle?
Sergey

11

J'ai également rencontré ce «problème» mais j'ai trouvé une solution plutôt logique en ajoutant l' nameattribut. Je ne me souvenais pas d'avoir eu ce problème dans d'autres langues.

http://www.w3.org/TR/html401/interact/forms.html#h-17.13.2

  • ...
  • Si un formulaire contient plusieurs boutons d'envoi, seul le bouton d'envoi activé réussit.
  • ...

Cela signifie que les valueattributs de code suivants peuvent être modifiés, localisés, internationalisés sans avoir besoin de vérifier le code supplémentaire des fichiers de ressources fortement typés ou des constantes.

<% Html.BeginForm("MyAction", "MyController", FormMethod.Post); %>
<input type="submit" name="send" value="Send" />
<input type="submit" name="cancel" value="Cancel" />
<input type="submit" name="draft" value="Save as draft" />
<% Html.EndForm(); %>`

Du côté de la réception, il vous suffira de vérifier si l'un de vos types d'envoi connus n'est pas null

public ActionResult YourAction(YourModel model) {

    if(Request["send"] != null) {

        // we got a send

    }else if(Request["cancel"]) {

        // we got a cancel, but would you really want to post data for this?

    }else if(Request["draft"]) {

        // we got a draft

    }

}

C'est la solution que nous avons choisi d'utiliser pour une application Web simple où nous voulions la fonctionnalité ASP.NET WebForms mais dans MVC.
BrandonG

10

Si vous n'avez pas de restrictions sur l'utilisation de HTML 5, vous pouvez utiliser la <button>balise avec formactionAttribute:

<form action="demo_form.asp" method="get">
   First name: <input type="text" name="fname" /><br />
   Last name: <input type="text" name="lname" /><br />
   <button type="submit">Submit</button><br />
   <button type="submit" formaction="demo_admin.asp">Submit as admin</button>
</form>

Référence: http://www.w3schools.com/html5/att_button_formaction.asp


9

Si votre navigateur prend en charge la formule d'attribut pour les boutons de saisie (IE 10+, vous n'êtes pas sûr des autres navigateurs), alors les éléments suivants devraient fonctionner:

@using (Html.BeginForm()){
    //put form inputs here

<input id="sendBtn" value="Send" type="submit" formaction="@Url.Action("Name Of Send Action")" />

<input id="cancelBtn" value="Cancel" type="submit" formaction="@Url.Action("Name of Cancel Action") />

}

Jetez un œil à ma réponse ci-dessous, elle ne repose pas sur un projet de spécifications. Votre réponse offre la possibilité d'avoir différentes URL d'action, ce que la mienne ne permet pas.
Tom Hofman

9

Il existe trois façons de résoudre le problème ci-dessus

  1. Manière HTML
  2. Jquery way
  3. Méthode "ActionNameSelectorAttribute"

Vous trouverez ci-dessous une vidéo qui résume les trois approches de manière démonstrative.

https://www.facebook.com/shivprasad.koirala/videos/vb.100002224977742/809335512483940

Manière HTML: -

De la manière HTML, nous devons créer deux formulaires et placer le bouton «Soumettre» à l'intérieur de chacun des formulaires. Et l'action de chaque formulaire pointera vers des actions différentes / respectives. Vous pouvez voir le code ci-dessous que le premier formulaire affiche sur «Action1» et le second formulaire sera affiché sur «Action2» en fonction du bouton «Soumettre» sur lequel vous avez cliqué.

<form action="Action1" method=post>
<input type=”submit name=”Submit1”/>
</form>

<form action="Action2" method=post>
<input type=”submit name=”Submit2”>
</form>

Voie Ajax: -

Si vous êtes un amoureux de l'Ajax, cette deuxième option vous exciterait davantage. De la manière Ajax, nous pouvons créer deux fonctions différentes "Fun1" et "Fun1", voir le code ci-dessous. Ces fonctions feront des appels Ajax en utilisant JQUERY ou tout autre framework. Chacune de ces fonctions est liée aux événements «OnClick» du bouton «Soumettre». Chacune de ces fonctions appelle les noms d'actions respectifs.

<Script language="javascript">
function Fun1()
{
$.post(“/Action1”,null,CallBack1);
}
function Fun2()
{
$.post(“/Action2”,null,CallBack2);
}
</Script>

<form action="/Action1" method=post>
<input type=submit name=sub1 onclick=”Fun2()”/>
</form>
<form action="/Action2" method=post>
<input type=submit name=sub2 onclick=”Fun1()”/>
</form>

Utilisation de "ActionNameSelectorAttribute": -

C'est une excellente option et propre. «ActionNameSelectorAttribute» est une classe d'attributs simple où nous pouvons écrire une logique de prise de décision qui décidera quelle action peut être exécutée.

Donc, la première chose est en HTML, nous devons mettre le nom correct sur les boutons de soumission pour les identifier sur le serveur.

Vous pouvez voir que nous avons mis «Enregistrer» et «Supprimer» dans les noms des boutons. Vous pouvez également remarquer dans l'action que nous venons de mettre le nom du contrôleur «Client» et non un nom d'action particulier. Nous nous attendons à ce que le nom de l'action soit décidé par «ActionNameSelectorAttribute».

<form action=”Customer method=post>
<input type=submit value="Save" name="Save" /> <br />
<input type=submit value="Delete" name="Delete"/>
</form>

Ainsi, lorsque le bouton d'envoi est cliqué, il frappe d'abord l'attribut "ActionNameSelector", puis en fonction de la soumission qui est déclenchée, il appelle l'action appropriée.

entrez la description de l'image ici

La première étape consiste donc à créer une classe qui hérite de la classe «ActionNameSelectorAttribute». Dans cette classe, nous avons créé une simple propriété "Nom".

Nous devons également remplacer la fonction "IsValidName" qui renvoie true ou flase. Cette fonction est l'endroit où nous écrivons la logique si une action doit être exécutée ou non. Donc, si cette fonction renvoie true, l'action est exécutée, sinon elle ne l'est pas.

public class SubmitButtonSelector : ActionNameSelectorAttribute
    {
        public string Name { get; set; }
        public override bool IsValidName(ControllerContext controllerContext, string actionName, System.Reflection.MethodInfo methodInfo)
        {
            // Try to find out if the name exists in the data sent from form
var value = controllerContext.Controller.ValueProvider.GetValue(Name);
            if (value != null)
            {
                return true;
            }
            return false;

        }
    }

Le cœur principal de la fonction ci-dessus est dans le code ci-dessous. La collection «ValueProvider» contient toutes les données qui ont été publiées à partir du formulaire. Il recherche donc d'abord la valeur "Nom" et s'il est trouvé dans la requête HTTP, il renvoie vrai ou bien il renvoie faux.

var value = controllerContext.Controller.ValueProvider.GetValue(Name);
if (value != null)
      {
        return true;
      }
      return false;

Cette classe d'attributs peut ensuite être décorée sur l'action respective et la valeur «Nom» respective peut être fournie. Donc, si la soumission frappe cette action et si le nom correspond au nom du bouton de soumission HTML, il exécute ensuite l'action autrement ou non.

public class CustomerController : Controller
{
        [SubmitButtonSelector(Name="Save")]
        public ActionResult Save()
        {
            return Content("Save Called");
        }
        [SubmitButtonSelector(Name = "Delete")]
        public ActionResult Delete()
        {
            return Content("Delete Called");
        }
}

7

David Findley écrit sur 3 options différentes que vous avez pour ce faire, sur son blog ASP.Net.

Lisez l'article plusieurs boutons dans le même formulaire pour voir ses solutions, ainsi que les avantages et les inconvénients de chacun. À mon humble avis, il fournit une solution très élégante qui utilise des attributs avec lesquels vous décorez votre action.


7

C'est la technique que j'utiliserais et je ne la vois pas encore ici. Le lien (publié par Saajid Ismail) qui inspire cette solution est http://weblogs.asp.net/dfindley/archive/2009/05/31/asp-net-mvc-multiple-buttons-in-the-same-form .aspx ). Il adapte la réponse de Dylan Beattie pour effectuer la localisation sans aucun problème.

Dans la vue:

<% Html.BeginForm("MyAction", "MyController", FormMethod.Post); %>
<button name="button" value="send"><%: Resources.Messages.Send %></button>
<button name="button" value="cancel"><%: Resources.Messages.Cancel %></button>
<% Html.EndForm(); %>

Dans le contrôleur:

public class MyController : Controller 
{
    public ActionResult MyAction(string button)
    {
         switch(button)
         {
             case "send":
                 this.DoSend();
                 break;
             case "cancel":
                 this.DoCancel();
                 break;
         }
    }
}

Ressemble à la solution fournie par Ironicnet.
Kris van der Mast

Certainement similaire, mais cela montre à la fois la localisation et le code du contrôleur, ce que je n'ai pas vu faire de cette façon dans ce fil. J'ai trouvé ce fil en cherchant comment le faire et j'ai voulu documenter ce que j'ai trouvé pour quelqu'un d'autre qui pourrait être dans le même bateau.
mlibby

1
En fait, ce n'est pas la même chose qu'Ironicnet au-delà de cela. Il utilise des <input>éléments. J'utilise <button>, ce qui est nécessaire pour faire la localisation sans avoir d'attributs de valeur variable.
mlibby

7

Ce script permet de spécifier un attribut action-formulaire de données qui fonctionnera comme l'attribut de formaction HTML5 dans tous les navigateurs (de manière discrète):

$(document).on('click', '[type="submit"][data-form-action]', function(event) {
    var $this = $(this),
    var formAction = $this.attr('data-form-action'),
    $form = $($this.closest('form'));
    $form.attr('action', formAction);             
});

Le formulaire contenant le bouton sera publié à l'URL spécifiée dans l'attribut action-formulaire de données:

<button type="submit" data-form-action="different/url">Submit</button>   

Cela nécessite jQuery 1.7. Pour les versions précédentes, vous devez utiliser à la live()place de on().


6

Voici une méthode d'extension que j'ai écrite pour gérer plusieurs boutons d'image et / ou de texte.

Voici le code HTML d'un bouton d'image:

<input id="btnJoin" name="Join" src="/content/images/buttons/btnJoin.png" 
       type="image">

ou pour un bouton d'envoi de texte:

<input type="submit" class="ui-button green" name="Submit_Join" value="Add to cart"  />
<input type="submit" class="ui-button red" name="Submit_Skip" value="Not today"  />

Voici la méthode d'extension avec laquelle vous appelez à partir du contrôleur form.GetSubmitButtonName(). Pour les boutons d'image, il recherche un paramètre de formulaire avec .x(qui indique qu'un bouton d'image a été cliqué) et extrait le nom. Pour les inputboutons normaux, il recherche un nom commençant par Submit_et extrait la commande par la suite. Parce que je résume la logique de détermination de la `` commande '', vous pouvez basculer entre les boutons image + texte sur le client sans changer le code côté serveur.

public static class FormCollectionExtensions
{
    public static string GetSubmitButtonName(this FormCollection formCollection)
    {
        return GetSubmitButtonName(formCollection, true);
    }

    public static string GetSubmitButtonName(this FormCollection formCollection, bool throwOnError)
    {
        var imageButton = formCollection.Keys.OfType<string>().Where(x => x.EndsWith(".x")).SingleOrDefault();
        var textButton = formCollection.Keys.OfType<string>().Where(x => x.StartsWith("Submit_")).SingleOrDefault();

        if (textButton != null)
        {
            return textButton.Substring("Submit_".Length);
        }

        // we got something like AddToCart.x
        if (imageButton != null)
        {
            return imageButton.Substring(0, imageButton.Length - 2);
        }

        if (throwOnError)
        {
            throw new ApplicationException("No button found");
        }
        else
        {
            return null;
        }
    }
}

Remarque: pour les boutons de texte, vous devez préfixer le nom avec Submit_. Je préfère cette façon, car cela signifie que vous pouvez modifier la valeur du texte (affichage) sans avoir à changer le code. Contrairement aux SELECTéléments, un INPUTbouton n'a qu'un «valeur» et aucun attribut «texte» distinct. Mes boutons disent des choses différentes dans des contextes différents - mais correspondent à la même «commande». Je préfère de loin extraire le nom de cette façon que d'avoir à coder == "Add to cart".


J'aime vérifier le nom comme alternative car vous ne pouvez pas toujours vérifier la valeur, par exemple. vous avez une liste d'articles et chacun a un bouton "Supprimer".
Max

6

Je n'ai pas assez de représentant pour commenter au bon endroit, mais j'ai passé toute la journée là-dessus, alors je veux partager.

Tout en essayant d'implémenter la solution "MultipleButtonAttribute" ValueProvider.GetValue(keyValue)reviendrait incorrectement null.

Il s'est avéré que je faisais référence à System.Web.MVC version 3.0 alors qu'il aurait dû être 4.0 (les autres assemblys sont 4.0). Je ne sais pas pourquoi mon projet n'a pas été mis à jour correctement et je n'ai eu aucun autre problème évident.

Donc, si votre ActionNameSelectorAttributene fonctionne pas ... vérifiez cela.


6

Je suis assez en retard pour la fête, mais voilà ... Mon implémentation emprunte à @mkozicki mais nécessite moins de chaînes codées en dur pour se tromper. Framework 4.5+ requis . Essentiellement, le nom de la méthode du contrôleur doit être la clé du routage.

Marquage . Le nom du bouton doit être saisi avec"action:[controllerMethodName]"

(notez l'utilisation de l' API nameof C # 6 , fournissant une référence spécifique au type du nom de la méthode de contrôleur que vous souhaitez invoquer.

<form>
    ... form fields ....
    <button name="action:@nameof(MyApp.Controllers.MyController.FundDeathStar)" type="submit" formmethod="post">Fund Death Star</button>
    <button name="action:@nameof(MyApp.Controllers.MyController.HireBoba)" type="submit" formmethod="post">Hire Boba Fett</button>
</form>

Contrôleur :

namespace MyApp.Controllers
{
    class MyController
    {    
        [SubmitActionToThisMethod]
        public async Task<ActionResult> FundDeathStar(ImperialModel model)
        {
            await TrainStormTroopers();
            return View();
        }

        [SubmitActionToThisMethod]
        public async Task<ActionResult> HireBoba(ImperialModel model)
        {
            await RepairSlave1();
            return View();
        }
    }
}

Attribut Magic . Remarquez l'utilisation de la CallerMemberNamebonté.

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class SubmitActionToThisMethodAttribute : ActionNameSelectorAttribute
{        
    public SubmitActionToThisMethodAttribute([CallerMemberName]string ControllerMethodName = "")
    {
        controllerMethod = ControllerMethodName;
        actionFormat = string.Concat(actionConstant, ":", controllerMethod);
    }
    const string actionConstant = "action";
    readonly string actionFormat;
    readonly string controllerMethod;

    public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
    {
        var isValidName = false;
        var value = controllerContext.Controller.ValueProvider.GetValue(actionFormat);

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

Bien que ce soit une bonne approche, vous ne pourrez pas utiliser le classeur de modèle mvc intégré car il utilise l'attribut "nom" des boutons.
ooXei1sh

Le but de cette solution est de contourner le routage de poste MVC. Veuillez décrire une amélioration.
Aaron Hudon

6
[HttpPost]
public ActionResult ConfirmMobile(string nameValueResend, string nameValueSubmit, RegisterModel model)
    {
        var button = nameValueResend ?? nameValueSubmit;
        if (button == "Resend")
        {

        }
        else
        {

        }
    }


    Razor file Content:
    @using (Html.BeginForm()
    {
        <div class="page registration-result-page">

            <div class="page-title">
                <h1> Confirm Mobile Number</h1>
            </div>

            <div class="result">
                @Html.EditorFor(model => model.VefificationCode)
                @Html.LabelFor(model => model.VefificationCode, new { })
                @Html.ValidationMessageFor(model => model.VefificationCode)
            </div>
            <div class="buttons">
                <button type="submit" class="btn" name="nameValueResend" value="Resend">
                    Resend
                </button>
                <button type="submit" class="btn" name="nameValueSubmit" value="Verify">
                    Submit
                </button>

            </div>
            </div>

    }

Ceci est un autre lien utile expliquant les différentes façons de publier un formulaire.
Himalaya Garg

5

J'ai essayé de faire une synthèse de toutes les solutions et j'ai créé un attribut [ButtenHandler] qui facilite la gestion de plusieurs boutons sur un formulaire.

Je l'ai décrit sur CodeProject Plusieurs boutons de formulaire paramétrés (localisables) dans ASP.NET MVC .

Pour gérer le cas simple de ce bouton:

<button type="submit" name="AddDepartment">Add Department</button>

Vous aurez quelque chose comme la méthode d'action suivante:

[ButtonHandler()]
public ActionResult AddDepartment(Company model)
{
    model.Departments.Add(new Department());
    return View(model);
}

Remarquez comment le nom du bouton correspond au nom de la méthode d'action. L'article décrit également comment avoir des boutons avec des valeurs et des boutons avec des index.


5
//model
    public class input_element
        {
         public string Btn { get; set; }
        }   

//views--submit btn can be input type also...
    @using (Html.BeginForm())
    {
            <button type="submit" name="btn" value="verify">
             Verify data</button>
            <button type="submit" name="btn" value="save">
             Save data</button>    
            <button type="submit" name="btn" value="redirect">
                 Redirect</button>
    }

//controller

    public ActionResult About()
        {
            ViewBag.Message = "Your app description page.";
            return View();
        }

        [HttpPost]
        public ActionResult About(input_element model)
        {
                if (model.Btn == "verify")
                {
                // the Verify button was clicked
                }
                else if (model.Btn == "save")
                {
                // the Save button was clicked
                } 
                else if (model.Btn == "redirect")
                {
                // the Redirect button was clicked
                } 
                return View();
        }

Vous ne l'avez peut-être pas réalisé, mais cette même réponse a déjà été postée à plusieurs reprises à cette question.
Andrew Barber

2
De plus, vous semblez publier des réponses qui ne contiennent que du code, sans explication. Envisageriez-vous d'ajouter un récit pour expliquer pourquoi le code fonctionne et ce qui en fait une réponse à la question? Ce serait très utile à la personne qui pose la question et à toute autre personne qui se présente.
Andrew Barber

2
Bien sûr, cela fonctionne bien. Mais cette réponse a déjà été donnée par d'autres , il y a longtemps. Et ils ont également expliqué pourquoi cela fonctionne.
Andrew Barber

5

c'est le meilleur moyen que j'ai trouvé:

http://iwayneo.blogspot.co.uk/2013/10/aspnet-mvc-action-selector-with-list.html

Voici le code:

    /// <summary>
    /// ActionMethodSelector to enable submit buttons to execute specific action methods.
    /// </summary>
    public class AcceptParameterAttribute : ActionMethodSelectorAttribute
   {
        /// <summary>
        /// Gets or sets the value to use to inject the index into
        /// </summary>
       public string TargetArgument { get; set; }

       /// <summary>
       /// Gets or sets the value to use in submit button to identify which method to select. This must be unique in each controller.
       /// </summary>
       public string Action { get; set; }

       /// <summary>
       /// Gets or sets the regular expression to match the action.
       /// </summary>
       public string ActionRegex { get; set; }

       /// <summary>
       /// Determines whether the action method selection is valid for the specified controller context.
       /// </summary>
       /// <param name="controllerContext">The controller context.</param>
       /// <param name="methodInfo">Information about the action method.</param>
       /// <returns>true if the action method selection is valid for the specified controller context; otherwise, false.</returns>
       public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
       {

           if (controllerContext == null)
           {
               throw new ArgumentNullException("controllerContext");
           }

           Func<NameValueCollection> formGetter;
           Func<NameValueCollection> queryStringGetter;

           ValidationUtility.GetUnvalidatedCollections(HttpContext.Current, out formGetter, out queryStringGetter);

           var form = formGetter();
           var queryString = queryStringGetter();

           var req = form.AllKeys.Any() ? form : queryString;

           if (!string.IsNullOrEmpty(this.ActionRegex))
           {
               foreach (var key in req.AllKeys.Where(k => k.StartsWith(Action, true, System.Threading.Thread.CurrentThread.CurrentCulture)))
               {
                   if (key.Contains(":"))
                   {
                       if (key.Split(':').Count() == this.ActionRegex.Split(':').Count())
                       {
                           bool match = false;
                           for (int i = 0; i < key.Split(':').Count(); i++)
                           {
                               if (Regex.IsMatch(key.Split(':')[0], this.ActionRegex.Split(':')[0]))
                               {
                                   match = true;
                               }
                               else
                               {
                                   match = false;
                                   break;
                               }
                           }

                           if (match)
                           {
                               return !string.IsNullOrEmpty(req[key]);
                           }
                       }
                   }
                   else
                   {
                       if (Regex.IsMatch(key, this.Action + this.ActionRegex))
                       {
                           return !string.IsNullOrEmpty(req[key]);
                       }
                   }

               }
               return false;
           }
           else
           {
               return req.AllKeys.Contains(this.Action);
           }
       }
   }

Profitez d'un futur bouton de soumission multiple sans odeur de code.

Merci


Lien actuellement rompu, il s'agit de la version archivée la plus proche que j'ai pu trouver: web.archive.org/web/20110706230408/http://blogs.sonatribe.com/…
Ian Kemp

Salut Ian - merci d'avoir creusé cela - je l'ai republié ici: iwayneo.blogspot.co.uk/2013/10/…

4

Version modifiée de la HttpParamActionAttributeméthode mais avec un correctif de bogue pour ne pas provoquer d'erreur sur les publications de session expirées / non valides. Pour voir s'il s'agit d'un problème avec votre site actuel, ouvrez le formulaire dans une fenêtre et juste avant de cliquer sur Saveou Publish, ouvrez une fenêtre en double et déconnectez-vous. Revenez maintenant à votre première fenêtre et essayez de soumettre votre formulaire en utilisant l'un ou l'autre bouton. Pour moi, j'ai eu une erreur, donc ce changement résout ce problème pour moi. J'omets un tas de trucs par souci de concision mais vous devriez avoir l'idée. Les éléments clés sont l'inclusion de ActionNamesur l'attribut et s'assurer que le nom transmis est le nom de la vue qui montre le formulaire

Classe d'attribut

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class HttpParamActionAttribute : ActionNameSelectorAttribute
{
    private readonly string actionName;

    public HttpParamActionAttribute(string actionName)
    {
        this.actionName = actionName;
    }

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

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

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

Manette

[Authorize(Roles="CanAddContent")]
public ActionResult CreateContent(Guid contentOwnerId)
{
    var viewModel = new ContentViewModel
    {
        ContentOwnerId = contentOwnerId
        //populate rest of view model
    }
    return View("CreateContent", viewModel);
}

[Authorize(Roles="CanAddContent"), HttpPost, HttpParamAction("CreateContent"), ValidateAntiForgeryToken]
public ActionResult SaveDraft(ContentFormModel model)
{
    //Save as draft
    return RedirectToAction("CreateContent");
}

[Authorize(Roles="CanAddContent"), HttpPost, HttpParamAction("CreateContent"), ValidateAntiForgeryToken]
public ActionResult Publish(ContentFormModel model)
{
    //publish content
    return RedirectToAction("CreateContent");
}

Vue

@using (Ajax.BeginForm("CreateContent", "MyController", new { contentOwnerId = Model.ContentOwnerId }))
{
    @Html.AntiForgeryToken()
    @Html.HiddenFor(x => x.ContentOwnerId)

    <!-- Rest of your form controls -->
    <input name="SaveDraft" type="submit" value="SaveDraft" />
    <input name="Publish" type="submit" value="Publish" />
}

3

Mon approche JQuery utilisant une méthode d'extension:

public static MvcHtmlString SubmitButtonFor<TController>(this HtmlHelper helper, Expression<Action<TController>> action, string value) where TController : Controller
{
    RouteValueDictionary routingValues = Microsoft.Web.Mvc.Internal.ExpressionHelper.GetRouteValuesFromExpression(action);

    var onclick = string.Format("$('form').first().attr('action', '/{0}')", string.Join("/", routingValues.Values.ToArray().Where(x => x != null).Select(x => x.ToString()).ToArray()));
    var html = "<input type=\"submit\" value=\"" + value + "\" onclick=\"" + onclick + "\" />";

    return MvcHtmlString.Create(html);
}

Vous pouvez l'utiliser comme ceci:

@(Html.SubmitButtonFor<FooController>(c => c.Save(null), "Save"))

Et ça rend comme ça:

<input type="submit" value="Save" onclick="$('form').first().attr('action', '/Foo/Save')" >

3

Pour chaque bouton d'envoi, ajoutez simplement:

$('#btnSelector').click(function () {

    $('form').attr('action', "/Your/Action/);
    $('form').submit();

});

3

Sur la base de la réponse de mkozicki, je trouve une solution un peu différente. J'utilise toujours ActionNameSelectorAttributeMais j'avais besoin de gérer deux boutons «Enregistrer» et «Sync». Ils font presque la même chose, donc je ne voulais pas avoir deux actions.

attribut :

public class MultipleButtonActionAttribute : ActionNameSelectorAttribute
{        
    private readonly List<string> AcceptedButtonNames;

    public MultipleButtonActionAttribute(params string[] acceptedButtonNames)
    {
        AcceptedButtonNames = acceptedButtonNames.ToList();
    }

    public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
    {            
        foreach (var acceptedButtonName in AcceptedButtonNames)
        {
            var button = controllerContext.Controller.ValueProvider.GetValue(acceptedButtonName);
            if (button == null)
            {
                continue;
            }                
            controllerContext.Controller.ControllerContext.RouteData.Values.Add("ButtonName", acceptedButtonName);
            return true;
        }
        return false;
    }
}

vue

<input type="submit" value="Save" name="Save" />
<input type="submit" value="Save and Sync" name="Sync" />

manette

 [MultipleButtonAction("Save", "Sync")]
 public ActionResult Sync(OrgSynchronizationEditModel model)
 {
     var btn = this.RouteData.Values["ButtonName"];

Je tiens également à souligner que si les actions font des choses différentes, je suivrais probablement mkozicki post.


1

J'ai créé une ActionButton méthode de HtmlHelper . Il générera un bouton d'entrée normal avec un peu de javascript dans l' événement OnClick qui soumettra le formulaire au contrôleur / action spécifié.

Vous utilisez l'aide comme ça

@Html.ActionButton("MyControllerName", "MyActionName", "button text")

cela générera le code HTML suivant

<input type="button" value="button text" onclick="this.form.action = '/MyWebsiteFolder/MyControllerName/MyActionName'; this.form.submit();">

Voici le code de la méthode d'extension:

VB.Net

<System.Runtime.CompilerServices.Extension()>
Function ActionButton(pHtml As HtmlHelper, pAction As String, pController As String, pRouteValues As Object, pBtnValue As String, pBtnName As String, pBtnID As String) As MvcHtmlString
    Dim urlHelperForActionLink As UrlHelper
    Dim btnTagBuilder As TagBuilder

    Dim actionLink As String
    Dim onClickEventJavascript As String

    urlHelperForActionLink = New UrlHelper(pHtml.ViewContext.RequestContext)
    If pController <> "" Then
        actionLink = urlHelperForActionLink.Action(pAction, pController, pRouteValues)
    Else
        actionLink = urlHelperForActionLink.Action(pAction, pRouteValues)
    End If
    onClickEventJavascript = "this.form.action = '" & actionLink & "'; this.form.submit();"

    btnTagBuilder = New TagBuilder("input")
    btnTagBuilder.MergeAttribute("type", "button")

    btnTagBuilder.MergeAttribute("onClick", onClickEventJavascript)

    If pBtnValue <> "" Then btnTagBuilder.MergeAttribute("value", pBtnValue)
    If pBtnName <> "" Then btnTagBuilder.MergeAttribute("name", pBtnName)
    If pBtnID <> "" Then btnTagBuilder.MergeAttribute("id", pBtnID)

    Return MvcHtmlString.Create(btnTagBuilder.ToString(TagRenderMode.Normal))
End Function

C # (le code C # est juste décompilé à partir de la DLL VB, donc il peut être embelli ... mais le temps est si court :-))

public static MvcHtmlString ActionButton(this HtmlHelper pHtml, string pAction, string pController, object pRouteValues, string pBtnValue, string pBtnName, string pBtnID)
{
    UrlHelper urlHelperForActionLink = new UrlHelper(pHtml.ViewContext.RequestContext);
    bool flag = Operators.CompareString(pController, "", true) != 0;
    string actionLink;
    if (flag)
    {
        actionLink = urlHelperForActionLink.Action(pAction, pController, System.Runtime.CompilerServices.RuntimeHelpers.GetObjectValue(pRouteValues));
    }
    else
    {
        actionLink = urlHelperForActionLink.Action(pAction, System.Runtime.CompilerServices.RuntimeHelpers.GetObjectValue(pRouteValues));
    }
    string onClickEventJavascript = "this.form.action = '" + actionLink + "'; this.form.submit();";
    TagBuilder btnTagBuilder = new TagBuilder("input");
    btnTagBuilder.MergeAttribute("type", "button");
    btnTagBuilder.MergeAttribute("onClick", onClickEventJavascript);
    flag = (Operators.CompareString(pBtnValue, "", true) != 0);
    if (flag)
    {
        btnTagBuilder.MergeAttribute("value", pBtnValue);
    }
    flag = (Operators.CompareString(pBtnName, "", true) != 0);
    if (flag)
    {
        btnTagBuilder.MergeAttribute("name", pBtnName);
    }
    flag = (Operators.CompareString(pBtnID, "", true) != 0);
    if (flag)
    {
        btnTagBuilder.MergeAttribute("id", pBtnID);
    }
    return MvcHtmlString.Create(btnTagBuilder.ToString(TagRenderMode.Normal));
}

Ces méthodes ont différents paramètres, mais pour la facilité d'utilisation, vous pouvez créer une surcharge qui ne prend que les paramètres dont vous avez besoin.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.