Arctus Blog: Advanced ASP.NET: Storing VIEWSTATE in a databasetag:blog.arctus.co.uk,2005:TypoTypo2007-04-26T04:31:12-04:00Adrian O'Connoradrian@arctus.co.ukurn:uuid:a0177b8c-80be-4e89-95dd-8ef26df858c62007-04-23T08:13:00-04:002007-04-26T04:31:12-04:00Advanced ASP.NET: Storing VIEWSTATE in a database<p>There are several ways that you can store viewstate in a database (rather than in a hidden field on the page), but there are very few examples of how to actually do it on the web. Maybe that’s because there aren’t many genuine reasons for changing the default behaviour. You certainly should think twice before changing such a fundamental element of your web applications.</p>
<p>Before <span class="caps">ASP</span>.NET 2.0, there was really just one way of achieving what we are about to do; You had to overload two methods in the Page class – LoadPageStateFromPersistenceMedium and SavePageStateToPersistenceMedium. In these methods you would use the exotically named and largely undocumented LosFormatter. This way still works, and you can find examples on Google.</p>
<p>With <span class="caps">ASP</span>.NET 2.0 Microsoft introduced a new class, PageStatePersister. By extending this class, we can create our own ViewState persisters. Two such classes already ship – the HiddenFieldPageStatePersister (classic behaviour), and SessionPageStatePersister (stored the same data in Session).</p>
<p>We’re going to create a DatabasePageStatePersister that will store our serialized data in a <span class="caps">SQL</span> Server database.</p>
<p>You should be aware that the code I will give you here works, but it is thoroughly untested in the real world.</p>
<h3>Our Database</h3>
<p>You will need a database, naturally. In this database you will need the following objects.</p>
<p>You will need a table:</p>
<div class="CodeRay">
<div class="code"><pre>CREATE TABLE [dbo].[ViewStateStore]
(
[guid] [char](128) NOT NULL,
[viewstatedata] [text] NOT NULL
)</pre></div>
</div>
<p>The table should have an index on the guid so that we don’t slow down too much as the table grows:</p>
<div class="CodeRay">
<div class="code"><pre>CREATE NONCLUSTERED INDEX [IDX_Main] ON [dbo].[ViewStateStore]
(
[guid] ASC
)</pre></div>
</div>
<p>You will also need two stored procuedres:</p>
<div class="CodeRay">
<div class="code"><pre>CREATE PROCEDURE dbo.GetViewState
(
@guid char(128)
) AS
SET NOCOUNT ON;
SELECT viewstatedata
FROM ViewStateStore
WHERE guid = @guid;</pre></div>
</div>
<div class="CodeRay">
<div class="code"><pre>CREATE PROCEDURE dbo.StoreViewState
(
@guid char(128),
@ViewStateData text
) AS
SET NOCOUNT ON;
IF (EXISTS(SELECT 1 FROM ViewStateStore WHERE guid = @guid)) BEGIN
UPDATE ViewStateStore SET
viewstatedata = @ViewStateData
WHERE guid = @guid
END ELSE BEGIN
INSERT INTO ViewStateStore (guid, viewstatedata)
VALUES (@guid, @ViewStateData);
END</pre></div>
</div>
<p>You must also make sure that you have a database login that can access the database and execute the two procedures.</p>
<h3>The Code</h3>
<p>We must create a class that extends PageStatePersister.</p>
<p>We will need to provide a constructor taking one parameter (the Page that is creating it), a Load() method and a Save() method. Load and Save are responsible for taking the StateBag and turning it in to a serialized string (and vice-versa).</p>
<p>Because each page’s viewstate is seperate, we must somehow tie the serialized string to the page. For Microsoft’s HiddenElement approach, it isn’t a problem, because the ViewState is intrinsicly tied to the page. For our model, we must ensure that each page has it’s own ViewState that doesn’t interfere with other pages that the user might open (for example, the user is looking at a page that opens a pop-up window—the pop-up window must not interfere with our stored viewstate for the original page).</p>
<p>My solution is to create a <span class="caps">GUID</span> in the Page class that is creating the Persister. We shall see this in a second. First, our persister class:</p>
<div class="CodeRay">
<div class="code"><pre><span style="color:#080; font-weight:bold">public</span> <span style="color:#080; font-weight:bold">class</span> DatabasePageStatePersister : PageStatePersister
{
<span style="color:#339; font-weight:bold">string</span> _GUID = <span style="background-color:#fff0f0"><span style="color:#710">"</span><span style="color:#710">"</span></span>;
<span style="color:#080; font-weight:bold">public</span> DatabasePageStatePersister(Page p, <span style="color:#339; font-weight:bold">string</span> GUID) : <span style="color:#080; font-weight:bold">base</span>(p)
{
_GUID = GUID;
}
<span style="color:#080; font-weight:bold">public</span> <span style="color:#080; font-weight:bold">override</span> <span style="color:#080; font-weight:bold">void</span> Load()
{
<span style="color:#339; font-weight:bold">string</span> ViewStateData = GetViewState(_GUID);
<span style="color:#080; font-weight:bold">this</span>.ViewState = <span style="color:#080; font-weight:bold">this</span>.StateFormatter.Deserialize(ViewStateData);
}
<span style="color:#080; font-weight:bold">public</span> <span style="color:#080; font-weight:bold">override</span> <span style="color:#080; font-weight:bold">void</span> Save()
{
<span style="color:#339; font-weight:bold">string</span> ViewStateData = <span style="color:#080; font-weight:bold">this</span>.StateFormatter.Serialize(<span style="color:#080; font-weight:bold">this</span>.ViewState);
StoreViewState(_GUID, ViewStateData);
}
<span style="color:#888">// Database functions ...</span>
}</pre></div>
</div>
<p>Our constructor takes a Page and a guid (as a string). The base class constructor will make some associations for us, so we must be careful to call it.</p>
<p>Save() and Load() use the standard helper class StateFormatter to do the hardwork. All we need to do is get/set the values in the database.</p>
<p>Here’s the database code – it should go where the comment is in the extract above.</p>
<div class="CodeRay">
<div class="code"><pre><span style="color:#080; font-weight:bold">private</span> SqlConnection _c = <span style="color:#080; font-weight:bold">new</span> SqlConnection(<span style="background-color:#fff0f0"><span style="color:#710">"</span><span style="color:#D20">SERVER=.;DATABASE=ViewState;UID=ViewStateDemo;PWD=ViewStateDemo;</span><span style="color:#710">"</span></span>);
<span style="color:#080; font-weight:bold">private</span> <span style="color:#080; font-weight:bold">void</span> StoreViewState(<span style="color:#339; font-weight:bold">string</span> guid, <span style="color:#339; font-weight:bold">string</span> data)
{
_c.Open();
<span style="color:#080; font-weight:bold">try</span>
{
SqlCommand cmd = <span style="color:#080; font-weight:bold">new</span> SqlCommand(<span style="background-color:#fff0f0"><span style="color:#710">"</span><span style="color:#D20">StoreViewState</span><span style="color:#710">"</span></span>, _c);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(<span style="color:#080; font-weight:bold">new</span> SqlParameter(<span style="background-color:#fff0f0"><span style="color:#710">"</span><span style="color:#D20">guid</span><span style="color:#710">"</span></span>, SqlDbType.Char, <span style="color:#00D; font-weight:bold">128</span>));
cmd.Parameters[<span style="background-color:#fff0f0"><span style="color:#710">"</span><span style="color:#D20">guid</span><span style="color:#710">"</span></span>].Value = guid;
cmd.Parameters.Add(<span style="color:#080; font-weight:bold">new</span> SqlParameter(<span style="background-color:#fff0f0"><span style="color:#710">"</span><span style="color:#D20">ViewStateData</span><span style="color:#710">"</span></span>, SqlDbType.Text));
cmd.Parameters[<span style="background-color:#fff0f0"><span style="color:#710">"</span><span style="color:#D20">ViewStateData</span><span style="color:#710">"</span></span>].Value = data;
cmd.ExecuteNonQuery();
}
<span style="color:#080; font-weight:bold">finally</span>
{
_c.Close();
}
}
<span style="color:#080; font-weight:bold">private</span> <span style="color:#339; font-weight:bold">string</span> GetViewState(<span style="color:#339; font-weight:bold">string</span> guid)
{
<span style="color:#339; font-weight:bold">string</span> Data = <span style="background-color:#fff0f0"><span style="color:#710">"</span><span style="color:#710">"</span></span>;
_c.Open();
<span style="color:#080; font-weight:bold">try</span>
{
SqlCommand cmd = <span style="color:#080; font-weight:bold">new</span> SqlCommand(<span style="background-color:#fff0f0"><span style="color:#710">"</span><span style="color:#D20">GetViewState</span><span style="color:#710">"</span></span>, _c);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(<span style="color:#080; font-weight:bold">new</span> SqlParameter(<span style="background-color:#fff0f0"><span style="color:#710">"</span><span style="color:#D20">guid</span><span style="color:#710">"</span></span>, SqlDbType.Char, <span style="color:#00D; font-weight:bold">128</span>));
cmd.Parameters[<span style="background-color:#fff0f0"><span style="color:#710">"</span><span style="color:#D20">guid</span><span style="color:#710">"</span></span>].Value = guid;
SqlDataReader r = cmd.ExecuteReader();
<span style="color:#080; font-weight:bold">if</span> (r.Read())
{
Data = r[<span style="background-color:#fff0f0"><span style="color:#710">"</span><span style="color:#D20">viewstatedata</span><span style="color:#710">"</span></span>].ToString();
}
}
<span style="color:#080; font-weight:bold">finally</span>
{
_c.Close();
}
<span style="color:#080; font-weight:bold">return</span> Data;
}</pre></div>
</div>
<p>Nothing exciting there.</p>
<h3>Inside our Page</h3>
<p>In the code behind for your page, we must wire up the Persister class that we just created by overloading the property PageStatePersister. Add this code to the body of your code-behind class:</p>
<div class="CodeRay">
<div class="code"><pre><span style="color:#080; font-weight:bold">private</span> PageStatePersister _PageStatePersister;
<span style="color:#080; font-weight:bold">protected</span> <span style="color:#080; font-weight:bold">override</span> System.Web.UI.PageStatePersister PageStatePersister
{
get
{
<span style="color:#080; font-weight:bold">if</span> (_PageStatePersister == <span style="color:#038; font-weight:bold">null</span>)
{
<span style="color:#339; font-weight:bold">string</span> guid = <span style="background-color:#fff0f0"><span style="color:#710">"</span><span style="color:#710">"</span></span>;
<span style="color:#080; font-weight:bold">if</span> (Request[<span style="background-color:#fff0f0"><span style="color:#710">"</span><span style="color:#D20">__DATABASE_VIEWSTATE</span><span style="color:#710">"</span></span>] == <span style="color:#038; font-weight:bold">null</span>)
{
Guid g = Guid.NewGuid();
guid = g.ToString();
}
<span style="color:#080; font-weight:bold">else</span>
{
guid = Request[<span style="background-color:#fff0f0"><span style="color:#710">"</span><span style="color:#D20">__DATABASE_VIEWSTATE</span><span style="color:#710">"</span></span>].ToString();
}
_PageStatePersister = <span style="color:#080; font-weight:bold">new</span> DatabasePageStatePersister(<span style="color:#080; font-weight:bold">this</span>, guid);
Literal l = <span style="color:#080; font-weight:bold">new</span> Literal();
l.Text = <span style="background-color:#fff0f0"><span style="color:#710">"</span><span style="color:#D20"><div><input type=</span><span style="color:#04D">\"</span><span style="color:#D20">hidden</span><span style="color:#04D">\"</span><span style="color:#D20"> name=</span><span style="color:#04D">\"</span><span style="color:#D20">__DATABASE_VIEWSTATE</span><span style="color:#04D">\"</span><span style="color:#D20"> value=</span><span style="color:#04D">\"</span><span style="color:#710">"</span></span> + guid + <span style="background-color:#fff0f0"><span style="color:#710">"</span><span style="color:#04D">\"</span><span style="color:#D20"> /></div></span><span style="color:#710">"</span></span>;
<span style="color:#080; font-weight:bold">this</span>.Form.Controls.Add(l);
}
<span style="color:#080; font-weight:bold">return</span> _PageStatePersister;
}
}</pre></div>
</div>
<p>As you can see, we are changing the behaviour of the page so that the first time that anything tries to access the PageStatePersister variable we will jump in and create an instance of our own class. We also generate a <span class="caps">GUID</span> (always guaranteed to be unique) and store this in a hidden element at the end of our form. This is how we link the page request to the data that we stored in the database.</p>
<p>I strongly recommend that the code we created just now for your Page class be placed in its own class (that inherits from System.Web.UI.Page), and that your own webpages inherit from your new class.</p>
<p><a href="http://blog.arctus.co.uk/files/PageStatePersist.zip">download sample code</a></p>