Extend Dynamic TextBox Control View State To Store

原创 2004年08月20日 14:33:00
download.gif  Download C# Source Code
Only a developer would know the absolute joy of moving a heavily database centric desktop application to a web environment.  Can you smell the sarcasm?  Aside from storing data in cache, session, or static objects there is no storage place in memory for us to put data for fast access and hold onto it until the user closes the application the same way a desktop application can.  Thus, we end up having to run expensive queries to retrieve meta data (information stored in our database that helps describe our data) each time the page loads as well as when it posts back to itself.  Wouldn't it be nice to store this information somewhere so we don't have to run the same query again on page post back?
This nice to have feature has really become a bit of a necessity for a rather complex project our team has been working on that is a little bit out of the norm.  Let me briefly summarize the requirements of our data entry pages.  The entire data entry page is database driven and in many cases this includes user specific settings for each data entry point.  A typical page might have 40 or so dynamically generated data entry points.  A wide variety of settings or meta data exists for each data entry point that range from display oriented settings, server side calculation settings, relational settings to combinations of data entry points, and settings determining how and when to save the data.  As you can see, this can get rather complicated.
Let's also review the application environment.  A typical user analysis model would consist of around a 100 of these data entry pages.  The Web site itself supports an unlimited number of analysis models.  Thus, the database is quite large and the process of obtaining the meta data is quite complex.  Running more queries than absolutely necessary can be a real drag on performance.  Also keep in mind that a typical user will spend anywhere from 15 - 45 minutes on a single data entry page completing that section of the analysis model.
Before trying to implement the ViewState solution described below, I looked into utilizing session, static objects, and the application cache.  Each of these had their pros and cons as it applied to our specific situation.  I opted for this solution but it is important to note that if your requirements aren't quite as complex and particularly if the meta data is fairly static, then I'd look towards loading it up in a static object or use the cache.  The ViewState solution I've come up with is strictly for those times when you need to attach additional properties to controls and maintain the state of the control and the state of the extended properties together.  In other words, I've opted to show you that this can be done.  It doesn't mean that you always should.  The performance impact of having larger than normal ViewState and the process of managing ViewState isn't necessarily prohibitive but if you are trying to squeeze out every once of performance, doing this without it being necessary could hurt your cause.

buycontent.gif
New Message Board Posts
the question of full-text index
How to compare 2 timings?
DTS - Execute SQL Task
authenticate using ADSI and give access to files
Display a graph in a tooltip
<SCRIPT type=text/javascript><!-- google_ad_client = "pub-8682474657542641"; google_alternate_ad_url = "http://www.eggheadcafe.com/alternateads.asp"; google_ad_width = 300; google_ad_height = 250; google_ad_format = "300x250_as"; google_ad_channel ="0679176943"; google_color_border = "FFFFFF"; google_color_bg = "FFFFFF"; google_color_link = "6D99CA"; google_color_url = "008000"; google_color_text = "000000"; //--></SCRIPT> <SCRIPT src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type=text/javascript> </SCRIPT> <IFRAME name=google_ads_frame marginWidth=0 marginHeight=0 src="http://pagead2.googlesyndication.com/pagead/ads?client=ca-pub-8682474657542641&amp;random=1092983545734&amp;lmt=1092983545&amp;alternate_ad_url=http%3A%2F%2Fwww.eggheadcafe.com%2Falternateads.asp&amp;format=300x250_as&amp;output=html&amp;channel=0679176943&amp;url=http%3A%2F%2Fwww.eggheadcafe.com%2Farticles%2Fextendtextboxviewstate.asp&amp;color_bg=FFFFFF&amp;color_text=000000&amp;color_link=6D99CA&amp;color_url=008000&amp;color_border=FFFFFF&amp;ref=http%3A%2F%2Fwww.asp.net%2FDefault.aspx%3Ftabindex%3D0%26tabid%3D1" frameBorder=0 width=300 scrolling=no height=250 allowTransparency><img height="1" width="1" border="0" src="http://pagead2.googlesyndication.com/pagead/imp.gif?client=ca-pub-8682474657542641&random=1092983545734&lmt=1092983545&alternate_ad_url=http%3A%2F%2Fwww.eggheadcafe.com%2Falternateads.asp&format=300x250_as&output=html&channel=0679176943&url=http%3A%2F%2Fwww.eggheadcafe.com%2Farticles%2Fextendtextboxviewstate.asp&color_bg=FFFFFF&color_text=000000&color_link=6D99CA&color_url=008000&color_border=FFFFFF&ref=http%3A%2F%2Fwww.asp.net%2FDefault.aspx%3Ftabindex%3D0%26tabid%3D1&event=noiframe" /></IFRAME>

