Friday, 28 March 2008
Fix for my Moving ViewState solution
After a little investigation I discovered the source of the problem. The WebForm_InitCallback(); function used for Callbacks appears to rely on the "theForm" variable created in the __doPostBack function script. Worse still I discovered that my previous solution accidentally reverses (oops) the order of these two scripts, so that when WebForm_InitCallBack() is called "theForm" variable has not yet been defined!
I needed to find a way to move the viewstate to the bottom of the form without affecting the order of scripts etc. To do this I needed to understand what was writing out the two scripts in question and then figure out a way to make sure I didn't mess the ordering up.
Firstly, if you want to figure out how .NET actually works an invaluable tool (before they finally get round to making the source code available) is Lutz Roeders .NET Reflector. If you've never heard of it, then let me explain. Basically it lets you peek under the covers of .NET and see exactly whats going on by letting you read the disassembled code. This is invaluable when you want to root cause issues like this or if you just want to understand it a bit better.
Using Reflector and a couple of simple tests I figured out how the re-ordering was happening. I must confess I actually realised the re-ordering was occuring when I developed the initial solution, but I didn't understand the extent of the problem. I also had deadlines to meet and it worked for the job I needed it for, so I ignored it (shame on me). My sincerest apologies if you used it and encountered this issue. Anyway, for those of you who didn't read my last post, here's a quick recap of how .NET writes out the ViewState field...
1. ViewState is written (along with lots of other bits and pieces) by a method called Page.BeginFormRender().
2. Page.BeginFormRender() is called by the HtmlForm's RenderChildren() method before it calls base.RenderChildren()
3. Control.RenderChildren() causes the rendering of the Form controls children
4. Ideally we want to switch the order of the calls to Page.BeginFormRender() and base.RenderChildren() so that BeginFormRender() happens afterwards.
5. Unfortunately Page.BeginFormRender() is defined as "internal" so we can't call it directly (boo).
There are other solutions to the problem of moving ViewState, which are covered in my earlier post, as are my reason for not choosing them. In case you've forgotten, here is a quick recap of my solution...
My idea was to try to separate "our" HTML from the bits of HTML that ASP.NET adds to the Form to make it all work. I did this by introducing a surrogate HtmlTextWriter.InnerWriter at the start of the HtmlForm.RenderChildren method that actually wrote to a local StringWriter instance. Then when my first child's Render method was called I raised an event to my Form to tell it to swap the surrogate back out for the original InnerWriter. Then after all the children had been written out (i.e. base.RenderChildren() had completed) I could write out the contents of my surrogate StringWriter. This moved the ViewState et all to after Form children's HTML on the page and everything seemed rosey.
Unfortunatelty, ASP.NET does not write all of it's HTML before the form's child controls are rendered. It also writes bits of HTML afterwards when HtmlForm.RenderChildren() calls Page.EndFormRender(). Page.EndFormRender() writes out things like the "__PREVIOUSPAGE" and "__EVENTVALIDATION" hidden fields for example. Unfortunately, I had inadvertently moved the HTML written in Page.BeginFormRender() to after the HTML written out by Page.EndFormRender(). Page.BeginFormRender() declares "theForm" in the "__doPostBack" script and Page.EndFormRender() writes out the call to "WebForm_InitCallBack()", which is why the order of the two scripts got fatally reversed.
The solution was simple...
I needed to make sure that I wrote out the buffered pre-children HTML captured in Form.RenderChildren() before the call to Page.EndFormRender(), thus preserving the order of things. Since I can't hook into the ASP.NET code, the only solution was to insert another dummy control as the last child in the Form (after the "real" children). This control would raise an event back to the Form when it's Render() method was called. On notification of this event the Form would write out the buffered pre-children HTML to the page. Since my dummy control's Render() method is called before the call to Page.EndFormRender() in HtmlForm.RenderChildren() the ViewState is moved but the order is preserved!
The changes are actually quite small:
1. Buffer the HTML captured in Form.RenderChildren() that we want to move in a StringBuilder.
2. Remove the line of code in MyForm.RenderChildren() that used to write out the buffered HTML after the call to base.RenderChildren() had completed.
3.In the BeginRender event handler for our lastChild control, write out the contents of the StringBuilder to the HtmlTextWriter.
That's it. I've tested it with ICallbackEventHandlers and everything seems OK now. All the code is included below. Please let me know if you have a different experience....
MyRenderingPlaceHolder class
public class MyRenderingPlaceHolder : WebControl
{
public event EventHandler BeginRender;
public MyRenderingPlaceHolder()
: base()
{
}
protected override void Render(HtmlTextWriter writer)
{
OnBeginRender(null);
}
protected virtual void OnBeginRender(EventArgs e)
{
if (BeginRender != null)
{
BeginRender(this, e);
}
}
}
MyForm class
public class MyForm : HtmlForm
{
private HtmlTextWriter _htmlWriter;
private System.IO.TextWriter _savedInnerWriter;
private StringBuilder _viewStateBuilder;
public MyForm() : base()
{
}
protected override void RenderChildren(HtmlTextWriter writer)
{
_viewStateBuilder = new StringBuilder();
using (System.IO.StringWriter viewStateWriter = new System.IO.StringWriter(_viewStateBuilder))
{
_htmlWriter = writer;
_savedInnerWriter = writer.InnerWriter;
writer.InnerWriter = viewStateWriter;
base.RenderChildren(writer);
viewStateWriter.Close();
}
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
MyRenderingPlaceHolder renderingPlaceHolder = new MyRenderingPlaceHolder();
renderingPlaceHolder.ID = "FirstChild";
renderingPlaceHolder.BeginRender += new EventHandler(FirstChild_BeginRender);
renderingPlaceHolder.EnableViewState = false;
Controls.AddAt(0, renderingPlaceHolder);
}
protected override void OnPreRender(EventArgs e)
{
// Register a rendering placeholder as the last child so we can switch the writer back before
// any scripts / html written by page.EndFormRender(writer, this.UniqueID) and page.OnFormPostRender();
// occurs. This ensures we preserve the order of javascript which otherwise fails for Callbacks due to
// undeclared variables...
base.OnPreRender(e);
MyRenderingPlaceHolder renderingPlaceHolder = new MyRenderingPlaceHolder();
renderingPlaceHolder.ID = "LastChild";
renderingPlaceHolder.BeginRender += new EventHandler(LastChild_BeginRender);
renderingPlaceHolder.EnableViewState = false;
Controls.Add(renderingPlaceHolder);
}
private void FirstChild_BeginRender(object sender, EventArgs e)
{
_htmlWriter.InnerWriter = _savedInnerWriter;
}
private void LastChild_BeginRender(object sender, EventArgs e)
{
// Write the viewstate before the Page.EndFormRender and Page.OnFormPostRender code executes so that
// we maintain javascript order etc..
_htmlWriter.Write(_viewStateBuilder.ToString());
}
}
AJAX ScriptManager Problem
Well thats not quite it. My solution encounters an additional problem when using AJAX. The AJAX ScriptManager control renders it's own Javascript (Sys.WebForms.PageRequestManager._initialize...) that relies on code defined in a script included through ScriptResource.axd. Unfortunately, the reference to ScriptResource.axd gets moved with the ViewState and ends up after the ScriptManager controls' javascript in the document order. This results in a "Sys is undefined" error on the page and AJAX is broken.
Unfortunately, there is the potential for this to occur with any control that renders its own Javascript (in-place) which a) Executes when the page is loaded and b) Depends on an included script.
My current workaround is to modify the code so that you can specify exactly where you want the ViewState moved to in the rendering sequence of the Form's children. You do this by inserting a MyRenderingPlaceHolder on the page with a specific ID of "ViewStatePlaceHolder". When the form is rendered and this controls Render() method is called it raises an event that causes the buffered ViewState (and script includes) to be written out. Therefore, by positioning the ViewStatePlaceHolder control before the ScriptManager control in the forms children, you can ensure that the script included by ScriptResource.axd occurs before the inline script that depends on it. If you do not add your own "ViewStatePlaceHolder" control to the Form one will be automatically added for you after the last child. This means that you only have to use this technique when you are using the ScriptManager control (or another control that behaves in the same way).
The code changes to support this workaround are included below:
1. Add this function to the MyForm control.
private MyRenderingPlaceHolder GetViewStateRenderingPlaceholder()
{
foreach (Control candidate in Controls)
{
if (candidate.ID != null && candidate.ID.Equals("ViewStatePlaceHolder") && candidate is MyRenderingPlaceHolder)
{
return (MyRenderingPlaceHolder)candidate;
}
}
return null;
}
2. Replace MyForm.OnPreRender with the following code:
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
MyRenderingPlaceHolder renderingPlaceHolder = GetViewStateRenderingPlaceholder();
if (renderingPlaceHolder == null)
{
renderingPlaceHolder = new MyRenderingPlaceHolder();
renderingPlaceHolder.ID = "ViewStatePlaceHolder";
renderingPlaceHolder.BeginRender += new EventHandler(ViewStatePlaceHolder_BeginRender);
renderingPlaceHolder.EnableViewState = false;
Controls.Add(renderingPlaceHolder);
}
else
{
renderingPlaceHolder.BeginRender += new EventHandler(ViewStatePlaceHolder_BeginRender);
}
}
3. Rename LastChild_BeginRender event to ViewStatePlaceHolder_BeginRender
This name makes more sense now since it is not necessarily the last child. The code has not changed at all.
private void ViewStatePlaceHolder_BeginRender(object sender, EventArgs e)
{
// Write the viewstate before the Page.EndFormRender and Page.OnFormPostRender code executes so that
// we maintain javascript order etc..
_htmlWriter.Write(_viewStateBuilder.ToString());
}
Include the rendering placeholder on your Page
If you want (or need) to explicity control where the ViewState and script includes are rendered then include the MyRenderingPlaceHolder as a child of your MyForm control as follows...
<sug:MyForm ID="form1" runat="server">
<div>
<!-- Put your non script-include sensitive controls here -->
<sug:MyRenderingPlaceHolder ID="ViewStatePlaceHolder" runat="server" />
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
</div>
</sug:MyForm>
Unfortunately, time constraints have limited my AJAX testing to a simple "hello world" application, so please let me know if any further problems arise. And finally, a big thank you to Corne for all his AJAX testing.
Labels: ASP.NET, Moving ViewState Field
Wednesday, 28 November 2007
Creating string valued C# enums
I've recently been writing an email subscription application for one of our e-commerce sites and re-used a pattern that I developed a couple of years ago. I thought it might be worth sharing to see if anyone else is doing anything similar...
The Problem
I want a C# enum with a string or character value. In otherwords, I want to do something like:
enum Status : string
{
Active = "A",
PendingActivation = "P",
Deleted = "D"
...
}
In my application I want to use the nice friendly enum constants e.g. "Status.Active", but in my database I'm optimising and only want to store a single character to represent each state. I also want a centralised point where I convert to/from the value so that if I add or change states I only have one place to change the code. Finally, I need to be able to compare instances of the enum for equality, e.g. If (Subscription.Status == Status.PendingActivation).
I guess some of the database folks out there will say that I should have a new database table "STATUS" and have records in it for each possible status and then use the STATUS record's ID elsewhere in the database. May'be, but to be honest sometimes that is total overkill. Sometimes, I just want a simple flag column that happens to have more than two states (e.g. true or false) and so I cannot use a boolean.
Unfortunately, string enum values are not supported in C#, so I decided to develop a "pseudo enum" class that worked like an enum but with string values...
Base Class
Firstly, since I'm going to need more than one of these types I created a base class to do most of the work:
public abstract class EnumObject
{
private string _caption;
private string _value;
protected EnumObject()
{
}
protected EnumObject(string caption, string theValue)
: base()
{
_caption = caption;
_value = theValue;
}
public string Caption
{
get { return _caption; }
set { _caption = value; }
}
public string Value
{
get { return _value; }
set { _value = value; }
}
public static bool operator ==(EnumObject a, EnumObject b)
{
if ((object)a == null && (object)b == null)
{
return true;
}
else if ((object)a == null || (object)b == null)
{
return false;
}
return a.Value == b.Value;
}
public static bool operator !=(EnumObject a, EnumObject b)
{
return !(a == b);
}
public override bool Equals(object b)
{
if (b is EnumObject)
{
return (this == (EnumObject)b);
}
else
{
return false;
}
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
Note: if you don't need to support XML serialization you can remove the parameterless constructor and the SET operations from the Value and Caption properties.
This base class gives us a basic Caption + Value pair and the ability to compare instances based on their Value.
An Instance
Now to define a pseudo-enum we need to define a new class that inherits from EnumObject. The example below defines a new "enum" called SubscriptionStatus with two constants - "PendingActivation" and "Active". In C# if strings were supported as enum values it might look like this...
enum SubscriptionStatus : string
{
Active = "A",
PendingActivation = "P"
}
You will notice in the code below, for each enum constant we define a static property that returns an instance of our new type prepopulated with the appropriate Caption and Value. Since this is a fully fledged class we can also add other useful behaviour like a static Create() method that returns an instance of the enum based on a Value. This is useful when we need to read a Value from the database and turn it into an instance of the enum class. It achieves my objective of centralising the translation code.
public class SubscriptionStatus : EnumObject
{
public SubscriptionStatus()
: base()
{
}
private SubscriptionStatus(string caption, string theValue)
: base(caption, theValue)
{
}
public static SubscriptionStatus PendingActivation
{
get { return new SubscriptionStatus("PendingActivation", "P"); }
}
public static SubscriptionStatus Active
{
get { return new SubscriptionStatus("Active", "A"); }
}
// Add more enum constants here...
public static SubscriptionStatus Create(string code)
{
switch (code.ToUpper())
{
case "P":
return PendingActivation;
case "A":
return Active;
default:
throw new ArgumentException("Invalid status code:" + code);
}
}
}
Finally, when we want to write the Value to the database we can simple pass the .Value property of the enum instance. For example, mySubscription.Status.Value.
So thats my enum object pattern, let me know what you think or if you have any improvements.
Labels: ASP.NET, C# enum, enum class
Friday, 5 October 2007
Moving the ViewState hidden field in ASP.NET 2.0
Breaking news...Fix for ICallbackEventHandler available!
This solution has been updated to fix a javascript sequencing problem encountered with pages/controls that use ICallbackEventHandler. Click here for details and a full code listing.
The Problem:
I wanted to produce XHTML Strict compliant ASP.NET output that was "search engine friendly". My goal was to produce ASP.NET output that was as close to our legacy XHTML as possible. We are moving lots of static (aside from the odd Server Side Include) pages to ASP.NET so that we can inject dynamic content. Getting ASP.NET to produce XHTML Strict output was pretty easy, although there are a couple of "gotchas". For example, browser capabilities mean that what you see in IE / Firefox isn't necessarily what every user-agent will receive. Obviously, you want to make sure that search engines aren't getting "dirty" HTML that is claiming to be XHTML Strict. Anyway, I'm going to write a separate posting about those issues later. I should mention that although moving the ViewState field was my main target, I was really interested in relocating all the "control" fields and code further down the page. Now I'm not going to get into debates about whether this is necessary or not, thats a separate discussion, but my approach has always been "better safe than sorry" when it comes to not falling out with the search engines.
Scott Hanselman's Solution:
So naturally I googled this problem first and checked out quite a few of the solutions that came up. The most referenced appears to be Scott Hanselman's solution of intercepting the HTML string produced by the HTMLTextWriter, finding and moving the ViewState field within it and passing it on. Initially, I tried this and it worked fine. However, once I turned on XHTML Strict (which wraps the ViewState field in a DIV for compliance) I was left with an empty DIV. Furthermore, once I added cross page posting (needed for our search script) I saw additional fields added to the top of the form which I really wanted to move too. I didn't really want to create a lot of string manipulation code that was totally dependent on how ASP.NET decided to write out its control fields and scripts, so I started digging some more...
Wilco Bauwer's Solution
I was trying to understand how and when ASP.NET was rendering the ViewState and other control fields, when I came across a blog Wilco Bauwer that discusses the problem and shows the rendering process. Personally, I was not happy with the full-trust security requirement of their solution and I'm uneasy about trying to replicate chunks of ASP.NET code (Form.RenderChildren) - what happens if it changes? However, reading this really helped me to come up with my own approach, so I'm definitely "standing on the shoulders of giants" here. This is a simplification of the ViewState rendering process:
- ASP.NET Renders the form contents by calling the Form.RenderChildren method
- The Form control calls Page.BeginFormRender
- The Page BeginFormRender() method writes out the ViewState and other control fields
- The Form control renders its children
The obvious solution was to move the call to Page.BeginFormRender to after the forms child controls had been rendered (i.e. switch steps 2 & 4). Wilco points to this solution too. Unfortunately, we cannot easily do that because Page.BeginFormRender is declared as internal so we can't call it directly. Also we would have to duplicate the original Form.RenderChildren code to allow us to tweak the ordering and as stated I don't like doing this as it creates a big code dependency. What happens when someone at Microsoft changes that method, suddenly my code breaks or something stops working! What I needed to do was to allow ASP.NET to do it's stuff as normal, but to somehow intercept its output so I could move it in it's entirety later. This is exactly what I ended up doing...
My Solution: Using A Custom Form and Placeholder Control
OK so this is my solution, let me know what you think. If you've posted this somewhere already and I've missed it I apologise.
If you look back at the problem what we really want to do is to is to split the Form.RenderChildren method into the piece that happens before the forms "real" children start getting rendered (i.e. when ViewState et al are written) and the piece where the "real" children are being rendered. We then want to swap the order of the two pieces. I acheived this by swapping in a surrogate StringWriter as the HtmlTextWriter.InnerWriter for ASP.NET to write to, which I then swap back to the original HttpWriter before the children are rendered. Then after the children are rendered I write the content of the surrogate StringWriter to the HtmlTextWriter before the form tag is closed. The key is having a control that notifies us when the rendering of the forms "real" children has started so we can do the switch back. I do this by using a placeholder control inserted at position 0 in the control collection. This placeholder raises an event in it's render method to signal that the switch back needs to occur. The code is included below...
1. Create a new WebControl called MyRenderingPlaceHolder. Override its Render method and raise the BeginRender event when it is called. Do not call base.Render() as we don't want any HTML to be written out for this control.
public class MyRenderingPlaceHolder : WebControl
{
public event EventHandler BeginRender;
public MyRenderingPlaceHolder() : base()
{
}
protected override void Render(HtmlTextWriter writer)
{
OnBeginRender(null);
}
protected virtual void OnBeginRender(EventArgs e)
{
if (BeginRender != null)
{
BeginRender(this, e);
}
}
}
2. Create your own Form control that inherits from HTMLForm called MyForm
public class MyForm : HtmlForm
{
private HtmlTextWriter _htmlWriter;
private System.IO.TextWriter _savedInnerWriter;
3. In your MyForm control override the OnInit method. Create a MyRenderingPlaceHolder control and add it as the first control in the collection. Register for the MyRenderingPlaceHolder.BeginRender event. Don't forget to call base.OnInit.
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
MyRenderingPlaceHolder renderingPlaceHolder = new MyRenderingPlaceHolder();
renderingPlaceHolder.ID = "FirstChild";
renderingPlaceHolder.BeginRender += new EventHandler(FirstChild_BeginRender);
renderingPlaceHolder.EnableViewState = false;
Controls.AddAt(0, renderingPlaceHolder);
}
4. In MyForm override the RenderChildren method and subsitute the writer.InnerWriter for your own StringWriter instance (viewStateWriter). Keep the original HttpWriter to switch back to later, then call base.RenderChildren. After the call to base.RenderChildren write the contents of viewStateWriter to the HtmlTextWriter. By the time we reach this point the switch back will have occurred and we are now effectively writing the ViewState et al to the HttpWriter.
protected override void RenderChildren(HtmlTextWriter writer)
{
using (System.IO.StringWriter viewStateWriter = new System.IO.StringWriter())
{
_htmlWriter = writer;
_savedInnerWriter = writer.InnerWriter;
writer.InnerWriter = viewStateWriter;
base.RenderChildren(writer);
writer.Write(viewStateWriter.ToString());
viewStateWriter.Close();
}
}
5. In the MyForm handler for MyRenderingPlaceHolder.BeginRender event swap the writer.InnerWriter back to the original one we swapped out earlier. The subsequent child controls will now be rendered to the HttpWriter and underlying response stream.
private void FirstChild_BeginRender(object sender, EventArgs e)
{
_htmlWriter.InnerWriter = _savedInnerWriter;
}
To get viewstate moved on a page all you need to do is subsitute the MyForm control for the standard HTMLForm control on your aspx pages.
<mycontrols:myform id="form1" runat="server">
The main benefit of this solution is that it doesn't make any assumption about what "control" fields ASP.NET is going to write out or how the HTML for them is going to look. It also moves the whole lot to the end of the form and will automatically cope with any changes to that part of ASP.NET. Finally, it also does not rely on duplicating critical chunks of the existing HTML forms behavour and therefore has very few dependencies that might cause it to stop working in the future.
So that's it. I don't think my solution breaks anything, but if you find otherwise then let me know. Don't forget that when you move the ViewState you introduce the issue that you could get a viewstate exception if a user causes a form post before the ViewState field has rendered in the browser. However, as long as you keep your pages "light" (e.g. avoid using ViewState unnecessarily) and server response times low then this should not be a problem.
Anyway, hopefully I've managed to explain it well enough. Let me know what you think or if you have a better way.
Labels: ASP.NET, Moving ViewState Field
Subscribe to Posts [Atom]
