Friday 5 September 2008

Disable Event Validation for an ASP.NET control

I blogged yesterday about fixing the 'Invalid postback or callback...' problem in an ASP.NET web page. The posted solution involved programatically turning off the EnableEventValidation property for the entire page. I mentioned that there are some security issues with this approach and that caution is advised when turning off security features in any code that one writes.

Well, with a bit more digging, I have now managed to turn off event validation at the control level rather than the page level. This means that you can ensure that the page is still validated, but that a specific control does not participate in event validation. From a security perspective, this is a significant improvement on the previous solution (but still not perfect).

Very briefly, the problem we are trying to solve is that ASP.NET 2.0 validates postbacks. It does this by storing the unique ID of a control (for example a drop down list) and all of it's possible values in a hash. When the client does a postback, the runtime checks that the unique ID and value combination submitted by the client exist in the hash. If the client has changed the unique id, or changed/added any values the server will not know about it and will not be able to validate the postback. This is to prevent malicious code from spoofing a postback. (see http://odetocode.com/Blogs/scott/archive/2006/03/20/3145.aspx for more info)

With things like AJAX playing about with javascript under the covers, the potential for this happening is increased.

To prevent postback validation at a control level, one must create a custom web control. Imagine that you want to turn off event validation for a drop down list, but the drop down list exposes no property/method to turn off validation.

This can be overcome by creating a custom drop down list and ommiting the [SupportsEventValidation] attribute from the class. This attribute is in fact ommited by default, so all you actually need to do is create a custom control which inherits from System.Web.Ui.WebControls.DropDownList, remove any custom implementations (by default, Visual Studio creates an override for the RenderContents method and the Text property) and you're done!

Now use this control in place of a standard drop down list and hey presto... no more errors :-)

Thursday 4 September 2008

Invalid Postback or callback argument. A code behind solution to this problem...

I have recently been taking my first tentative steps into the world of AJAX, and believe me, it's a mine field out there!!

A problem that was really bugging me was getting the infamous error:

Invalid Postback or callback argument . Event validation is enabled using in configuration or <%@ Page EnableEventValidation="true" %>in a page. For security purposes, this feature verifies that arguments to Postback or callback events originate from the server control that originally rendered them. If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation method in order to register the Postback or callback data for validation.

As I did not write the code I was working with, I couldn't identify exactly which controls were causing the problem, nor did I know exactly which pages were affected. As a result adding the
EnableEventValidation="False" to the @Page tag of all the aspx files was not possible.

Luckily however, all of the pages on the site derive from a custom Page class (which inherits from System.Web.UI.Page).

This meant that I could override the EnableEventValidation property from the code behind rather than relying on finding all the page tags.

The code in the Page class goes a bit like this:

public override bool EnableEventValidation
{
get {return false;}
set {base.EnableEventValidation = value;}
}

This simply ensures that event validation is always false, regardless of what the @Page tag says!

I hope this helps someone, but it is worth pointing out one or two issues here:
  • EventValidation is a security feature added to ASP.NET 2.0 to prevent postback spoofing. Turning it off could leave your website vulnerable to injection attacks.
  • Overriding the EnableEventValidation at the base class level will ignore anything set in the @page directive. Developers may therefore believe they have told the page to do one thing and be very confused when it does not behave as expected. Event worse, they may never notice that event validation is turned off, which could pose unidentified issues when the website is live.


For more information, you could try http://aspnet.4guysfromrolla.com/demos/printPage.aspx?path=/articles/122006-1.aspx which pointed me in the right direction for this solution. Also
http://odetocode.com/Blogs/scott/archive/2006/03/20/3145.aspx and http://odetocode.com/Blogs/scott/archive/2006/03/21/3153.aspx are very useful.

Collapsing the AjaxToolkit CollapsiblePanelExtender from server code

Well, this one had me going for quite some time so I just thought I'd share my solution with you :-)

First off, i'm tired of typing CollapsiblePanelExtender, so from here on in it's a CPE!

Secondly setting the Collapsed and ClientState on the CPE properties do not work a unless the page/control containing the CPE is refreshed with a postback, which in this scenario it is not.

Imagine, you have page called MyPage.aspx. This has a control called MyControl.ascx on it. MyControl.ascx has a CPE on it called MyCPE. MyCPE has a usercontrol on it (called MyDetails.ascx). MyDetails.ascx has a number of controls on it, but one of them is a 'Cancel' button. You want this to collapse the panel (This is in addition to the CollapseControl already assigned to the CPE). Phew, that's quite a heirarchy, but it's simpler than the one I had to work with!!

There are two main stages to this solution:

First - Wire up events
Wire up the OnClick event of the aforementioned 'Cancel' button to the parent page/control. So if the page MyPage.aspx has a control on it called MyControl.ascx, and this control has MyCPE on it. MyCPE has MyDetails.ascx on it, and MyDetails.ascx has the cancel button on it.

MyControl needs to have an event handler which is fired when the OnClick event is fired on MyDetails.ascx. To acheive this, modify your code something like;

MyControl.ascx.cs

Add the following line to the Page_Load event:

this.MyDetails.CanclButtonHasBeenClicked += CancelButtonClicked

Add the following empty event handler (we'll populate it later):

private void CancelButtonClicked(object sender, EventArgs e)
{
}

MyDetails.ascx.cs

Publicly expose the Cancel OnClick event by adding a public event handler to the class

Public EventHandler CancelButtonHasBeenClicked;

Fire your new event handler when the cancel button is clicked. Add the following to the actual event handler for the cancel button:

CancelButtonHasBeenClicked(sender, e);

Second - Collapse the CPE

Ok, now your parent user control (MyControl.ascx) is notified when the cancel button is clicked on the CPE (via the MyDetails.ascx control). Now you just need to inject some javascript to collapse the CPE. Add the following code to the empty CancelButtonClicked event handler created above:

string JavaScriptCode="$find('MyCPEBehaviour').collapsePanel();";
ScriptManager.RegisterStartupScript(Page, Page.GetTpye(),"MyScript",JavaScriptCode,true);

Thanks to James Ashley who pointed me on the right direction on that one!

Finally, add the behaviour id to your CPE in MyControl.ascx by adding the following to the CPE tag:

BehaviorID="MyCPEBehaviour"

Job Done. Now your CPE should collapse when you click on the 'Cancel' button on MyDetails.ascx within the CPE on MyControl.ascx.

Hopefully this waffle makes some sense and helps someone one day...