views:

462

answers:

3

This should be simple, but the answer is eluding me. If I've got a Save action in my controller, and the save fails, how do I cancel the action without disturbing what the user entered? For example, Index is strongly-typed of "MyTable":

Function Index() As ActionResult
    ViewData("message") = "Hello"
    Return View(New MyTable)
End Function

<ActionName("Index"), AcceptVerbs(HttpVerbs.Post)> _
Function Save(ByVal form As MyTable) As ActionResult
    Try
        SaveMyData(form)
        Return RedirectToAction("Index")
    Catch
        AddModelError("form", "An error occurred.")
        ???
    End Try
End Function

In the Catch, if I put Return View(form), I lose the message passed via ViewData. If I redirect to Index, I'll lose what the user entered. I think I've seen the simple (correct) way to handle this before, but if you don't know what to search for, it's hard to find. What am I missing?

A: 

You can use TempData to store the data from the form during the redirect and fetch the data from TempData in the Index action. Here's a discussion of TempData.

Your actions would then look like (Pardon my sophomoric knowledge of VB.Net syntax):

Function Index() As ActionResult
    ViewData("message") = "Hello"

    If TempData["FormData"] == Nothing
        Return View(New MyTable)
    Else
        Return View(TempData["FormData"])
    EndIf
End Function

<ActionName("Index"), AcceptVerbs(HttpVerbs.Post)> _
Function Save(ByVal form As MyTable) As ActionResult
    Try
        SaveMyData(form)
    Catch
        AddModelError("form", "An error occurred.")
        TempData["FormData"] = form;
    End Try

    Return RedirectToAction("Index")
End Function

TempData will store data for exactly one request from the same host. It's pretty much the same as the flash feature in Rails.

dustyburwell
+1  A: 

You don't need to actually store the form values explicitly, as they are preserved by the framework.

Replacing your "????" with the following will alert your view to errors (via the ModelState property) as well as re-display the entered values:

Return View(form)
Josh E
I think this was what I was thinking was the simple solution. The problem I ran into was that I had dropdowns not created with the helper functions that were not remembering their values, which incorrectly led me to believe the whole form was forgetting the changed values. It looks like my problem is deeper than my (contrived) example. Thanks for reinforcing how this works.
gfrizzle
+1  A: 

In the Catch, if I put Return View(form), I lose the message passed via ViewData.

From my understanding, you're not losing the message passed via ViewData - that function simply doesn't run.

And I would highly advise against doing a redirect or using TempData for what you're trying to achieve, there is just no point in it and not the way MVC is supposed to work.

Going on what you've got I would have a private function that both actions call to return the view. It has your ViewData("message") in one place and you still have the previous values in the form (and inside ModelState like Josh E said).

WARNING: VB.NET air code from a C# programmer ;-)

Function Index() As ActionResult
    Return IndexView(New MyTable)
End Function

<ActionName("Index"), AcceptVerbs(HttpVerbs.Post)> _
Function Save(ByVal form As MyTable) As ActionResult
    Try
        SaveMyData(form)
        Return RedirectToAction("Index")
    Catch
        AddModelError("form", "An error occurred.")
    End Try

    Return IndexView(form)
End Function

Private Function IndexView(ByVal form As MyTable) As ActionResult
    ViewData("message") = "Hello"
    Return View(form)
End Function

HTHs, Charles

Ps. I would just like to add that I find it odd you've got an index page that posts back to itself and then if the post has errors it redisplays the index page BUT if there aren't any errors it redirects back to the index page.

I don't know what the app is doing but it does seem like you're using the index page/view for far too many things.

Charlino
Excellent job straightening out what I was trying to accomplish and explaining why it's behaving like it is. BTW, this was a contrived example that's probably over-simplified - I'm not trying to overload the Index view in my real app, but still good to point it out.
gfrizzle