views:

469

answers:

4

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
    }
}
A: 

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.

LukLed
I'm not passing the id around, I'm passing the entire object. And the object has not yet been saved to the database, therefore I can't magically do a "lookup" from the repository.
Jon Davis
Then you should read about model binders. You should construct Dinner object from Form values. Why do you want to pass dinner multiple times without storing it in DB?
LukLed
I'll read up on model binders. Thanks for the suggestion. I want to collect information from the user from couple of different views and when I have the information that I need, I'm going to save it. Instead of making unnecessary multiple database calls.What bothers me is that, it works for one instance and doesn't work for the other. It's the same freaking concept.
Jon Davis
+1  A: 

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.

DM
Thank you for the suggestion. Unfortunately, hidden fields are not an option. There is a single value that I do not want the user to sniff into the source code and see. I can perhaps encrypt this, but that would be my last resort. Thank you!
Jon Davis
A: 

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>

<% } %>
Aaron Carlson
Resetting TempData on AnotherAction doesn't help. My understanding is that TempData is only good after RedirectToAction. Anyway, I tried this method as well, with and without RedirectToAction. Of course RedirectToAction enter in an infinite loop calling the Get version of AnotherAction and setting TempData without RedirectToAction gives me nothing. Therefore, I'm using the DinnerFormViewModel. If you take a look at the code, on my first Action (Get) I do the same thing. Here, I can modify the Dinner object however I want and I'll get the same Dinner object back on Action (Post).
Jon Davis
Jon, I added an example for re-setting TempData in another action to have it available for the post back to another action. I am able to retrieve the dinner object from TempData successfully. However, since my view does not post the proper form variables to AnotherAction the dinner instance passed into the action is null. FYI, I do not have the nerd dinner project so I created my own Dinner class with a single Name property.
Aaron Carlson
That freaking works Aaron, amazing! You know, I thought I had tried that before, so I followed your steps with my current project and it didn't work. Then I started a new project with just your code and it worked there so I knew I had some minor mistakes somewhere. So I turned to be an idiot. Thank you!
Jon Davis
I am happy that worked. However, You may want to consider following Baddie's advice and just store the Dinner instance in Session State. From what I know TempData is an abstraction on top of Session State any ways. Then you would not have the goofy need to reset TempData for every request until you don't need it.
Aaron Carlson
A: 

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.

Baddie
I agree with Baddie. TempData is using Session State behind the curtain for you any ways. I would stop using TempData and just store the object in Session State directly. Of course, only if you can't use hidden fields.
Aaron Carlson