Loring Software, Inc

A Software Developer's Notebook

Okay, it has been a long time since last blogging here. But, alas, that is not why you are here, so I will spare you the stories of cross country trips, and Red Sox championships...

So, I have a client who needed a clean interface for his website, and I worked with a colleague on a new design. At first, we used a trial license of the Componant Art Grid to do all our grids.  But, sorry, that grid is too awkward to work with.  After searching about, I came across jqGrid. I have to admit, Tony Tomov did an incredible job writing this library, and Oleg has done a huge job supporting it on Stack Overflow. The guy is a machine.

Anyway.  I began loving jqGrid the second I started working with it.  They thought of everything when writing it.  The only thing missing was that I had to write everything in Javascript, and I am working in ASP.Net, so there was no way to consistently control the JS from the code behind.  The makers of jqGrid offer an ASP.Net control to do this, but, being a programmer myself, I thought it was about time I bit off writing a Server Conrtol myself.

So, the first thing you do to write a Server Control in ASP.Net, is Google ASP.Net Server Control.  Good luck with that.  The main articles are: Developing Custom ASP.NET Server Controls and, when you really want to get down and dirty, Custom Property State Management Example.  You basically start off leaning about how a control works, and how the postback state works.

Now, if you don't know how ViewState works on an ASP.Net page, then you need to brush up on that (I won't put a link here, as it will most likey go stale, but just google "asp.net view state", and start reading).  But the examples given on the MS sites will lead you into writing a lot of code, and, eventually, set you up for a costly rewrite.

For starters, let's look at their idea of property setters in a WebControl:

        public virtual BookType BookType
        {
            get
            {
                object t = ViewState["BookType"];
                return (t == null) ? BookType.NotDefined : (BookType)t;
            }
            set
            {
                ViewState["BookType"] = value;
            }
        }

Ok, you want to write this for every property in your class?  Wouldn't it be easier to write the following, instead?

        public int? Height
        {
            get { return GetProp<int?>("Height"); }
            set { SetProp(value, "Height"); }
        }

(And better yet, if you use .Net 4.5, and add a little code on the library, you can just write:

        public int? Height
        {
            get { return GetProp<int?>(); }
            set { SetProp(value); }
        }

but we'll get to that later).

The next problem with the examples has to do with tags nested within your server control's tags.  Take, for example, a common construct.  Like a collection of items within your control.  You have the base control, which is happy to collect viewstate for all the tag's attributes for you using the above construct.  But the examples for nested tags are nuts.  Look at how you are expected to save and load viewstate for contained (nested) tags:

        protected override void LoadViewState(object savedState)
        {
            Pair p = savedState as Pair;
            if (p != null)
            {
                base.LoadViewState(p.First);
                ((IStateManager)Author).LoadViewState(p.Second);
                return;
            }
            base.LoadViewState(savedState);
        }

        protected override object SaveViewState()
        {
            object baseState = base.SaveViewState();
            object thisState = null;

            if (authorValue != null)
            {
                thisState = ((IStateManager)authorValue).SaveViewState();
            }

            if (thisState != null)
            {
                return new Pair(baseState, thisState);
            }
            else
            {
                return baseState;
            }

        }

This is from their "Custom Property State Management Example".  Note how they have invented this very specific case where they use a "Pair" class to store the control class's view state, and then add a "Second" item to the class to hold the nested class's state.  This will work great if you only ever have one nested class.  But what about 6 classes.  Or classes nesting classes, and so on.

So, when I stared my jqGrid Server Control, I laid out the "schema" of the class as follows:

    <jq:Grid ID="GLMasterGrid" runat="server" ClientIDMode="Static" Caption="Current GL Master Groups"
           
SortColumn="SeqNum" PixelsFromBottom="440" OnSelectRow="edit">
       
<Ajax WebMethod="GetGLMasterGrid" EditMethod="AddGLMaster" />
       
<Editor Width="400" />
       
<Pager>
           
<ExcelExport FileName="TripsWareGLMasterGroups" />
       
</Pager>
       
<Columns>
           
<jq:Column PropertyName="CategoryGroupID" Visible="false" IsKey="true" ExcelIgnore="true" />
           
<jq:Column ID="DescName" PropertyName="DescName" Title="GL Master Group" Width="225" Search="true" >
               
<Edit Rules="required:true" Options="size:30, maxlength:150" />
           
</jq:Column>
           
<jq:Column ID="GLGroupCode" PropertyName="GLGroupCode" Title="GL Master Group Code" Width="225" Search="true" >
               
<Edit Options="size:30, maxlength:50" />
           
</jq:Column>
            <jq:Column ID="SeqNum" PropertyName="SeqNum" Title="Display Seq" Width="90" Align="Right">
               
<Edit Rules="number:true,minvalue:0" Options="maxlength:8,width:40" />
           
</jq:Column>
           
<jq:Column ID="DeleteTemplate" FormatterTemplate="DeleteTemplate" Width="80" Align="Center" Sortable="false" />
       
</Columns>
   
</jq:Grid>

This is just an example, but demonstrates what I wanted to accomplish.  With the above tags, I was able to easily emit the jqGrid JavaScript that could drive a grid for my client.  There were properties to set the width of the grid, and to drive the pager and editor, and specify the ajax calls to make.  Actually, the first cut at writing it went very fast, since I only handled loading the control, and didn't deal with view state.  That is kind of the first dirtly little secret of server controls: If you are going to write an Ajax control that doesn't care about postbacks, then, heck, you can just write a POCO class and your're done.  And that is what I had for my client for a few months.  When I did need to deal with changes to the underlying class on postback, I just set the values again.

But to do it right, and really right, I was going to have to deal with the nested viewstate problem.

So I broke the problem into 2 parts:

A typical WebControl comes raring to go with it's own ViewState object.  This object can serialize common types into the viewstate hidden field (int, string, and even arrays).  But don't try to store a complex type in there. Like the MS example above, you need to develop some kind of trick to store and load their viewstate.  Instead of storing my nested tag and its atributes in a Second parameter in a Pair class (or a third in a Triplet, and so on), I just derived my nested tag's class from a new, IStateManager, class, called StateManager.  This class has the exact same behavior as a WebControl class, in that it has a contained ViewState StateBag where it can store its attributes, and deals with Save and Load (and IsTracking).  Furthermore, I introduced a new "object store" which sits next to the WebContol and StateManager's ViewState which stores these nested objects, and can call on them to Save and Load, since the nested objects stored in this dictionary are IStateManager classes.

Next, I created a common class (StateManagerViewState) to store either WebControl's ViewState, or a new StateBag for the the StateManager class, as well as any nested objects, and whenever the control or one of its nested controls sets a property, it trickles down to this contained class.  A call to SaveViewState or LoadViewState to either the WebControl or StateManager object will correctly save and load all the properties as well as nested objects.  So, on postback, we get all the properties set for all the nested objects

Finally, I implemented a StateManagerCollection<T> class, borrowing from the MS StateManagedCollection class, but only collecting my new, StateManager classes, and I piggy backed on the StateManager class to create a Dictionary class, StateManagedDictionary<T>, which just stores values of type T in the StateManager's base class's ViewState.

So, how does it all work?  Well, all properties, whether they are in the control or nested, simply have to be set by calling SetProp<T>(value, "propName").  A nested object is set or retrieved with the SetNestedProp<StateManager>(value, "propName") call, where the nested class must be derived from StateManager.

    public class Grid : StateManagerWebControl
   
{
       
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
       
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
       
public Pager Pager { get { return GetNestedProp<Pager>("Pager"); } }

       
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
       
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
       
public Ajax Ajax { get { return GetNestedProp<Ajax>("Ajax"); } }
 

       
[Bindable(true), Category("Appearance"), DefaultValue(""), Localizable(true)]
       
public new string Width
       
{
           
get { return GetProp<string>("Width"); }
           
set { SetProp(value, "Width"); }
       
}
 

       
[Bindable(true), Category("Appearance"), DefaultValue(""), Localizable(true)]
       
public new string Height
        
{
           
get { return GetProp<string>("Height"); }
           
set { SetProp(value, "Height"); }
       
}

and so on.  Note how the class does not have to be cognizant of View State.  It is just getting and setting the property values by name.  The base classes take care of holding on to the view state, or nested classes and their view states, and tracking and Save/Load.  And, for example, the Ajax class that is nested in this WebControl, above, is simply:

    public class Ajax : StateManager
   
{
       
public string WebMethod
       
{
           
get { return GetProp<string>("WebMethod"); }
           
set { SetProp(value, "WebMethod"); }
       
}
 

       
public string EditMethod
       
{
           
get { return GetProp<string>("EditMethod"); }
           
set { SetProp(value, "EditMethod"); }
       
}

and so on.  And, with the CallerMemberName attribute added to .Net 4.5, we can just add a small change to the SetProp and GetProp functions to trim off the "get_" and "set_" of the properties, and we can dispense with the "propertyName" parameters altogether.  At that point, everything would be compiler checked!

   public class Ajax : StateManager
   
{
       
public string WebMethod
       
{
           
get { return GetProp<string>(); }
           
set { SetProp(value); }
       
}
 

       
public string EditMethod
       
{
           
get { return GetProp<string>(); }
           
set { SetProp(value); }
       
}

Lastly, there is the code to support a collection:

     public class Column : StateManager
   
{
       
public bool? IsKey
       
{
           
get { return GetProp<bool?>("IsKey"); }
           
set { SetProp(value, "IsKey"); }
       
}
       
public bool? Visible
       
{
           
get { return GetProp<bool?>("Visible"); }
           
set { SetProp(value, "Visible"); }
       
}
 etc

and the collection which holds it:

    public class JqColumnCollection : StateManagerCollection<Column>
   
{
       
public void RenderContents(string tableID, HtmlTextWriter output)
       
{
           
output.WriteLine("var colModel = [");
           
output.Indent++;
           
string comma = " ";  // first line has no prepended comma
           
foreach (Column column in this)
           
{
               
output.Write(comma);
               
column.RenderContents(tableID, output);
               
comma = ","; // now start prepending commas here on out
           
}
           
output.Indent--;
           
output.WriteLine("];");
       
}|

and so on.

Here is the link to the code: StateManager.cs I doubt anyone will just try and dump it in their code, but the essence is there.  Feel free to email me with questions, and I will try and answer you.

Hopefully this is a cleaner way to start your Server Control.  I know now that I have writen this, I will not shy away from writing them, as now I can focus on the control and the HTML it emits, as opposed to worrying about whether I have saved state correctly.



Copyright © 2024 Loring Software, Inc. All Rights Reserved
Questions about what I am writing about? Email me