¿Cómo maneja múltiples botones de envío en ASP.NET MVC Framework?

734

¿Hay alguna manera fácil de manejar múltiples botones de envío desde el mismo formulario? Por ejemplo:

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

¿Alguna idea de cómo hacer esto en ASP.NET Framework Beta? Todos los ejemplos que busqué en Google tienen botones únicos en ellos.

Spoike
fuente
66
Vale la pena mencionar que a partir de ASP.NET Core existen soluciones mucho más fáciles que las que se enumeran aquí.
Steven Jeuris
Este tutorial puede ayudar: Enviar un formulario a diferentes métodos de acción en ASP.NET MVC
Hooman Bahreini

Respuestas:

629

Aquí hay una solución basada en atributos en su mayoría limpia para el problema del botón de envío múltiple basado en gran medida en la publicación y los comentarios 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;
    }
}

maquinilla de afeitar:

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

y controlador:

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

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

Actualización: las páginas de Razor parecen proporcionar la misma funcionalidad fuera de la caja. Para un nuevo desarrollo, puede ser preferible.

mkozicki
fuente
3
Encontré que esta solución es el matrimonio feliz de las otras técnicas utilizadas. Funciona perfectamente y no afecta la localización.
trevorc
17
¡Gran solución! Mucho más útil que esas respuestas mejor calificadas
Sasha
11
Un problema con este enfoque es que si intenta hacerlo return View(viewmodel)en el caso de que su modelo tenga errores, intentará devolver una vista llamada Sendo en función del nombre de su argumento.
Zapato
15
@Shoe: acabo de encontrar algo similar. Asegúrese de especificar explícitamente el nombre de la vista a devolver si está utilizando este método:return View("Index", viewModel)
ajbeaven
44
solo una información, necesitamos agregar el sistema. Reflexión para MethodInfo
Vignesh Subramanian
469

Dé un nombre a sus botones de envío y luego inspeccione el valor enviado en su método de controlador:

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

publicar en

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

}

EDITAR:

Para extender este enfoque para trabajar con sitios localizados, aísle sus mensajes en otro lugar (por ejemplo, compilando un archivo de recursos en una clase de recursos fuertemente tipada)

Luego modifique el código para que funcione como:

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

y su controlador debería verse así:

// 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());
}
Dylan Beattie
fuente
28
lástima que dependas del texto que se muestra en el botón, es un poco complicado con una interfaz de usuario en
varios idiomas
3
Switch / case solo funciona con constantes, por lo que la versión localizada no puede usar switch / case. Debe cambiar a if else u otro método de envío.
mlibby el
10
debe usar un <button type = "submit" en lugar de <input type, porque el valor de un <button type no es el texto;). Entonces puede tener algo como esto: <button name = "mySubmitButton" type = "submit" value = "keyValue"> YourButtonText </button>
J4N
44
¿Cómo funcionaría esto al pasar el modelo a la acción en lugar de solo el valor de envío?
bizzehdee
2
Tenga cuidado de no nombrar sus botones "acción" si está usando jQuery. Causa un conflicto dentro de la biblioteca que rompe la URL de acción.
HotN
121

Puede verificar el nombre en la acción como se ha mencionado, pero puede considerar si este es un buen diseño o no. Es una buena idea considerar la responsabilidad de la acción y no acoplar demasiado este diseño a los aspectos de la interfaz de usuario, como los nombres de los botones. Entonces considere usar 2 formas y 2 acciones:

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

Además, en el caso de "Cancelar", generalmente no está procesando el formulario y va a una nueva URL. En este caso, no es necesario que envíe el formulario y solo necesita un enlace:

