jump to navigation

Composite Controls: Dude, where’s my data? June 25, 2009

Posted by codinglifestyle in ASP.NET, C#, CodeProject.
Tags: , , , , , , , , ,
3 comments

I started my .NET career writing WebParts for SharePoint 2003.  You would think this would make me a bit of a server control expert, as WebParts are essentially gussied up server controls.  Yet, I’ve officially wasted 2 days on an age old composite control problem which typically involves these kinds of google searches:

  • server control postback missing data
  • postback server control child control missing
  • composite control event not firing
  • webcontrol createchildcontrol postback control
  • viewstate webcontrol data missing empty blank

Sound familiar?  I understand your pain and hope I can help.  Composite controls can be tricky, if they aren’t set up just right even the most basic example won’t work properly.

First, keep in mind there are two types of custom controls developers typically write.  There are user controls (.ascx) and server controls (.cs).   We will focus on a server control.  One gotcha for a composite control is using the right base class*.  We want to use the CompositeControl base class, not WebControl or Control.  This will tell ASP.NET to ensure the IDs of our child controls are unique (via INamingContainer).  This is simple but very important, in order for ASP.NET to wire up events and serialize ViewState the ID of a control needs to be identical at all times.  So only assign a literal string to your child control and let .NET worry about making it unique.

Now there are two cases to consider, the first is where our composite server control creates a static set of controls.  This is a straightforward case, because we can create the controls at any time.  The most important function in a server control is CreateChildControls().   This is where you can create and assign your controls to member variables.  We can have properties and get and set values straight to our member controls.   In every property just call EnsureChildControls() in each get and set.

private TextBox _TextBox;

[Bindable(true), Category(“TextBoxEx”), DefaultValue(“”), Localizable(true)]

public string Text

{

get

{

EnsureChildControls();

return _TextBox.Text;

}

set

{

EnsureChildControls();

_TextBox.Text = value;

}

}

 

protected override void CreateChildControls()

{

_TextBox = new TextBox();

_TextBox.ID = “_Text”;

 

Controls.Add(_TextBox);

}

 

In reality we may have properties or logic which determine which controls are created or how our control behaves.  This is a complicated scenario, because we cannot create the controls before the logic has been initialized (otherwise our logic will not know which controls to dynamically create)!  In this case, we want our control’s own properties independent of the child controls and we’ll store this information in ViewState, not a control.  We want to avoid calling EnsureChildControls() and delay calling CreateChildControls() prematurely.  This allows the control to be initialized first so that when CreateChildControls() is called our logic will know which controls to create.  First let’s see how to store a property in ViewState.

 

private const string STR_CreateTextBox       = “CreateTextBox”;

public bool CreateTextBox

{

get

{

if (ViewState[STR_CreateTextBox] == null)

return false;

else return (bool)ViewState[STR_CreateTextBox];

}

set

{

ViewState[STR_CreateTextBox] = value;

}

}

If you were wondering if ViewState was the right place to store properties the answer is maybe.  For a bound property it is overkill as it will be set back to its original value every postback.  In that case we simply need a property with a backing store (or in C# 3.0+ a simple get; set; will do).  But when our logic needs to be stored ViewState is the place to persist it.  Just remember that the dynamically created controls don’t need ViewState of their own so we’ll be sure to turn that off when we create them.  The right place to create and add our dynamic controls is in CreateChildControls().  Let’s create a TextBox based on some logic stored in the composite control’s ViewState.

protected override void CreateChildControls()

{

if (CreateTextBox)

{

_TextBox = new TextBox();

_TextBox.ID = “_Text”;

_TextBox.EnableViewState = false;

_TextBox.TextChanged += new EventHandler(Text_TextChanged);

Controls.Add(_TextBox);

}

}

void Text_TextChanged(object sender, EventArgs e)

{

string sText = ((TextBox)sender).Text;

}

Lets take a look at what we’re doing here.  We are dynamically creating our TextBox control in CreateChildControls().  We are setting the ID to a literal string, ASP.NET will make sure our name is unique because we inherit from CompositeControl.  We are setting EnableViewState to false because, as discussed, our composite control is already taking care of ViewState.  We are adding an event as an example as this is the right place to setup any events you might need.

Now here is the interesting bit:  How do we get the user’s value back from a dynamic TextBox?  Take another look at the property Text above and note that the getter calls CreateChildControls().  This will ensure our textbox is recreated and the form will syncronize the user’s form data back in to the textbox.  We could also capture the value using the TextBox’s text changed event to do some processing or whatever.  With this event, when we postback, our Text_TextChanged event will fire before a OK button’s click event on the page due its place in the control hierarchy.  This allows our event to manipulate the TextBox’s text value before our OK button’s click event occurs.

I’ll just note that you may be reading some advice on forum’s to override OnInit.  Contrary to that advice, CreateChildControls() is the right place to dynamically create your controls and events.  OnPreRender() is a great place for initialization as it is called after all properties are set.  And, of course, Render() gives you complete control on how your control will be drawn.

* Now I have to mention Repeater.  Repeater may cause a lot of pain with your server controls.  You may see your control working properly outside a repeater and suddenly all go pearshaped when used with one.  After a lot of trial and error I discovered this had to do with the ID assigned to the dynamic controls.  We know ASP.NET depends on the ID being identical at all points for events and serialization with the form and viewstate to take place.    Sad to say when our composite control is inside a Repeater we can not trust our IDs to .NET.  So we do not want to inherit from CompositeControl or INamingContainer.  Instead, assign a unique id yourself.  Being that it is a repeater, this can not be a literal string because no 2 controls can have the same ID.  Instead try _TextBox.ID = this.ID + “_Text”;

So although there are several points and gotchas to consider, think about using a custom control to lend some OO design to your UI.  Be sure to unit test on a simple website to make development of the control easier.  Good luck!

Advertisement

Cascading Dropdown Lists with jQuery (parent / child select options) March 9, 2009

Posted by codinglifestyle in ASP.NET, C#, CodeProject, Javascript, jQuery.
Tags: , , , , , , , ,
add a comment

 

Recently I was tasked with merging 3 similar screens in to one.  I took stock of the commonalities and decided a Category and Subcategory dropdown list would suffice.   As you’d expect the contents of the Subcategory dropdown depend on the selected Category.  Obviously I wanted to avoid postbacks so I first looked to the Ajax Control Toolkit’s CascadingDropDown extender to link the parent to the child.  In my case, there were only a dozen subcategories so a webservice seemed like overkill.

After attending Tech-Ed 2008 and seeing Microsoft throwing its weight behind jQuery I decided to have a look.  So what follows is my first foray in to jQuery and I’m quite pleased with the results.

First, we define an enum in the codebehind which can be used for bitwise operations.

public enum Category

{

Invoice     = 1,

Order       = 2,

Shipment    = 4   //Fourth item would be 8, then 16, 32, …

}

Next I wanted to be able to use the enum to specify the Category (only specifying one) and Subcategory items (bitwise OR any combination).

_DropDownCategory.Items.Add(GenerateListItem(“Orders”, Category.Order, false));

_DropDownCategory.Items.Add(GenerateListItem(“Invoices”, Category.Invoice, false));

_DropDownCategory.Items.Add(GenerateListItem(“Shipments”, Category.Shipment, false));

_DropDownSubcategory.Items.Add(GenerateListItem(“Purchase Orders”, Category.Invoice | Category.Order | Category.Shipment, false));

_DropDownSubcategory.Items.Add(GenerateListItem(“Quote Number”, Category.Order, false));

_DropDownSubcategory.Items.Add(GenerateListItem(“Customer Project Number”, Category.Shipment, true));

The enum was converted to a number and added as a custom attribute to the new list item in GenerateListItem():

li.Attributes.Add(“Category”, ((int)eCat).ToString());

Enter jQuery, first we’re going to need to keep a copy of all the available Subcategory items.  Each time we change the Category we will be showing a subset of this array which we must keep separate in memory.

var optSubCat = null;

$(function() {

//Make a copy in memory of all the subcat options

optSubCat = $(“#_DropDownSubcategory”).children().clone();

//Default subcat to “Orders”

ddSubcategoryUpdate(2);

});

Then we need an event to fire when the Category is changed.

$(function() {

$(“#_DropDownCategory”)

.bind(“change”, function(event) {

var eCat = this[this.selectedIndex].attributes(“Category”).value;

ddSubcategoryUpdate(eCat);

});

});

Note how jQuery allows us to wire up DOM events after the fact to our .NET controls.  Here we add an onchange event dynamically rather than injecting this via the codebehind which forces us to put javascript code in the wrong place.  All we’re doing is pulling the value of the enum from the selected Category and then passing it to a function which will update the Subcategory items.

function ddSubcategoryUpdate(eCat) {

//Remove all items from drop down

$(“#_DropDownSubcategory”).children().remove();

//For each subcat item: test if it belongs in eCat, if so add it

$(optSubCat).each(function() {

if (($(this).attr(“Category”) & eCat) > 0) {

$(“#_DropDownSubcategory”).append($(this).clone()[0]);

}

});

}

And finally, in the if statement we see we test each Subcategory item previously saved in memory to see if it matches the enum value of the Category.

What we’ve ended up with is a very powerful yet simple mechanism for cascading dropdowns using enums in our codebehind.  It’s easier to wire up than what the Ajax Control Toolkit provides, its all script so the UI looks good, and while this could have been done in straight javascript it demonstrates the power and simplicity of the jQuery library.

Exposing C# controls as Active-X and the effects on developing with the download cache January 5, 2006

Posted by codinglifestyle in C#, Javascript, Winform.
Tags: , , , ,
add a comment

A C# assembly can be easily exposed as an Active-X control using the object tag like so:

<OBJECT id=”Bob” classid=”url to assembly#full name of control></OBJECT>

So for example my assembly is located in a virtual directory on server VM2003S2:88 called MyObject.  MyObject has a namespace of Chris.ActiveX.Controls and the class is MyControl.  My object tag would be: http://vm2003s2:88/myobject.dll#chris.activex.controls.mycontrol

This complete I can now use javascript to talk to “Bob” via MyControl’s public methods.  This is all well and good however you may experience frustration in seeing your updates during development.

The assembly is automatically pulled down to the download cache which is located in c:\windows\assembly\downloads on Win2003.  Unfortunately, its not smart enough to read version strings and realize a newer copy is available on the server.  Therefore you must clear this cache using:

gacutil /cdl

But that’s not all!  A copy is also made in your temporary internet files.  You won’t be able to simply delete it using del from the post-build event as Window’s treats the directory as special.  So either write a small program utilizing the API to delete your assembly from the cache or use one of the miriad of privacy utilities to clean this for you.  I found CleanUp! which can be quietly executed from the command line and added to my post build event.  The result is the assembly is pulled every time (on the development machine) so you see your changes with each build.

Here is a good example explaining Embedded Windows User Controls into Internet Explorer:

http://www.devhood.com/tutorials/tutorial_details.aspx?tutorial_id=187