Come gestite più pulsanti di invio in ASP.NET MVC Framework?


734

C'è un modo semplice per gestire più pulsanti di invio dallo stesso modulo? Per esempio:

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

Qualche idea su come eseguire questa operazione in ASP.NET Framework Beta? Tutti gli esempi per cui ho cercato su Google hanno singoli pulsanti.


6
Vale la pena ricordare che a partire da ASP.NET Core ci sono soluzioni molto più facili rispetto a quelle elencate qui.
Steven Jeuris,

Risposte:


629

Ecco una soluzione per lo più pulita, basata su attributi, al problema del pulsante di invio multiplo basato pesantemente sul post e sui commenti di 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;
    }
}

rasoio:

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

e controller:

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

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

Aggiornamento: le pagine Razor sembrano fornire la stessa funzionalità pronta all'uso. Per i nuovi sviluppi, potrebbe essere preferibile.


3
Ho trovato questa soluzione il felice matrimonio delle altre tecniche utilizzate. Funziona perfettamente e non influisce sulla localizzazione.
trevorc,

17
Ottima soluzione !! Molto utile di quelle risposte migliori
Sasha,

11
Un problema con questo approccio è che se si tenta di return View(viewmodel)nel caso in cui il modello presenti errori, tenterà di restituire una vista chiamata Sendo in base al nome dell'argomento.
Scarpa,

15
@Shoe - ho appena trovato una cosa simile. Assicurati di specificare esplicitamente il nome della vista da restituire se stai usando questo metodo:return View("Index", viewModel)
ajbeaven

4
solo un'informazione, dobbiamo aggiungere il sistema. Riflessione per MethodInfo
Vignesh Subramanian

469

Assegna un nome ai pulsanti di invio, quindi controlla il valore inviato nel metodo del controller:

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

inviando a

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

}

MODIFICARE:

Per estendere questo approccio al lavoro con siti localizzati, isolare i messaggi altrove (ad esempio compilando un file di risorse in una classe di risorse fortemente tipizzata)

Quindi modificare il codice in modo che funzioni come:

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

e il tuo controller dovrebbe apparire così:

// 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
peccato che tu dipenda dal testo visualizzato sul pulsante, è un po 'complicato con un'interfaccia utente multilingue
Omu

3
Switch / case funziona solo con costanti, quindi la versione localizzata non può usare switch / case. Devi passare a if else o qualche altro metodo di spedizione.
mlibby

10
dovresti usare un <button type = "submit" invece di <input type, perché il valore di <button type non è il testo;). Quindi puoi avere qualcosa del genere: <button name = "mySubmitButton" type = "submit" value = "keyValue"> YourButtonText </button>
J4N

4
Come funzionerebbe con il passaggio del modello all'azione anziché solo il valore di invio?
Bizzehdee,

2
Fai attenzione a non nominare i pulsanti "azione" se stai usando jQuery. Causa un conflitto all'interno della libreria che interrompe l'URL dell'azione.
HotN,

121

Puoi controllare il nome nell'azione come è stato menzionato, ma potresti considerare se questo è un buon design. È una buona idea considerare la responsabilità dell'azione e non associare troppo questo design agli aspetti dell'interfaccia utente come i nomi dei pulsanti. Quindi considera l'utilizzo di 2 moduli e 2 azioni:

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

Inoltre, nel caso di "Annulla", di solito non stai elaborando il modulo e stai andando a un nuovo URL. In questo caso non è necessario inviare il modulo e basta un link:

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

53
Questo va bene quando non hai bisogno degli stessi dati del modulo per ogni pulsante di invio. Se hai bisogno di tutti i dati in forma comune di Dylan Beattie è la strada da percorrere. C'è un modo più elegante per farlo?
zidane,

3
A proposito della presentazione visiva, come in questo caso il pulsante "Invia" accanto al pulsante "Annulla"?
Kris-I

1
Dylan: Beh, per un pulsante di annullamento non è necessario inviare i dati ed è una buona pratica associare il controller agli elementi dell'interfaccia utente. Tuttavia, se riesci a fare un "comando" più o meno generico, penso che sia ok, ma non lo legherei a "submitButton" in quanto quello è il nome di un elemento dell'interfaccia utente.
Trevor de Koekkoek,

