【问题标题】:How do you handle multiple submit buttons in ASP.NET MVC Framework?如何处理 ASP.NET MVC 框架中的多个提交按钮?
【发布时间】:2010-10-01 08:36:38
【问题描述】:

是否有一些简单的方法可以处理来自同一个表单的多个提交按钮?例如:

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

知道如何在 ASP.NET Framework Beta 中执行此操作吗?我搜索过的所有示例中都有一个按钮。

【问题讨论】:

标签: c# html asp.net-mvc http-post form-submit


【解决方案1】:

这是一个非常干净的基于属性的解决方案,主要基于来自Maarten Balliauw 的帖子和 cmets。

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

剃须刀:

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

和控制器:

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

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

更新:Razor pages 看起来提供了开箱即用的相同功能。对于新的开发,它可能更可取。

【讨论】:

  • 我发现这个解决方案是其他技术的完美结合。完美运行,不影响本地化。
  • 是啊啊啊啊。适用于 MVC 4.5。另一个似乎不起作用。很棒的 +1
  • 这种方法的一个问题是,如果您在模型有错误的情况下尝试return View(viewmodel),它将尝试返回一个名为Send 的视图,或者取决于您的参数名称是什么.
  • @Shoe - 刚刚发现了一个类似的东西。如果您使用此方法,请确保明确指定要返回的视图的名称:return View("Index", viewModel)
  • 只是一个信息,我们需要为MethodInfo添加system.Reflection
【解决方案2】:

为您的提交按钮命名,然后在您的控制器方法中检查提交的值:

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

发帖到

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

}

编辑:

要将此方法扩展为使用本地化网站,请将您的消息隔离在其他地方(例如,将资源文件编译为强类型资源类)

然后修改代码,使其工作如下:

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

你的控制器应该是这样的:

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

【讨论】:

  • 太糟糕了,你依赖于按钮上显示的文本,多语言用户界面有点棘手
  • switch/case 只适用于常量,所以本地化版本不能使用 switch/case。您需要切换到 if else 或其他一些调度方法。
  • 您应该使用
  • 如何将模型传递给操作,而不仅仅是提交值?
  • 如果您使用 jQuery,请注意不要将按钮命名为“动作”。它会导致库内发生冲突,从而破坏操作 URL。
【解决方案3】:

您可以检查前面提到的动作中的名称,但您可能会考虑这是否是好的设计。考虑动作的责任而不是将这种设计过多地与按钮名称等 UI 方面结合起来是一个好主意。所以考虑使用 2 个表单和 2 个动作:

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

此外,在“取消”的情况下,您通常只是不处理表单,而是转到一个新的 URL。在这种情况下,您根本不需要提交表单,只需要一个链接:

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

【讨论】:

  • 当您不需要为每个提交按钮提供相同的表单数据时,这是可以的。如果您需要通用格式的所有数据,那么 Dylan Beattie 是您的最佳选择。有没有更优雅的方法来做到这一点?
  • 关于视觉呈现,在这种情况下,“取消”按钮旁边的“发送”按钮如何?
  • Dylan:对于取消按钮,您根本不需要提交数据,将控制器耦合到 UI 元素是一种不好的做法。但是,如果您可以创建或多或少通用的“命令”,那么我认为没问题,但我不会将它与“submitButton”联系起来,因为那是 UI 元素的名称。
  • @Kris:您可以使用 CSS 定位按钮,它们仍然可以驻留在 2 个不同的表单部分中。
  • 认真的吗?除了我,这对任何人都没有味道吗?!
【解决方案4】:

Eilon 建议您可以这样做:

如果你有多个按钮,你 可以通过给出来区分它们 每个按钮都有一个名字:

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

在您的控制器操作方法中,您 可以添加以 HTML 输入标签名称:

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

如果任何值被发布到其中之一 那些参数,这意味着 按钮是被点击的那个。 网络浏览器只会发布一个值 对于被点击的 one 按钮。 所有其他值都将为空。

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

我喜欢这种方法,因为它不依赖于提交按钮的 value 属性,该属性比分配的名称更有可能发生变化,并且不需要启用 javascript

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

