Hello,
I've been struggling for some time to get a menu control derived from EO.Web (build 7.0.79.2) menu working correctly when data binding to an XmlDataSource. I am trying to save custom item data along with most of menu items and persist that data across postbacks using ViewState.
There are several problems, all of them boils down to that if I override any standard System.Web.UI.Control method (be it any of OnInit/OnLoad/OnPreRender, or even SaveViewState/LoadViewState), the menu control stops working correctly or even starts crashing.
So here is a (probably incomplete) list of problems:
1) When I override OnInit, automatic data binding stops working and additional code is required to handle data binding
2) If I override OnPreRender, control crashes when rendering. It crashes even if the only thing that overridden OnPreRender does is call base.OnPreRender. Stack trace:
[NullReferenceException: Object reference not set to an instance of an object.]
EO.Web.Internal.k.a(HtmlTextWriter A_0) +294
EO.Web.BaseNavigator.b(HtmlTextWriter A_0) +917
EO.Web.WebControlBase.o(HtmlTextWriter A_0) +54
EO.Web.WebControlBase.a(HtmlTextWriter A_0) +140
System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +27
System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +99
System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25
System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +134
System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +19
System.Web.UI.HtmlControls.HtmlForm.RenderChildren(HtmlTextWriter writer) +163
System.Web.UI.HtmlControls.HtmlContainerControl.Render(HtmlTextWriter writer) +32
System.Web.UI.HtmlControls.HtmlForm.Render(HtmlTextWriter output) +51
System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +27
System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +99
System.Web.UI.HtmlControls.HtmlForm.RenderControl(HtmlTextWriter writer) +40
System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +134
System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +19
System.Web.UI.Page.Render(HtmlTextWriter writer) +29
System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +27
System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +99
System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +1266
3) If I override SaveViewState/LoadViewState, control forgets it's state between postbacks and menu items disappear. Even overridden methods that only call base implementation cause this.
This is the control I am using to test functionality.
~/App_Code/TestMenu.cs:
Code: C#
using System.Web.UI;
using EO.Web;
using System.Collections.Generic;
using System;
using System.Xml;
namespace Test
{
public class TestMenu : EO.Web.Menu
{
class ItemData
{
public int Custom
{
get;
set;
}
}
Dictionary<string, ItemData> m_customData = new Dictionary<string, ItemData> ();
// Attaching event handlers in constructor because overriding OnInit prevents menu from data binding automatically
public TestMenu ()
{
this.ItemDataBound += TestMenu_ItemDataBound;
}
/*
// Overriding OnInit prevents control from automatically data binding to datasource
// That's why I need OnPreRender/PreRender event handler and DataBindingPerformed property
protected override void OnInit (EventArgs e)
{
this.ItemDataBound += TestMenu_ItemDataBound;
//this.PreRender += TestMenu_PreRender;
base.OnInit (e);
}
// Workaround for crashing OnPreRender
void TestMenu_PreRender (object sender, EventArgs e)
{
if (!this.DataBindingPerformed)
this.DataBind ();
}
protected override void OnDataBinding (EventArgs e)
{
this.DataBindingPerformed = true;
base.OnDataBinding (e);
}
bool DataBindingPerformed
{
get
{
object o = ViewState["DataBindingPerformed"];
return (null == o) ? false : (bool)o;
}
set
{
ViewState["DataBindingPerformed"] = value;
}
}
// Overriding OnPreRender causes EO.Web.Menu to crash in Render stage
protected override void OnPreRender (EventArgs e)
{
if (!this.DataBindingPerformed)
this.DataBind ();
base.OnPreRender (e);
}
*/
void TestMenu_ItemDataBound (object sender, NavigationItemEventArgs e)
{
XmlNode itemSource = (XmlNode)e.MenuItem.DataItem;
ItemData itemData = new ItemData ();
itemData.Custom = int.Parse (itemSource.Attributes["Custom"].Value);
m_customData[e.MenuItem.ItemID] = itemData;
}
// Overriding LoadViewState/SaveViewState disables control's view state management and menu items disappear after a postback
protected override void LoadViewState (object savedState)
{
if (null == savedState)
return;
Triplet viewState = (Triplet)savedState;
base.LoadViewState (viewState.First);
string[] IDs = (string[])viewState.Second;
int[] custom = (int[])viewState.Third;
int index = 0;
foreach (string id in IDs)
{
ItemData itemData = new ItemData ();
itemData.Custom = custom[index];
m_customData.Add (IDs[index], itemData);
index++;
}
}
protected override object SaveViewState ()
{
object o = base.SaveViewState ();
string[] IDs = new string[m_customData.Count];
int[] custom = new int[m_customData.Count];
int index = 0;
foreach (KeyValuePair<string, ItemData> pair in m_customData)
{
IDs[index] = pair.Key;
custom[index] = pair.Value.Custom;
index++;
}
return new Triplet (o, IDs, custom);
}
}
}
~/menu.aspx:
Code: HTML/ASPX
<%@ Page Language="C#" Debug="true" %>
<%@ Register Namespace="Test" TagPrefix="test" %>
<%@ Register Assembly="EO.Web" Namespace="EO.Web" TagPrefix="eo" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Menu test </title>
<meta http-equiv="X-UA-Compatible" content="IE=8" />
</head>
<body>
<form runat="server">
<asp:XmlDataSource runat="server" ID="datasource" XPath="/Menu/*">
<Data>
<Menu>
<Item ID="test1" Label="test1_label" Custom="1">
<Item ID="test1_1" Label="test11_label" Custom="11"></Item>
<Item ID="test1_2" Label="test12_label" Custom="12"></Item>
<Item ID="test1_3" Label="test13_label" Custom="13"></Item>
</Item>
<Item ID="test2" Label="test2_label" Custom="2">
<Item ID="test2_1" Label="test21_label" Custom="21"></Item>
<Item ID="test2_2" Label="test22_label" Custom="22"></Item>
</Item>
<Item ID="test3" Label="test3_label" Custom="3"></Item>
</Menu>
</Data>
</asp:XmlDataSource>
<test:TestMenu runat="server" ControlSkinID="Office2003" ID="menu1" DataSourceID="datasource">
<TopGroup>
<Bindings>
<eo:DataBinding Property="ItemID" DataField="@ID" />
<eo:DataBinding Property="Text" DataField="@Label" />
</Bindings>
</TopGroup>
</test:TestMenu>
<asp:Button runat="server" Text="post" />
</form>
</body>
</html>