1
@Kris: puoi posizionare i tuoi pulsanti con CSS e possono comunque risiedere in 2 diverse sezioni del modulo.
Trevor de Koekkoek,

8
sul serio? non ha odore per nessuno tranne me ?!

99

Eilon suggerisce che puoi farlo in questo modo:

Se hai più di un pulsante, puoi distinguerli assegnando un nome a ciascun pulsante:

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

Nel metodo di azione del controller è possibile aggiungere parametri che prendono il nome dai nomi dei tag di input HTML:

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

Se un valore viene registrato in uno di questi parametri, significa che quel pulsante è stato quello su cui è stato fatto clic. Il browser web solo inserire un valore per l'uno tasto che ha ottenuto cliccato. Tutti gli altri valori saranno nulli.

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

Mi piace questo metodo in quanto non si basa sulla proprietà value dei pulsanti di invio che è più probabile che cambi rispetto ai nomi assegnati e non richiede che javascript sia abilitato

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


2
Se qualcuno incontra questa vecchia domanda, questa è la risposta più pulita se non vuoi usare elementi <button> HTML5. Se non ti dispiace HTML5, usa <button> con l'attributo value.
Kugel,

Come lo faresti se crei una chiamata Ajax su questo modulo. Sembra che form.serialize () non raccolga il nome del pulsante di invio.
mko,

@Kugel ha ragione, questa è ancora la risposta più pulita. Grazie
Arif YILMAZ il

45

Ho appena scritto un post su questo: più pulsanti di invio con ASP.NET MVC :

Fondamentalmente, invece di usare ActionMethodSelectorAttribute, sto usando ActionNameSelectorAttribute, il che mi permette di fingere che il nome dell'azione sia quello che voglio che sia. Per fortuna,ActionNameSelectorAttribute non mi fa solo specificare il nome dell'azione, ma posso scegliere se l'azione corrente corrisponde alla richiesta.

Quindi c'è la mia classe (tra l'altro non mi piace troppo il nome):

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

Per usare basta definire un modulo come questo:

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

e controller con due metodi

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

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

Come vedi, l'attributo non richiede di specificare nulla. Inoltre, il nome dei pulsanti viene tradotto direttamente nei nomi dei metodi. Inoltre (non l'ho provato) dovrebbero funzionare anche come normali azioni, quindi puoi pubblicare direttamente su uno di essi.


1
Bellissimo! Penso che questa sia la soluzione più elegante. Elimina il valore del submittag dalla considerazione, il che è l'ideale poiché si tratta di un attributo dell'interfaccia utente pura che non dovrebbe influire sul flusso di controllo. Invece, l' nameattributo univoco di ciascun submittag si traduce direttamente in un metodo di azione discreto sul controller.
Kirk Woll,

+1 Per me, è di gran lunga la soluzione migliore per questo problema. Da quando l'ho implementato, noto che molto traffico passa attraverso HttpParamActionAttribut ma rispetto a tutte le altre cose che Asp.Net MVC deve fare durante l'elaborazione di una richiesta, è del tutto accettabile. L'hacking che devo solo fare è mettere una "Azione" vuota denominata nel mio controller per impedire a Resharper di avvisarmi che l'azione "Azione" non esiste. Grazie mille!
Samuel,

Ho esaminato tutte le soluzioni e sono anche d'accordo che questa è una soluzione semplice, elegante e piacevole. Ottimo bc non ci sono dichiarazioni condizionali e robuste in cui è possibile definire una nuova azione del controller quando si dispone di un nuovo pulsante. Ho chiamato la mia classe MultiButtonActionHandler FYI ;-)
ejhost

36

è breve e suite:

Fu risposto da Jeroen Dop

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

e fare così in code behinde

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

In bocca al lupo.


Eccezionale. esattamente quello che facevo nei webform.
Amico

Molto più semplice della risposta migliore! Grazie!
pabben,

35

