There are a number of
ways to serialize and store objects in the ASP.NET Runtime - the Session
collection, Cache object, Application state, the HhttpContext.Items collection,
and more. String type items can be passed on the querystring or in hidden
Form fields as well. Object Graphs can also be stored in the AppDomain
Cache where they are available to any assembly running in the same AppDomain,
for example a database class library assembly that may be used by an ASP.NET
page, but does not explicitly derive from any of the System.Web namespace
classes and therefore does not have access to the Session, Cache, etc.
objects. |
|
Here I want to preview a somewhat different method of persisting state
- one where you can serialize a live class instance, pass it to another
ASP.NET page, deserialize it into a new instance of the same type, and
use the properties and members as you see fit. In a previous article,
"Serialize/Deserialize
Classes into SQL Server", I showed how it is possible to use
the BinaryFormatter class and persist live objects to a database. |
We will also use the BinaryFormatter class here, a member of the
System.Runtime.Serialization.Formatters
namespace, in this article. However, I also want to highlight a little
- known, undocumented class : LosFormatter. LosFormatter,
a member of the System.Web.UI namespace, is a "stripped down"
version of BinaryFormatter that is used internally by the ASP.NET runtime
to create and retrieve Page - level objects to / from the hidden _VIEWSTATE
field. |
LosFormatter, although undocumented, can also be easily used explicitly
by the developer. One of the handy features it has is that the serialized
string it generates is automatically Base64 encoded so that the ViewState
Collection can be persisted through page POSTs. Of course, if you explicitly
call the methods of LosFormatter, you are not restricted to keeping the
resulting string in the hidden _VIEWSTATE field - you can do whatever
you want with it. |
Let's start out with a simple class that can be easily populated with
a public string member and a public ArrayList member: |
using System;
using System.Collections;
namespace PAB
{
[Serializable]
public class theClass
{
public string TheString =String.Empty;
public ArrayList TheArrayList= new ArrayList();
public void setMeUp(string theString, ArrayList theArrayList)
{
TheString=theString;
TheArrayList=theArrayList;
}
}
} |
Note that we need to decorate our class with [SerializableAttribute]
or else we would need to create custom serialization code in order to use
any of the members of the Formatters namespace. |
Now let's take a look at the methods we'll use for the LosFormatter: |
private string SaveObjectToViewState(object obj)
{
System.Web.UI.LosFormatter output = new System.Web.UI.LosFormatter();
StringWriter writer = new StringWriter();
output.Serialize(writer, obj);
return writer.ToString();
}
private object RetrieveObjectFromViewState( string viewstate)
{
System.Web.UI.LosFormatter input = new System.Web.UI.LosFormatter();
return input.Deserialize(viewstate);
}
|
SaveObjectToViewState accepts an object parameter (the
class instance you want to serialize) , and uses the LosFormatter.Serialize
method, which can accept either a Stream or a TextWriter to write the
Limited Object Serialization (LOS) result into. You can, of course, call
these methods anything you want. However, you can also override the intrinsic
Page.SavePageStateToPersistenceMedium and the
Page.LoadPageStateFromPersistenceMedium methods, which expect
the hidden field "_VIEWSTATE" to be present. |
These methods use the System.Web.UI.Triplet class, which
is designed to hold three values making up the state (or a portion of
the state) of the various controls on the page so that on a postback,
the control states can be restored. The internal XML representation of
Triplet looks like the following: |
<?xml version="1.0" encoding="utf-16"?>
<viewstate>
<triplet>
<string>-1279334384</string>
<string>whatever </string>
<string>whoever </string>
</triplet>
</viewstate> |
We will also need to add the methods used for the
BinaryFormatter: |
public byte[] SerializeAndSave(object c)
{
try
{
MemoryStream ms = new MemoryStream();
BinaryFormatter b = new BinaryFormatter();
b.Serialize(ms,c);
return ms.ToArray();
}
catch(Exception ex)
{
throw new Exception(ex.Message);
}
}
public object RetrieveAndDeserialize(byte[] buf )
{
try
{
MemoryStream ms2 = new MemoryStream();
ms2.Write ( buf,0,buf.Length );
ms2.Seek(0,0);
BinaryFormatter b=new BinaryFormatter();
object c =b.Deserialize(ms2);
ms2.Close();
return c;
}
catch(Exception ex)
{
throw new Exception(ex.Message);
}
}
|
In our Page_Load handler on our first page, we will create a new instance
of "theClass ", and call the SetMeUp method to populate
its public members: |
private void Page_Load(object sender, System.EventArgs e)
{
ArrayList al = new ArrayList();
al.Add("This is the First ArrayList element (ArrayList[0])");
al.Add("This is the second ArrayList elenent (ArrayList[1])");
string myString = "Hello There! I am a string! (myString)";
theClass tc = new theClass();
// populate our public class members with the setMeUp method...
tc.setMeUp(myString, al);
// Serialize using BinaryFormatter...
byte[] b = SerializeAndSave(tc);
string s = Server.UrlEncode(System.Text.Encoding.Default.GetString(b));
//assign (string)BinaryFormatter object graph to hidden field
hiddenView.Value=s;
lblMessage.Text=s;
// serialize using LosFormatter..
string s2 = Server.UrlEncode(SaveObjectToViewState(tc));
// Assign LosFormatter object graph to hidden field
hiddenView2.Value =s2;
tc=null;
// if form is posted, get back new instance of
// the populated serialized class..
if(IsPostBack)
{
object obj=RetrieveAndDeserialize(
System.Text.Encoding.Default.GetBytes(Server.UrlDecode(hiddenView.Value)));
theClass tc2 = (theClass)obj;
lblMessage.Text="RESULT: " +tc2.TheString;
lblMessage.Text+= " " + tc2.TheArrayList[0].ToString() + " "
+tc2.TheArrayList[1].ToString();
}
} // end pageload
|
|
What we do here is create an instance of the "theClass" class,
populate its TheString and TheArrayList members, and pass the instance
into both the BinaryFormatter and the LosFormatter serialize methods.
We use UrlEncode on the BinaryFormatter method because it doesn't intrinsically
apply HTML - safe encoding to the byte array it generates. The strings
are stored in the two hidden fields, hiddenView and hiddenView2 respectively. |
We have a button on our first page that does a postback, and in our Page_Load
we have the if(IsPostBack) section that extracts our
class by sending the UrlDecoded hiddenView field contents back through
the RetrieveAndDeserialize BinaryFormatter (you can do it with the LosFormatter
field too, I've just saved that for the second page). |
We also have another button on our page that posts to the second page: |
<form action="losFormatter2.Aspx"
method="post">
<input type="hidden" id="hiddenView" name="hiddenView"
runat="server">
<input type="submit" VALUE="Next Page!">
<input type="hidden" id="hiddenView2" name="hiddenView2"
runat="server">
</form> |
As you can see, this second Form is also the one
that holds the two hidden fields (Yes, you can have as many forms as you
want on an ASP.NET page; but only one can have the runat=server directive). |
Now in our second page that receives the POST,
we have: |
private void Page_Load(object sender, System.EventArgs e)
{
lblMessage2.Text= Request.Params["hiddenView"].Trim();
// first get object from BinaryFormatter (hiddenView)
object tc=RetrieveAndDeserialize(System.Text.Encoding.Default.GetBytes(
Server.UrlDecode(Request.Params["hiddenView"])));
theClass tc2=(theClass) tc;
lblMessage.Text="RESULT: " +tc2.TheString;
lblMessage.Text+= " " + tc2.TheArrayList[0].ToString() +
" " +tc2.TheArrayList[1].ToString();
// now deserialize object from LosFormatter (hiddenView2)
object obj =RetrieveObjectFromViewState(Server.UrlDecode(
Request.Params["hiddenView2"]));
theClass tc3=(theClass)obj;
lblMessage2.Text="RESULT: " +tc3.TheString;
lblMessage2.Text+= " " + tc3.TheArrayList[0].ToString() +
" " +tc3.TheArrayList[1].ToString();
} |
This accepts the two hidden field values and passes them to the same
two deserializer methods, and writes the ArrayList values and the String
value of the passed-in class to the two label controls on the page. Presto!
You now have a simple mechanism to pass classes around from page to page.
You can download the full solution in C# at the link below. |
Download
the code that accompanies this article
|
|
|
| Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform. |
|