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



Answers

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

publicando 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 al trabajo con sitios localizados, aísle sus mensajes en otro lugar (p. Ej., 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());
}
Question

¿Hay alguna manera fácil de manejar múltiples botones de envío desde la misma forma? 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 he buscado tienen botones individuales en ellos.




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

Al intentar implementar la solución "MultipleButtonAttribute", ValueProvider.GetValue(keyValue) volvería incorrectamente a ser 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 ActionNameSelectorAttribute no funciona ... verifique eso.




My JQuery approach using an extension method:

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

And it renders like this:

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



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




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



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 con el nombre de los nombres de etiqueta de entrada HTML:

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

Si se publica algún valor en uno de esos parámetros, eso significa que el botón fue el que recibió el clic. El navegador web solo publicará un valor para el botón en el que se hizo 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 depende de la propiedad de valor de los botones de envío, que es más probable que cambie que los nombres asignados y no requiere javascript para ser habilitado

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




Este script permite especificar un atributo de forma de acción de datos que funcionará como el atributo de formatación de HTML5 en todos los navegadores (de forma 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 de acción de formulario de datos:

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

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




this is the best way that i have found:

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

Enjoy a code-smell-less multi submit button future.

gracias




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

Lea los múltiples botones del artículo en la misma forma para ver sus soluciones, y las ventajas y desventajas de cada una. En mi humilde opinión, ofrece una solución muy elegante que utiliza los atributos con los que decora su acción.




Based on mkozicki answer I come up with a bit different solution. I still use ActionNameSelectorAttribute But I needed to handle two buttons 'Save' and 'Sync'. They do almost the same so I didn't want to have two actions.

attribute :

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

controller

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

I also want to point out that if actions do different things I would probably follow mkozicki post.




Algo que no me gusta de ActionSelectName es que se llama 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 tenga 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. Creé una enumeración para todos los tipos de botones:

public enum ButtonType
{
    Submit,
    Cancel,
    Delete
}

En lugar de ActionSelectName, utilizo 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 cualquiera 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" />



I've created an ActionButton method for the HtmlHelper . It will generate normal input button with a bit of javascript in the OnClick event that will submit the form to the specified Controller/Action.

You use the helper like that

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

this will generate the following HTML

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

Here is the extension method code:

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# (the C# code is just decompiled from the VB DLL, so it can get some beautification... but time is so short :-))

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

These methods have various parameters, but for the ease of use you can create some overload that take just the parameters you need.




Hay tres formas de resolver el problema anterior

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

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 forma apuntará a acciones diferentes / respectivas. Puede ver el siguiente código: el primer formulario se publica en "Acción1" y el segundo se publicará en "Acción2", según el botón "Enviar".

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

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

Manera Ajax: -

En caso de que seas un amante de Ajax, esta segunda opción te emocionaría más. En el modo Ajax, podemos crear dos funciones diferentes, "Fun1" y "Fun1", ver 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 nombres de las acciones respectivas.

<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 gran y una opción limpia. 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" en los nombres de los botones. También puede observar 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 Enviar, primero se golpea el atributo "Selector de nombre de acción" y luego, dependiendo del envío que se envíe, se invoca la acción adecuada.

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 tenemos que anular la función "IsValidName" que devuelve true o flase. Esta función es donde escribimos la lógica de si una acción debe ser ejecutada o no. Entonces, si esta función devuelve verdadero, entonces la acción se ejecuta o de lo contrario no lo es.

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 código siguiente. 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 puede decorarse en la acción respectiva y se puede proporcionar el valor de "Nombre" respectivo. Por lo tanto, si el envío está haciendo clic en 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 adelante o de lo contrario 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");
        }
}



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

<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




Sugeriría que las partes interesadas 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 atributo MultiButton aplicado a una acción del controlador para indicar con qué botón hacer clic debe relacionarse la acción.




Related