1

I have a problem which I cant figure out how to fix. please note I'm very new to MVC.

Im designing a survey which has 8 questions. Im creating each question in a view.

What I need to is to keep the data between my views, but I lose the data even though I passed them through as a single view model.

Please help....

I have the following ViewModel

 public class SurveyViewModelNew
        {        
            public string description { get; set; }

            public Question1ViewModel QuestionText1 { get; set; }
            public Question2ViewModel QuestionText2 { get; set; }
            public Question3ViewModel QuestionText3 { get; set; }
            public Question4ViewModel QuestionText4 { get; set; }
            public Question5ViewModel QuestionText5 { get; set; }
            public Question6ViewModel QuestionText6 { get; set; }
            public Question7ViewModel QuestionText7 { get; set; }
            public Question8ViewModel QuestionText8 { get; set; }
        }

And a view Model for each question:

  public class Question1ViewModel
        {
            public string QuestionText { get; set; }
            public AnswerViewModel Answers { get; set; }
        }

and I here is how my view look like ?

 @model survey.Models.SurveyViewModelNew

   @using (Html.BeginForm("QuestionOneNext", "CreateSurveyStep2", FormMethod.Post, new { @class = "form-horizontal", role = "form", Model = Model }))

   {

        <div class="form-horizontal">

            <hr />
            @Html.ValidationSummary(true, "", new { @class = "text-danger" })

            <div class="form-group">
                @Html.Label("Question 1: What question would you like to ask", htmlAttributes: new { @class = "control-label col-md-4" })
                <div class="col-md-5">
                    @Html.EditorFor(model => model.QuestionText1.QuestionText, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.QuestionText1.QuestionText, "", new { @class = "text-danger" })
                </div>
            </div>
            <br/>
            <br/>          
            <div class="form-group">
                @Html.Label("Answer A", htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.QuestionText1.Answers.AnswerTextA, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.QuestionText1.Answers.AnswerTextA, "", new { @class = "text-danger" })
                </div>
            </div>    
            <div class="form-group">
                @Html.Label("Answer B", htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.QuestionText1.Answers.AnswerTextB, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.QuestionText1.Answers.AnswerTextB, "", new { @class = "text-danger" })
                </div>
            </div>
            <div class="form-group">
                @Html.Label("Answer C", htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.QuestionText1.Answers.AnswerTextC, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.QuestionText1.Answers.AnswerTextC, "", new { @class = "text-danger" })
                </div>
            </div>
            <div class="form-group">
                @Html.Label("Answer D", htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.QuestionText1.Answers.AnswerTextD, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.QuestionText1.Answers.AnswerTextD, "", new { @class = "text-danger" })
                </div>
            </div>
            <div class="form-group">
                @Html.Label("Answer E", htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.QuestionText1.Answers.AnswerTextE, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.QuestionText1.Answers.AnswerTextE, "", new { @class = "text-danger" })
                </div>
            </div>
            <div class="form-group">
                @Html.Label("Answer F", htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.QuestionText1.Answers.AnswerTextF, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.QuestionText1.Answers.AnswerTextF, "", new { @class = "text-danger" })
                </div>
            </div>
            <div class="form-group">
                @Html.Label("Answer G", htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.QuestionText1.Answers.AnswerTextG, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.QuestionText1.Answers.AnswerTextG, "", new { @class = "text-danger" })
                </div>
            </div>

            <div class="form-group">
                @Html.Label("Answer H", htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.QuestionText1.Answers.AnswerTextH, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.QuestionText1.Answers.AnswerTextH, "", new { @class = "text-danger" })
                </div>
            </div>
            <div class="form-group">
                @Html.Label("Answer K", htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                   @Html.Label("Not applicable", htmlAttributes: new { @class = "control-label col-md-2" })
                </div>
            </div>
            <div class="form-group">
                @Html.Label("Answer L", htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                   @Html.Label("Flag inappropriate question/refuse to answer", htmlAttributes: new { @class = "control-label col-md-5" })
                </div>
            </div>
                  <br/>
            <br/>
            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <input type="submit" value="Previous Step" class="btn btn-info" name="direction"/>
                    <input type="submit" value="Add a New Question to this survey" class="btn btn-danger" name="direction" />
                    <input type="submit" value="Countinue to submit this survey" class="btn btn-success" name="direction" />
                  </div>
            </div>
        </div>
    }

and here is how my controller look like:

[HttpPost]
public ActionResult QuestionOneNext(SurveyViewModelNew surveyViewModel, string direction)
{

    if (direction == "Countinue to submit this survey")
        return RedirectToAction("Edit", "something", surveyViewModel);

    if (direction == "Add a New Question to this survey")

    {
        return View("QuestionTwo", surveyViewModel);
        //return RedirectToAction("QuestionTwo", "CreateSurveyStep2", surveyViewModel);
    }
    if (direction == "Previous Step")
        return RedirectToAction("Index", "CreateSurveyStep1", surveyViewModel);

    return View();
}

[HttpPost]
public ActionResult QuestionTwo(SurveyViewModelNew surveyViewModel, string direction)
{

    if (direction == "Countinue to submit this survey")
    {
        return RedirectToAction("Edit", "NutStorage", surveyViewModel);
    }

    if (direction == "Add a New Question to this survey")
    {
        //return RedirectToAction("QuestionThree", "CreateSurveyStep2", surveyViewModel);
        return View("QuestionThree", "CreateSurveyStep2", surveyViewModel);
    }
    if (direction == "Previous Step")
        return RedirectToAction("QuestionOne", "CreateSurveyStep2", surveyViewModel);

    return View();
}

what happens is when I get to "QuestionTwo" action in my controller I lose the answer to the first question.

I fixed this by using TemData, the following is my fixed controller:

 [HttpGet]
    public ActionResult QuestionOne(SurveyViewModelNew surveyViewModel)
    {
        var currentSurveyViewModel = (SurveyViewModelNew)TempData["SurveyView"];

        if (currentSurveyViewModel != null)
        {
            if (currentSurveyViewModel.description != null)
            {
                surveyViewModel.description = currentSurveyViewModel.description;
            }

            if (currentSurveyViewModel.QuestionText1 != null)
            {
                surveyViewModel.QuestionText1 = currentSurveyViewModel.QuestionText1;
            }
            if (currentSurveyViewModel.QuestionText2 != null)
            {
                surveyViewModel.QuestionText2 = currentSurveyViewModel.QuestionText2;
            }
            if (currentSurveyViewModel.QuestionText3 != null)
            {
                surveyViewModel.QuestionText3 = currentSurveyViewModel.QuestionText3;
            }
            if (currentSurveyViewModel.QuestionText4 != null)
            {
                surveyViewModel.QuestionText4 = currentSurveyViewModel.QuestionText4;
            }
            if (currentSurveyViewModel.QuestionText5 != null)
            {
                surveyViewModel.QuestionText5 = currentSurveyViewModel.QuestionText5;
            }
            if (currentSurveyViewModel.QuestionText6 != null)
            {
                surveyViewModel.QuestionText6 = currentSurveyViewModel.QuestionText6;
            }
            if (currentSurveyViewModel.QuestionText7 != null)
            {
                surveyViewModel.QuestionText7 = currentSurveyViewModel.QuestionText7;
            }

            TempData["SurveyView"] = surveyViewModel;
        }

        if (surveyViewModel.description!= null)
        {
            surveyViewModel.description = surveyViewModel.description;
            TempData["SurveyView"] = surveyViewModel;
        }
        return View("QuestionOne", surveyViewModel);
    }

    [HttpGet]
    public ActionResult QuestionOneNext(SurveyViewModelNew surveyViewModel, string direction)
    {
        var currentSurveyViewModel = (SurveyViewModelNew)TempData["SurveyView"];
        if (currentSurveyViewModel != null)
        {
            if (currentSurveyViewModel.description != null)
            {
                surveyViewModel.description = currentSurveyViewModel.description;
            }
            if (currentSurveyViewModel.QuestionText1 != null)
            {
                surveyViewModel.QuestionText1 = currentSurveyViewModel.QuestionText1;
            }
            if (currentSurveyViewModel.QuestionText2 != null)
            {
                surveyViewModel.QuestionText2 = currentSurveyViewModel.QuestionText2;
            }
            if (currentSurveyViewModel.QuestionText3 != null)
            {
                surveyViewModel.QuestionText3 = currentSurveyViewModel.QuestionText3;
            }
            if (currentSurveyViewModel.QuestionText4 != null)
            {
                surveyViewModel.QuestionText4 = currentSurveyViewModel.QuestionText4;
            }
            if (currentSurveyViewModel.QuestionText5 != null)
            {
                surveyViewModel.QuestionText5 = currentSurveyViewModel.QuestionText5;
            }
            if (currentSurveyViewModel.QuestionText6 != null)
            {
                surveyViewModel.QuestionText6 = currentSurveyViewModel.QuestionText6;
            }
            if (currentSurveyViewModel.QuestionText7 != null)
            {
                surveyViewModel.QuestionText7 = currentSurveyViewModel.QuestionText7;
            }
        }
        if (direction == "Countinue to submit this survey")
            return View("something", surveyViewModel);

        else if (direction == "Add a New Question to this survey")
        {
            TempData["SurveyView"] = surveyViewModel;
            return View("QuestionTwo", surveyViewModel);                 
        }                                     

        else if (direction == "Previous Step")
        {
            TempData["SurveyView"] = surveyViewModel;
            return View("Index", surveyViewModel);
        }
        else return View();
    }
Joe
  • 13
  • 3
  • possible duplicate of [Can we pass model as a parameter in RedirectToAction?](http://stackoverflow.com/questions/22505674/can-we-pass-model-as-a-parameter-in-redirecttoaction) – JasonWilczak May 19 '15 at 18:15
  • You **must** save the model to a repository, and retrieve it again in the method you redirect to. –  May 19 '15 at 23:03

4 Answers4

0

Instead of fighting the MVC framework in this way, here are a couple of other ways you could do it that might make more sense.

1)consider saving answer as you go to the database.

The record could be some kind of temporary record that isn't considered "real" until the end of the questionaire when the user does a final submit.

2) If the question data is not too big, render each question in it's own section (a div, a tab control) in the one view, and deal with switching between sections in javascript on button clicks. You could use your existing ViewModel to carry all questions into the view, and have them all posted and saved at the end. Make it a list of questions instead of 8 properties that are question, and you could even have a for loop in your view that renders a partial for each question. The partial could deal with next and previous buttons etc.

ozz
  • 5,098
  • 1
  • 50
  • 73
-1

To pass the complete model from one page to the next, you have to ensure that all the properties of the model are part of the form and get posted back with each form submit operation back to the server.

To accomplish this, in your view you need to create hidden form fields, using @HTML.HiddenFor that contain all the data of the model even if you are not showing it to the user on a particular page.

Gaurav
  • 154
  • 1
  • 5
  • I think OP is able to get the model back to the first action, but attempts a redirect and is getting a null model in the redirection. – JasonWilczak May 19 '15 at 18:22
  • Thank you for your answer. Do you mean I need to add this : @Html.HiddenFor(model => Model) to my second view? – Joe May 19 '15 at 18:29
  • Unfortunately, it is not as simple as that. Just like you have to do a Html.EditorFor for each individual property, you would have to do a Html.HiddenFor for the others as well. e.g. Html.HiddenFor(model => model.QuestionText2.QuestionText), etc. A better design might be to maintain the questions in a database and just pass the question number as a query string. Then the controller can retrieve the question from the database and show it using a much simpler model. – Gaurav May 19 '15 at 19:18
  • This doesn't address the OP's problem. Their form is submitting to the first action, QuestionOneNext with a model that has data. They are attempting a 'RedirectToAction', which is passing a null model. This is due to an incorrect use of RedirectToAction. – JasonWilczak May 19 '15 at 19:36
  • Jason, thank you for the comments. Hopefully OP can fix the issue with RedirectToAction. I understand that my answer doesn't address OP's problem as already specified in your first comment. I was just replying to his question about how to use Html.HiddenFor. – Gaurav May 19 '15 at 20:59
-1

Every time you make a synchronous HTTP request you are, in fact, reloading the same page, just with a different returned value. If you want the data to persist, you would benefit from learning more about asynchronous requests (AJAX) that can send/receive information without redirection...otherwise you need to persist the answer to question 1 and return it along with the view for question 2...then for question 3 you would need to ensure question 1 and 2 are persisted and return their values along with the context for question 3.

You 'could' go about this with hidden fields as mentioned, but you should really consider this is as a chance to learn something new and useful. Utilizing hidden fields is behind the times and really really obsolete.

beauXjames
  • 8,222
  • 3
  • 49
  • 66
  • This is certainly a valid way of handling the OP's overall architecture, but doesn't answer why the model param is null when redirecting to another action. – JasonWilczak May 19 '15 at 18:21
  • sure it does...the page is refreshed and the user's answer is obliterated – beauXjames May 19 '15 at 19:19
  • OP uses: return RedirectToAction("Edit", "NutStorage", surveyViewModel); in their first controller action, the model is full here. However, when it gets to "Edit" action, the page hasn't refreshed yet since it hasn't returned to the client yet. The problem is with passing a full model via RedirectToAction, which I why I tagged as a duplicate and the related question is in the comment. – JasonWilczak May 19 '15 at 19:34
-2

Option 1: TempData

This same issue is addressed here: Can we pass model as a parameter in RedirectToAction?

The problem is your use of RedirectToAction. You cannot pass a model that way, only primitive types. You need to utilize TempData for this process:

[HttpPost]
public ActionResult QuestionOneNext(SurveyViewModelNew surveyViewModel, string direction)
{
    var currentSurveyViewModel = surveyViewModel ?? (SurveyViewModel)TempData["SurveyView"];
    if (direction == "Countinue to submit this survey")
    {
        TempData["SurveyView"] = currentSurveyViewModel;
        return RedirectToAction("Edit", "something", null);
    }

    if (direction == "Add a New Question to this survey")

    {
        return View("QuestionTwo", currentSurveyViewModel);
        //return RedirectToAction("QuestionTwo", "CreateSurveyStep2", surveyViewModel);
    }
    if (direction == "Previous Step")
    {
        TempData["SurveyView"] = currentSurveyViewModel;
        return RedirectToAction("Index", "CreateSurveyStep1", null);
    }

    return View();
}

[HttpPost]
public ActionResult QuestionTwo(SurveyViewModelNew surveyViewModel, string direction)
{
    var currentSurveyViewModel = surveyViewModel ?? (SurveyViewModel)TempData["SurveyView"];
    if (direction == "Countinue to submit this survey")
    {
        TempData["SurveyView"] = currentSurveyViewModel ;
        return RedirectToAction("Edit", "NutStorage", null);
    }

    if (direction == "Add a New Question to this survey")
    {
        //return RedirectToAction("QuestionThree", "CreateSurveyStep2", surveyViewModel);
        return View("QuestionThree", "CreateSurveyStep2", currentSurveyViewModel );
    }
    if (direction == "Previous Step")
    {
        TempData["SurveyView"] = currentSurveyViewModel ;
        return RedirectToAction("QuestionOne", "CreateSurveyStep2", null);
    }

    return View();
}

Option 2: RouteValueDictionary

There is another option for utilizing complex types and that is using the RouteValueDictionary. This is covered in this very similar question: Passing object in RedirectToAction

The general idea, for your RedirectToAction calls would be to do this:

return RedirectToAction("Edit", "NutStorage", new RouteValueDictionary(surveyViewModel));
Community
  • 1
  • 1
JasonWilczak
  • 2,303
  • 2
  • 21
  • 37
  • The second option will not work because creating a new `RouteValueDictionary` from a complex object internally calls the `ToString()` method of each property. Because OP's models contains properties which are complex objects the route values will be `QuestionText1 = "someAssembly.Question1ViewModel "` (i.e a `string`) which cannot be bound to a complex object and binding will fail (the properties wil; be `null` –  May 19 '15 at 23:00
  • And `TempData` is not a good solution. If the use refreshes the browser the data is lost. –  May 19 '15 at 23:01
  • @StephenMuecke TempData only last for one request. So, in this case, the user is doing something and clicking submit. The web server is redirecting from the original action. I think TempData would more than be appropriate in this case, however, I provided a seconded alternative that, I'm sure most would feel, is the better of the two. – JasonWilczak May 20 '15 at 04:07
  • Thank you for your answer, I'm going to try your suggestions now and I let you know. @JasonWilczak – Joe May 20 '15 at 07:39
  • I managed to fixed my problem by using TempData as you suggested. But I needed to write a lot of code so each time a user click on previous or next I keep the updated data in TemData. I posted my code in the question so you can what Ive done. @JasonWilczak – Joe May 20 '15 at 11:45
  • @Joe Option2 is significantly smaller and may be a better fit, depending on your needs, but I'm glad you got it working! – JasonWilczak May 20 '15 at 12:33
  • @JasonWilczak, Yes `TempData` last only one request which is exactly my point. If the users refreshes the browser (F5) then everything is lost! –  May 21 '15 at 00:03