<%=Html.ActionLink("Cancel", "List", "MyController") %>
Trevor de Koekkoek
fuente
53
Esto está bien cuando no necesita los mismos datos de formulario para cada botón de envío. Si necesita todos los datos en forma común, Dylan Beattie es el camino a seguir. ¿Hay alguna forma más elegante de hacer esto?
zidane
3
Acerca de la presentación visual, ¿cómo en este caso tiene el botón "Enviar" junto al botón "Cancelar"?
Kris-I
1
Dylan: Bueno, para un botón de cancelación, no es necesario que envíe los datos y es una mala práctica acoplar el controlador a los elementos de la interfaz de usuario. Sin embargo, si puede hacer un "comando" más o menos genérico, creo que está bien, pero no lo vincularía con "submitButton", ya que ese es el nombre de un elemento de la interfaz de usuario.
Trevor de Koekkoek
1
@Kris: puede colocar sus botones con CSS y aún pueden residir en 2 secciones de formulario diferentes.
Trevor de Koekkoek
8
¿seriamente? ¿eso no huele a nadie más que a mí?
99

Eilon sugiere que puedes hacerlo así:

Si tiene más de un botón, puede distinguirlos dando un nombre a cada botón:

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

En su método de acción del controlador, puede agregar parámetros nombrados después de los nombres de las etiquetas de entrada HTML:

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

Si algún valor se publica en uno de esos parámetros, eso significa que ese botón fue el que se hizo clic. El navegador web sólo publicará un valor para el uno botón que consiguió hace clic. Todos los demás valores serán nulos.

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

Me gusta este método, ya que no se basa en la propiedad de valor de los botones de envío, que es más probable que cambie que los nombres asignados y no requiere que JavaScript esté habilitado

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

PabloBlamirez
fuente
2
Si alguien se encuentra con esta vieja pregunta, esta es la respuesta más clara si no desea utilizar elementos HTML5 <button>. Si no le importa HTML5, use <button> con el atributo value.
Kugel
¿Cómo lo haría si crea una llamada ajax en este formulario? Parece que form.serialize () no recoge el nombre del botón de envío ..
mko
@ Kugel tiene razón, esta sigue siendo la respuesta más clara. Gracias
Arif YILMAZ
45

Acabo de escribir una publicación sobre eso: Múltiples botones de envío con ASP.NET MVC :

Básicamente, en lugar de usar ActionMethodSelectorAttribute, estoy usando ActionNameSelectorAttribute, lo que me permite fingir que el nombre de la acción es lo que quiero que sea. Afortunadamente, ActionNameSelectorAttributeno solo me hace especificar el nombre de la acción, sino que puedo elegir si la acción actual coincide con la solicitud.

Así que ahí está mi clase (por cierto, no me gusta demasiado el nombre):

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

Para usar solo defina una forma como esta:

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

y controlador con dos métodos

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

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

Como puede ver, el atributo no requiere que especifique nada en absoluto. Además, el nombre de los botones se traduce directamente a los nombres de los métodos. Además (no lo he intentado), estas también deberían funcionar como acciones normales, por lo que puede publicar en cualquiera de ellas directamente.

Andrey Shchekin
fuente
1
¡Hermoso! Creo que esta es la solución más elegante. Elimina el valor de la submitetiqueta de la consideración, lo cual es ideal ya que es un atributo de interfaz de usuario puro que no debería tener ninguna relación con el flujo de control. En cambio, el nameatributo único de cada submitetiqueta se traduce directamente en un método de acción discreto en su controlador.
Kirk Woll
+1 Para mí, es la mejor solución para este problema. Desde que lo implementé, noto que pasa mucho tráfico a través de HttpParamActionAttribut, pero en comparación con todas las otras cosas que Asp.Net MVC tiene que hacer mientras procesa una solicitud, es totalmente aceptable. Para hackear solo lo que tengo que hacer es poner una 'Acción' vacía nombrada en mi controlador para evitar que Resharper me advierta que la acción 'Acción' no existe. ¡Muchas gracias!
Samuel
Revisé todas las soluciones y también estoy de acuerdo en que esta es una solución elegante, simple y agradable. Excelente porque no hay declaraciones condicionales y robustas donde puede definir una nueva acción del controlador cuando tiene un nuevo botón. Llamé a mi clase MultiButtonActionHandler FYI ;-)
ejhost
36

es corto y suite:

Fue respondido por Jeroen Dop

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

y hacer esto en código behinde

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

Buena suerte.

Ali Golgol
fuente
Increíble. exactamente lo que solía hacer en los formularios web. Saludos amigo
djack109
¡Mucho más simple que la respuesta superior! ¡Gracias!
pabben
35

Sugeriría a las partes interesadas que echen un vistazo a la solución de Maarten Balliauw . Creo que es muy elegante.

En caso de que el enlace desaparezca, está usando el MultiButtonatributo aplicado a una acción del controlador para indicar a qué botón hacer clic esa acción debería relacionarse.

Izmoto
fuente
Esta es la solución que estamos usando ahora y es muy clara. ¿Es solo MVC 2?
Simon Keep
¡Esto es hermoso! ¡No había visto esto antes! Si bien estoy de acuerdo en que es posible que desee rediseñar cualquier solución que esté utilizando múltiples envíos para usar solo un botón, estoy en un lugar donde estoy bloqueado y debo hacer esto. ¡Esta respuesta debería haber ganado!
Rikon
Esta es una gran solución. Muy limpio
Arnej65
Probé este enfoque y no pude hacerlo funcionar en MVC3. Una variación del número 1 para obtener votos funcionó para mí.
Scott Lawrence
Corto y dulce ... pero no para mvc 3+
Piotr Kula
21

Debería poder nombrar los botones y darles un valor; luego asigne este nombre como argumento a la acción. Alternativamente, use 2 enlaces de acción separados o 2 formas.

Marc Gravell
fuente
Con mucho, la solución más limpia y fácil que he visto.
Jason Evans
13

Podrías escribir:

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

Y luego, en la página, verifique si el nombre == "Enviar" o el nombre == "Cancelar" ...

Ironicnet
fuente
1
Aunque esto está funcionando, pero creo que es una práctica incorrecta tener dos elementos con el mismo nombre.
Péter
1
No es necesariamente incorrecto. Depende de cómo esté utilizando las entradas. Puede tener múltiples elementos con el mismo nombre y esperar recibir múltiples datos (así es como funcionan los botones de radio y las casillas de verificación). Pero sí, si está utilizando este método es porque lo está haciendo "mal" ... Por eso pongo "Podría" pero no "Debería": P
Ironicnet
12

Algo que no me gusta de ActionSelectName es que se llama a IsValidName para cada método de acción en el controlador; No sé por qué funciona de esta manera. Me gusta una solución donde cada botón tiene un nombre diferente en función de lo que hace, pero no me gusta el hecho de que tenga que tener tantos parámetros en el método de acción como botones en el formulario. He creado una enumeración para todos los tipos de botones:

public enum ButtonType
{
    Submit,
    Cancel,
    Delete
}

En lugar de ActionSelectName, uso 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;
            }
        }
    }
}

El filtro encontrará el nombre del botón en los datos del formulario y si el nombre del botón coincide con alguno de los tipos de botón definidos en la enumeración, encontrará el parámetro ButtonType entre los parámetros de acción:

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

y luego en vistas, puedo usar:

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

Esto es lo que funciona mejor para mí:

<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();
}
Sergey
fuente
Obtengo un valor nulo para onDelete y onSave en el método del controlador. ¿Sabes por qué?
Diganta Kumar
Cualquiera de los dos no será nulo si hace clic en el botón correspondiente. ¿En qué botón haces clic para obtener el valor nulo?
Sergey
11

También me encontré con este 'problema' pero encontré una solución bastante lógica al agregar el nameatributo. No recuerdo haber tenido este problema en otros idiomas.

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

  • ...
  • Si un formulario contiene más de un botón de envío, solo el botón de envío activado es exitoso.
  • ...

Lo que significa que los siguientes valueatributos de código pueden cambiarse, localizarse, internacionalizarse sin la necesidad de código adicional que verifique los archivos o constantes de recursos fuertemente tipados.

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

En el extremo receptor solo necesitaría verificar si alguno de sus tipos de envío conocidos no es 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

    }

}
Tom Hofman
fuente
Esta es la solución que elegimos usar para una aplicación web simple donde queríamos la funcionalidad ASP.NET WebForms pero dentro de MVC.
BrandonG
10

Si no tiene restricciones en el uso de HTML 5, puede usar la <button>etiqueta con formactionAtributo:

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

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

Acaz Souza
fuente
9

Si su navegador admite la formación de atributos para los botones de entrada (IE 10+, no estoy seguro acerca de otros navegadores), entonces lo siguiente debería funcionar:

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

}
usuario3361233
fuente
Eche un vistazo a mi respuesta a continuación, no se basa en especificaciones preliminares. Su respuesta permite la posibilidad de tener diferentes URL de acción, que la mía no.
Tom Hofman
9

Hay tres formas de resolver el problema anterior.

  1. Forma HTML
  2. Jquery way
  3. Forma "ActionNameSelectorAttribute"

A continuación se muestra un video que resume los tres enfoques de manera demostrativa.

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

Forma HTML: -

En la forma HTML, necesitamos crear dos formularios y colocar el botón "Enviar" dentro de cada uno de los formularios. Y la acción de cada formulario apuntará a acciones diferentes / respectivas. Puede ver el siguiente código: el primer formulario se publica en "Acción1" y el segundo formulario se publicará en "Acción2", según el botón "Enviar" que se haga clic.

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

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

Forma Ajax: -

En caso de que seas un amante del Ajax, esta segunda opción te entusiasmaría más. En la forma Ajax podemos crear dos funciones diferentes "Fun1" y "Fun1", vea el siguiente código. Estas funciones harán llamadas Ajax utilizando JQUERY o cualquier otro marco. Cada una de estas funciones se vincula con los eventos "OnClick" del botón "Enviar". Cada una de estas funciones realiza llamadas a los respectivos nombres de acción.

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

Usando "ActionNameSelectorAttribute": -

Esta es una excelente y limpia opción. El "ActionNameSelectorAttribute" es una clase de atributo simple donde podemos escribir la lógica de toma de decisiones que decidirá qué acción se puede ejecutar.

Entonces, lo primero es en HTML, necesitamos poner nombres propios a los botones de envío para identificarlos en el servidor.

Puede ver que hemos puesto "Guardar" y "Eliminar" a los nombres de los botones. También puede notar en la acción que acabamos de poner el nombre del controlador "Cliente" y no un nombre de acción en particular. Esperamos que el nombre de la acción se decida por "ActionNameSelectorAttribute".

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

Entonces, cuando se hace clic en el botón de envío, primero toca el atributo "ActionNameSelector" y luego, dependiendo de qué envío se activa, invoca la acción apropiada.

ingrese la descripción de la imagen aquí

Entonces, el primer paso es crear una clase que herede de la clase "ActionNameSelectorAttribute". En esta clase hemos creado una propiedad simple "Nombre".

También necesitamos anular la función "IsValidName" que devuelve verdadero o flase. Esta función es donde escribimos la lógica de si una acción tiene que ejecutarse o no. Entonces, si esta función devuelve verdadero, entonces la acción se ejecuta o no.

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;

        }
    }

El corazón principal de la función anterior está en el siguiente código. La colección "ValueProvider" tiene todos los datos que se han publicado desde el formulario. Por lo tanto, primero busca el valor "Nombre" y, si se encuentra en la solicitud HTTP, devuelve verdadero o, de lo contrario, devuelve falso.

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

Esta clase de atributo se puede decorar en la acción respectiva y se puede proporcionar el valor de "Nombre" respectivo. Entonces, si el envío está presionando esta acción y si el nombre coincide con el nombre del botón de envío HTML, entonces ejecuta la acción más o no lo hace.

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

David Findley escribe sobre 3 opciones diferentes que tiene para hacer esto, en su weblog ASP.Net.

Lea el artículo de varios botones en la misma forma para ver sus soluciones y las ventajas y desventajas de cada uno. En mi humilde opinión, proporciona una solución muy elegante que hace uso de los atributos con los que decoras tu acción.

Saajid Ismail
fuente
7

Esta es la técnica que usaría y todavía no la veo aquí. El enlace (publicado por Saajid Ismail) que inspira esta solución es http://weblogs.asp.net/dfindley/archive/2009/05/31/asp-net-mvc-multiple-buttons-in-the-same-form .aspx ). Adapta la respuesta de Dylan Beattie para hacer la localización sin ningún problema.

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