Vorrei suggerire alle parti interessate di dare un'occhiata alla soluzione di Maarten Balliauw . Penso che sia molto elegante.

Nel caso in cui il link scompaia, sta usando l' MultiButtonattributo applicato a un'azione del controller per indicare a quale pulsante fare clic a cui deve fare riferimento l'azione.


Questa è la soluzione che stiamo usando ora ed è molto pulita. È MVC 2 solo se?
Simon Keep,

Questo è bellissimo! Non l'avevo mai visto prima! Mentre sono d'accordo che potresti voler riprogettare qualsiasi soluzione che utilizza più invii per utilizzare solo un pulsante, sono in un punto in cui sono bloccato e devo farlo. Questa risposta avrebbe dovuto vincere!
Rikon,

Questa è un'ottima soluzione Molto pulito
Arnej65,

Ho provato questo approccio e non sono riuscito a farlo funzionare in MVC3. Una variante del voto n ° 1 ha funzionato per me.
Scott Lawrence,

Breve e dolce .. ma non per mvc 3+
Piotr Kula

21

Dovresti essere in grado di nominare i pulsanti e dare loro un valore; quindi associare questo nome come argomento all'azione. In alternativa, utilizzare 2 link azioni separati o 2 moduli.


Di gran lunga la soluzione più pulita e più semplice che abbia mai visto.
Jason Evans,

13

Puoi scrivere:

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

E poi nella pagina controlla se il nome == "Invia" o il nome == "Annulla" ...


1
Anche se funziona, ma penso che sia una pratica sbagliata avere due elementi con lo stesso nome.
Péter,

1
Non è necessario sbagliato. Dipende da come stai usando gli input. Puoi avere più elementi con lo stesso nome e aspettarti di ricevere più dati (è così che funzionano i pulsanti radio e le caselle di controllo). Ma sì, se stai usando questo metodo è perché lo stai facendo "sbagliato" ... Ecco perché ho messo "Potresti" ma non "Dovresti": P
Ironicnet,

12

Qualcosa che non mi piace di ActionSelectName è che IsValidName viene chiamato per ogni metodo di azione nel controller; Non so perché funzioni in questo modo. Mi piace una soluzione in cui ogni pulsante ha un nome diverso in base a ciò che fa, ma non mi piace il fatto che devi avere tanti parametri nel metodo di azione come i pulsanti nel modulo. Ho creato un enum per tutti i tipi di pulsanti:

public enum ButtonType
{
    Submit,
    Cancel,
    Delete
}

Invece di ActionSelectName, utilizzo 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;
            }
        }
    }
}

Il filtro troverà il nome del pulsante nei dati del modulo e se il nome del pulsante corrisponde a uno dei tipi di pulsante definiti nell'enum, troverà il parametro ButtonType tra i parametri di azione:

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

e quindi nelle viste, posso usare:

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

11

Ecco cosa funziona meglio per me:

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

Ottengo un valore nullo per onDelete e onSave nel metodo del controller. Sai perché?
Diganta Kumar,

Nessuno dei due sarà nullo se si fa clic sul pulsante corrispondente. Quale pulsante fai clic per ottenere il null?
Sergey,

11

Ho riscontrato anche questo "problema", ma ho trovato una soluzione piuttosto logica aggiungendo l' nameattributo. Non ricordo di aver avuto questo problema in altre lingue.

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

  • ...
  • Se un modulo contiene più di un pulsante di invio, solo il pulsante di invio attivato ha esito positivo.
  • ...

Ciò significa che i seguenti valueattributi di codice possono essere modificati, localizzati, internazionalizzati senza la necessità di un codice aggiuntivo per il controllo di file di risorse o costanti fortemente tipizzati.

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

Per quanto riguarda la ricezione, dovresti solo verificare se uno dei tuoi tipi di invio noti non lo è 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

    }

}

Questa è la soluzione che abbiamo scelto di utilizzare per una semplice app Web in cui volevamo funzionalità ASP.NET WebForms ma all'interno di MVC.
BrandonG

10

Se non si hanno restrizioni sull'uso di HTML 5, è possibile utilizzare il <button>tag con formactionAttributo:

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

Riferimento: http://www.w3schools.com/html5/att_button_formaction.asp


9

Se il tuo browser supporta la formulazione degli attributi per i pulsanti di input (IE 10+, non sono sicuro di altri browser), dovrebbe funzionare quanto segue:

@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") />

}

Dai un'occhiata alla mia risposta di seguito, non si basa su bozze di specifiche. La tua risposta consente la possibilità di avere diversi URL di azione, cosa che la mia no.
Tom Hofman,

9

Esistono tre modi per risolvere il problema sopra riportato

  1. Modo HTML
  2. Modo Jquery
  3. Modo "ActionNameSelectorAttribute"

Di seguito è riportato un video che riassume in modo dimostrativo tutti e tre gli approcci.

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

Modo HTML: -

In modo HTML dobbiamo creare due moduli e posizionare il pulsante "Invia" all'interno di ciascuno dei moduli. E l'azione di ogni modulo punterà ad azioni diverse / rispettive. Puoi vedere il codice qui sotto che il primo modulo sta postando su "Action1" e il secondo modulo pubblicherà su "Action2" a seconda del pulsante "Invia".

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

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

Modo Ajax: -

Se sei un amante dell'Ajax, questa seconda opzione ti entusiasmerebbe di più. Nel modo Ajax possiamo creare due diverse funzioni "Fun1" e "Fun1", vedere il codice seguente. Queste funzioni effettueranno chiamate Ajax utilizzando JQUERY o qualsiasi altro framework. Ognuna di queste funzioni è associata agli eventi "OnClick" del pulsante "Invia". Ognuna di queste funzioni chiama i rispettivi nomi di azione.

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

Utilizzando "ActionNameSelectorAttribute": -

Questa è un'ottima e pulita opzione. "ActionNameSelectorAttribute" è una semplice classe di attributi in cui possiamo scrivere una logica decisionale che deciderà quale azione può essere eseguita.

Quindi la prima cosa è in HTML che dobbiamo inserire il nome proprio nei pulsanti di invio per identificarli sul server.

Puoi vedere che abbiamo messo "Salva" e "Elimina" per i nomi dei pulsanti. Inoltre puoi notare nell'azione che abbiamo appena inserito il nome del controller "Cliente" e non un nome di azione particolare. Prevediamo che il nome dell'azione sarà deciso da "ActionNameSelectorAttribute".

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

Pertanto, quando si fa clic sul pulsante di invio, viene innanzitutto colpito l'attributo "ActionNameSelector" e quindi, a seconda di quale invio viene attivato, viene richiamata l'azione appropriata.

inserisci qui la descrizione dell'immagine

Quindi il primo passo è creare una classe che eredita dalla classe "ActionNameSelectorAttribute". In questa classe abbiamo creato una semplice proprietà "Nome".

Dobbiamo inoltre sostituire la funzione "IsValidName" che restituisce true o flase. Questa funzione è dove scriviamo la logica se un'azione deve essere eseguita o meno. Quindi se questa funzione ritorna vera allora l'azione viene eseguita altrimenti non lo è.

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;

        }
    }

Il cuore principale della funzione sopra è nel codice qui sotto. La raccolta "ValueProvider" contiene tutti i dati che sono stati pubblicati dal modulo. Quindi cerca prima il valore "Nome" e se si trova nella richiesta HTTP restituisce vero oppure restituisce falso.

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

Questa classe di attributi può quindi essere decorata sulla rispettiva azione e può essere fornito il rispettivo valore "Nome". Quindi, se l'invio sta colpendo questa azione e se il nome corrisponde al nome del pulsante di invio HTML, allora esegue ulteriormente l'azione oppure no.

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 scrive su 3 diverse opzioni che hai per farlo, sul suo blog ASP.Net.

Leggi l'articolo più pulsanti nello stesso modulo per vedere le sue soluzioni, i vantaggi e gli svantaggi di ciascuno. IMHO fornisce una soluzione molto elegante che sfrutta gli attributi con cui decori la tua azione.


7

Questa è la tecnica che userei e non la vedo ancora qui. Il link (pubblicato da Saajid Ismail) che ispira questa soluzione è http://weblogs.asp.net/dfindley/archive/2009/05/31/asp-net-mvc-multiple-buttons-in-the-same-form .aspx ). Adatta la risposta di Dylan Beattie per eseguire la localizzazione senza problemi.

Nella vista:

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

Nel controller:

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

Sembra la soluzione fornita da Ironicnet.
Kris van der Mast,

Certamente simile, ma questo mostra sia la localizzazione che il codice del controller, cosa che non ho visto fare in questo thread. Ho trovato questo thread mentre cercavo come farlo e volevo documentare quello che mi è venuto in mente per chiunque potesse trovarsi nella stessa barca.
mlibby,

1
In realtà, non è lo stesso di Ironicnet oltre a questo. Usa gli <input>elementi. Io uso <button>, che è necessario per eseguire la localizzazione senza avere attributi di valore variabile.
mlibby,

7

Questo script consente di specificare un attributo data-form-action che funzionerà come attributo di formulazione HTML5 in tutti i browser (in modo discreto):

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

Il modulo contenente il pulsante verrà pubblicato nell'URL specificato nell'attributo data-form-action:

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

Ciò richiede jQuery 1.7. Per le versioni precedenti è necessario utilizzare live()invece di on().


6

Ecco un metodo di estensione che ho scritto per gestire più pulsanti immagine e / o testo.

Ecco il codice HTML per un pulsante immagine:

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

o per un pulsante di invio testo:

<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"  />

Ecco il metodo di estensione che chiami dal controller con form.GetSubmitButtonName(). Per i pulsanti immagine cerca un parametro del modulo con .x(che indica che è stato fatto clic su un pulsante immagine) ed estrae il nome. Per i inputpulsanti regolari cerca un nome che inizia con Submit_ed estrae il comando da allora in poi. Poiché sto astrarre la logica di determinare il 'comando', puoi passare tra i pulsanti immagine + testo sul client senza modificare il codice lato server.

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

Nota: per i pulsanti di testo è necessario aggiungere il prefisso al nome Submit_. Preferisco così perché significa che puoi cambiare il valore del testo (display) senza cambiare il codice. A differenza degli SELECTelementi, un INPUTpulsante ha solo un 'valore' e nessun attributo 'testo' separato. I miei pulsanti dicono cose diverse in contesti diversi, ma mappano allo stesso "comando". Preferisco di gran lunga estrarre il nome in questo modo piuttosto che dover codificare == "Add to cart".


Mi piace controllare il nome come alternativa perché non è sempre possibile verificare il valore per es. hai un elenco di elementi e ognuno ha un pulsante "Elimina".
Max

6

Non ho abbastanza rappresentante per commentare nel posto giusto, ma ho trascorso tutto il giorno su questo, quindi voglio condividere.

Durante il tentativo di implementare la soluzione "MultipleButtonAttribute" ValueProvider.GetValue(keyValue)non viene restituito correttamente null.

Si è scoperto che stavo facendo riferimento a System.Web.MVC versione 3.0 quando avrebbe dovuto essere 4.0 (altri assembly sono 4.0). Non so perché il mio progetto non sia stato aggiornato correttamente e non ho avuto altri ovvi problemi.

Quindi se il tuo ActionNameSelectorAttributenon funziona ... controlla quello.


6

Sono in ritardo alla festa, ma qui va ... La mia implementazione prende in prestito da @mkozicki ma richiede meno stringhe codificate per sbagliare. Framework 4.5+ richiesto . In sostanza, il nome del metodo del controller dovrebbe essere la chiave del routing.

Markup . È necessario digitare il nome del pulsante"action:[controllerMethodName]"

(notare l'uso dell'API name # C # 6 , che fornisce un riferimento specifico al tipo del nome del metodo del controller che si desidera richiamare.

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

Controller :

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

Attributo magico . Nota l'uso della 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;
    }
}

