Avoiding serialization issues in SharePoint property bags by using JSON

Storing application settings in a SharePoint solution can be done in numerous ways. One of the more robust solutions is to use the property bags that exist all throughout the hierarchy of the SharePoint object model.

The only real issue that we have in all of these property bags are that we can store any value of any type as long as it can be serialized. “…as long as it can be serialized…”

SharePoint property bags provide an easy-to-use storage mechanism, but developers risk corrupting the configuration database or the content database if they attempt to persist non-serializable types.

One excellent way to get around some of these issues is to use PnP’s SharePoint Guidance (SPG) framework. Even though you do you might still have issues serializing types. Especially when you use it from the sandbox where you have to get around some of these issues by providing serialization assemblies to go with your sandbox settings (C# classes) that you want to store. Not so straight forward I would say.

One thing you can be sure of though is that you can always store a string without getting into these serialization issues. And what notation that can work as classes but internally is just a string??

That’s right…Json

So, despite if you use SPG (which you can and probably should combine with the following solution) or not…if you store values as Json (strings) you’ll never get into serialization issues.

JSON.NET to the rescue

I’ll start with a little utilities C# project (SP.Utilities) that will later serve as the repository for reading and writing settings. Now, I will start off by including JSON.NET by James Newton-King via the NuGet package manager.

image

image

No, once that is installed let’s implement a simple solution for storing and retrieving settings in SharePoint.

Basic Architecture

image

Here’s the basic rundown on how I’m going to solve this issue.

  1. In the SP.Utilities project I’m constructing an extension method, Settings(), on the SPWeb class that returns
  2. A local, internal, implementation of the ISettingCollection interface which has all the CRUD operations (GET, SET, etc).
  3. SPWebSettingsCollection is the internal class that does all the magic and that we will look at next.

SPWebSettingsCollection

You will use the extension method called Settings on the SPWeb object which look like this:

image

You can see that from the “outside” you’re just using an interface, ISettingCollection, but internally the actual implementation of that interface is called SPWebSettingsCollection. The SPWeb object is passed along to the SPWebSettingsCollection class in the constructor.

You could potentially make any number of implementations of this interface, and expose them via extension methods, this is just showing you how to make one of them:

    internal class SPWebSettingsCollection : ISettingCollection
    {
        SPWeb m_Web;

        public SPWebSettingsCollection(SPWeb web)
        {
            this.m_Web = web;
        }

        public ISettingCollection Set<TSetting>(string key, TSetting setting) 
            where TSetting : ISetting
        {
            string value = JsonConvert.SerializeObject(setting);
            this[key] = value;

            return this;
        }


        public bool TryGet<TSetting>(string key, out TSetting setting) 
            where TSetting : ISetting
        {
            bool output = false;
            setting = default(TSetting);

            if (this.Contains(key))
            {
                try
                {
                    setting = this.Get<TSetting>(key);
                    output = true;
                }
                catch (Exception e)
                {
                    // TODO: add logging
                }
            }

            return output;

        }




        public TSetting Get<TSetting>(string key) where TSetting : ISetting
        {
            string value = this[key];
            return JsonConvert.DeserializeObject<TSetting>(value);
        }

        public string this[string key]
        {
            get
            {
                return this.m_Web.AllProperties[key] as string;
            }

            set
            {
                if (this.Contains(key))
                {
                    this.m_Web.SetProperty(key, value);
                }
                else
                {
                    this.m_Web.AddProperty(key, value);
                }
                Update();
            }
        }

        public bool Contains(string key)
        {
            bool exists = false;
            if (this.m_Web.AllProperties.Contains(key))
            {
                exists = this.m_Web.AllProperties[key] != null;
            }
            return exists;
        }

        private void Update()
        {
            this.m_Web.Update();
        }


        public ISettingCollection Delete(string key)
        {
            if (this.Contains(key))
            {
                this.m_Web.DeleteProperty(key);
                Update();
            }

            return this;
        }
    }

Have a look at the highlighted lines (13 and 49).

On line 13, when an object comes in to be stored I’ll start by serializing it into Json (a string that is) before storing it in the SPWeb’s property collection.

And on line 49, deserialize it back from a Json string into the actual C# object. All of this made possible by using JSON.NET

I’m not going to go through all the methods by I think you get the idea right?

Make a quick test with a console application

Let’s make a quick console application to see if the concept works then.

First of all there is a class, ColorSetting, that we’ll use to store a color (simple enough test). It implements a “marker” interface, ISetting, simply because want I to be able to enforce things on settings later on. (optional of course)

    class ColorSetting : ISetting
    {
        public string DefaultColor { get; set; }
    }

    class Program
    {
        const string ColorSettingKey = "johanleino.com.ColorSetting";

        static void Main(string[] args)
        {
            using (SPSite sitecollection = new SPSite("http://www.johanleino.com"))
            {
                SPWeb web = sitecollection.RootWeb;

                web
                    .Settings()
                    .Set<ColorSetting>(ColorSettingKey, new ColorSetting
                    {
                        DefaultColor = "#000000"
                    });

                var color = web
                     .Settings()
                     .Get<ColorSetting>(ColorSettingKey);

                System.Console.WriteLine("The default color of johanleino.com is: {0}", color.DefaultColor);


                color.DefaultColor = "#333333";

                web
                    .Settings()
                    .Set<ColorSetting>(ColorSettingKey, color);

                var updatedColor = web
                                    .Settings()
                                    .Get<ColorSetting>(ColorSettingKey);

                System.Console.WriteLine("The new default color of johanleino.com is: {0}", updatedColor.DefaultColor);
            }

            System.Console.Read();
        }
    }

The code does in short:

Store setting with value #000000 –> get the setting and print the value –> update the setting with value #333333 –> store the updated setting –> get the setting again and print the new value

image

How about the Sandbox?

You could leave it as it is now and it will work in a farm solution situation.

But I’m taking it one step further though and making this work from a sandbox solution since (as a stated at the beginning of this post) that’s usually where you’ll have serialization issues.

So, from the sandbox you basically have two options. One of them is to construct a proxy (but if you do that you you to make some changes to the extension method…maybe another post). The second option is to deploy the SP.Utilities dll via a farm solution and use it from the sandbox code. I’m showing the second option here.

In short: Add SP.Utilities + Json.NET to GAC via a farm scoped WSP.

From a new sandbox project I have a feature which will store a setting in the feature activating event:

        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {               

            SPWeb web = properties.UserCodeSite.RootWeb;

            web
                .Settings()
                .Set<SiteSetting>("johanleino.com.SiteSetting", new SiteSetting
                {
                    Creator = "Johan Leino"
                });
        }

Same idea as before (in the console) but this time I’m storing a setting called SiteSetting which have a property for Creator. Now if you run this code you will see:

“Error occurred in deployment step ‘Activate Features’: Unhandled exception was thrown by the sandboxed code wrapper’s Execute method in the partial trust app domain: An unexpected error has occurred”

That’s because a sandbox solution is a partial trust caller. So to SP.Utilities you have to add this:

image

To test the setting once it has been added via the feature I’ll add a simple web part to display the value of the setting:

image

Build –> Deploy –> Add web part to a page and…:

image

TADA!!

Easy right!?

Advertisements

, , , , , , , ,

  1. #1 by Lazze on January 25, 2012 - 20:18

    You’re the man!

  2. #2 by Alex on March 15, 2014 - 22:08

    Great post! Is it possible to publish the whole projects? Some parts are missing!

    • #3 by Johan Leino on March 15, 2014 - 22:22

      Sorry, that code no longer exist. Is there any specific part that you’re missing? Maybe I can help out… /J

      • #4 by Alex on March 16, 2014 - 16:21

        Hi, For example the two interfaces: ISettingCollection and ISetting? Another thing how do you deploy JSON.NET with the solution (farm)? Should the dll be added to the package?

      • #5 by Johan Leino on March 16, 2014 - 19:05

        You can see a screenshot that gives you the design of these 2 interfaces at the top of the post. ISetting is just a marker interface if I remember it correctly.
        As goes for JSON.net, yep you need to add it to the package to deploy it with your code yes.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: