0

User requests page for Step1, fills out and submits form that contains selected person, so far so good. After validation of ModelState the next viewmodel is constructed properly using the selected person. I then attempt a redirect to action using the newVM but find on entry to Step2 that MVC wipes out the viewmodel attempted to be passed in. I suspect this is due to how MVC attempts to new up and instance based on query string results. I'll put a breakpoint in and check that, but am wondering how does one change a view from a post back with a new view model passed in?

public ActionResult Step1()
{
    var vm = new VMStep1();
    return View(vm);
}

[HttpPost]
public ActionResult Step1(VMStep1 vm)
{
    if (ModelState.IsValid)
    {
        var newVM = new VMStep2(vm.SelectedPerson);
        return RedirectToAction("Step2", newVM);
    }
    return View(vm);
}

public ActionResult Step2(VMStep2 vm)
{
    return View(vm);
}

I can fix this by containing VMStep2 and a partial to Step2 in Step1 view, but that requires hide and seek logic when really I just want user to see Step2.

JWP
  • 6,672
  • 3
  • 50
  • 74
  • You need to assign `newVM` to session (or save something to the database) and then retrieve it in the `Step2` method (could also use `TempData` but it would fail if the user refreshed the browser) –  Oct 28 '14 at 23:46
  • Thanks Stephen that's what I was afraid of, but hey we have to follow MVC rules. – JWP Oct 29 '14 at 14:31
  • Not sure what you mean by _have to follow MVC rules_? Two ways of creating a multi-step wizard are 1. Single form with sections where each section contains next/back buttons which validate each section then make next section visible and hide current section with last section containing submit button, or 2. [as shown in this answer](http://stackoverflow.com/questions/6402628/multi-step-registration-process-issues-in-asp-net-mvc-splitted-viewmodels-sing/6403485#6403485) –  Oct 30 '14 at 00:02
  • MVC rules are this: If you are using strong typed views and controllers that accept those types as parameters, then by default MVC takes over. It enforces entry, and actionresults based on strong typed views. There's no current way to enter a controller with one view model and return a result with another view model type. So what I did to overcome this was to contain the second view model within the first and simply call a partial view based on that "other" view model. – JWP Nov 04 '14 at 20:33
  • That's not a 'rule'! And if `VMStep2` contains only primitive properties then you code would work fine (a RouteValueDictionary is generated based on the properties of your model). –  Nov 04 '14 at 20:58
  • When using strong types the MVC binder engine imposes it's own rules on anyone using them. It's not documented well but indeed it's there! Nothing I said was untrue, nor am I interested in using primitives as they are well, they are primitive for what I'm doing. – JWP Nov 04 '14 at 21:15
  • Rubbish. I suggest you spend some time studying the MVC source code to understand the intricacies of model binding and how `ModelState` works. And what do you mean your not _interested in using primitives_? Primitive types are `Int32`, `Boolean`, `Char` etc. defined in the CLR and you might have a bit of trouble creating a model without them. You accepted an answer that works for you which is good, but you could just have easily passd `vm.SelectedPerson` to `Step2` and then initialized `VMStep2` in that method. –  Nov 04 '14 at 21:48
  • Look at my original question once more. The post from Step 1 takes a View Model (not a primitive). The Action Method for Step 2 also takes a View Model and NOT a primitive. My question had nothing to do with primitives. I suggest you look at the code because everything I've mentioned before was proved by me in extensive testing concerning how MVC handles View Models. MVC has it's own internal rules on how it deals with ViewModels and or models. The primary rule is they are already objects before the controller sees them, regardless of what was passed in. – JWP Nov 04 '14 at 22:44
  • Ahhhhh! I know your passing a model, but that model contains primitives (or value types). When you pass a model using `RedirectToAction();`, internally a `RouteValueDictionary` is created. If that model contained only say `int ID` and `string Name` it might look like `ID="1", Name="ABC"` and it will be bound correctly. If however the model contains properties that are complex types it wont work because the route values might contain something like `MyComplexObjectProperty="MyAssembly.MyComplexObject"` which wont bind. –  Nov 04 '14 at 23:12
  • Ok thanks for tips my next stop is looking into RouteValueDictionary to see how it works. I am very interested in the modelbinding aspect of MVC. In my case I did have complex types because I'm using Viewmodels for strong-type view-model binding for everything the user sees or returns. I came from MVVM world so in a sense I'm duplicating (server side view binding). In addition, I'm also picking up vibes that the community is moving the binding strictly to client side (knockout etc.) as well as routing... So once I get a chance I'll dig into that stuff starting with Knockout. Thanks! – JWP Nov 04 '14 at 23:24

2 Answers2

1

I don't see why you should want to call RedirectToAction! What it does it the following: it tells your browser to redirect and like it or not your browser doesn't understand how to handle your object -- what it does understand is JSON. So if you really insist on using return RedirectToAction("Step2", newVM); you should consider a way to serialize your VMStep2 object to JSON and when the browser requests the Redirect, it will be properly passed and created in your action method public ActionResult Step2(VMStep2 vm)

HOWEVER I'd use a much simpler way --- instead of

return RedirectToAction("Step2", newVM);

I would use

return View("Step2", newVM);
noobed
  • 1,329
  • 11
  • 24
  • Ok I think you confirmed what I found, that when Step2 is entered, the data is lost because the MVC model binder was using the request data (similar to JSON) to build the ViewModel for Step2. The data was lost because there never was any speicific inbound request data for VMStep2. – JWP Oct 29 '14 at 14:33
1

Thanks to everyone for the great input!

Here's what I did...

  1. I created three views MainView, Step1View, Step2View (Step 1 and 2 were partial strong typed views)
  2. I created a MainViewModel that contained VMStep1 and VMStep2
  3. When controller served Step1 the MainViewModel only initialized VMStep1 and set state logic to tell MainView Step1 was to be shown.
  4. When user posted back the MainView containing the MainViewModel, the MainViewModel knew what to do by the answers provided in VMStep1.
  5. VMStep2 was initialized on the post back, and state was set to tell MainView to show Step2. VMStep1 was no longer relevant and was set to null.
  6. User was now able to answer using VMStep2 and all was well.

The key to this working is that some flag tells the view which partial to show, the partial takes a model supporting it's strong type which is initialized at the right time. End result is fast rendering and good state machine progression.

JWP
  • 6,672
  • 3
  • 50
  • 74