My Resume

  • My Resume (MS Word) My Resume (PDF)


Affiliations

  • Microsoft Most Valuable Professional
  • INETA Community Champion
  • Leader, NJDOTNET: Central New Jersey .NET User Group

Sunday, May 3, 2009

Leverage ASP.NET Control Adapters for a (slightly) Better UX

If you’re anything like me, you’ve heard of ASP.NET Control Adapters, but had just dismissed them as a tool that CSS enthusiasts and control freaks could use to make the Web Forms controls render out exactly the way they wanted.  Wanted a <div> instead of a <table> layout? Use a Control Adapter!  Want to… oh, I can’t even come up with a second one.  Point is, until recently I’d basically been dismissing Control Adapters as one of those extension points that the ASP.NET Framework offers, but nobody really has to use to get their usual work done.  Actually, I still pretty much feel that way, but I did recently come with what I think is a pretty good application for a Control Adapter.  I’ll explain it below you let me know what you think!

Pet Peeve:  Drop-Downs with a Single Selection

Select or Drop-down lists (or “combo boxes” as everyone else calls them) are a pretty useful UI element, so it makes sense that they’re used pretty liberally across the web.  But, have you ever gotten halfway through filling out that form and come across this?

image

Yeah, me too. And it's pretty annoying, especially since they're not usually as evident as this one is and you actually waste time expanding it just to find out that you never had an option to begin with.  The first approach most developers take is to just disable the control, graying it out so it is "clear" to the user that they have no other options to select.  I'm talking about something like this:

image

Meh. It certainly doesn't suck as much as the first example, but it's far from an ideal interaction. Users are still left wondering, "Well, what other options do I have that they won't let me see?" (and - depending upon their level of self-esteem - maybe something like, "What, am I not good enough for those other options? Man, this always happens to me - people are always leaving me out and [...]"). While there's not a whole lot you can do to raise your users' self-esteem (or if there is, that's a whole separate blog post altogether), you can eliminate this whole situation altogether in a very simple and straight-forward way: just tell them what the value will be. Just do this:

image

Looks simple enough, right? I'll bet for the developers in the crowd, your wheels are already churning, trying to figure out the best way to do this. Just like me, your knee-jerk reaction is probably going to involve extending or wrapping DropDownList, but the problem with that is that you now have this new control and in order to use it you have scour your entire site and replace any instances of DropDownList with MySuperAwesomeDropDownList. But, since that really wasn't an option for me, my response was to create a Control Adapter.

Implementing a Custom Control Adapter

ASP.NET Control Adapters are a neat way of controlling exactly how controls get rendered down to your clients… even the ASP.NET framework controls!  To take advantage of them, there are two steps: first, create your adapter; then, register it in your .browsers file so that the framework will pick it up.

To achieve the behavior I showed earlier, what we’re going to want to do is override the way our DropDownList controls get rendered out and insert some logic.  Namely, if we’ve got any more than one item, let the control do its thing… but, if we’ve got only one item, take over and instead just render the text of the item out instead of the combo box.  Here’s the code:

public class SmartListControlAdapter
: System.Web.UI.Adapters.ControlAdapter
{
protected ListControl WrappedControl
{
get { return this.Control as ListControl; }
}

protected bool ShouldDisplaySmartText
{
get
{
return WrappedControl.Items.Count < 2
&& WrappedControl.SelectedItem != null;
}
}

protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
if (ShouldDisplaySmartText)
writer.Write(smartText());
else
base.Render(writer);
}

private string smartText()
{
return string.Format("<span class='smartListValue'>{0}</span>",
WrappedControl.SelectedItem.Text);
}
}



You’ll see I added the WrappedControl property to cast the base Control property to a ListControl so I don’t have to do that every time I access it.  Wait – why a ListControl when I said earlier that we were targeting a DropDownList control instead?  Well, after I was done writing all the code you see above, ReSharper let me know that based on the way I was using my reference, I was only using those properties and methods defined in the ListControl base class.  Even though I probably won’t ever use this for anything other than the DropDownList, I figured why limit myself? :)



You’ll also notice that – outside of the casting to a ListControl – that nowhere in this adapter code does it say which control it’s targeting.  In order to actually apply this adapter to the controls on my pages, I’ll need to tell the framework in a separate location which controls I’d like to apply it to.  This is where the .browsers file(s) come in.  If your project doesn’t have an App_Browsers folder, you can right-click on your project and click Add > Add ASP.NET Folder > App_Browsers.  Once this is complete, you can again right-click on this new folder and add a new item using the Browser File template (the name, other than .browser, doesn’t matter).  You can then paste the following inside this file:



<browsers>
<browser refID="Default">
<controlAdapters>
<adapter
controlType="System.Web.UI.WebControls.DropDownList"
adapterType="ControlAdapters.SmartDropDownListAdapter"
/>
</controlAdapters>
</browser>
</browsers>


Simple enough, right?  Here in the ControlAdapters section for the Default (every) browser, we’re telling the framework to wrap all of our DropDownList instances with our new SmartDropDownListAdapter.  It really doesn’t get much simpler than that!



Now we can create a quick test page:



<p>
Regular Drop-Down:
<asp:DropDownList ID="RegularDropDown" runat="server" AutoPostBack="true">
<asp:ListItem Text="First" Value="1" />
<asp:ListItem Text="Second" Value="2" />
<asp:ListItem Text="Third" Value="3" Selected="True" />
<asp:ListItem Text="Fourth" Value="4" />
</asp:DropDownList>
<br />
<em>Selected Value: <%= RegularDropDown.SelectedValue%></em>
</p>

<p>
Smart Drop-Down:
<asp:DropDownList ID="SmartDropDown" runat="server">
<asp:ListItem Text="One Value" Value="1" />
</asp:DropDownList>
<br />
<em>Selected Value: <%= SmartDropDown.SelectedValue%></em>
</p>


You can see I added a few test lines that write out the SelectedValue after each of the controls to prove that the underlying DropDownList control is not modified, just displayed differently.  This means that the SelectedValue (along with everything else) can still be used as normal.



Finally, the moment we’ve all been waiting for; the results of the previous snippet:



image



Not incredibly styled, but beautiful nonetheless!





Your Thoughts



So, what do you think about this approach?  Has this problem bothered you before?  What ways have you solved it?  I’d love to hear about them!

1 comments:

cbp said...

Sounds like a clever use of the Control Adapter but this technique (and your dismissal of CSS) wouldn't fly in any company where design, usability or accessibility are of some consequence.

The one problem I have with control adapters... perhaps you can think of a solution (an elegant one... not a hack one)... is that they are difficult to turn off in individual circumstances. From experience, there's always some unforeseen reason why one particular combo box just must have only one item in it and cannot be a label under any circumstances whatsoever.

Personally I see Control Adapters as Microsoft's sheepish way of fixing the crappy and essentially broken controls that they built for .NET 1.