5

Can someone explain to me why this doesn't work? I've read lots of posts about multiple submit buttons on one page, but I don't understand what's going on in the background. I've tried to follow what I see others doing in those posts, but none of it seems to work. I'm just trying to understand what is really going on here. I suspect it has something to do with what exactly happens when a page is posted.

Both buttons do a completely different thing. The first button posts the form. In the form's post code, it is then redirected to Vehicle/Index. The second button does not post the form, and goes directly to /Vehicle/CreateNewVehiclePart.

Here is what I want. I want both buttons to submit the form (so I can perform validation and save the changes to the Vehicle before moving on). Once the Vehicle data is saved, I want to move to /Vehicle/AddPartsToVehicle or /Vehicle/CreateNewVehiclePart based upon which button is clicked.

<table class="layouttable">
    <tr>
        <td>
            @using (Html.BeginForm("AddPartsToVehicle", "Vehicle", FormMethod.Post, null))
            {
                <input type="submit" value="Add Parts To Vehicle" />
            }
        </td>
        <td>
            @using (Html.BeginForm("CreateNewVehiclePart", "Vehicle", FormMethod.Post, null))
            {
                <input type="submit" value="Create New Vehicle Part" />
            }
        </td>
    </tr>
</table>

Here is the Vehicle model:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.Globalization;


namespace ShopLog.Models
{
    public class Vehicle
    {
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid VehicleID { get; set; }

    [Timestamp]
    public Byte[] Timestamp { get; set; }

    [Required]
    [Display(Name = "Vehicle Name")]
    [MaxLength(30, ErrorMessage = "The {0} must be no more than {2} characters long.")]
    public string VehicleName { get; set; }

    [Display(Name = "Assigned To")]
    [MaxLength(30, ErrorMessage= "The {0} must be no more than {2} characters long.")]
    public string AssignedTo { get; set; }

    [Display(Name = "ID Number")]
    [MaxLength(30, ErrorMessage = "The {0} must be no more than {2} characters long.")]
    public string IDNumber { get; set; }

    [Display(Name = "Active")]
    public bool Active { get; set; }

    [Display(Name = "Sign Out Vehicle")]
    public bool SignOutVehicle { get; set; }

    [Display(Name = "Have Title")]
    public bool HaveTitle { get; set; }

    [Display(Name = "Mileage or Hours")]
    public int? MileageOrHours { get; set; }

    [Display(Name = "Make")]
    [MaxLength(30, ErrorMessage = "The {0} must be no more than {2} characters long.")]
    public string Make { get; set; }

    [Display(Name = "Model")]
    [MaxLength(30, ErrorMessage = "The {0} must be no more than {2} characters long.")]
    public string Model { get; set; }

    [Display(Name = "Year")]
    public int? Year { get; set; }

    [Display(Name = "Engine")]
    [MaxLength(30, ErrorMessage = "The {0} must be no more than {2} characters long.")]
    public string Engine { get; set; }

    [Display(Name = "Transmission Gears")]
    public int? TransmissionGears { get; set; }

    [Display(Name = "VIN")]
    [MaxLength(30, ErrorMessage = "The {0} must be no more than {2} characters long.")]
    public string VIN { get; set; }

    [Display(Name = "Serial Number")]
    [MaxLength(30, ErrorMessage = "The {0} must be no more than {2} characters long.")]
    public string SerialNumber { get; set; }

    [Display(Name = "Color")]
    [MaxLength(30, ErrorMessage = "The {0} must be no more than {2} characters long.")]
    public string Color { get; set; }

    [Display(Name = "Air Filter")]
    [MaxLength(30, ErrorMessage = "The {0} must be no more than {2} characters long.")]
    public string AirFilter { get; set; }

    [Display(Name = "Engine Oil Filter")]
    [MaxLength(30, ErrorMessage = "The {0} must be no more than {2} characters long.")]
    public string EngineOilFilter { get; set; }

    [Display(Name = "Fuel Filter")]
    [MaxLength(30, ErrorMessage = "The {0} must be no more than {2} characters long.")]
    public string FuelFilter { get; set; }

    [Display(Name = "Transmission Oil Filter")]
    [MaxLength(30, ErrorMessage = "The {0} must be no more than {2} characters long.")]
    public string TransmissionOilFilter { get; set; }

    [Display(Name = "HydraulicOilFilter")]
    [MaxLength(30, ErrorMessage = "The {0} must be no more than {2} characters long.")]
    public string HydraulicOilFilter { get; set; }

    [Display(Name = "Interior A/C Filter")]
    [MaxLength(30, ErrorMessage = "The {0} must be no more than {2} characters long.")]
    public string InteriorACFilter { get; set; }

    [Display(Name = "Differential Oil")]
    [MaxLength(30, ErrorMessage = "The {0} must be no more than {2} characters long.")]
    public string DifferentialOil { get; set; }

    [Display(Name = "Transmission Oil")]
    [MaxLength(30, ErrorMessage = "The {0} must be no more than {2} characters long.")]
    public string TransmissionOil { get; set; }

    [Display(Name = "Engine Oil")]
    [MaxLength(30, ErrorMessage = "The {0} must be no more than {2} characters long.")]
    public string EngineOil { get; set; }

    [Display(Name = "Tire Size")]
    [MaxLength(30, ErrorMessage = "The {0} must be no more than {2} characters long.")]
    public string TireSize { get; set; }

    [Display(Name = "Tire Pressure")]
    [MaxLength(30, ErrorMessage = "The {0} must be no more than {2} characters long.")]
    public string TirePressure { get; set; }

    [Display(Name = "Smog Due Date")]
    [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:d}")]
    public DateTime? SmogDue { get; set; }

    [Display(Name = "Registration Due Date")]
    [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:d}")]
    public DateTime? RegistrationDue { get; set; }

    [Display(Name = "Insurance Due Date")]
    [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:d}")]
    public DateTime? InsuranceDue { get; set; }

    [Display(Name = "License Plate")]
    [MaxLength(30, ErrorMessage = "The {0} must be no more than {2} characters long.")]
    public string LicensePlate { get; set; }

    [Display(Name = "Owner")]
    [MaxLength(30, ErrorMessage = "The {0} must be no more than {2} characters long.")]
    public string Owner { get; set; }

    [Display(Name = "Previous Owner")]
    [MaxLength(30, ErrorMessage = "The {0} must be no more than {2} characters long.")]
    public string PreviousOwner { get; set; }

    [Display(Name = "Date Acquired")]
    [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:d}")]
    public DateTime? DateAcquired { get; set; }

    [Display(Name = "Notes")]
    [DataType(DataType.MultilineText)]
    public string Notes { get; set; }

    [Required(ErrorMessage="Vehicle Type is required.")]
    public Guid VehicleTypeID { get; set; }
    public Guid? TransmissionTypeID { get; set; }

    //A vehicle can only have one VehicleType and one TransmissionType
    [Display(Name = "Vehicle Type")]
    public virtual VehicleType VehicleType { get; set; }

    [Display(Name = "Transmission Type")]
    public virtual TransmissionType TransmissionType { get; set; }


    //A vehicle can have many Parts
    [Display(Name = "Parts")]
    public virtual ICollection<Part> Parts { get; set; }

    //A vehicle can have many Warranties
    [Display(Name = "Warranties")]
    public virtual ICollection<Warranty> Warranties {get; set; }
}

}

Here is the Part model:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.Globalization;

namespace ShopLog.Models
{
    public class Part
    {
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid PartID { get; set; }

    public string Name { get; set; }
    public decimal Cost { get; set; }

    [DataType(DataType.MultilineText)]
    public string Notes { get; set; }

    //A Part can belong to many Vehicles
    public virtual ICollection<Vehicle> Vehicles { get; set; }
    }
}
user1304444
  • 1,713
  • 3
  • 21
  • 38
  • Try adding a name to each button. Does that help? That way the buttons should be distinct post params – TGH Mar 31 '12 at 02:17
  • name="btn1" and name="btn2" etc – TGH Mar 31 '12 at 02:19
  • For this page that contains the code you posted, I take it the model is a Vehicle class of some kind. Is that correct? And the two forms should respectively add an existing part type to the vehicle, or create a new part definition entirely? – McGarnagle Mar 31 '12 at 02:21
  • Tried naming the buttons, but that didn't change anything. – user1304444 Mar 31 '12 at 02:22
  • I think I see the error. The top form doesn't have appropriate {} open and close – TGH Mar 31 '12 at 02:25
  • @user1304444 This looks a bit too vague to answer as it stands ... Can you post what your model looks like? I think that might help. The basic problem is that you need actual data inside your forms. Right now they're empty, so you're just posting nothing back to the server. – McGarnagle Mar 31 '12 at 02:28
  • the form has the right tags in my code. I manually typed it in here. I will post the model. – user1304444 Mar 31 '12 at 02:29
  • @user1304444 ah, so you it's only a partial code sample? Can you please add ellipses where appropriate so that's clearer. – McGarnagle Mar 31 '12 at 02:32
  • Did you see this http://stackoverflow.com/questions/442704/how-do-you-handle-multiple-submit-buttons-in-asp-net-mvc-framework. It's mvc 2 but maybe useful. Looks like your solution is the same though... – TGH Mar 31 '12 at 02:34
  • dbaseman: the code that is left out is before and after the table. I only posted this code, because I thought that was all that applied. I can post the entire view if you'd like. – user1304444 Mar 31 '12 at 02:36
  • TGH: I did read that post, but I didn't find anything that solved my problem. – user1304444 Mar 31 '12 at 02:37
  • Don't overlook the possibility that it's elementary. I tend to do dumb things sometimes. – user1304444 Mar 31 '12 at 02:47
  • Just wanted to add, I think this is a really great question ... I think if I had to implement this it would involve the Ajax Forms and some hackish stuff with Javascript. Very curious to see what people come up with. – McGarnagle Mar 31 '12 at 02:47
  • Can you put the two buttons in the same form, but with different names. They will hit the same action, but the shared action will redirect2Action (to the appropriate action) based on the button that submitted the form – TGH Mar 31 '12 at 02:51
  • TGH: I tried that based upon some of the other posts I've read, but it didn't change anything. I ended up with the same behavior. – user1304444 Mar 31 '12 at 02:54
  • You should be able to test against Burton name in the controller and redirect based on which Burton is clicked. Unfortunately I'm on a phone and posting code would be difficult, but everything in your submit controller should be the same, just a different end result redirect. – Brad Christie Mar 31 '12 at 16:38

3 Answers3

1

I tried this in a simple project

@model MvcApplication2.Models.LogOnModel

@{
    ViewBag.Title = "Home Page";
}

@using (Html.BeginForm("About", "Home", FormMethod.Post, null))
{
    <input type="submit" name="btn2" value="About" />
    @Html.TextAreaFor(m => m.UserName)
}

@using (Html.BeginForm("Index", "Home", FormMethod.Post, null))
{        
    <input type="submit" name="btn1" value="Index" />
    @Html.TextAreaFor(m => m.UserName)
}

public class HomeController : Controller
{
    [HttpPost]
    public ActionResult Index(LogOnModel a)
    {
        ViewBag.Message = "Welcome to ASP.NET MVC!";


        return View();
    }

    [HttpGet]
    public ActionResult Index()
    {
        return View();
    }

    [HttpPost]
    public ActionResult About(LogOnModel a)
    {

        return View();
    }
}

From what I can tell this works out of the box. I would check your routes...

Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
TGH
  • 38,769
  • 12
  • 102
  • 135
  • I think you're code is different than mine in a few ways. I need both buttons to hit the code for the POST – user1304444 Mar 31 '12 at 03:19
  • I need the RedirectToAction() to fire after the data has been saved. – user1304444 Mar 31 '12 at 03:19
  • I am hitting the distinct posts per button. Each button posts to a different action. I updated the model with an actual object – TGH Mar 31 '12 at 03:25
  • I created a new project, and tried to get the two buttons you showed to run the POST code from the controller. I put a breakpoint in the code, and that code was never hit by either button. The button just performed a GET for the other pages-never hit the POST. – user1304444 Mar 31 '12 at 03:27
  • Made a few tweaks, but the thing is functional now. Sends the whole object as a post in both scenarios – TGH Mar 31 '12 at 03:34
  • I copied exactly what you have. I changed MVCApplication2 to TestApplication. I put a breakpoint at "return View();" in the HttpPost About(). The button on top (About) hit the POST code. The button on bottom (Index) never did. Is this a version issue? I'm using MVC3 on Visual Web Developer 2010 Express. – user1304444 Mar 31 '12 at 04:00
  • Hm.. I am using the same exact tools.... The one thing to point out is that I am using Index as my landing view... So the first thing that fires is Index(). After that I can post to both Post methods uniquely per button – TGH Mar 31 '12 at 04:02
  • hmmm...let me clarify what's going on. When I click the "About" button, the POST for About() fires. When I click the "Index" button, the POST for Index() fires. Let me see if I can tweak this to work for me. I will just need to have the two separate events both handle the "Save()" logic, and then redirect from there. – user1304444 Mar 31 '12 at 04:10
  • Yes that's what my code is doing. You can redirect to Action from both. I guess the save logic can be put in a private helper method and reused between the two action methods? – TGH Mar 31 '12 at 04:13
  • That's what I was thinking for the save code. I will try to implement this in the morning if I can't get dbaseman's solution working. – user1304444 Mar 31 '12 at 04:29
  • There is obviously something wrong with my project. Despite my best efforts, one of my buttons still posts to "Edit()", and the other one posts where it should: "CreateNewVehiclePart()". I have not changed any routes. – user1304444 Mar 31 '12 at 16:46
  • Figured it out. There was a save button at the top of the page also. Once I wrapped that button with a "@using Html.BeginForm{}" everything worked as expected. – user1304444 Mar 31 '12 at 17:40