Anche se questo è un buon approccio, non sarai in grado di usare il raccoglitore di modelli mvc incorporato perché utilizza l'attributo "name" dei pulsanti.
ooXei1sh,

Lo scopo di questa soluzione è di aggirare il routing post MVC. Si prega di descrivere un miglioramento.
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>

    }

Questo è un altro link utile che spiega i diversi modi di inviare il modulo.
Himalaya Garg,

5

Ho provato a fare una sintesi di tutte le soluzioni e ho creato un attributo [ButtenHandler] che semplifica la gestione di più pulsanti in un modulo.

L'ho descritto su CodeProject Più pulsanti di modulo parametrizzati (localizzabili) in ASP.NET MVC .

Per gestire il semplice caso di questo pulsante:

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

Avrai qualcosa come il seguente metodo di azione:

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

Notare come il nome del pulsante corrisponde al nome del metodo di azione. L'articolo descrive anche come avere pulsanti con valori e pulsanti con indici.


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

Potresti non averlo capito, ma questa stessa risposta è stata già pubblicata parecchie volte a questa domanda.
Andrew Barber,

2
Inoltre, sembra che pubblichi risposte che contengono solo codice, senza spiegazioni. Considereresti di aggiungere un po 'di narrativa per spiegare perché il codice funziona e cosa lo rende una risposta alla domanda? Ciò sarebbe molto utile per la persona che pone la domanda e per chiunque altro vi si presenti.
Andrew Barber,

2
Certo, funziona benissimo. Ma quella risposta è già stata data da altri , molto tempo fa. E includevano anche spiegazioni sul perché funzioni.
Andrew Barber,

5

questo è il modo migliore che ho trovato:

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

Ecco il codice:

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

Goditi un futuro pulsante di invio multiplo senza odore di codice.

grazie


Link attualmente non funzionante, questa è la versione archiviata più vicina che ho trovato: web.archive.org/web/20110706230408/http://blogs.sonatribe.com/…
Ian Kemp

Ciao Ian - grazie per averlo scoperto - l'ho ripubblicato qui: iwayneo.blogspot.co.uk/2013/10/…

4

Versione modificata del HttpParamActionAttributemetodo ma con una correzione di bug per non causare un errore nei postback di sessione scaduti / non validi. Per vedere se questo è un problema con il tuo sito attuale, apri il modulo in una finestra e appena prima di andare a fare clic Saveo Publish, apri una finestra duplicata e disconnettiti. Ora torna alla prima finestra e prova a inviare il modulo utilizzando uno dei due pulsanti. Per me ho ricevuto un errore, quindi questa modifica risolve il problema per me. Ometto un sacco di cose per brevità, ma dovresti avere l'idea. Le parti chiave sono l'inclusione diActionName dell'attributo e la verifica che il nome passato sia il nome della vista che mostra il modulo

Classe di attributi

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

controllore

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

Visualizza

@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

Il mio approccio JQuery usando un metodo di estensione:

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

Puoi usarlo in questo modo:

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

E rende in questo modo:

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

3

Per ogni pulsante di invio basta aggiungere:

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

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

});

3

Sulla base della risposta di mkozicki, mi viene in mente una soluzione un po 'diversa. Uso ancora ActionNameSelectorAttributeMa avevo bisogno di gestire due pulsanti "Salva" e "Sincronizza". Fanno quasi lo stesso, quindi non volevo fare due azioni.

attributo :

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

Visualizza

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

controllore

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

Voglio anche sottolineare che se le azioni fanno cose diverse probabilmente seguirò il post di mkozicki.


1

Ho creato un metodo ActionButton per HtmlHelper . Si genererà normale tasto di input con un po 'di javascript in OnClick che presenterà il modulo per il controller / azione specificata.

Usi l'aiutante in quel modo

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

questo genererà il seguente HTML

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

Ecco il codice del metodo di estensione:

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 # (il codice C # è appena decompilato dalla DLL VB, quindi può ottenere un po 'di abbellimento ... ma il tempo è così breve :-))

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

Questi metodi hanno vari parametri, ma per la facilità d'uso è possibile creare un sovraccarico che accetta solo i parametri necessari.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.