In the code sample below (and the downloadable zip file above), we'll see how this can be accomplished.  In a nutshell, I've extended the existing ASP.NET TextBox server control by adding a custom made namevaluecollection class and overrode how ViewState is handled.  The custom namevaluecollection class inherits the base namevaluecollection class and adds two methods for loading the collection from a string and converting the collection to a string.  Our own Peter Bromberg contributed the loading method and I took care of the rest.  These methods were needed because serializing the entire namevaluecollection will cause our ViewState to grow enormously and take up bandwidth unnecessarily.  All we really want to do is store a few strings and ints for each data point in ViewState.  So, we'll store a string representation of the namevaluepair, similar to what you would see on a querystring, in ViewState.  Then, when we load and save view state, we'll move the data in and out of the NameValueCollection to make it easier for our user interface developers to work with the control.
For your convenience, I've hard coded in all of the base properties of the textbox control that can be stored in ViewState.  These properties are not stored in ViewState by ASP.NET controls.  In your implementation, you will probably want to exclude any of those properties you don't need.  You'll also want to note that controls created at runtime via the Page_Init event will not have their ViewState contents loaded until the Page_Load or OnLoad event fire (assuming you aren't overriding the Page.LoadViewState event).
The Webform1.aspx page has a method called BuildDynamicControls and the OnLoad event.  Put breakpoints in these two and watch the Output section of the IDE.  By stepping through the code, you can watch the controls get created and populated the first time the page is accessed.  Then, watch what happens to the values held in ViewState after you've changed a few textbox values in the browser and clicked the button to submit.
When you review the source code for this extended server control, keep in mind that this is just a proof of concept and not necessarily 100% ready for your production use.  I also didn't bother to wire up any of the normal textbox events but this is certainly still available just like it always has been.  If you need assistance with this, EggHeadCafe.com has another article Extending a TextBox Control in ASP.NET that does a good job of explaining the whole process including setting up the the control so that it shows up in the Visual Studio .NET IDE.
Please take a moment to rate this article (opens new browser window).  Rate Article
WebForm1.aspx
using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Diagnostics; 

namespace TextBoxSample
{
 
  public class WebForm1 : System.Web.UI.Page
  {
    protected System.Web.UI.WebControls.Button Button1;
    protected CustomControls.TxtBox TextBox2;
    protected System.Web.UI.WebControls.PlaceHolder ph1;
      
   private void Page_Init(object sender, System.EventArgs e)
   {
     BuildDynamicControls();
   }
 
   protected override void OnLoad( EventArgs e )
   {
      CustomControls.TxtBox oTextBox = null;
 
      for(int i=1;i<ph1.Controls.Count;i++)
      {
        if (ph1.Controls[i].ID.StartsWith("TextBox") != true) { continue;}
        oTextBox = (CustomControls.TxtBox)ph1.Controls[i];
        Debug.Write(oTextBox.ID + " ");
        Debug.Write(oTextBox.Text + " ");
        Debug.Write(oTextBox.NameValuePairs["test1"].ToString() + " ");
        Debug.WriteLine(oTextBox.NameValuePairs["test2"].ToString());
      }
		 
   }
	 
      
   private void BuildDynamicControls()
   {
      try
      {
		 
        for(int i=0;i<10;i++)
        {
          CustomControls.TxtBox TextBox1 = new CustomControls.TxtBox();

          TextBox1.ID = "TextBox" + i.ToString();
          TextBox1.EnableViewState = true;
                 
          if (!Page.IsPostBack) 
          {
            TextBox1.Text = "eggheadcafe " + i.ToString();
            TextBox1.ForeColor = System.Drawing.Color.Orchid;
 
            if (i%2==0)
            {
              TextBox1.ForeColor = System.Drawing.Color.Red; 
            }
				
            TextBox1.NameValuePairs.Add("test1",i.ToString());
            TextBox1.NameValuePairs.Add("test2","10" + i.ToString());
          }

          ph1.Controls.Add(TextBox1);  
 
         }
       }
       catch (Exception e) { Debug.WriteLine("Build Controls: " + e.Message); }
       
 
     }
 
  // Web Designer Code Goes Here
 }
}

CustomControls.TxtBox
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics; 
 

namespace CustomControls
{
 
    [DefaultProperty("Text"),
    ToolboxItem(true),
    ToolboxData("<{0}:TxtBox runat=server></{0}:TxtBox>")]
    public class TxtBox : System.Web.UI.WebControls.TextBox 
    {
  
      private NameValueCollectionEx colNameValuePairs = new NameValueCollectionEx();
 
      
      [Bindable(false),Category("Design")]
      public NameValueCollectionEx NameValuePairs
      {
        get  { return this.colNameValuePairs; }
        set  { this.colNameValuePairs=value; }
      }
 
  
 
      protected override void Render(System.Web.UI.HtmlTextWriter writer) 
      {
         base.Render(writer);
      }
 

      protected override object SaveViewState() 
      {          
        object[] newstate = new object[23];
        try
        {
          newstate[0]=(object)this.NameValuePairs.ConvertPairsToString();  
          newstate[1] = (object)this.AutoPostBack;  
          newstate[2] = (object)this.Columns;                              
          newstate[3] = (object)this.MaxLength;   
          newstate[4] = (object)this.TextMode;  
          newstate[5] = (object)this.ReadOnly;   
          newstate[6] = (object)this.Rows;  
          newstate[7] = (object)this.Text;   
          newstate[8] = (object)this.Wrap;   
          newstate[9] = (object)this.AccessKey;  
          newstate[10] = (object)this.BackColor;  
          newstate[11] = (object)this.BorderColor;  
          newstate[12] = (object)this.BorderWidth;  
          newstate[13] = (object)this.BorderStyle;  
          newstate[14] = (object)this.CssClass;  
          newstate[15] = (object)this.Enabled;  
          newstate[16] = (object)this.ForeColor;  
          newstate[17] = (object)this.Height;  
          newstate[18] = (object)this.TabIndex;  
          newstate[19] = (object)this.ToolTip;  
          newstate[20] = (object)this.Width;  
          newstate[21] = (object)this.Site;  
          newstate[22] = (object)this.Visible;  
        }
       catch { }
        return newstate;
      }
      


      protected override void LoadViewState(object savedState)
      {
        try
        {
          object[] newstate = (object[])savedState;
          this.colNameValuePairs.FillFromString((string)newstate[0]);  
          this.AutoPostBack = (System.Boolean)newstate[1]; 
          this.Columns = (System.Int32)newstate[2];                      
          this.MaxLength = (System.Int32)newstate[3]; 
          this.TextMode = (System.Web.UI.WebControls.TextBoxMode)newstate[4];
          this.ReadOnly = (System.Boolean)newstate[5];
          this.Rows = (System.Int32)newstate[6];
          this.Text = (System.String)newstate[7];
          this.Wrap = (System.Boolean)newstate[8];
          this.AccessKey = (System.String)newstate[9];
          this.BackColor = (System.Drawing.Color)newstate[10];
          this.BorderColor = (System.Drawing.Color)newstate[11];
          this.BorderWidth = (System.Web.UI.WebControls.Unit)newstate[12];
          this.BorderStyle = (System.Web.UI.WebControls.BorderStyle)newstate[13];
          this.CssClass =  (System.String)newstate[14];
          this.Enabled = (System.Boolean)newstate[15];
          this.ForeColor = (System.Drawing.Color)newstate[16];
          this.Height = (System.Web.UI.WebControls.Unit)newstate[17];
          this.TabIndex =  (System.Int16)newstate[18];
          this.ToolTip =  (System.String)newstate[19];
          this.Width = (System.Web.UI.WebControls.Unit)newstate[20];
          this.Site = (System.ComponentModel.ISite)newstate[21];
          this.Visible = (System.Boolean)newstate[22];
      }
      catch { }
   }


  }
}

CustomControls.NameValueCollectionEx
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Runtime;
using System.Runtime.Serialization;
using System.Text;
using System.Web;

namespace CustomControls
{
 
  [SerializableAttribute()]
  public class NameValueCollectionEx:NameValueCollection
  {
		 
    internal string ConvertPairsToString()
    {

       StringBuilder sb = new StringBuilder();
       string Ret="";

       try
       {
         for(int i=0;i<this.Count;i++)
         {
           sb.Append(this.Keys[i].ToString() + "=");
           sb.Append(this.Get(this.Keys[i].ToString()));
           sb.Append("&");
         }

         if (this.Count>0)
         {
           Ret = sb.ToString();
           Ret = Ret.Substring(0,Ret.Length - 1);
         }
       }
       catch { }

       return Ret;

    }
 


    internal void FillFromString(string s)
    {
      char chNameValueDelim=Convert.ToChar(" ");
      char chPairDelim=Convert.ToChar(" ");
      /*
       You may choose to implement the next three variables
       as parameters to this method and the ConvertPairsToString
       so I've left in certain logic to support this.
      */
      bool urlencoded = false;
      char nameValueDelim = Convert.ToChar(" ");
      char pairDelim = Convert.ToChar(" ");
      Encoding encoding = System.Text.Encoding.ASCII;
 
      if(nameValueDelim==Convert.ToChar(" "))
      {
        chNameValueDelim ='=';
        chPairDelim='&';
      }
      else
      {
       chNameValueDelim =nameValueDelim;
       chPairDelim=pairDelim;
      }

      int i1 = (s == null) ? 0 : s.Length;
      for (int j = 0; j < i1; j++)
      {
        int k = j;
        int i2 = -1;
        for (; j < i1; j++)
        {
          char ch = s[j];
          if (ch == chNameValueDelim)
          {
            if (i2 < 0)
            {
              i2 = j;
            }
          }
          else if (ch == chPairDelim)
          {
            break;
          }
        }
        string strName = null;
        string strValue = null;
        if (i2 >= 0)
        {
          strName = s.Substring(k, i2 - k);
          strValue = s.Substring(i2 + 1, j - i2 - 1);
        }
        else
        {
          strValue = s.Substring(k, j - k);
        }
        if (urlencoded)
        {
          Add(HttpUtility.UrlDecode(strName, encoding), HttpUtility.UrlDecode(strValue, encoding));
        }
        else
        {
          Add(strName, strValue);
        }
        if (j == i1 - 1 && s[j] == chPairDelim)
        {
          Add(null, "");
        }
       }
    }


  }
}


Robbe Morris is a 2004 Microsoft MVP (Visual C#) and Senior Software Engineer (Decision Tools, TCO Schools, CIO Exp, and TVO) at Gartner in Maitland, FL.  He is a co-developer of EggHeadCafe.com which is hosted by his web site development and hosting company RobbeMorris.com Inc.  In his spare time, he also contributes political commentary and satire to GOP MessageBoard.com.
收藏助手
不良信息举报
您举报文章:Extend Dynamic TextBox Control View State To Store
举报原因:
原因补充:

(最多只允许输入30个字)