Tuesday, December 4, 2007

AutoCompleteExtender, key-value issue and beyond ...

Here is the issue - an ASP.NET application and a database table consisting of unknown number of records, 50 or 50000. Then, the interface control which should allow the user to pick one of the items. The DropDownList does not work here - although there's nothing wrong in putting 50 items into the DropDownList, the list with 50000 elements is rather a bad idea.

My first and almost obvious choice was the AutoCompleteExtender from the ASP.NET Ajax Toolkit. After few tech studies, I've realized how many drawbacks this control has:

  1. it's not obvious how to assign values to items put into the drop part of the extender. New versions of the Toolkit seems to target this issue (look at this blog entry from Phani Raj). It seems that the value is available at client side and after you get it, you are on your own. It means you and the javascript on a date.
  2. suppose the user picks a correct entry from the list and then modifies the textbox value so it does not correspond to any of available items. Guess what? The suggestion from the article above does not work. The hidden textbox still holds the correct value selected from the list. Cyril Durand notes this issue in his blog entry here.
  3. unfortunately, even Cyril's trick does not work when the user just types the correct value into the textbox and does not pick anything directly from the list.

Why it is important to have the value of an item available? Well, suppose that the dropdown holds a list of all towns in your country. There are surely many towns with the same name. Instead of putting just a name into the drop down list, you'd rather format items in a custom way so that the user is able to pick the correct item from the list. Now, the page posts back and you get a literal which is surely a name of a town but formatted in a custom way. How are you supposed to retrieve it's database identity? God one knows.

However, the major issue of the AutoCompleteExtender is the need to type something into the textbox while in fact the user could have no idea what he/she should type. In the "list of towns" example - suppose I know the country, I know the region and I know that there are 50 towns in that region but have no idea of their names. If I am to see the complete list, I will surely know what item to pick. However, the AutoCompleteExtender will only show me 10 first items.

Yet another example - a drop down list to pick users. Suppose I know his age, his gender, his address, his company but I completely forgot his name. How am I to pick the user from the list using the AutoCompleteExtender?

This is where my idea comples to play - instead of the AutoCompleteExtender the user will see a plain textbox with small button next to it. Clicking the button will spawn a new browser window with rich user interface to define a custom filter and a paged GridView showing a list of items matching current filter criteria. After user is able to narrow the list so that the correct item can be picked, the new window will return both name and the value (database identity) of the item to the parent window.

So the issue now is - how are we supposed to build a communication link between two browser windows so that when the user picks an item in a child window, the information of the selection will appear in a parent window?

Let's start by putting two textboxes in a parent window.

   1: <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
   2: <input id="Hidden1" type="hidden" runat="server" />

The first one will receive the item's name, the second one will receive item's value.


Let's also put a html button on the form which will be responsible for spanning a new window:



   1: <input type="button" id="Button1" runat="server" value="wybierz" />

