ASPNL logo (1 kb)
Friday, September 03, 2010




Microsoft MVP

.NET Codewise Community
<< vorige | overzicht | volgende >>

The Vision Column - Een ASP.NET Control raadsel

The Vision Column wordt verzorgd door ontwikkelaars van The Vision Web (tegenwoordig Ordina).

Door Michiel van Otegem
12 juli 2005

Soms ben je ergens mee bezig en heb je ineens zoiets van "Wat is hier nou in godsnaam aan de hand?" Zo ook iets waar ik recentelijk mee bezig was.

Onlangs was ik bezig met een Tab-control voor ASP.NET. Het idee was simpel: ik heb een control en daarin kunnen tabs gedefinieerd worden waarbij er altijd één zichtbaar is. Bij de implementatie maakte ik gebruik van een Panel-control voor iedere tab. So far, so good… totdat ik controls in de Panels zette.

Als ik veranderde van tab, dan werd de ViewState van de controls op andere tabs niet bewaard. De oplossing was uiteindelijk eenvoudig doch subtiel, maar daar kwam ik pas achter nadat ik van alles geprobeerd had. Hierdoor vond ik wel iets uit dat op z'n minst opmerkelijk te noemen is.

Om te weten wat er mis ging, wilde ik graag zien wat er gebeurde bij het opslaan van de ViewState. Ik maakte een TestBox-control die overerft van TextBox, zodat ik overrides kon maken en daarin breakpoints kon zetten, als volgt:
public class TestBox : System.Web.UI.WebControls.TextBox {}

De definitie hierboven geeft een control die 100% gelijk is aan de TextBox. Althans, dat zou je denken. Maar… als ik de TestBox in een Panel zette, werd de ViewState daarvan wel opgeslagen, terwijl dat van een TextBox in dezelfde Panel niet zo was. What the ....?!?! Dit was voor mij een dermate groot raadsel dat ik besloot de jongens in Redmond even lastig te vallen. Het antwoord:
"Ja, dat klopt. Van onze eigen controls weten we precies wanneer er wel/geen ViewState nodig is (mits alles goed geprogrammeerd is), dus slaan we die echt alleen op als dat noodzakelijk is. Van controls die geërfd zijn, weten we niet zeker dat de ViewState nog hetzelfde werkt, dus wordt voor de zekerheid de ViewState daarvan wel opgeslagen."
Met andere woorden er staat ergens gewoon de volgende pseudo-code:
if(control.IsInherited) control.SaveViewState();

Het nut van deze informatie is natuurlijk beperkt, maar het is wel eens leuk om te weten waarom sommige dingen anders werken dan je zou verwachten.

Maar wat was nu de oorzaak van het niet werken van mijn control? Het moment waarop ik instelde welke tab zichtbaar moet zijn: ik deed dit in eerste instantie in de Render methode van de control. Dan is de ViewState echter al opgeslagen, en weet ASP.NET niet dat bepaalde controls onzichtbaar gaan worden en dat daarvan de ViewState opgeslagen moet worden. Op het moment dat ik de zichtbaar/onzichtbaar logica verplaatste naar het RaisePostbackEvent (dat uitgevoerd wordt als je op een van de tabs klikt), dat vóór het opslaan van de ViewState plaatsvindt, waren mijn problemen verleden tijd…

Toch wel een domme fout voor iemand die de volgorde van de events in controls van voor naar achter en weer terug kent. Ik troost mij met de gedachte dat iedereen wel eens iets over het hoofd ziet dat voor de hand ligt. Ter referentie staat hieronder nog de code die niet werkt.

De control

  1: using System;
  2: using System.ComponentModel;
  3: using System.Web.UI;
  4: using System.Web.UI.WebControls;
  5:  
  6: namespace Dev.WebControls
  7: {
  8:     [ParseChildren(false)]
  9:     [ControlBuilder(typeof(TestControlBuilder))]
 10:     public class TestControl : WebControl, IPostBackEventHandler
 11:     {
 12:         protected int SelectedPanelIndex 
 13:         {
 14:             get 
 15:             {     
 16:                 object o = ViewState["SelectedPanelIndex"];
 17:                 if(o == null) return 0;
 18:                 return (int)o;
 19:             }
 20:             set 
 21:             {
 22:                 ViewState["SelectedPanelIndex"]= value;
 23:             }
 24:         }
 25:  
 26:         public override ControlCollection Controls
 27:         {
 28:             get
 29:             {
 30:                 this.EnsureChildControls();
 31:                 return base.Controls;
 32:             }
 33:         }
 34:  
 35:         protected override void
     RenderContents(System.Web.UI.HtmlTextWriter writer)
 36:         {
 37:             for(int i = 0; i < this.Controls.Count; i++)
 38:             {
 39:                 Panel panel = (Panel)this.Controls[i];
 40:                 writer.AddAttribute(HtmlTextWriterAttribute.Href,
     Page.GetPostBackClientHyperlink(this, i.ToString()));
 41:                 writer.RenderBeginTag(HtmlTextWriterTag.A);
 42:                 writer.Write(panel.ID);
 43:                 writer.RenderEndTag();
 44:  
 45:                 //Dit is dus waar het fout gaat.
 46:                 //Deze code moet voor het opslaan van de ViewState.
 47:                 if(this.SelectedPanelIndex != i)
 48:                 {
 49:                     panel.Visible = false;
 50:                 }
 51:  
 52:                 writer.WriteFullBeginTag("br");
 53:             }
 54:             writer.WriteFullBeginTag("hr");
 55:             base.RenderContents(writer);
 56:  
 57:             foreach(Control control in this.Controls)
 58:             {
 59:                 if(control is Panel) control.Visible = true;
 60:             }
 61:         }
 62:  
 63:         #region IPostBackEventHandler Members
 64:  
 65:         public void RaisePostBackEvent(string eventArgument)
 66:         {
 67:             this.SelectedPanelIndex = Convert.ToInt32(eventArgument);
 68:         }
 69:  
 70:         #endregion
 71:     }
 72:  
 73:     public class TestControlBuilder : ControlBuilder
 74:     {
 75:         public override bool AllowWhitespaceLiterals()
 76:         {
 77:             return false;
 78:         } 
 79:  
 80:         public override Type GetChildControlType(string tagName,
     System.Collections.IDictionary attribs)
 81:         {
 82:             if(string.Compare(tagName, "asp:Panel", true) == 0)
 83:             {
 84:                 return typeof(Panel);
 85:             }
 86:             else
 87:             {
 88:                 throw new ApplicationException("Only panels
     are allowed as child controls.");
 89:             }
 90:         }
 91:     }
 92: }
De pagina
  1: <%@ Register TagPrefix="cc1" Namespace="Dev.WebControls"
     Assembly="Dev.WebControls" %>
  2: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
  3: <html>
  4: <body>
  5: <form id="Form1" method="post" runat="server">
  6:     <cc1:TestControl id="TestControl1" runat="server">
  7:     <asp:panel id="Panel1" runat="server">
  8:       <cc1:TestBox ID="TestBox1" Runat="server">123</cc1:TabBox>
  9:       <asp:TextBox ID="Textbox1" Runat="server">456</asp:TextBox>
 10:     </asp:panel>
 11:     <asp:panel id="Panel2" runat="server">
 12:       <asp:TextBox ID="Textbox2" Runat="server">789</asp:TextBox>
 13:     </asp:panel>
 14:     </cc1:TestControl>
 15: </form>
 16: </body>
 17: </html>
<< vorige | ^ naar boven | overzicht | volgende >>
copyright 2000-2007 ASPNL