Sunday, 21 June 2009

Building a Content Management System Preview Function

I've recently had to build a preview function in a customer content management system, and came up with what seemed a fairly neat way of implementing this.

The principle requirement was to allow users to preview content without making the change to the underlying data in the database, so allow them to roll-back or amend their changes until they were happy with them. At this point they would publish and the records in the database tables that represented the specific article would be updated.

I planned to implement this using the Session object, in the following way:
  • User clicks to edit an article - an article object is instantiated fromt the database and bound to the form.
  • User updates the information and presses the Preview button.
  • In an IFRAME, the main website article template is loaded and via a querystring parameter it is instructed to use the Article object hosted in the session, rather than retrieving it from the database as it would in the normal public view.
  • The user can then click an Amend button to go back to the form, or Save, which will retrieve the Article object form the session and pass it to my data access layer for saving to the database.
The first step to get this working was to ensure the Article object and any dependent ones are marked as Serializable.
[Serializable()]
public class Article {
}
This is necessary if you plan to use State Server or SQL Server for session state storage, and I was using the latter.

Secondly, as I was hosting my content management system and website as two seperate web projects, I had to take steps to ensure that the two projects could share session state (as in the normal state of affairs, they are, as expected, isolated).

To allow this to occur, both websites need to be configured to use the same database for session state, with the same application name. This is done via the web.config file:

<sessionstate mode="SQLServer"
sqlconnectionstring="Data Source=127.0.0.1; Integrated Security=SSPI; Application Name=MyApp"
cookieless="false"
timeout="20">
</sessionstate>

It's also necessary to make a small change to one of the ASPState database's stored procedures.



Having done all this though, I found one further problem - although this setup will work with the CMS and website in seperate projects as described, it doesn't work if the two websites are hosted on seperate domains (or even on seperate sub-domains).

The reason is due to the session cookie not being recognised as from the same site, even if the server side is matched up correctly.

Some research led to one interesting technique of modifying the domain setting of the ASP.Net session cookie itself, but this seemed to have some cross browser issues in practice and hence wasn't reliable.

So my final approach was to abandon the saving of the Article object to session, and instead serialize it to my own database tables where it can be retrieved for preview purposes.

For this I created a database table with 3 fields:
  • the user's session ID (a GUID generated on the Session_Start application event)
  • a key for the type of object (article, product etc.)
  • a field with a datatype of image to hold the serialized binary object representation
With the first 2 fields set as the primary key, I could use these to save and lookup the object details.

So the only issue remaing was actually carrying out the serializing and deserializing of objects, which was carried out using the following two functions:

public static Object GetPreview(string sessionKey, string objectKey)
{
Object result = null;

//Get serialized data from database
byte[] serializedData = DAL.GetPreview(sessionKey, objectKey);

//Deserialize to object, which can then be cast to required type
BinaryFormatter formatter = new BinaryFormatter();
using (MemoryStream stream = new MemoryStream(serializedData))
{
result = formatter.Deserialize(stream);
}
return result;
}

public static void SavePreview(string sessionKey, string objectKey, Object objectToSerialize)
{
//Serialize object
byte[] serializedData;
using (MemoryStream stream = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, objectToSerialize);
serializedData = stream.ToArray();
stream.Close();
}

//Save serialized data to database
DAL.SavePreview(strSessionKey, strObjectKey, bytData);
}

No comments:

Post a Comment