En el controlador:

public class MyController : Controller 
{
    public ActionResult MyAction(string button)
    {
         switch(button)
         {
             case "send":
                 this.DoSend();
                 break;
             case "cancel":
                 this.DoCancel();
                 break;
         }
    }
}
mlibby
fuente
Parece que la solución que proporcionó Ironicnet.
Kris van der Mast el
Ciertamente similar, pero esto muestra tanto la localización como el código del controlador, que es algo que no vi hecho de esta manera en este hilo. Encontré este hilo mientras buscaba cómo hacer esto y quería documentar lo que se me ocurrió para cualquier otra persona que pudiera estar en el mismo barco.
mlibby el
1
De hecho, no es lo mismo que Ironicnet más allá de eso. Él usa <input>elementos. Yo uso <button>, que se requiere para hacer la localización sin tener atributos de valor variable.
mlibby el
7

Este script permite especificar un atributo de acción de formulario de datos que funcionará como el atributo de formación de HTML5 en todos los navegadores (de una manera discreta):

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

El formulario que contiene el botón se publicará en la URL especificada en el atributo data-form-action:

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

Esto requiere jQuery 1.7. Para versiones anteriores debe usar en live()lugar de on().

Jay Douglass
fuente
6

Aquí hay un método de extensión que escribí para manejar múltiples botones de imagen y / o texto.

Aquí está el HTML para un botón de imagen:

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

o para un botón de enviar texto:

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

Aquí está el método de extensión con el que llama desde el controlador form.GetSubmitButtonName(). Para los botones de imagen, busca un parámetro de formulario con .x(que indica que se hizo clic en un botón de imagen) y extrae el nombre. Para los inputbotones normales , busca un nombre que comience Submit_y extrae el comando de más adelante. Como estoy abstrayendo la lógica de determinar el 'comando', puede cambiar entre los botones de imagen + texto en el cliente sin cambiar el código del lado del servidor.

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: Para los botones de texto, debe prefijar el nombre con Submit_. Prefiero esta forma porque significa que puede cambiar el valor del texto (visualización) sin tener que cambiar el código. A diferencia de los SELECTelementos, un INPUTbotón solo tiene un 'valor' y ningún atributo de 'texto' separado. Mis botones dicen cosas diferentes en contextos diferentes, pero se asignan al mismo 'comando'. Prefiero extraer el nombre de esta manera que tener que codificar == "Add to cart".

Simon_Weaver
fuente
Me gusta verificar el nombre como alternativa porque no siempre se puede verificar el valor, por ejemplo. tiene una lista de elementos y cada uno tiene un botón "Eliminar".
Max
6

No tengo suficiente representante para comentar en el lugar correcto, pero pasé todo el día en esto, así que quiero compartir.

Al intentar implementar la solución "MultipleButtonAttribute", ValueProvider.GetValue(keyValue)volvería incorrectamente null.

Resultó que estaba haciendo referencia a System.Web.MVC versión 3.0 cuando debería haber sido 4.0 (otros ensamblados son 4.0). No sé por qué mi proyecto no se actualizó correctamente y no tuve otros problemas obvios.

Entonces, si su ActionNameSelectorAttributeno funciona ... verifique eso.

HeatherD
fuente
6

Llego bastante tarde a la fiesta, pero aquí va ... Mi implementación toma prestada de @mkozicki pero requiere menos cadenas codificadas para equivocarse. Se requiere Framework 4.5+ . Básicamente, el nombre del método del controlador debe ser la clave del enrutamiento.

Marcado . El nombre del botón debe estar tecleado con"action:[controllerMethodName]"

(nótese el uso del C # 6 nombredel API, proporcionando referencia de tipo-específica al nombre del método controlador que desea invocar.

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

Controlador :

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

Atributo Magia . Note el uso de la CallerMemberNamebondad.

[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;
    }
}
Aaron Hudon
fuente
Aunque este es un buen enfoque, no podrá usar la carpeta de modelo mvc integrada porque usa el atributo "nombre" de los botones.
ooXei1sh
El propósito de esta solución es evitar el enrutamiento posterior de MVC. Por favor describa una mejora.
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>

    }
