Wednesday, October 1, 2014

Storing Complex/Serialized objects in Session

Storing objects in Session state-bag is commonplace and relatively easy.
What about storing an object that has a property which is a serialized string of another object ? Trying to use that straight out of the box is either tedious and repetitive, or useless.

Consider a case where you have a database user record which contains various user properties and a property Settings that stores serialized data.
It follows reason that to begin with we make a C# class to represent the thing we want to store as serialized string. Our class will just have one property - Bar

public class UserSettings : IChanged {
    [XmlIgnore, ScriptIgnore, JsonIgnore]
    public bool Dirty { get; set; }

    private string _Bar { get; set; }
    public string Bar {
        get {
            return this._Bar;
        }
        set {
            if (this._Bar != value) {
                this._Bar = value;
                this.Dirty = true;
            }
        }
    }
}

Note that I am mandating the IChanged interface that requires you to implement a Dirty bit.This bit management is manual for now, so you would have to code your own Dirty logic for whatever bits you have.
Next, we create a wrapper class that will actually be part of the session state.

public class UserHelper : DatabaseFieldHelper<UserSettings> {
        
    public override string KeyName {
        get { return "UserSessionKey"; }
    }
        
    public override string DefaultSessionValue {
        get {
            return SessionControl.User.Settings;
        }
    }

    public override void Save() {
        using (var db = new DbContext()) {
            var userService = new UserService(db);
            var user = userService.Get(SessionControl.User.UserId);
            user.Settings = JsonConvert.SerializeObject(base.Value);
            db.SaveChanges();
        }
    }
}

Here I inherit from the DatabaseFieldHelper with type of our UserSettings, the abstract database field helper manages storing, de-serializing, and calling the Save method. KeyName, DefaultSessionValue, and Save() are all required for you to implement. All that's left is adding a property in your session control class to get or set your new DatabaseFieldHelper static object, and use away! Something like, SessionControl.UserSettings.Bar for get or set, and you would have to manually call .Save() when you've changed the properties and would like to store the data back to the database, as string, of course.
    string bar = SessionControl.UserSettings.Bar;
    bar = "New Value";
    SessionControl.UserSettings.Save();

Under the hood


/// <summary>
/// Primary purpose of this class is to recover string value from entity's property already in session to help with 
/// deserializing it into T object, using this object, and saving to database when T's properties are changed.
/// </summary>
/// <typeparam name="T">Type of setting that is serialized</typeparam>
public abstract class DatabaseFieldHelper<T> where T : class, IChanged, new() {
    /// <summary>
    /// Gets a keyname, should be session-unique
    /// </summary>
    public abstract string KeyName { get; }

    /// <summary>
    /// Default value to use when the Helper class values are not yet in session
    /// </summary>
    public abstract string DefaultSessionValue { get; }

    /// <summary>
    /// Method for saving the setting to the database.
    /// This method should set appropriate entity's column with the value from base.Serialize() string.
    /// </summary>
    public abstract void Save();

    /// <summary>
    /// Gets the serialization settings.
    /// When saving to database we should ignore property values that equal default values of those properties' types to save space.
    /// </summary>
    private JsonSerializerSettings SerializerSettings {
        get {
            return new JsonSerializerSettings() { DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore };
        }
    }

    /// <summary>
    /// Deseralizaed object.
    /// </summary>
    public T Value {
        get {
            // first look if our helper class is already in session
            T t = SessionControl.Session[KeyName] == null ? null : (T)SessionControl.Session[KeyName];
            if (t == null) {
                // when not in session look to the default value.
                string settings = this.DefaultSessionValue;
                if (!string.IsNullOrEmpty(settings)) {
                    // using the default value from somewhere else we deserialize object 
                    t = JsonConvert.DeserializeObject<T>(settings);
                } else {
                    // object is null, initialze with default T, set Dirty so we can save.
                    t = new T() {
                        Dirty = true
                    };
                }
                // save the deserealized object into the helper class's session key
                SessionControl.Session[KeyName] = t;
                // trigger the database save, should be saving new default instance of serealized object being handled
                this.Save();
            }
            return t;
        }

        private set {
            SessionControl.Session[KeyName] = value;
        }
    }

    /// <summary>
    /// Method for serializing current object with recommended settings.
    /// </summary>
    /// <returns>Serialized string safe to be stored in the database</returns>
    protected string Serialize() {
        return JsonConvert.SerializeObject(this.Value, this.SerializerSettings);
    }
}


No comments:

Post a Comment

CodeBlocks