Question

I'm new to ASP.NET MVC. After working with traditional ASP.NET model for so long, it's taking some time for me to get to understand this model.

I am going through NerdDinner to understand how things work.

So, I have an object that needs to be passed through couple of views. Similar to the article NerdDinner Step 6: ViewData and ViewModel.

I retain the data from Get to Post for the first time, then I put it in TempData and pass it to another action (AnotherAction). Once I get my data on Get I cannot retain it on Post.

Here's my code:

public class DinnerFormViewModel
{
    public Dinner Dinner { get; private set; }

    public DinnerFormViewModel(Dinner dinner)
    {
        Dinner = dinner;
    }
}

public class DinnersController : Controller
{
    public ActionResult Action()
    {
        Dinner dinner = new Dinner();
        return View(new DinnerFormViewModel(dinner));
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Action(Dinner dinner, FormCollection collection)
    {
        try
        {
            // Some code
            TempData["Dinner"] = dinner;
            return RedirectToAction("AnotherAction");
        }
        catch
        {
            return View();
        }
    }

    public ActionResult AnotherAction()
    {
        Dinner dinner = (Dinner)TempData["Dinner"]; // Got my dinner object
        return View(new DinnerFormViewModel(dinner));
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult AnotherAction(Dinner dinner, FormCollection collection)
    {
        // Lost my dinner object, dinner comes in as null
    }
}
Was it helpful?

Solution

According to this blog post TempData is only around for 1 single request after its set.

Here is a quote from the post:

If you set TempData and your action then returns a ViewResult, then the next request, whatever it happens to be (an AJAX request, another page the user opened in a different tab, etc.), is going to see the TempData value you set, and no other request will see it.

So given the code that I'm seeing, you can get the dinner from TempData on the get from AnotherAction which is the first request after you set it on Action. However looking at the code and not seeing the code for view for AnotherAction it is unclear how you are passing the data to the post for AnotherAction. The dinner instance is not going to be in TempData for that request because it's the second request after you set it in TempData. And if you do not have the proper form tags set on the AntoherAction view the framework will not have the proper form values to instantiate a dinner object in the post.

So either you'll have to reset TempData with the dinner instance it the first AnotherAction call and then retrieve the dinner out of TempData in the post AnotherAction, or you can follow the advice of dm and use hidden fields in your view.

IMO, you should use DMs way of doing this and avoid using TempData.

Edit Added example of reseting the TempData in AnotherAction to get access to it in the post.

Model:

  public class Dinner
  {
    public string Name{get;set;}
  }

  public class DinnerFormViewModel
  {
    public Dinner Dinner {get;private set;}

    public DinnerFormViewModel( Dinner dinner )
    {
      Dinner = dinner;
    }
  }

Controller:

  public class DinnersController : Controller
  {
    public ActionResult Action()
    {
      Dinner dinner = new Dinner();
      return View( new DinnerFormViewModel( dinner ) );
    }

    [AcceptVerbs( HttpVerbs.Post )]
    public ActionResult Action( Dinner dinner, FormCollection collection )
    {
      try
      {
        // Some code
        TempData[ "Dinner" ] = dinner;
        return RedirectToAction( "AnotherAction" );
      }
      catch
      {
        return View();
      }
    }

    public ActionResult AnotherAction()
    {
      Dinner dinner = ( Dinner )TempData[ "Dinner" ]; // Got my dinner object
      TempData[ "Dinner" ] = dinner; // Reset the dinner object in temp data
      return View( new DinnerFormViewModel( dinner ) );
    }

    [AcceptVerbs( HttpVerbs.Post )]
    public ActionResult AnotherAction( Dinner dinnerFromPostedFormValues, FormCollection collection )
    {
      //dinnerFromPostedFormValues is null
      var dinnerFromTempData = TempData[ "Dinner" ] as Dinner; // Got my dinner object

      return View( "Action", new DinnerFormViewModel( dinnerFromTempData ) );
    }
  }

Action View:

<h2>Action</h2>

<% using (Html.BeginForm()) {%>

    <fieldset>
        <legend>Fields</legend>
        <p>
           Name: <%= Html.TextBox("Name", Model.Dinner.Name ) %>
        </p>
        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>

<% } %>

AnotherAction View:

<h2>AnotherAction</h2>

<% using (Html.BeginForm()) {%>

  <fieldset>
      <legend>Fields</legend>
      <p>
          Name:
          <%= Html.Encode(Model.Dinner.Name) %>
      </p>

      <p>
          <input type="submit" value="Do Something" />
      </p>
  </fieldset>

<% } %>

OTHER TIPS

To get the format that you're expecting you may have to populate some hidden fields as you collect the information from various views.

Also, using model binding you could make your code look at bit better and avoid TempData in places:

public ActionResult Action()
{
    Dinner dinner = new Dinner();
    return View(new DinnerFormViewModel(dinner));
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Action(Dinner dinner)
{
    try
    {
        return RedirectToAction("AnotherAction", dinner);
    }
    catch
    {
        return View();
    }
}

public ActionResult AnotherAction(Dinner dinner)
{
    return View(new DinnerFormViewModel(dinner));

    //In this view is where you may want to populate some hidden fields with the Dinner properties that have already been entered... so when the Post action picks up the dinner object it will be fully populated
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult AnotherAction(Dinner dinner)
{
    //work with fully populated Dinner object
}

So in the AnotherAction view you would have something like:

<% using(Html.BeginForm()) { %>

    <%= Html.Hidden("dinnerProperty1") %>
    <%= Html.Hidden("dinnerProperty2") %>
    <%= Html.Hidden("dinnerProperty3") %>
    <%= Html.TextBox("dinnerProperty4") %>
    <%= Html.TextBox("dinnerProperty5") %>
    <%= Html.TextBox("dinnerProperty6") %>

<% } %>

There is no user friendliness in the above example but you get the point.

You can't pass raw C# objects from Views to Controllers.

In ASP.NET MVC, when an action takes an object for a parameter, ASP.NET MVC looks at all the POST/GET data and looks for values which coincide with property names on the parameter object.

public ActionResult SomeAction(Dinner myDinner)
{
        // do stuff
}

myDinner object will be populated ONLY if you post to the action with form fields that correspond to the Dinner object's properties (location, date, etc.) or if you were to place that information in a GET URL (Dinners/SomeAction?location=chicago&date=12/1/2009 etc.)

If you absolutely can't use hidden fields (As DM suggested), then Sessions is probably your only option.

You should get dinner from repository. You action should be like:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult AnotherAction(int dinnerId, FormCollection collection)
{
    var dinner = dinnerRepository.Get(dinnerId);
    ... do something with dinner ...
    dinnerRepository.Save(dinner);
    return RedirectToAction("AnotherAction", ... pass dinner id ...);
}

GET actions can also take from repository, so you can pass only id.

EDIT

If you want to create wizard style page, you can store previously entered data in Session object.

Session['CurrentDinner'] = dinner;

Data stored in Session persist through requests.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top