1

Here's how I would do the second part, at least. I'll come back and update if I figure out the first. Inside your "CreateNewVehiclePart", above the submit button, render a partial view:

@using (Html.BeginForm("CreateNewVehiclePart", "Vehicle", FormMethod.Post, null))
{
    @Html.Partial("AddPart", new ShopLog.Models.Part())
    <input type="submit" value="Create New Vehicle Part" />
}

The partial view "AddPart" would be like this:

@model ShopLog.Models.Part
@Html.EditorForModel()

Then in the controller, "CreateNewVehiclePart" would handle the post-- just add the part to the database, and redirect back to your original view.

EDIT: Ok, here's what I would do for the first part, also a partial view. Setup your form with a hidden field with the Vehicle ID, and a drop-down list for the parts:

@using (Html.BeginForm("AddPartsToVehicle", "Vehicle", FormMethod.Post, null))
(
    @Html.HiddenFor(model=>model.VehicleID)
    @Html.Partial("SelectPart", PartsList)
    <input type="submit" value="Add Parts To Vehicle" />
}

(Note that "PartsList" is something you'll have to set up in your code block at the top-- just a list of all available parts.) Then the partial view "SelectPart" would be like this:

@model IEnumerable<ShopLog.Models.Part>
@Html.DropDownList("part_id", Model.Select(m=>new SelectListItem() { Text = m.Name, Value = m.PartID.ToString() }))

Then again, as before, your "AddPartsToVehicle" would handle just those two parameters, and add the part with ID part_id to the vehicle.

Obviously I have left some gaps to fill in here, but I think this is a decent approach ...

McGarnagle
  • 101,349
  • 31
  • 229
  • 260
  • Okay, I've never worked with partial views before, so I will need some help. The Vehicle/Edit view loads. I have a breakpoint at the line: @Html.Partial("SelectPart", Model.Parts); The line is hit, and there are 2 Parts in "Model.Parts". However, I never see the partial view "SelectPart." (No drop down list appears). I coded the partial view exactly as you have written it. – user1304444 Mar 31 '12 at 03:53
  • I think I see the problem. You're passing "Model.Parts" into the view, which is probably empty, no? In any case, that should be the complete list of parts for the drop-down list. What I had in mind was to create an object in the code block at the beginning of the view, like this @{ IEnumerable PartsList = (get all your parts from database, static method, cache, wherever you have them) } – McGarnagle Mar 31 '12 at 04:07
  • When my code hits the breakpoint, Model.Parts has 2 items in it. I'm having trouble with "@{IEnumerable partsList = Model.Parts}". When the page is hit, it keeps saying "error ';' expected". I tried adding a semicolon after the line, but it still gave the error. When I remove that line of code, the page loads fine (but with no partial page). – user1304444 Mar 31 '12 at 04:27
  • Ha! figured it out. I was just dumb. – user1304444 Mar 31 '12 at 04:34
  • I didn't put the "@" on the front of the "@Html.Partial()" – user1304444 Mar 31 '12 at 04:34
  • Should I use the existing Vehicle Controller to handle the posts from the buttons? – user1304444 Mar 31 '12 at 04:35
0

I ended up using a combination of the two answers on this page. The ultimate problem was that I had a Save() button at the top of the page that was was not wrapped in its own "@Using Html.BeginForm{}". "TGH" helped me to map out where my POST code was actually going. That's what led me to solve the ultimate problem.

Using partial forms really helped me to make the application more concise (fewer page redirects). "dbaseman" helped me to get these set up correctly.

Thanks to both of you!

user1304444
  • 1,713
  • 3
  • 21
  • 38