Friday, June 13, 2008

Maintain Some Value Across Postbacks

This is mostly a noob post, but I had to write it because I've had to work with a project written by a colleague of mine and her method of maintaining value across postbacks was to use HiddenFields. I will explore that option and the ViewState option.

First of all, what are the advantages of using a hidden field? I can see only two:
1. it would work even if ViewState is disabled
2. its value is accesible through javascript
The disadvantages are:
1. it creates additional HTML markup
2. it can only store stuff in string format
3. its value is accesible through javascript

I would not use the hidden field option mainly because it gives people the ability to see and change the value through simple javascript manipulation. It's a security risk, even if most of the times you don't really care about the security of some value maintained through postback. I would use it only when _I_ need to change that value through javascript.

For some code, I assume I want to store an integer value called MyValue. There will be a field called _myValue that will store the value during a cycle, but it is used mainly for caching (reading Request and ViewState is slow) and it is declared like this:

private int? _myValue;


Now, about the structure of such a code. The simplest method is to actually create a (or many) hidden field(s) in your page. You can then use the values directly. It is simple, but hardly maintainable:

Markup:

<asp:HiddenField id=hfMyValue runat=server>

C# code:

public int MyValue
{
get
{
if (_myValue == null)
{
if (String.IsNullOrEmpty(hfMyValue.Value)) MyValue = 10;
else _myValue = Int32.Parse(hfMyValue.Value);
}
return _myValue.Value;
}
set
{
hfMyValue.Value = value.ToString();
_myValue = value;
}
}


I've wrapped the functionality of the hidden field in a property so I can easily use it through my code.

Another method of doing this is to use the RegisterHiddenField method of the ScriptManager like this:

C# code only:

public int MyValue
{
get
{
if (_myValue==null)
{
if (Request["MyValue"] == null) MyValue = 10;
else _myValue = Int32.Parse(Request["MyValue"]);
}
return _myValue.Value;
}
set
{
PreRender -= MyValue_Registration;
PreRender += MyValue_Registration;
_myValue = value;
}
}

void MyValue_Registration(object sender, EventArgs e)
{
if (_myValue.HasValue)
ScriptManager.RegisterHiddenField(this, "MyValue", _myValue.Value.ToString());
}


As you can see, there is no need of my changing the markup. There is the ugly part of having to attach to the prerender event to register the hidden field because the ScriptManager doesn't have any way of accessing the registered hidden field after you did it or at least a way to un-register it. Registering it again doesn't change its value, either.

In both these cases the value is accessible through javascript:

<script>var myValue=parseInt(document.getElementById('<%=hfMyValue.ClientID%>').value);</script>
<script>var myValue=parseInt(document.getElementById('MyValue').value);</script>


But there is an easier way of storing the values through postback, and that is by using ViewState. In order to do that, your object needs only to be Serializable. It can be anything from a string to a complex DataSet. There is no way to access it through javascript, though. Here is the C# code for it:

public int MyValue
{
get
{
if (_myValue == null)
{
if (ViewState["MyValue"] == null) MyValue = 10;
else _myValue = (int)ViewState["MyValue"];
}
return _myValue.Value;
}
set
{
ViewState["MyValue"] = value;
_myValue = value;
}
}


Doesn't that look a lot simpler? And the beauty of it is, the ViewState can be sent inside the markup, as in the default behaviour, but it can just as easily be stored on the server, either by using a SessionStatePersister or by other ways.

Update: Also, a more complicated, but a lot more flexibile way of doing things is described on the DimeBrain blog: Frictionless data persistence in ASP.NET WebForms.

0 comments: