jmogera
jmogera

Reputation: 1759

Wizard in ASP.NET MVC3 and Multiple HttpPost

I am using the Wizard control described in http://afana.me/post/create-wizard-in-aspnet-mvc-3.aspx

It works great, but I need to have Multiple HttpPost within the same Controller. In my scenario, I need to add to a collection before moving to next step. In the partial view for that step. I have following set up:

@using (Html.BeginForm("AddJobExperience", "Candidate"))
{
   <input type="submit" value="Add Experience" />
}

When I press the Add Experience input, it is routed to the

[HttpPost, ActionName("Index")]
public ActionResult Index(CandidateViewModel viewModel)
{
}

instead of

[HttpPost, ActionName("AddJobExperience")]
public ActionResult AddJobExperience(CandidateViewModel col)
{

}

what am I doing wrong?

Upvotes: 2

Views: 1544

Answers (3)

Shivkumar
Shivkumar

Reputation: 1903

you need to use ActionMethodSelectorAttribute or ActionNameSelectorAttribute which allow to add new attribute on action to call different action on respective of button click In View:

@using (Html.BeginForm())
{
   <input type="submit" value="Add Experience" name="AddExperience" />
   <input type="submit" value="Add Experience" name="AddJobExperience" />
}

add new class FormValueRequiredAttribute in application which extend ActionMethodSelectorAttribute class to check on which button is clicked

//controller/FormValueRequiredAttribute.cs

public class FormValueRequiredAttribute :  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]);
        }

    }

then you should add this attribute on action to call corresponding action

In Controller

[HttpPost]
[FormValueRequired(ButtonName = "AddExperience")]
public ActionResult Index(CandidateViewModel viewModel)
{
  return View();
}

[HttpPost]
[ActionName("Index")]
[FormValueRequired(ButtonName = "AddJobExperience")]
public ActionResult AddJobExperience_Index(CandidateViewModel viewModel)
{
  return View();
}

Note if your Html.BeginForm method in Index.cshtml then you don't need specify ActionName attribute on Index Action, now AddJobExperience_Index act same as Index Action.

Upvotes: 1

StanK
StanK

Reputation: 4770

It sounds like you need to break up your CandidateViewModel into separate ViewModels and your big Wizard View into separate Views so that there is one per action for each step of the wizard.

Step one they add the job experience, so have a view, viewmodel and an action for that, Step two they do whatever else and you have a separate view, viewmodel and action for that as well. etc, etc

Breaking up your CandidateViewModel into separate ViewModels will mean that you can just focus on the data required for that step, and can add the validation, then when they click submit, it posts the data to the next step.

Then, when you want to improve the UI behaviour, add some AJAX, and maybe use something like JQuery UI Tabs to make it behave more like a wizard in a desktop app.

Upvotes: 1

danludwig
danludwig

Reputation: 47375

It sounds like you still have nested forms. Don't do this, it is not valid HTML.

You have 2 options here, depending on what you are trying to achieve. If you want to post your job experiences separately one at a time, then put them in their own @using(Html.BeginForms, but don't nest them in an outer form. When a user clicks the Add Experience button, do your work on that one experience and then return a new view to the user.

If you want to post all of the job experiences at the same time, then wrap all of them in a single @using(Html.BeginForm and do not put @using(Html.BeginForm in your partial views. See another question I answered here for more info on how to post a collection of items in a single HTTP POST.

The 2nd method is what it sounds like you are trying to achieve, and for this, you should probably use AJAX to add multiple job experiences to your collection without doing a full postback. You can then do 1 HTTP POST to submit all job experiences in the collection to your wizard controller. It's not very difficult to implement a feature like this:

Given I can see the Add Experience button
When I click the Add Experience button
Then I should see a new experience partial view with input fields
And I should enter data in these fields

When I click the Add Experience button a second time
Then I should see another new experience partial view with input fields
And I should enter data in these fields

When I click the Add Experience button a third time
Then I should see a third new experience partial view with input fields
And I should enter data in these fields

When I click the Next button in the wizard
Then my controller will receive data for all 3 experiences I submitted in a single form

Upvotes: 1

Related Questions