Como você lida com vários botões de envio no ASP.NET MVC Framework?

733

Existe alguma maneira fácil de lidar com vários botões de envio do mesmo formulário? Por exemplo:

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

Alguma idéia de como fazer isso no ASP.NET Framework Beta? Todos os exemplos que pesquisei no Google têm botões únicos.

Spoike
fonte
6
Vale mencionar que, a partir do ASP.NET Core, existem soluções muito mais fáceis do que as listadas aqui.
Steven Jeuris

Respostas:

629

Aqui está uma solução baseada em atributos, na maior parte limpa, para a questão do botão de envio múltiplo, baseada fortemente na postagem e nos comentários 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;
    }
}

navalha:

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

e controlador:

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

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

Atualização: as páginas do Razor parecem fornecer a mesma funcionalidade pronta para uso. Para um novo desenvolvimento, pode ser preferível.

mkozicki
fonte
3
Eu achei essa solução o casamento feliz das outras técnicas usadas. Funciona perfeitamente e não afeta a localização.
trevorc
17
Ótima solução !! Muito nore útil do que aqueles que o melhor marcador respostas
Sasha
11
Um problema com essa abordagem é que, se você tentar return View(viewmodel)no caso em que seu modelo possui erros, ele tentará retornar uma visualização chamada Sendou dependendo do nome do seu argumento.
Shoe
15
@ Sapato - acabei de encontrar uma coisa semelhante. Certifique-se de especificar explicitamente o nome do modo de exibição para retornar se você estiver usando este método:return View("Index", viewModel)
ajbeaven
4
Para obter mais informações, consulte Métodos.Instalação do método MethodInfo
Vignesh Subramanian
469

Dê um nome aos botões de envio e inspecione o valor enviado no seu método do controlador:

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

postando para

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 estender essa abordagem para trabalhar com sites localizados, isole suas mensagens em outro lugar (por exemplo, compilar um arquivo de recurso para uma classe de recurso fortemente tipada)

Em seguida, modifique o 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(); %>

e seu controlador deve ficar assim:

// 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
fonte
28
Pena que dependem do texto exibido no botão, é meio complicado com uma interface multilíngüe
Omu
3
Switch / case funciona apenas com constantes; portanto, a versão localizada não pode usar switch / case. Você precisa mudar para if else ou algum outro método de despacho.
mlibby
10
você deve usar um <tipo de botão = "enviar" em vez de <tipo de entrada, porque o valor de um <tipo de botão não é o texto;). Então você pode ter algo parecido com isto: <button name = "mySubmitButton" type = "submit" value = "keyValue"> YourButtonText </button>
J4N
4
Como isso funcionaria com a passagem do modelo para a ação, em vez de apenas o valor de envio?
bizzehdee
2
Cuidado para não nomear seus botões como "ação" se você estiver usando o jQuery. Isso causa um conflito na biblioteca que quebra o URL da ação.
HotN
121

Você pode verificar o nome na ação conforme mencionado, mas pode considerar se esse é um bom design ou não. É uma boa idéia considerar a responsabilidade da ação e não associar muito esse design aos aspectos da interface do usuário, como nomes de botões. Portanto, considere usar 2 formulários e 2 ações:

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

Além disso, no caso de "Cancelar", normalmente você não processa o formulário e está indo para um novo URL. Nesse caso, você não precisa enviar o formulário e precisa apenas de um link:

<%=Html.ActionLink("Cancel", "List", "MyController") %>
Trevor de Koekkoek
fonte
53
Tudo bem quando você não precisa dos mesmos dados de formulário para cada botão de envio. Se você precisar de todos os dados de forma comum, então Dylan Beattie é o caminho a percorrer. Existe alguma maneira mais elegante de fazer isso?
zidane
3
Sobre a apresentação visual, como, neste caso, o botão "Enviar" ao lado do botão "Cancelar"?
que você
1
Dylan: Bem, para um botão de cancelamento, você não precisa enviar os dados e é uma má prática associar o controlador aos elementos da interface do usuário. No entanto, se você puder criar um "comando" mais ou menos genérico, acho que está ok, mas não o vincularia a "submitButton", pois esse é o nome de um elemento da interface do usuário.
Trevor de Koekkoek 16/03/11
1
@ Kris: você pode posicionar seus botões com CSS e eles ainda podem residir em 2 seções de formulário diferentes.
Trevor de Koekkoek 16/03/11
8
a sério? isso não cheira a ninguém além de mim ?!
99

Eilon sugere que você possa fazer assim:

Se você tiver mais de um botão, poderá distinguir entre eles, dando um nome a cada botão:

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

No seu método de ação do controlador, você pode adicionar parâmetros nomeados após os nomes das tags de entrada HTML:

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

Se algum valor for lançado em um desses parâmetros, significa que foi o botão que foi clicado. O navegador da web postará apenas um valor para o único botão que foi clicado. Todos os outros valores serão nulos.

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

Eu gosto desse método, pois não depende da propriedade value dos botões de envio, que é mais provável de mudar do que os nomes atribuídos e não requer que o javascript esteja ativado

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

PabloBlamirez
fonte
2
Se alguém se deparar com essa pergunta antiga, esta é a resposta mais limpa, se você não quiser usar os elementos <5button> HTML5. Se você não se importa com HTML5, use <button> com o atributo value.
Kugel
Como você faria isso se estivesse criando uma chamada ajax neste formulário. Parece que form.serialize () não pega o nome do botão de envio ..
mko
@ Kugel está certo, esta ainda é a resposta mais limpa. Graças
Arif YILMAZ
45

Acabei de escrever um post sobre isso: Vários botões de envio com o ASP.NET MVC :

Basicamente, em vez de usar ActionMethodSelectorAttribute, estou usando ActionNameSelectorAttribute, o que me permite fingir que o nome da ação é o que eu quero que seja. Felizmente, ActionNameSelectorAttributenão apenas me faz especificar o nome da ação, mas posso escolher se a ação atual corresponde à solicitação.

Então, há a minha classe (btw eu não gosto muito do 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;
    }
} 

Para usar apenas defina um formulário como este:

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

e controlador com dois métodos

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

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

Como você vê, o atributo não requer que você especifique nada. Além disso, o nome dos botões é convertido diretamente nos nomes dos métodos. Além disso (ainda não tentei isso), elas também devem funcionar como ações normais, para que você possa postar diretamente em qualquer uma delas.

Andrey Shchekin
fonte
1
Bonita! Eu acho que essa é a solução mais elegante. Ele elimina o valor da submittag da consideração, o que é ideal, pois é um atributo de interface do usuário pura que não deve ter influência no fluxo de controle. Em vez disso, o nameatributo exclusivo de cada submittag se converte diretamente em um método de ação discreto no seu controlador.
precisa
+1 Para mim, é de longe a melhor solução para esse problema. Desde que o implementei, noto que muito tráfego passa pelo HttpParamActionAttribut, mas comparado a todas as outras coisas que o Asp.Net MVC precisa fazer durante o processamento de uma solicitação, é totalmente aceitável. Para hackear apenas o que eu preciso fazer é colocar uma 'Ação' vazia nomeada no meu controlador para evitar que o Resharper me avise que a ação 'Ação' não existe. Muito obrigado!
Samuel
Analisei todas as soluções e também concordo que esta é uma solução simples e elegante e agradável. Ótimo bc, não há instruções condicionais e robustas, nas quais você pode definir uma nova ação do controlador quando tiver um novo botão. Chamei minha classe MultiButtonActionHandler FYI ;-)
ejhost
36

é curto e suite:

Foi respondido por Jeroen Dop

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

e faça assim no código antes

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

Boa sorte.

Ali Golgol
fonte
Impressionante. exatamente o que eu costumava fazer em webforms. Felicidades camarada
djack109
Muito mais simples que a resposta principal! Obrigado!
pabben
35

