84

I've been trying to pass data to an action after a redirect by using TempData like so:

if (!ModelState.IsValid)
{
    TempData["ErrorMessages"] = ModelState;
    return RedirectToAction("Product", "ProductDetails", new { code = model.ProductCode });
}

but unfortunately it's failing with the following message:

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

I've found an issue in the MVC project in Github, but while it explains why I'm getting this error, I can't see what would be a viable alternative.

One option would be to serialize the object to a json string and then deserialize it back and reconstruct the ModelState. Is this the best approach? Are there any potential performance issues I need to take into account?

And finally, are there any alternatives for either serializing complex object or using some other pattern that doesn't involve using TempData?

elolos
  • 4,310
  • 2
  • 28
  • 40
  • you should not be doing this. If the modelstate is not valid the default behaviour is just to return the same view with the invalid model. So you should only do this return View(model); and not redirect to action – hjgraca Jan 11 '16 at 15:10
  • 1
    This is just one example, I'm looking for a way to store any complex object in TempData, not necessarily ModelState. Also, there might be scenarios where you cannot follow your advice, which I agree is the best practice – elolos Jan 11 '16 at 15:54
  • 1
    @hjgraca the use case for this type of situation is a view that has multiple partial views for adding and editing a list of data. For example, the model for the view is actually a list of the models, then there is an inline add form that has its own model (whose errors need sent back) and then each item is edited inline as well (each having their own errors). This is done easily with client-side frameworks like Angular, but not quite so easy with Razor. – Mike Perrenoud Jan 23 '16 at 16:28
  • FYI, you can't just serialize to JSON because the ModelError class does not have the appropriate constructors. In fact, that's the one problem child, the internal ModelError classes. Therefore, I think the solution is to serialize the KVP and then deserialize that and add them back. I'll post those filters when I'm finished with them. – Mike Perrenoud Jan 23 '16 at 16:30
  • @MichaelPerrenoud I'm doing something along these lines to store a custom class (not ModelState) in TempData. I was just wondering if there is a better approach. – elolos Jan 23 '16 at 23:22

3 Answers3

170

You can create the extension methods like this:

public static class TempDataExtensions
{
    public static void Put<T>(this ITempDataDictionary tempData, string key, T value) where T : class
    {
        tempData[key] = JsonConvert.SerializeObject(value);
    }

    public static T Get<T>(this ITempDataDictionary tempData, string key) where T : class
    {
        object o;
        tempData.TryGetValue(key, out o);
        return o == null ? null : JsonConvert.DeserializeObject<T>((string)o);
    }
}

And, you can use them as follows:

Say objectA is of type ClassA. You can add this to the temp data dictionary using the above mentioned extension method like this:

TempData.Put("key", objectA);

And to retrieve it you can do this:

var value = TempData.Get<ClassA>("key") where value retrieved will be of type ClassA

hem
  • 2,100
  • 1
  • 10
  • 11
  • Works like a charm. Love Newtonsoft! – Stephen McDowell Sep 11 '18 at 17:45
  • 1
    I also fixed this by marking adding the `Serializable` attribute to the class that I was using for the object being placed into `TempData` – brendonparker Mar 25 '19 at 20:00
  • @brendonparker This didn't work for me, probably just depends on the object. The above code seems to be copypasted everywhere and IMO is bound to lead to a confusing controller and view setup. – perustaja Jan 21 '20 at 23:40
  • 1
    @brendonparker i have used [Serializable()] attribute and implemented ISerializable, but it didn't work. can you share your solution? – Abubakar Riaz May 31 '20 at 17:27
  • I can confirm it worked with my Razor pages (net 5.0) project, with a `List`. But then I tried calling `Put()` with a simple `string` and the whole TempData was wiped out for some weird reason. So for the `string` I had to use the `Add()` method. – Eduardo Almeida Feb 05 '21 at 16:29
  • 1
    @EduardoAlmeida For string, int, or bool values, just use "TempData[key] = value;" directly. – IdahoB May 28 '21 at 14:36
  • What do I have to change to make this work with Type `decimal` and `long`? – duichwer Jul 30 '21 at 09:36
  • @duichwer Maybe create separate extension methods to support those types? – hem Jul 30 '21 at 10:54
17

I can not comment but I added a PEEK as well which is nice to check if there or read and not remove for the next GET.

public static T Peek<T>(this ITempDataDictionary tempData, string key) where T : class
{
    object o = tempData.Peek(key);
    return o == null ? null : JsonConvert.DeserializeObject<T>((string)o);
}

Example

var value = TempData.Peek<ClassA>("key") where value retrieved will be of type ClassA
Chris Go
  • 615
  • 7
  • 15
15

Using System.Text.Json in .Net core 3.1 & above

using System.Text.Json;

    public static class TempDataHelper
    {
        public static void Put<T>(this ITempDataDictionary tempData, string key, T value) where T : class
        {
            tempData[key] = JsonSerializer.Serialize(value);
        }

        public static T Get<T>(this ITempDataDictionary tempData, string key) where T : class
        {
            tempData.TryGetValue(key, out object o);
            return o == null ? null : JsonSerializer.Deserialize<T>((string)o);
        }

        public static T Peek<T>(this ITempDataDictionary tempData, string key) where T : class
        {
            object o = tempData.Peek(key);
            return o == null ? null : JsonSerializer.Deserialize<T>((string)o);
        }
    }
MarredCheese
  • 17,541
  • 8
  • 92
  • 91
Ajt
  • 1,719
  • 1
  • 20
  • 42