32

I'm using MVC3 Razor. I have 2 submit buttons setup on my view but the problem I'm having is that both submit buttons cause the validation of the model. I want to hook up individual submit buttons with specific input controls for validation.

greatwolf
  • 18,899
  • 13
  • 64
  • 102
sandeep
  • 675
  • 2
  • 11
  • 13

8 Answers8

64

I know this is a few months old but the solutions here seemed needlessly complex and there's no accepted answer yet. If you name your inputs the same but give them different values, you can get that value in your controller just by including a string with the name of the input as a variable. This is how I solved this problem:

View:

 <input type="submit" id="EnterprisePush" name="btnSubmit" value="Push" />
 <input type="submit" id="EnterprisePull" name="btnSubmit" value="Pull" />

Controller:

[HttpPost]
public ActionResult EnterpriseAdmin(int id, string btnSubmit, FormCollection collection)
{
  switch (btnSubmit) {
    case "Push":
      /* Do Something here */
      break;
    case "Pull":
      /* Do Something else here */
      break;
  }
Joe Ratzer
  • 16,789
  • 3
  • 34
  • 49
Rojzik
  • 882
  • 8
  • 8
  • 2
    This should be marked as an answer. I also found that this solution works well with custom validation. – broguyman Oct 12 '12 at 17:52
  • 2
    Just to add to you solution, this can be done with regular buttons as well: `` and then use `RemoteControl(string Command)` for the method. I had a case where the "Command" was different from what I wanted displayed. – Mason240 Apr 10 '13 at 20:51
  • This doesn't answer the question the OP asked. He asked how to have conditional validation, not how to have multiple submit buttons. He already has multiple submit buttons working. – Jason Cheng Aug 01 '18 at 16:26
30

The browser is always going to submit the entire form regardless of which submit button you press.

The best solution would be to have two submit buttons with the same value for the name attribute and different values for the value attributes.

When you submit the form, the value of the button will be submitted as well. In your action which handles that form submission, you check to see the value of the button and perform the correct validation based on that.

In your form you would have something like this:

<button type="submit" name="Command" value="command1">Do Command #1</button>
<button type="submit" name="Command" value="command2">Do Command #2</button>

Your Form Model would look like this:

public class MyFormModel() {
    public string Command {get;set;}
    public string SomeOtherVal {get;set;}
}

Your controller\action would look like this:

public ActionResult HandleFormSubmit(MyFormModel model) {
    if (model.Command == "command1") {
        // do something
    } else if (model.Command == "command2") {
        // do something else
    }
}
Bryan Migliorisi
  • 8,274
  • 3
  • 30
  • 43
  • In your example the MyFormModel is a parameter for Action. In this case model validating is already done. You should use the FormCollection instead the MyFormModel to make this examlpe work – Anubis Jun 19 '11 at 06:43
  • Not sure what you mean. I think you mean model binding and yes you are right, it happens already, but it just binds any string value to `Command` property, irrespective of the value. All we are doing inside the action is checking *which* value it is and acting based upon that. – Bryan Migliorisi Jun 19 '11 at 14:39
  • I mean that if you check ModelState.IsValid in your code lines before if (model.Command == "command1") you can see that Command or SomeOtherVal appears invalid. I mean that in spite of we submit just one model the second can be invalid and user vill see that in ValidationSummary and this will look confusing – Anubis Jun 21 '11 at 07:38
  • I suppose you could have a second parameter to the action method which contains the Command property rather than having it inside the form model. This way the form model validation would be separate from the command. – Bryan Migliorisi Jun 21 '11 at 11:48
  • While this is a neat solution, I think it's worth mentioning that IE6 and IE7 have some major issues with the button tag. See this http://stackoverflow.com/q/1903453 where it concludes "Button tag: not worth the trouble, just use input for now." – Daniel Liuzzi Jun 27 '11 at 20:27
  • I suppose the same thing could be achieved with an `input` element of type `submit` – Bryan Migliorisi Jun 28 '11 at 14:02
  • You could also get the value from the `Request.Form` instead of changing the model. This way you would avoid mixing UI behavior with you data representation. – Felipe Sabino Mar 09 '12 at 22:58
21

Firstly, you can disable client validation on your cancel button simply by adding the CSS class 'cancel' to it. See: Disable client-side validation in MVC 3 "cancel" submit button

Secondly, as well testing the submit element's form name as described above, you can use a custom action selector. Here's mine, which I originally took from the blog post shown in the comment:

/// <summary>
/// Used to vary an action method based on which button in a form was pressed. This
/// is useful but is an anti-pattern because it couples the controller to names
/// used in the form elements. 
/// </summary>
/// <remarks>
/// See the example at http://weblogs.asp.net/dfindley/archive/2009/05/31/asp-net-mvc-multiple-buttons-in-the-same-form.aspx
/// </remarks>
public class AcceptButtonAttribute : ActionMethodSelectorAttribute
{
    public string ButtonName { get; set; }

    public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
    {
        var req = controllerContext.RequestContext.HttpContext.Request;
        return !string.IsNullOrEmpty(req.Form[this.ButtonName]);
    }
}

In your controller:

    [HttpPost]
    [ActionName("Edit")]
    [AcceptButton(ButtonName = "Cancel")]
    public ActionResult Edit_Cancel(MyModel model)
    {
        return RedirectToAction("Index");
    }

    [HttpPost]
    [AcceptButton(ButtonName = "Save")]
    public ActionResult Edit(MyModel model)
    {
        // do real work here
    }

Note that you need the [ActionName("Edit")] attribute to tell MVC that although using a different method name, it is for the Edit action.

And in your View:

    <input type="submit" name="Save" value="Save" />
    <input type="submit" name="Cancel" value="Cancel" class="cancel" />
Community
  • 1
  • 1
Rob Kent
  • 5,093
  • 4
  • 31
  • 52
  • 2
    Wow, this is really clean. Not only should this be marked as the answer, this should be included in the MVC framework. You should consider submitting a pull request for MVC4. I don't care if this is an anti-pattern or not, it is incredibly useful!!! – Dan VanWinkle Apr 03 '12 at 16:09
  • 2
    I Agree with @DanVanWinkle. This is useful in certain cases. +1 – Andrew Grothe Apr 12 '12 at 00:05
  • 1
    This is worth being the answer! – Marc Feb 06 '13 at 22:04
3

My solution was to do two things. Say we have a Save button and another Add Something button. When user clicks on Save we want client validation and server validation to be performed. For later button we don't want any validation to take place.

  1. Temporarily disable client validation for second button (on click):

<input type="submit" name="submit-button" value="Save" />

<input type="submit" name="submit-button" value="Add Something" onclick="document.forms[0].noValidate = true; document.forms[0].submit();" />

Good thing about it is when JavaScript is disabled the client validation would never have taken place anyway.

  1. Take care of server side

Similar to what Bryan is saying when you click any submit button within a form, the entire form and the clicked submit button value is posted. You can differentiate which button was clicked by the name posted. In example above when user clicks on Save button and we read Request.Form["submit-button"] in controller post action we get "Save". If user clicked on Add Something we would get "Add Something". This is the way HTML is supposed to work.

Now to get around having magic strings all over the place I usually have a public static class within the controller, like so:

public class HomeController
{
    public static class Buttons
    {
        public const string Save = "Save";
        public const string AddSomething = "Add something";
    }
    // Action methods
}

So you can use these for rendering form:

&lt;input type="submit" name="submit-button" value="@HomeController.Buttons.Save" /&gt;

And you can easily read the button clicked in controller:

[HttpPost]
public ActionResult Index(Model viewModel)
{
    var buttonClicked = Request.Form["submit-button"];
    switch (buttonClicked) {
        case HomeController.Buttons.Save:
            return Save(viewModel);
        case HomeController.Buttons.AddSomething:
            return AddSOmething(viewModel);
    }
    return View();
}

In Save method you first ask if ModelState.IsValid and return view model if not but in AddSomething method we will clear any errors:

public ActionResult AddSomething(Model viewModel)
{
    ModelState.Clear();
    // your code to add something to model
    return View(viewModel);
}

This was you keep everything clean, tidy and testable. And you can introduce a constant for submit-button html name attribute. It might be possible to do all the constants with T4MVC too. A similar solution applies to when you need a "auto postback" combo box, except you need a hidden field that is set via onchange event of the select element.

Hope this helps.

NMathur
  • 809
  • 1
  • 17
  • 33
Ales Potocnik Hahonina
  • 2,537
  • 1
  • 24
  • 32
2

Just use this code as a template:

@{
    var nextButtonVal = "Next >>";
    var backButtonVal = "<< Back";
    if (IsPost) {
      if(Request["navigate"].Equals(backButtonVal)){Response.Redirect("~/pageFoo");}
      if(Request["navigate"].Equals(nextButtonVal)){Response.Redirect("~/pagebar");}
    }
}

<input type="submit" value="@backButtonVal" title="Back" name="navigate"/>
<input type="submit" value="@nextButtonVal" title="Next" name="navigate"/>
Chad Brewbaker
  • 2,387
  • 2
  • 18
  • 24
1

One final thing I would do is instead of using intelligent strings, use an enum to determine the value for each input tag. Using razor syntax:

@Enum.GetName(typeof(YourEnumType), yourEnum.WhateverValue)

then in your controller:

public ActionResult DoSomethingBasedOnEnumValue(string enumValue)
{
    YourEnumType localVar = (YourEnumType)Enum.Parse(typeof(YourEnumType), enumValue);

    switch(localVar)
    {
        case YourEnumType.Action1:
            //do something
            break;

        case YourEnumType.Action2:
            //do something else
            break;
    }
    return View();
}
David
  • 176,566
  • 33
  • 178
  • 245
0

If you want to have separate action for delete, try this.

add a delete action in the controller and mark it as HttpDelete,

[HttpDelete]
public ActionResult Edit(int id, string foo) {
   ...
}

And in the view, button name should be X-HTTP-Method-Override and value should be DELETE

<button name="X-HTTP-Method-Override" value="DELETE" formnovalidate="formnovalidate" class="cancel">Delete</button>

note: all most all the browsers don't allow for other HTTP methods, like HEAD, PUT, or DELETE. but by add a header to the HTTP request, X-HTTP-Method-Override, that is supposed to be interpreted by the service and acted upon regardless of the actual HTTP method used. So above code will add a header to the request like X-HTTP-Method-Override: DELETE. and .net framework will do the rest of the things and direct you to delete action.

Chamika Sandamal
  • 22,027
  • 5
  • 58
  • 81
0

Submit button name don't come to server side if in all from this situation you are will be use [Remote] attribute for validation model property.