Sugiro que as partes interessadas dêem uma olhada na solução de Maarten Balliauw . Eu acho muito elegante.

Caso o link desapareça, ele está usando o MultiButtonatributo aplicado a uma ação do controlador para indicar a qual botão clicar com o qual a ação deve se relacionar.

Izmoto
fonte
Esta é a solução que estamos usando agora e é muito elegante. É apenas o MVC 2?
Simon Mantenha
Isso é lindo! Eu não tinha visto isso antes! Embora eu concorde que você pode redesenhar qualquer solução que esteja usando vários envios para usar apenas um botão, estou em um ponto em que estou com dificuldade e preciso fazer isso. Esta resposta deveria ter vencido!
Rikon
Esta é uma otima soluçao. Muito limpo
Arnej65
Tentei essa abordagem e não conseguiu fazê-la funcionar no MVC3. Uma variação do número 1 na votação funcionou para mim.
Scott Lawrence
Curto e doce .. mas não para mvc 3+
Piotr Kula
21

Você deve poder nomear os botões e atribuir um valor a eles; em seguida, mapeie esse nome como um argumento para a ação. Como alternativa, use 2 links de ação separados ou 2 formulários.

Marc Gravell
fonte
De longe, a solução mais limpa e fácil que eu já vi.
Jason Evans
13

Você pode escrever:

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

E então, na página, verifique se o nome == "Enviar" ou nome == "Cancelar" ...

Ironicnet
fonte
1
Embora isso esteja funcionando, mas acho que é uma prática incorreta ter dois elementos com o mesmo nome.
Péter
1
Não é necessário errado. Depende de como você está usando as entradas. Você pode ter vários elementos com o mesmo nome e esperar receber vários dados (é assim que os botões de opção e as caixas de seleção funcionam). Mas sim, se você estiver usando este método é porque você está fazendo isso "errado" ... É por isso que eu coloquei "Você poderia", mas não "Você deveria": P
Ironicnet
12

Algo que eu não gosto no ActionSelectName é que IsValidName é chamado para todos os métodos de ação no controlador; Não sei por que funciona dessa maneira. Gosto de uma solução em que cada botão tem um nome diferente com base no que faz, mas não gosto do fato de que você precisa ter tantos parâmetros no método de ação quanto botões no formulário. Eu criei uma enumeração para todos os tipos de botão:

public enum ButtonType
{
    Submit,
    Cancel,
    Delete
}

Em vez de ActionSelectName, eu uso um 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;
            }
        }
    }
}

O filtro encontrará o nome do botão nos dados do formulário e, se o nome do botão corresponder a algum dos tipos de botão definidos na enumeração, ele encontrará o parâmetro ButtonType entre os parâmetros de ação:

