aspnetcore: ModelState cannot be added to TempData (cannot be serialized)

I found this when dealing with ModelState and transfering using TempData to follow PRG pattern.

TempData.Add("ModelStateTransfer", ModelState);


InvalidOperationException: 
The 'Microsoft.AspNet.Mvc.ViewFeatures.SessionStateTempDataProvider' cannot serialize an object
of type 'Microsoft.AspNet.Mvc.ModelBinding.ModelStateDictionary' to session state.

Shouldn’t be ModelState serializable?

I was pointed out to this tests: https://github.com/aspnet/Mvc/blob/25eb50120eceb62fd24ab5404210428fcdf0c400/test/Microsoft.AspNetCore.Mvc.Core.Test/SerializableErrorTests.cs

so I tried using SerializableError with no success

TempData.Add("ModelStateTransfer", new SerializableError(ModelState));

InvalidOperationException: 
The 'Microsoft.AspNet.Mvc.ViewFeatures.SessionStateTempDataProvider' cannot serialize an object
of type 'Microsoft.AspNet.Mvc.SerializableError' to session state.

Help? Ideas?

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 34 (17 by maintainers)

Most upvoted comments

Laravel (PHP) has redirect function in controller action which can be fluently chained to include errors and form inputs.

https://laravel.com/docs/5.1/validation#other-validation-approaches

class ProductController extends Controller {
    public function post(Request $request) {
        // make $validator here...

        if ($validator->fails()) {
            return redirect('post/create')
                    ->withErrors($validator)
                    ->withInput();
        }

        // else: do something...
    }
}

Related to issue topic, it’d be nice if we can do this, instead of manually playing with TempData / manually serializing ModelState

return Redirect("~/tosomewhere").WithModelState();
return RedirectToPage(...).WithModelState();
return RedirectToAction(...).WithModelState();
// etc.

That’s similar of what I ended up doing. But that isn’t a supported scenario on 1.1. That’s just read the data serialize and create the model again in a very manual fashion. While it works, I still think it should be baked in the framework and not responsibility for the developer or external Nuget.

In other words, instead of saving the data in Temp, we use a local static field to save the data.

@sipi41 - the problem with this though is that static variables is that they remain in memory past the current request - they retain value for the lifetime of the application. It may work in test with a single user, but with multiple users, this is very dangerous. Every user will see the same value for that static variable. Not to mention potential concurrency issues.

The problem I describe doesn’t happen with the Post-Redirect-Get (PRG) pattern because error cases are still a POST, so if you refresh, first of all the browser warns you, and even if you do post, you just re-post the same (bad) data, in which case you see the same errors again. If you do an initial redirect w/ TempData then the browser might not warn you, and if you proceed, you lose your data.

Is the problem I’m describing not a concern? We can certainly implement what’s described here, it just doesn’t seem to me like it would promote a good UI for end-users.

I’m reactivating this for consideration. We might not do exactly this but we should do something for this scenario

I still don’t believe that TempData is a solution for this. One hit of <kbd>F5</kbd> and your TempData goes away.

I’m going to chime in here as well with a 👍 - this is one of those death by a thousand cuts that makes MVC painful to use. IMO, it’s fine when you can post to the same URL that you’re currently on, it falls apart when you need to start posting to different URL’s and handling error conditions.

@Bartmax That’s true. PRG by far seems the best way of dealing with this kinda scenarios.

Recently I came across this article that demonstrates how to push the new ModelState into TempData (as we used to do in previous versions of asp.net).

One thing that got me thinking is that a guy in the comment section alleges that this functionality is supported on aspnet core 1.1 through cookie tempdata provider. Would love someone providing any thoughts on that matter.

@dougbu For simple types like int and string yes, I’m aware. But if I have a list of an complex type, when post happens, it will always be null (pretty obvious). In those cases we have to rebuild the view model to display the correct data again.

That’s when PRG shines, because only the GET action needs to know how to build your views. If I’m always checking model state on POST, I’ll have to duplicate the code that builds the view every single time (both on GET and POST).

All that beeing said, what are your thoughts on reusing that code? I know that a view model builder or something like that is an option, but I’m wondering what other approaches could fit that case instead of PRG.

Update: Beware though that are some complex cases/approaches where view data is populated through TempData/ViewBag. In those specific cases we must have access to either of them to re-insert the values, and this kinda breaks the use of an out-of-controller builder abstraction.

Andrew Lock provides a workaround for this problem in MVC: https://andrewlock.net/post-redirect-get-using-tempdata-in-asp-net-core/ Unfortunately his method doesn’t work in Razor Pages, so I’ve created a IPageFilter for this, maybe it’ll be helpful for someone: https://gist.github.com/Yaevh/e87f682a3c3ac35d1504c068c9f5e8ab

I however agree that it should be supported by the framework itself and not require additional work by the developer.

@Eilon I don’t think I quite understood your explanation. The reason to preserve ModelState here is to provide form usability. Imagine a huge form, if you treat failed requests just as a normal POST, then the user will have to fill every single form field again.

The way I see it, we have two options when ModelState.IsValid = false:

  • Rebuilds the model and return view (duplicates code on GET and POST actions)
  • (PRG) Redirects to GET that has the responsability to build the view

Of course the second options is preferable because of DRY. What is your approach right now? Anyone else could bring some thoughts on this matter?

Basically, I want to save ModelState on a POST action so I can redirect and have the ModelState on next (get) request. Basic PRG pattern.

[HttpGet]
public IActionResult Create(){
    if (TempData.ContainsKey("ModelStateTransfer") {
        ModelState.Merge(TempData["ModelStateTransfer"] as ModelStateDictionary);
    }
    return View();
}

[HttpPost]
public IActionResult CreateSpecificData(CreateBidningModel binding) {
    if (!ModelState.IsValid) {
        TempData.Add("ModelStateTransfer", ModelState);
        return RedirectToAction(nameof(Create));
    }
    // ....
}

Since this is the source thread, I figured I would post what helped me and it works great on any kind of model you create.

https://stackoverflow.com/questions/34638823/store-complex-object-in-tempdata-in-mvc-6