【讨论】:

  • 如果有人遇到这个老问题,如果您不想使用 HTML5
  • 如果在这个表单上创建一个 ajax 调用,你会怎么做。似乎 form.serialize() 没有获取提交按钮名称..
  • @Kugel 是对的,这仍然是最干净的答案。谢谢
【解决方案5】:

刚刚写了一篇关于这个的帖子: Multiple submit buttons with ASP.NET MVC:

基本上,我不使用ActionMethodSelectorAttribute,而是使用 ActionNameSelectorAttribute,这让我可以假装动作名称是我想要的任何名称。幸运的是,ActionNameSelectorAttribute 不只是让我指定动作名称,而是我可以选择当前动作是否匹配请求。

所以有我的班级(顺便说一句,我不太喜欢这个名字):

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

要使用,只需定义这样的表单:

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

和控制器有两种方法

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

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

如您所见,该属性根本不需要您指定任何内容。此外,按钮的名称直接转换为方法名称。此外(我没有尝试过)这些也应该像正常操作一样工作,所以你可以发布到其中的任何一个 直接。

【讨论】:

  • 漂亮!我认为这是最优雅的解决方案。它消除了submit 标签的,这是理想的,因为它是一个纯UI 属性,应该与控制流无关。相反,每个 submit 标签的唯一 name 属性会直接转换为控制器上的离散操作方法。
  • +1 对我来说,这是迄今为止解决此问题的最佳解决方案。自从我实现它以来,我注意到大量流量通过 HttpParamActionAttribut,但与 Asp.Net MVC 在处理请求时必须做的所有其他事情相比,它是完全可以接受的。我唯一要做的就是在我的控制器中放置一个名为“Action”的空“Action”,以防止 Resharper 警告我“Action”操作不存在。非常感谢!
  • 我查看了所有的解决方案,也同意这是一个优雅、简单的解决方案。伟大的 bc 没有条件语句并且很健壮,当您有一个新按钮时,您可以在其中定义一个新的控制器操作。调用我的类 MultiButtonActionHandler 仅供参考 ;-)
【解决方案6】:

它是短而套房:

Jeroen Dop已回复

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

并在后台代码中这样做

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

祝你好运。

【讨论】:

  • 太棒了。正是我以前在网络表单中所做的。干杯哥们
  • 比上面的答案简单多了!谢谢!
【解决方案7】:

我建议有兴趣的人have a look at Maarten Balliauw's solution。我认为它非常优雅。

如果链接消失,它会使用应用于控制器操作的MultiButton 属性来指示该操作应与哪个按钮单击相关。

【讨论】:

  • 这是我们现在使用的解决方案,非常简洁。它只是 MVC 2 吗?
  • 这太美了!我以前没见过这个!虽然我同意您可能想要重新设计任何使用多个提交的解决方案以仅使用一个按钮,但我处于一个我受不了的地方,必须这样做。这个答案应该赢了!
  • 这是一个很好的解决方案。很干净
  • 尝试了这种方法,但无法让它在 MVC3 中工作。 #1 投票者的变体对我有用。
  • 短小精悍.. 但不适用于 mvc 3+
【解决方案8】:

您应该能够为按钮命名并赋予它们一个值;然后将此名称作为参数映射到操作。或者,使用 2 个单独的操作链接或 2 个表单。

【讨论】:

  • 迄今为止我见过的最干净、最简单的解决方案。
【解决方案9】:

你可以写:

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

然后在页面中检查 name == "Send" 或 name == "Cancel"...

【讨论】:

  • 虽然这行得通,但我认为让两个元素同名是错误的做法。
  • 没必要错。这取决于您如何使用输入。您可以拥有多个具有相同名称的元素,并期望接收多个数据(这就是单选按钮和复选框的工作方式)。但是,是的,如果你使用这种方法是因为你做的“错误”......这就是为什么我说“你可以”而不是“你应该”:P
【解决方案10】:

我不喜欢 ActionSelectName 的一点是,控制器中的每个操作方法都会调用 IsValidName;我不知道为什么它会这样工作。我喜欢这样一种解决方案,其中每个按钮根据其功能具有不同的名称,但我不喜欢动作方法中的参数必须与表单中的按钮一样多的事实。我为所有按钮类型创建了一个枚举:

public enum ButtonType
{
    Submit,
    Cancel,
    Delete
}

我使用 ActionFilter 代替 ActionSelectName:

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

过滤器会在表单数据中找到按钮名称,如果按钮名称与枚举中定义的任何按钮类型匹配,它将在动作参数中找到 ButtonType 参数:

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

然后在视图中,我可以使用:

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

【讨论】:

    【解决方案11】:

    以下是最适合我的方法:

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

    【讨论】:

    • 我在控制器方法中得到了 onDelete 和 onSave 的空值。你知道为什么吗?
    • 如果您单击相应的按钮,任何一个都不会为空。你点击哪个按钮得到空值?
    【解决方案12】:

    如果您的浏览器支持输入按钮的属性formaction(IE 10+,不确定其他浏览器),那么以下应该可以工作:

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

    【讨论】:

    • 看看我下面的答案,它不依赖于草案规范。您的回答确实允许有不同的操作网址,而我的则没有。
    【解决方案13】:

    我也遇到过这个“问题”,但通过添加name 属性找到了一个相当合乎逻辑的解决方案。我不记得在其他语言中遇到过这个问题。

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

    • ...
    • 如果一个表单包含多个提交按钮,只有激活的提交按钮是成功的。
    • ...

    意思是下面的代码value 属性可以被更改、本地化、国际化,而需要额外的代码检查强类型资源文件或常量。

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

    在接收端,您只需检查是否有任何已知的提交类型不是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
    
        }
    
    }
    

    【讨论】:

    • 这是我们选择用于一个简单的 Web 应用程序的解决方案,我们需要 ASP.NET WebForms 功能但在 MVC 中。
    【解决方案14】:

    如果对HTML 5的使用没有限制,可以使用&lt;button&gt;标签和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>
    

    参考:http://www.w3schools.com/html5/att_button_formaction.asp

    【讨论】:

      【解决方案15】:

      您可以通过三种方式解决上述问题

      1. HTML 方式
      2. jquery方式
      3. “ActionNameSelectorAttribute”方式

      下面是一个视频,以演示的方式总结了所有三种方法。

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

      HTML方式:-

      在 HTML 方式中,我们需要创建两个表单并在每个表单中放置“提交”按钮。并且每个表单的动作都会指向不同/各自的动作。您可以看到下面的代码,第一个表单发布到“Action1”,第二个表单将发布到“Action2”,具体取决于单击了哪个“提交”按钮。

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

      Ajax 方式:-

      如果您是 Ajax 爱好者,那么第二个选项会让您更加兴奋。在 Ajax 方式中,我们可以创建两个不同的函数“Fun1”和“Fun1”,见下面的代码。这些函数将使用 JQUERY 或任何其他框架进行 Ajax 调用。这些函数中的每一个都与“提交”按钮的“OnClick”事件绑定。这些函数中的每一个都调用相应的操作名称。

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

      使用“ActionNameSelectorAttribute”:-

      这是一个很棒且干净的选择。 “ActionNameSelectorAttribute”是一个简单的属性类,我们可以在其中编写决策逻辑来决定可以执行的操作。

      所以第一件事是在 HTML 中,我们需要将正确的名称放在提交按钮上,以便在服务器上识别它们。

      您可以看到我们在按钮名称中添加了“保存”和“删除”。您还可以注意到在操作中我们刚刚输入了控制器名称“客户”而不是特定的操作名称。我们预计动作名称将由“ActionNameSelectorAttribute”决定。

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

      因此,当单击提交按钮时,它首先会点击“ActionNameSelector”属性,然后根据触发的提交来调用相应的操作。

      所以第一步是创建一个继承自“ActionNameSelectorAttribute”类的类。在这个类中,我们创建了一个简单的属性“Name”。

      我们还需要重写返回 true 或 flase 的“IsValidName”函数。这个函数是我们编写一个动作是否必须执行的逻辑的地方。因此,如果此函数返回 true,则执行该操作,否则不执行。

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

      上述函数的核心在下面的代码中。 “ValueProvider”集合包含表单中发布的所有数据。因此它首先查找“Name”值,如果在 HTTP 请求中找到它,则返回 true,否则返回 false。

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

      然后可以在相应的操作上装饰此属性类,并可以提供相应的“名称”值。因此,如果提交正在执行此操作,并且名称与 HTML 提交按钮名称匹配,则它会进一步执行操作,否则不执行。

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

      【讨论】:

        【解决方案16】:

        David Findley 在他的 ASP.Net 网络博客上写了关于执行此操作的 3 种不同选择。

        阅读文章multiple buttons in the same form 了解他的解决方案,以及每种解决方案的优缺点。恕我直言,他提供了一个非常优雅的解决方案,它利用了您装饰动作的属性。

        【讨论】:

          【解决方案17】:

          这是我会使用的技术,但我还没有在这里看到。链接(由 Saajid Ismail 发布 ) 激发了这个解决方案的是http://weblogs.asp.net/dfindley/archive/2009/05/31/asp-net-mvc-multiple-buttons-in-the-same-form.aspx)。它改编了 Dylan Beattie 的回答,可以毫无问题地进行本地化。

          在视图中:

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

          在控制器中:

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

          【讨论】:

          • 看起来像 Ironicnet 提供的解决方案。
          • 当然相似,但这显示了本地化和控制器代码,这是我在这个线程中没有看到的。我在寻找如何做到这一点时发现了这个线程,并想记录我为可能在同一条船上的其他人提出的建议。
          • 事实上,它与 Ironicnet 的不一样。他使用&lt;input&gt; 元素。我使用&lt;button&gt;,这是在没有变量值属性的情况下进行本地化所必需的。
          【解决方案18】:

          此脚本允许指定一个 data-form-action 属性,该属性将在所有浏览器中用作 HTML5 formaction 属性(以不显眼的方式):

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

          包含按钮的表单将被发送到 data-form-action 属性中指定的 URL:

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

          这需要 jQuery 1.7。对于以前的版本,您应该使用live() 而不是on()

          【讨论】:

            【解决方案19】:
            [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>
            
                }
            

            【讨论】:

            • 这是另一个有用的link,解释了表单发布的不同方式。
            【解决方案20】:

            这是我编写的用于处理多个图像和/或文本按钮的扩展方法。

            这是图像按钮的 HTML:

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

            或者对于文本提交按钮:

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

            这是你从控制器调用的扩展方法form.GetSubmitButtonName()。对于图像按钮,它会查找带有.x 的表单参数(表示单击了图像按钮)并提取名称。对于常规的input 按钮,它会查找以Submit_ 开头的名称,然后从中提取命令。因为我正在抽象出确定“命令”的逻辑,所以您可以在客户端上的图像+文本按钮之间切换,而无需更改服务器端代码。

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

            注意:对于文本按钮,您必须在名称前加上 Submit_。我更喜欢这种方式,因为这意味着您可以更改文本(显示)值而无需更改代码。与SELECT 元素不同,INPUT 按钮只有一个“值”,没有单独的“文本”属性。我的按钮在不同的上下文中说不同的东西 - 但映射到相同的“命令”。我更喜欢以这种方式提取名称,而不是必须为== "Add to cart" 编码。

            【讨论】:

            • 我喜欢检查名称作为替代方法,因为您不能总是检查例如的值。您有一个项目列表,每个项目都有一个“删除”按钮。
            【解决方案21】:

            我没有足够的代表在正确的地方发表评论,但是我花了一整天的时间来分享。

            在尝试实现“MultipleButtonAttribute”解决方案时,ValueProvider.GetValue(keyValue) 会错误地返回 null

            原来我引用的是 System.Web.MVC 版本 3.0,而它应该是 4.0(其他程序集是 4.0)。我不知道为什么我的项目没有正确升级,也没有其他明显的问题。

            所以如果你的ActionNameSelectorAttribute 不工作...检查一下。

            【讨论】:

              【解决方案22】:

              我参加聚会已经很晚了,但是这里... 我的实现借鉴了@mkozicki,但需要较少的硬编码字符串才能出错。 需要框架 4.5+。本质上,控制器方法名称应该是路由的关键。

              标记。按钮名称必须用"action:[controllerMethodName]" 键入

              (注意 C#6 nameof API 的使用,它为您希望调用的控制器方法的名称提供特定类型的引用。

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

              控制器

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

              属性魔法。注意CallerMemberNamegoodness 的使用。

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

              【讨论】:

              • 虽然这是一个不错的方法,但您将无法使用内置的 mvc 模型绑定器,因为它使用了按钮的“名称”属性。
              • 此解决方案的目的是解决 MVC 后路由。请描述一项改进。
              【解决方案23】:

              我尝试综合所有解决方案并创建了一个 [ButtenHandler] 属性,可以轻松处理表单上的多个按钮。

              我已经在 CodeProject Multiple parameterized (localizable) form buttons in ASP.NET MVC 上描述了它。

              处理这个按钮的简单情况:

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

              你会有类似下面的动作方法:

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

              注意按钮的名称与操作方法的名称是如何匹配的。本文还介绍了如何使按钮具有值和具有索引的按钮。

              【讨论】:

                【解决方案24】:
                //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();
                        }
                

                【讨论】:

                • 您可能还没有意识到,但同样的答案已经在这个问题上发布了好几次了。
                • 另外,您似乎发布的答案只包含代码,没有任何解释。您是否会考虑添加一些叙述来解释代码为何有效,以及是什么使它成为问题的答案?这对提出问题的人以及其他任何出现的人都非常有帮助。
                • 当然,它工作正常。但这个答案已经其他人已经给出了,很久以前。他们还解释了为什么它也有效。
                【解决方案25】:

                这是我找到的最好的方法:

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

                代码如下:

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

                享受无代码异味的多提交按钮的未来。

                谢谢

                【讨论】:

                【解决方案26】:

                HttpParamActionAttribute 方法的修改版本,但修复了在过期/无效会话回发时不会导致错误的错误。要查看这是否是您当前站点的问题,请在一个窗口中打开您的表单,然后在您点击SavePublish 之前,打开一个重复的窗口,然后注销。现在回到您的第一个窗口并尝试使用任一按钮提交您的表单。对我来说,我遇到了一个错误,所以这个更改为我解决了这个问题。为了简洁起见,我省略了一堆东西,但你应该明白这一点。关键部分是在属性中包含ActionName,并确保传入的名称是显示表单的视图的名称

                属性类

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

                控制器

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

                查看

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

                【讨论】:

                  【解决方案27】:

                  我使用扩展方法的 JQuery 方法:

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

                  你可以这样使用它:

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

                  它呈现如下:

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

                  【讨论】:

                    【解决方案28】:

                    对于每个提交按钮,只需添加:

                    $('#btnSelector').click(function () {
                    
                        $('form').attr('action', "/Your/Action/);
                        $('form').submit();
                    
                    });
                    

                    【讨论】:

                      【解决方案29】:

                      根据 mkozicki 的回答,我想出了一些不同的解决方案。我仍然使用ActionNameSelectorAttribute 但我需要处理两个按钮“保存”和“同步”。他们做的几乎一样,所以我不想有两个动作。

                      属性

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

                      查看

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

                      控制器

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

                      我还想指出,如果动作做不同的事情,我可能会关注 mkozicki 的帖子。

                      【讨论】:

                        【解决方案30】:

                        我为 HtmlHelper 创建了一个 ActionButton 方法。它将在 OnClick 事件 中生成带有一点 javascript 的普通输入按钮,将表单提交到指定的控制器/动作。

                        你使用这样的助手

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

                        这将生成以下 HTML

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

                        这里是扩展方法代码:

                        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#(C#代码只是从VB DLL反编译出来的,所以可以美化一下……不过时间太短了:-))

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

                        这些方法有各种参数,但为了便于使用,您可以创建一些重载,只采用您需要的参数。

                        【讨论】:

                          猜你喜欢
                          • 2011-02-07
                          • 2016-08-02
                          • 2015-05-06
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          相关资源
                          最近更新 更多