[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, em vistas, eu posso usar:

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

Aqui está o que funciona melhor para mim:

<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
fonte
Eu recebo um valor nulo para onDelete e onSave no método do controlador. Você sabe por quê?
Diganta Kumar
Qualquer um deles não será nulo se você clicar no botão correspondente. Em qual botão você clica em obter o nulo?
Sergey
11

Também deparei com esse 'problema', mas encontrei uma solução bastante lógica adicionando o nameatributo Não me lembro de ter esse problema em outros idiomas.

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

  • ...
  • Se um formulário contiver mais de um botão de envio, apenas o botão de envio ativado será bem-sucedido.
  • ...

Significando que os seguintes valueatributos de código podem ser alterados, localizados e internacionalizados sem a necessidade de verificação extra de código, arquivos ou constantes de recursos fortemente 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(); %>`

No lado de recebimento, você só precisa verificar se algum dos seus tipos de envio conhecidos não é 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
fonte
Esta é a solução que escolhemos usar para um aplicativo Web simples, onde queríamos a funcionalidade ASP.NET WebForms, mas dentro do MVC.
precisa saber é o seguinte
10

Se você não tiver restrições ao uso do HTML 5, poderá usar a <button>tag com 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>

Referência: http://www.w3schools.com/html5/att_button_formaction.asp

Acaz Souza
fonte
9

Se o seu navegador suportar a formação de atributo para botões de entrada (IE 10+, não tenho certeza sobre outros navegadores), o seguinte deverá 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") />

}
user3361233
fonte
Dê uma olhada na minha resposta abaixo, ela não depende de especificações de rascunho. Sua resposta permite a possibilidade de ter diferentes URLs de ação, os quais os meus não.
Tom Hofman
9

Existem três maneiras pelas quais você pode resolver o problema acima

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

Abaixo está um vídeo que resume todas as três abordagens de maneira demonstrativa.

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

Maneira HTML: -

No modo HTML, precisamos criar dois formulários e colocar o botão "Enviar" dentro de cada um dos formulários. E a ação de cada formulário apontará para ações diferentes / respectivas. Você pode ver o código abaixo: o primeiro formulário está sendo postado em "Ação1" e o segundo formulário será postado em "Ação2", dependendo de qual botão "Enviar" for clicado.

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

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

Maneira do Ajax: -

Caso você seja um amante do Ajax, esta segunda opção o excitará mais. No modo Ajax, podemos criar duas funções diferentes “Fun1” e “Fun1”, veja o código abaixo. Essas funções farão chamadas ao Ajax usando JQUERY ou qualquer outra estrutura. Cada uma dessas funções está vinculada aos eventos "OnClick" do botão "Submit". Cada uma dessas funções chama os respectivos nomes de ação.

<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 é uma ótima e limpa opção. O "ActionNameSelectorAttribute" é uma classe de atributo simples onde podemos escrever uma lógica de tomada de decisão que decidirá qual ação pode ser executada.

Portanto, a primeira coisa é em HTML, precisamos colocar nomes próprios nos botões de envio para identificá-los no servidor.

Você pode ver que colocamos "Salvar" e "Excluir" nos nomes dos botões. Além disso, você pode notar na ação que acabamos de colocar o nome do controlador “Cliente” e não um nome de ação específico. Esperamos que o nome da ação seja decidido por "ActionNameSelectorAttribute".

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

Portanto, quando o botão de envio é clicado, ele primeiro atinge o atributo "ActionNameSelector" e, dependendo de qual envio é acionado, invoca a ação apropriada.

insira a descrição da imagem aqui

Portanto, o primeiro passo é criar uma classe que herda da classe "ActionNameSelectorAttribute". Nesta classe, criamos uma propriedade simples "Nome".

Também precisamos substituir a função "IsValidName" que retorna true ou flase. Essa função é onde escrevemos a lógica se uma ação deve ser executada ou não. Portanto, se essa função retornar verdadeira, a ação será executada ou não.

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;

        }
    }

O coração principal da função acima está no código abaixo. A coleção "ValueProvider" possui todos os dados que foram postados no formulário. Então, ele primeiro procura o valor "Nome" e, se encontrado na solicitação HTTP, retorna verdadeiro ou falso.

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

Essa classe de atributo pode ser decorada na ação respectiva e o respectivo valor "Nome" pode ser fornecido. Portanto, se o envio estiver atingindo essa ação e se o nome corresponder ao nome do botão de envio em HTML, ele executará a ação ainda mais, caso contrário não o fará.

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

David Findley escreve cerca de três opções diferentes que você tem para fazer isso, em seu blog ASP.Net.

Leia o artigo vários botões da mesma forma para ver suas soluções e as vantagens e desvantagens de cada um. IMHO ele fornece uma solução muito elegante que utiliza atributos com os quais você decora sua ação.

Saajid Ismail
fonte
7

Essa é a técnica que eu usaria e ainda não a vejo aqui. O link (publicado por Saajid Ismail) que inspira esta solução é http://weblogs.asp.net/dfindley/archive/2009/05/31/asp-net-mvc-multiple-buttons-in-the-same-form .aspx ). Adapta a resposta de Dylan Beattie para fazer a localização sem problemas.

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

No controlador:

public class MyController : Controller 
{
    public ActionResult MyAction(string button)
    {
         switch(button)
         {
             case "send":
                 this.DoSend();
                 break;
             case "cancel":
                 this.DoCancel();
                 break;
         }
    }
}
mlibby
fonte
Parece que a solução fornecida pela Ironicnet.
Kris van der Mast
Certamente semelhante, mas isso mostra a localização e o código do controlador, que é algo que eu não via dessa maneira neste segmento. Eu encontrei esse tópico enquanto procurava como fazer isso e queria documentar o que eu criei para qualquer pessoa que estivesse no mesmo barco.
mlibby
1
De fato, não é o mesmo que o Ironicnet além disso. Ele usa <input>elementos. Eu uso <button>, o que é necessário para fazer a localização sem ter atributos de valor variável.
mlibby
7

Este script permite especificar um atributo de ação de formulário de dados que funcionará como o atributo de formação HTML5 em todos os navegadores (de maneira 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);             
});

O formulário que contém o botão será postado no URL especificado no atributo data-form-action:

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

Isso requer o jQuery 1.7. Para versões anteriores, você deve usar em live()vez de on().

Jay Douglass
fonte
6

Aqui está um método de extensão que escrevi para lidar com vários botões de imagem e / ou texto.

Aqui está o HTML para um botão de imagem:

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

ou para um botão de envio de 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"  />

Aqui está o método de extensão que você chama do controlador form.GetSubmitButtonName(). Para botões de imagem, procura um parâmetro de formulário com .x(que indica que um botão de imagem foi clicado) e extrai o nome. Para inputbotões regulares , procura um nome que comece com Submit_e extrai o comando posteriormente. Como estou abstraindo a lógica de determinar o 'comando', você pode alternar entre botões de imagem + texto no cliente sem alterar o código do 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 botões de texto, você deve prefixar o nome com Submit_. Eu prefiro assim, pois significa que você pode alterar o valor do texto (exibição) sem precisar alterar o código. Ao contrário dos SELECTelementos, um INPUTbotão possui apenas um atributo 'value' e nenhum atributo 'text' separado. Meus botões dizem coisas diferentes em contextos diferentes - mas são mapeados para o mesmo 'comando'. Eu prefiro extrair o nome dessa maneira do que ter que codificar == "Add to cart".

Simon_Weaver
fonte
Eu gosto de verificar o nome como uma alternativa, porque você nem sempre pode verificar o valor, por exemplo. você tem uma lista de itens e cada um tem um botão "Excluir".
Max
6

Não tenho representante suficiente para comentar no lugar correto, mas passei o dia todo nisso, então quero compartilhar.

Ao tentar implementar a solução "MultipleButtonAttribute" ValueProvider.GetValue(keyValue), você retornaria incorretamente null.

Aconteceu que eu estava fazendo referência à System.Web.MVC versão 3.0 quando deveria ter sido 4.0 (outros assemblies são 4.0). Não sei por que meu projeto não foi atualizado corretamente e não tive outros problemas óbvios.

Portanto, se o seu ActionNameSelectorAttributenão estiver funcionando ... verifique isso.

HeatherD
fonte
6

Estou muito atrasado para a festa, mas aqui vai ... Minha implementação é emprestada do @mkozicki, mas requer menos strings codificadas para que você se engane. Framework 4.5+ necessário . Essencialmente, o nome do método do controlador deve ser a chave do roteamento.

Marcação . O nome do botão deve ser digitado com"action:[controllerMethodName]"

(Observe o uso do C # 6 nameof API, proporcionando referência de tipo específico para o nome do método de controlador que você deseja executar.

<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 . Observe o uso da CallerMemberNamebondade.

[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
fonte
Embora essa seja uma boa abordagem, você não poderá usar o fichário de modelo mvc embutido porque ele usa o atributo "name" dos botões.
precisa saber é o seguinte
O objetivo desta solução é uma solução alternativa para o pós-roteamento do MVC. Por favor, descreva uma melhoria.
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
fonte
Este é outro link útil que explica as diferentes formas de postagem do formulário.
Himalaya Garg
5

Tentei fazer uma síntese de todas as soluções e criei um atributo [ButtenHandler] que facilita o manuseio de vários botões em um formulário.

Eu o descrevi no CodeProject Multiple parametrizado (localizável) botões de formulário no ASP.NET MVC .

Para lidar com o caso simples deste botão:

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

Você terá algo como o seguinte método de ação:

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

Observe como o nome do botão corresponde ao nome do método de ação. O artigo também descreve como ter botões com valores e botões com índices.

codetuner
fonte
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
fonte
Você pode não ter percebido, mas essa mesma resposta já foi postada algumas vezes nesta pergunta.
Andrew Barber
2
Além disso, você parece postar respostas que contêm apenas código, sem explicações. Você consideraria adicionar alguma narrativa para explicar por que o código funciona e o que o torna uma resposta para a pergunta? Isso seria muito útil para a pessoa que fez a pergunta e para qualquer outra pessoa que aparecer.
Andrew Barber
2
Claro, funciona bem. Mas essa resposta já foi dada por outros , há muito tempo. E eles incluíram explicações sobre por que também funciona.
Andrew Barber
5

esta é a melhor maneira que eu encontrei:

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

Aqui está o 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);
           }
       }
   }

Desfrute de um futuro botão de envio múltiplo sem cheiro de código.

obrigado


fonte
Link quebrado atualmente, esta é a versão arquivada mais próxima que eu poderia encontrar: web.archive.org/web/20110706230408/http://blogs.sonatribe.com/…
Ian Kemp
Oi Ian - obrigado por desenterrar isso - eu o repostei
4

Versão modificada do HttpParamActionAttributemétodo, mas com uma correção de bug por não causar um erro nas postagens de sessão expiradas / inválidas. Para verificar se esse é um problema com o site atual, abra o formulário em uma janela e antes de clicar Saveou Publishabrir uma janela duplicada e sair. Agora volte à sua primeira janela e tente enviar seu formulário usando qualquer um dos botões. Para mim, recebi um erro, então essa alteração resolve esse problema para mim. Omiti um monte de coisas por uma questão de brevidade, mas você deve entender. As partes principais são a inclusão deActionName no atributo e garantir que o nome passado seja o nome da exibição que mostra o formulário

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

Visão

@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
fonte
3

Minha abordagem JQuery usando um método de extensão:

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

Você pode usá-lo assim:

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

E renderiza assim:

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

Para cada botão de envio, basta adicionar:

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

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

});
acidente
fonte
3

Com base na resposta mkozicki, venho com uma solução um pouco diferente. Eu ainda uso, ActionNameSelectorAttributemas precisava lidar com dois botões 'Salvar' e 'Sincronizar'. Eles fazem quase o mesmo, então eu não queria ter duas ações.

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

Visão

<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"];

Também quero ressaltar que, se as ações fizerem coisas diferentes, eu provavelmente seguiria o post mkozicki.

Petr J
fonte
1

Eu criei um método ActionButton para o HtmlHelper . Ele gerará um botão de entrada normal com um pouco de javascript no evento OnClick que enviará o formulário para o Controlador / Ação especificado.

Você usa o ajudante assim

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

isso irá gerar o seguinte HTML

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

Aqui está o código do método de extensão:

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 # (o código C # é apenas descompilado da DLL do VB, para que possa ser embelezado ... mas o tempo é tão curto :-))

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

Esses métodos têm vários parâmetros, mas, para facilitar o uso, você pode criar uma sobrecarga que utiliza apenas os parâmetros necessários.

Máx.
fonte