The button will raise local browser event and a new window will be shown to the user where he/she will be able to pick the item from the list.



   1: public partial class _Default : 
   2:   System.Web.UI.Page 
   3: {
   4:     protected void Page_Load(object sender, EventArgs e)
   5:     {
   6:         Button1.Attributes["onclick"] = 
   7:             string.Format( "showWindow( 'Child.aspx', '{0}', '{1}' );", 
   8:                  TextBox1.ClientID, Hidden1.ClientID );
   9:     }

The showWindow is a local javascript method to raise the new window:



   1: <script language="javascript" type="text/javascript">
   2: function showWindow(URL, controlID, targetControlID)
   3: {
   4:   noweOkno = window.open( URL + '?controlID='+controlID+'&targetControlID=' + 
   5:      targetControlID, '_blank', 
   6:      'menubar=no, toolbar=no, location=no, scrollbars=no, resizable=no, ' +
   7:      'status=no, width=640, height=480, left=30, top=30')
   8:   noweOkno.focus();
   9: }
  10: </script>    

The Child.aspx window (which will ultimately return both the name and the value of the item) can be arbitrarily complicated. The only interesting issue here is that I pass two parameters in the QueryString - the controlID parameter holds the name of a parent window control which will receive the name of the item and the targetControlID parameter holds the name of a parent window control which will receive the value of the item.


No matter what you do in the Child.aspx window, ultimately the user presses the OK button which should pass both name and the value of picked item to the parent window.



   1: class ChildWindow {
   2:   protected void OK_Click( object sender, EventArgs e )
   3:   {
   4:     string Script =
   5:         string.Format( 
   6:     "window.opener.document.getElementById( '{0}' ).value = '{1}';" +
   7:     "window.opener.document.getElementById( '{2}' ).value = '{3}';" +
   8:     "window.close();", 
   9:     this.Request.QueryString["controlID"], NameToBeReturnedBack, 
  10:     this.Request.QueryString["targetControlID"], ValueToBeReturnedBack );
  11:  
  12:     this.ClientScript.RegisterStartupScript( typeof( ChildWindow ), "close", "<script>"+Script+"</script>" );
  13:   }

This snippet does the magic trick. Using window.opener I refer to the parent window from the child window and this script sets both the name textbox and the hidden value textbox to proper values. After this the child window is immediately closed.


And that's it. In the parent window you can ask for the item's value stored in the hidden textbox:



   1: string SelectedValue = Hidden1.Value.ToString();
A simple "todo" task: the triple - TextBox, hidden TextBox and the button bringing the child window could be put into a web control which could then just expose ChildPageURL and SelectedValue properties. I will implement such user control in few days and write another entry to present the code.

Wednesday, November 21, 2007

The Definition of an Antipattern

A friend of mine has studied antipatterns extensively and asked me "What would be the best way to identify an antipattern in my code"?

To increase his confusion, I said "Almost everything from your code you've just shown to me is considered an antipattern".

Then I've added "Relax, this is not true. It's just you falling into paranoia of antipatterns".

Tuesday, November 20, 2007

Loose coupling of embedded DetailsViews

I often embed a DetailsView inside yet another DetailsView. Typical scenario - there is a "Person DetailsView" and "Address DetailsView". The former one has one TemplateField with the latter embedded.

Since I always work with objects, I also use ObjectDataSources. The ObjectDataSource will provide a business object as the data source for the control if and only if the underlying data provider provides the correct business object.

Usually I just add three sections to the ObjectDataSource - SelectParameters, UpdateParamteters and DeleteParameters each one providing exactly one parameter, the ID of the underlying object.

In case of one DetailsView embedded inside another one this does not work - the first DetailsView consumes the ID of the person supplied by the Select Parameter but the embedded DetailsView should use the Person's ID_ADDRESS value as the ID.

To overcome this difficulty I've just implemented a simple solution where one of the Page's controls has to implement following interface:

   1: namespace Vulcan.Uczniowie.UILayer
   2: {
   3:     public interface IParamValueProvider
   4:     {
   5:         object ProvideValue( string ParamName );
   6:     }
   7: }

and the SelectParameter just retrieves the value supplied by the control:



   1: namespace Vulcan.Uczniowie.UIHelpers
   2: {
   3:     [DefaultProperty( "ParamName" )]
   4:     public class ParamValueProviderParameter : 
   5:        QueryStringParameter
   6:     {
   7:         private string paramName;
   8:         public string ParamName
   9:         {
  10:             get
  11:             {
  12:                 return paramName;
  13:             }
  14:             set
  15:             {
  16:                 paramName = value;
  17:             }
  18:         }
  19:  
  20:         protected override object Evaluate( 
  21:             System.Web.HttpContext context, 
  22:             System.Web.UI.Control control )
  23:         {
  24:             if ( context != null &&
  25:                  context.Handler is Page
  26:                 )
  27:             {
  28:                 IParamValueProvider Provider = 
  29:                     FindParamValueProvider( context.Handler as Page );
  30:                 if ( Provider != null )
  31:                     return Provider.ProvideValue( ParamName );
  32:  
  33:                 return null;
  34:             }
  35:  
  36:             return null;
  37:         }
  38:  
  39:         private IParamValueProvider FindParamValueProvider( Control Control )
  40:         {
  41:             if ( Control != null )
  42:                 foreach ( Control Child in Control.Controls )
  43:                 {
  44:                     if ( Child is IParamValueProvider )
  45:                         return (IParamValueProvider)Child;
  46:  
  47:                     IParamValueProvider RecursiveRet = FindParamValueProvider( Child );
  48:                     if ( RecursiveRet != null )
  49:                         return RecursiveRet;
  50:                 }
  51:  
  52:             return null;
  53:         }
  54:     }
  55: }

The top level Details View just implements the interface:



   1: #region IParamValueProvider Members
   2:  
   3:    // implemented in the top level DetailsView
   4:    public object ProvideValue( string ParamName )
   5:    {
   6:        switch ( ParamName )
   7:        {
   8:            // somehow provide ID_ADDRESS for the embedded DetailsView
   9:            case "ID_ADDRESS" :
  10:            if ( ParameterHelper.GetParameterValue( 
  11:                   this.DataSource.SelectParameters["ID"] ) != null )
  12:            {
  13:               int id = Convert.ToInt32( 
  14:                 ParameterHelper.GetParameterValue( 
  15:                   this.DataSource.SelectParameters["ID"] ) );
  16:               Person Item = Person.Retrieve( Global.Model, id );
  17:  
  18:               return Item.ID_ADDRESS;
  19:            }
  20:            break;
  21:        }
  22:  
  23:        return null;
  24:    }
  25:  
  26: #endregion

and the embedded one defines the SelectParameter as:



   1: <SelectParameters>
   2:     <vlc:ParamValueProviderParameter Name="ID" ParamName="ID_ADDRESS" />
   3: </SelectParameters>

This way we have a perfect loose-coupling between the top level and embedded views - both communicate using the interface IParamValueProvider.


To complete the example I have to explain and provide code for the ParameterHelper.GetParameterValue method.


You see, the Select/Update/Delete parameters are directly available only inside the ObjectDataSource's Selecting/Inserting/Deleting event handlers! If you are to retrieve the current value of a parameter outside one of these handlers you will learn that there's no CurrentValue property on the Parameter class. Or rather, there is but it's private!


Therefore the ParameterHelper aims to retrieve the value so that it is available from any context, not only from within one of handlers I've mentioned.



   1: namespace Vulcan.Application.UIHelpers
   2: {
   3:     public class ParameterHelper
   4:     {
   5:         public static object GetParameterValue( Parameter Parameter )
   6:         {
   7:             return Parameter.GetType().InvokeMember(
   8:                 "ParameterValue",
   9:                 System.Reflection.BindingFlags.GetProperty | 
  10:                 System.Reflection.BindingFlags.NonPublic | 
  11:                 System.Reflection.BindingFlags.Instance,
  12:                 null, Parameter, null );
  13:         }
  14:     }
  15: }

PersistentStatePage with Event Validation

Few months ago I've came upon a great article "Persisting the state of a web page" by Martin Jericho.

The idea is very cool and useful - the viewstate of a web page can be stored on the server-side and restored on demand. Please refer to the article for further explanations if you are not familiar with Martin's solution.

As a kind of a hack, the solution is not perfect. There are few small problems with the implementation (for example, I've added ".ToLower()" in few places where addresses are compared) but the main issue with Martin's approach is connected with the ASP.NET event validation mechanism.

You see, to reduce the risk of cross-site request forgery attack, ASP.NET is rather paranoic when validating the input coming from the browser. Specifically, the validation signature is placed in the hidden __EVENTVALIDATION field and the signature is used to validate if the request is vaild.

Since in Martin's solution the viewstate is restored from the external resource, the event validation must be turned off, otherwise you get the "Invalid postback or callback argument" exception. However, turning off the event validation is a huge security risk.

I belive I have found a way to have the event validation turned on and still be able to use Martin's solution.

What's the problem, doc?

First of all, why it does not work with event validation turned on?

It seems that this just does not work:

 

   1: private void LoadPostData(Control control, ArrayList modifiedControls) {
   2: {
   3:   ..
   4:   // Call the framework's LoadPostData on this control using the name 
   5:   // attribute as the post data key:
   6:   if (((IPostBackDataHandler)control).LoadPostData(nameAttribute,PostData))
   7:       modifiedControls.Add(control);

The LoadPostData method, implemented internally in the .NET library, does the validation and just does not accept the external viewstate provided upon the state retrieval.


Solution? Well, almost

If we just were able to disable the validation only for these request which involve the persisted viewstate and keep validation on for all other valid requests... Unfortunately, it seems that the EnableEventValidation property is another paranoiac - if you see the inner implementation


 



   1: public virtual void set_EnableEventValidation(bool value)
   2: {
   3:     if (base.ControlState > ControlState.FrameworkInitialized)
   4:     {
   5:         throw new InvalidOperationException( ... );
   6:     }
   7:     this._enableEventValidation = value;
   8: }

then you will realise that the property is somewhat special - it cannot be switched always on your demand but rather before the engine initializes.


Few experiments reveal that the constructor is a good place to switch validation on/off and this is what Martin does - he turns the validation off in the constructor.


However, yet another place where you are allowed to turn the validation on/off is the DeterminePostBackMode method. The new implementation would be:


 



   1: protected override NameValueCollection DeterminePostBackMode() {
   2:     pageState=LoadPageState(Request.Url);
   3:  
   4:     // this line turns the validation off but only when the state
   5:     // is actually restored. otherwise the validation should remain
   6:     // turned on.
   7:     if ( IsRestoredPageState ) EnableEventValidation = false;
   8:  
   9:     NameValueCollection normalReturnObject = base.DeterminePostBackMode();
  10:     

What's interesting is that after this small enhancement, the code works as expected. Well, almost.


Yet another issue

It looks like turning the validation off does in fact two things. Not only it prevents the validation of the request on the server side but also prevents the __EVENTVALIDATION hidden field to be appended to the response.


This makes the above solution only partially succesfull - even though the restored page is accepted on the server side, since the response does not contain the validation signature, the page will likely fail on another postback! The engine will just see that the validation is turned on (it is, by default), there is no state to restore (it has been restored one postback ago) but the __EVENTVALIDATION is missing (since the validation was turned off last time the page had been processed on the server). Guess what? You will get the "Invalid postback or callback"!


What we need is then not only to turn the validation off before the state is retrieved but also to turn it on after it is retrieved, so that the __EVENTVALIDATION signature is appended to the response!


But how do we enable the validation if the EnableEventValiation property cannot be altered after the state is retrieved?


Well, using ... reflection.


I know, the hack is dirty but it works. It seems that the EnableEventValidation property is just a wrapper on the _enableEventProperty internal boolean field. So just after the state is retrieved, we just turn on the validation:


 



   1: override protected void OnLoad(EventArgs e) {
   2:     // this is Martin's code
   3:     if (IsRestoredPageState) {
   4:         // Populate controls with PostData, saving a list of those that were modified:
   5:         ArrayList modifiedControls=new ArrayList();
   6:         LoadPostData(this,modifiedControls);
   7:         // Raise PostDataChanged event on all modified controls:
   8:         foreach (IPostBackDataHandler control in modifiedControls)
   9:             control.RaisePostDataChangedEvent();
  10:         
  11:         // and this is my dirty hack which turns the validation on
  12:         // after the state is retrieved so that the
  13:         // __EVENTVALIDATION is correctly appended to the response
  14:         FieldInfo fi = typeof(Page)
  15:            .GetField( "_enableEventValidation", 
  16:                 BindingFlags.NonPublic | BindingFlags.Instance );
  17:         if ( fi != null )
  18:             fi.SetValue( this, true );
  19:     }
  20:     base.OnLoad(e);
  21: }

Well, this is all. Two small modifications, the first one in the DeterminePostbackMode and second one in the OnLoad. It works correctly in my test application, please feel free to share your experiences.