Store complex object in TempData
Asked Answered
G

5

92

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?

Guinevere answered 6/1, 2016 at 17:14 Comment(5)
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 actionVoltage
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 practiceGuinevere
@Voltage 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.Cushing
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.Cushing
@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.Guinevere
W
182

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

Wiltonwiltsey answered 27/1, 2016 at 16:11 Comment(8)
Works like a charm. Love Newtonsoft!Doerr
I also fixed this by marking adding the Serializable attribute to the class that I was using for the object being placed into TempDataShithead
@Shithead 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.Invoke
@Shithead i have used [Serializable()] attribute and implemented ISerializable, but it didn't work. can you share your solution?Anoa
I can confirm it worked with my Razor pages (net 5.0) project, with a List<string>. 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.Heloise
@EduardoAlmeida For string, int, or bool values, just use "TempData[key] = value;" directly.Dufrene
What do I have to change to make this work with Type decimal and long?Intercom
@Intercom Maybe create separate extension methods to support those types?Wiltonwiltsey
A
21

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);
        }
    }
Amieeamiel answered 9/9, 2020 at 9:52 Comment(2)
did you included <PackageReference Include="System.Text.Json" Version="5.0.1" /> in *.csproj?Amieeamiel
Damn, I didn't realize it was a package. I deleted my previous comment.Heloise
P
18

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
Powers answered 25/8, 2017 at 17:14 Comment(0)
F
0

Awesome answer @hem

I have attempted to improve upon it by:

  • using System.Text.Json instead of Newtonsoft

  • providing a bit of safety for cases where the extension methods are used for primitive types (following comment by @EduardoAlmeida)

      //  Adds support for non-primitive types in TempData
      //  Heavily inspired by https://stackoverflow.com/a/35042391.
      //  Modifications include :
      //  - replacement of Newtonsoft.Json with System.Text.Json 
      //  - default to original ITempDataDictionary methods when object types are primitive
    
      public static class TempDataExtensions
      {
    
          //primitive types are supported out of the box by ITempDataDictionary
          private static List<Type> PrimitiveTypes = new List<Type>
          {
              typeof(int),
              typeof(string),
              typeof(bool)
          };
    
          public static void AddObject<T>(this ITempDataDictionary tempData, string key, T value) where T : class
          {
              if (value is null || PrimitiveTypes.Contains(value.GetType()))
              {
                  tempData[key] = value;
              }
              else
              {
                  //System.Text.Json approach
                  tempData[key] = System.Text.Json.JsonSerializer.Serialize(value);
    
                  //Newtonsoft.Json approach
                  //tempData[key] = JsonConvert.SerializeObject(value);
              }
          }
    
          public static T Get<T>(this ITempDataDictionary tempData, string key) where T : class
          {
              if (PrimitiveTypes.Contains(typeof(T)))
              {
                  return (T)tempData[key];
              }
    
              object o;
              tempData.TryGetValue(key, out o);
    
              //System.Text.Json approach
              return o == null ? null : System.Text.Json.JsonSerializer.Deserialize<T>((string)o);
    
              //Newtonsoft.Json approach 
              //return o == null ? null : JsonConvert.DeserializeObject<T>((string)o);
          }
      }
    
Fussell answered 10/5 at 15:31 Comment(0)
H
-1

I was having problems while saving a class object in Tempdata and then retrieve that data in another action method in controller you can do is Save class object data in Tempdata

Student ClsStudent= new Student 

ClsStudent =getstudent(Student_id):
Tempdata["Studentdata"]= ClsStudent;

After that using that tempdata in other methods

Student clsStudent = null;
ClsStudent= (Student)Tempdata["Studentdata"]

Now you can use this class object data as before .Here we provide the Tempdata to class objects .

Hartman answered 5/3 at 0:44 Comment(1)
This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From ReviewZoila

© 2022 - 2024 — McMap. All rights reserved.