Mahendra
fuente
Este es otro enlace útil que explica las diferentes formas de publicación de formularios.
Himalaya Garg
5

Intenté hacer una síntesis de todas las soluciones y creé un atributo [ButtenHandler] que facilita el manejo de múltiples botones en un formulario.

Lo describí en CodeProject Botones de formulario parametrizados múltiples (localizables) en ASP.NET MVC .

Para manejar el caso simple de este botón:

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

Tendrá algo como el siguiente método de acción:

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

Observe cómo el nombre del botón coincide con el nombre del método de acción. El artículo también describe cómo tener botones con valores y botones con índices.

codetuner
fuente
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();
        }
SHAURAJ SINGH
fuente
Es posible que no se haya dado cuenta, pero esta misma respuesta ya se publicó varias veces a esta pregunta.
Andrew Barber
2
Además, parece publicar respuestas que solo contienen código, sin explicación. ¿Consideraría agregar una narrativa para explicar por qué funciona el código y qué lo convierte en una respuesta a la pregunta? Esto sería muy útil para la persona que hace la pregunta y para cualquier otra persona que se presente.
Andrew Barber
2
Claro, funciona bien. Pero esa respuesta ya la han dado otros , hace mucho tiempo. E incluyeron explicaciones sobre por qué funciona también.
Andrew Barber
5

Esta es la mejor manera que he encontrado:

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

Aquí está el código:

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

Disfrute de un botón de envío múltiple sin código de olor en el futuro.

gracias


fuente
Enlace actualmente roto, esta es la versión archivada más cercana que pude encontrar: web.archive.org/web/20110706230408/http://blogs.sonatribe.com/…
Ian Kemp
Hola Ian, gracias por desenterrarlo. Lo he vuelto a publicar aquí: iwayneo.blogspot.co.uk/2013/10/…
4

Versión modificada del HttpParamActionAttributemétodo pero con una corrección de errores para no causar un error en las devoluciones de sesión caducadas / no válidas. Para ver si esto es un problema con su sitio actual, abra el formulario en una ventana y justo antes de hacer clic Saveo Publish, abra una ventana duplicada y cierre la sesión. Ahora regrese a su primera ventana e intente enviar su formulario con cualquiera de los botones. En mi caso, recibí un error, por lo que este cambio me soluciona el problema. Omito un montón de cosas por razones de brevedad, pero deberías tener la idea. Las partes clave son la inclusión ActionNameen el atributo y asegurarse de que el nombre pasado sea el nombre de la Vista que muestra el formulario

Clase de atributo

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

Controlador

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

Ver

@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" />
}
Nick Albrecht
fuente
3

Mi enfoque JQuery usando un método de extensión:

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

Puedes usarlo así:

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

Y se presenta así:

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

Para cada botón de enviar simplemente agregue:

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

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

});
percance
fuente
3

Basado en la respuesta de mkozicki, llego a una solución un poco diferente. Todavía uso ActionNameSelectorAttributePero necesitaba manejar dos botones 'Guardar' y 'Sincronizar'. Hacen casi lo mismo, así que no quería tener dos acciones.

atributo :

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

ver

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

controlador

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

También quiero señalar que si las acciones hacen cosas diferentes, probablemente seguiría la publicación de mkozicki.

Petr J
fuente
1

He creado un método ActionButton para HtmlHelper . Generará un botón de entrada normal con un poco de javascript en el evento OnClick que enviará el formulario al Controlador / Acción especificado.

Usas el ayudante así

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

esto generará el siguiente HTML

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

Aquí está el código del método de extensión:

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 # (el código C # se acaba de descompilar de la DLL de VB, por lo que puede obtener algo de embellecimiento ... pero el tiempo es muy corto :-))

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

Estos métodos tienen varios parámetros, pero para facilitar su uso, puede crear una sobrecarga que tome solo los parámetros que necesita.

Max
fuente