Print

Custom Parameter Gotcha

This article describes a little gotcha that might emerge when writing a custom parameter that derives from System.Web.UI.WebControls.Parameter.

A couple of months ago I was building a custom control Parameter when I came across a little gotcha. The custom parameter I was building seemed to trigger a stack overflow. After some research I found out what was causing the StackOverflowException. It took some time for me to find the time to write about it, but I can finally share my conclusions with you.

There has been written a lot about the ASP.NET 2.0 Control Parameters including articles from Scott Guthrie, Fredrik Normén and Eilon Lipton. Despite the attention building custom controls had in weblogs, nobody seemed to notice the little gotcha that you should be aware off, and even the MSDN documentation lacks a warning.

Fredrik Normén shows in his blog how easy it is to write your own parameter. It basically comes down to overriding two methods, namely Clone and Evaluate. The simple custom parameter Fredrik build looks like this (I have made a few minor changes to his code):
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI.WebControls;

public class MyParameter : Parameter
{
public MyParameter(string name, object value)
: base(name)
{
this.MyProperty = value;
}

public MyParameter(string name, TypeCode type,
object value) : base(name, type)
{
this.MyProperty = value;
}

protected MyParameter(MyParameter original)
: base(original)
{
this.MyProperty = original.MyProperty;
}

protected override object Evaluate(
HttpContext context, Control control)
{
return this.MyProperty;
}

protected override Parameter Clone()
{
return new MyParameter(this);
}

public object MyProperty
{
get { return base.ViewState["MyProperty"]; }
set
{
if (this.MyProperty != value)
{
base.ViewState["MyProperty"] = value;
base.OnParameterChanged();
}
}
}
}
The above code snippet can be used as template when creating your own parameter. It looks rather easy, however there is a caveat that lies within the object returned from the Evaluate method. What you will find out quick enough is that returned objects should implement the ISerializable interface or should be marked with the SerializableAttribute. The runtime throws a decent exception, which message tells you exactly you returned an object that can’t be serialized. But another -not so obvious- detail is that an object returned by the Evaluate method must be equatable. This means you must return an object with an overridden Equals method (like all build-in value types do) or -when overriding Equals is not an option- you must make sure the object reference doesn't change on each call to Evaluate. When you fail to do this you could cause a StackOverflowException that is actually pretty hard to trace back to your custom parameter. In my case Cassini, the build in web server for Visual Studio, just crashed.

So what is going on here? When we use the Reflector tool to peek inside the Parameter class we can understand what's happening. The code below shows the Parameter's internal UpdateValue method.
namespace System.Web.UI.WebControls
{
public class Parameter : ICloneable,
IStateManager
{
[...]

internal void UpdateValue(
HttpContext context, Control control)
{
object p =
this.ViewState["ParameterValue"];

object e =
this.Evaluate(context, control);

this.ViewState["ParameterValue"] = e;

if ( ((e == null) && (p != null)) ||
((e != null) && !e.Equals(p)) )
{
this.OnParameterChanged();
}
}
}
}
The parameter's internal UpdateValue method calls it's Evaluate method and compares the returned object with the cached object from the ViewState (if any). When the object has changed the ParameterChanged event is triggered. The event will notify the parameter's parent that it's value has changed. To explain what the problem is, let's create a simple parameter that will cause a StackOverflowException:
public class StackOverflowParameter : Parameter
{
protected override object Evaluate(
HttpContext context, Control control)
{
return new int[0];
}
}
The StackOverflowException is caused by the array type int[] that has no overridden Equals method. This causes the UpdateValue method to compare the int[] objects by their reference (pointer). The StackOverflowParameter's Evaluate method however, creates a new object on every call. So although the compared arrays may contain the same information (in our case the arrays contain zero integers), they are considered unequal. Therefore it seems like the parameter's value is constantly changing, which causes the data source to keep refreshing itself until infinity and BANG... We've got ourselves a stack overflow.

The solution is rather simple: Override the Equals method for the type, or when this is not possible, like with the int[] array type, return the same object when possible, as shown in the code below.
public class NoOverflowParameter : Parameter
{
private int[] _evaluated = null;

protected override object Evaluate(
HttpContext context, Control control)
{
if (this._evaluated == null)
{
this._evaluated = new int[0];
}
return this._evaluated;
}
}
Most of the time however, your custom parameter will probably not create objects itself, but rather fetch data from another source. In that case the page developer must make sure those objects are equatable, but it’s even better to let your Evaluate method check this and throw on failure.

That's it for now. In a future post I'll show you an implementation of a parameter that fetches data from a field or property in the page.

Happy coding!

- ASP.NET, C# - No comments / No trackbacks - §

The code samples on my weblog are colorized using javascript, but you disabled javascript (for my website) on your browser. If you're interested in viewing the posted code snippets in color, please enable javascript.

No comments:


No trackbacks:

Trackback link:

Please enable javascript to generate a trackback url


  
Remember personal info?

/

Before sending a comment, you have to answer correctly a simple question everyone knows the answer to. This completely baffles automated spam bots.
 

  (Register your username / Log in)

Notify:
Hide email:

Small print: All html tags except <b> and <i> will be removed from your comment. You can make links by just typing the url or mail-address.