Thursday, September 20, 2007

DevExpress XPO, DropDownList and Custom Formatting in DetailsView's ItemTemplate

It is a common scenario to put a DropDownList into GridView's or DetailsView's Item Template. Since most of the time we use object-oriented architecture, the DropDownList's data source is a business method which returns list of business entities.

Long ago, when we've faced this scenario for the first time, we had to solve the formatting issue - when binding a list to the DropDownList, you specify DataTextField and DataValueField:

   1: <asp:DropDownList 
   2:     ID="TheDropDownList" 
   3:     runat="server" 
   4:     DataSource="<%# DataProvider.BusinessObjects %>" 
   5:     DataTextField="Name" DataValueField="ID" /> 

Issues occur when you need the items to be formatted in a custom way.


The most convenient and coherent method of adding custom formatting is to add a dummy property to the business object class:



   1: public class BusinessObject : IFormattable {
   2:  
   3:   private string name;
   4:   public string Name { ...
   5:   }
   6:  
   7:   public BusinessObject This {
   8:     get { return this; }
   9:   }
  10:  
  11:   public string ToString( string format, IFormatProvider formatProvider )
  12:   {
  13:     switch ( format )
  14:     {
  15:       case "s" :
  16:         return ...;
  17:       default :
  18:         return this.Name;
  19:     }
  20:   }
  21: }

so that you can force custom formatting of your business objects inside the DropDownList like this (note the This value for the DataTextField property) :



   1: <asp:DropDownList
   2:   ID="TheDropDownList"
   3:   runat="server"
   4:   DataSource="<%# DataProvider.BusinessObjects %>"
   5:   DataTextField="This" DataValueField="ID"
   6:   DataTextFormatString="{0:s}" />

This worked like a charm for us, well until few days ago when we've tried the same on business objects persisted with DevExpress XPO



   1: public class BusinessObject : XPObject, IFormattable
   2: {
   3:     public BusinessObject This
   4:     {
   5:         get
   6:         {
   7:             return this;
   8:         }
   9:     }
  10:  
  11:     ...

and instead of custom formatting I got DevExpress.XPO.XPCollection(14) Count(1), DevExpress.XPO.XPCollection(15) Count(1) ... instead of custom formatted descriptions.


After over 2 hours the battle was won. Following trick



   1: public class BusinessObject : XPObject, IFormattable
   2: {
   3:     public IFormattable This
   4:     {
   5:         get
   6:         {
   7:             return this;
   8:         }
   9:     }
  10:  
  11:     ...

does the job.


It turns out that XPObject inherits from XPCustomObject which inherits from XPBaseObject which has the ToString method defined and this is where the problem lies. Only changing the signature of the This property gives expected results.


The solution is, however, far beyond any explanation I am able to come up with.

Wednesday, September 12, 2007

IE 7 search engine feature examine local files?

One of the most convenient features of Internet Explorer 7 is the possibility to redirect the phrase typed into the address bar to the default search engine (I prefer Google over Live! but it is quite easy to switch between different providers).

Suppose I type "c# pdf library" directly into the address bar. Instead of an error, Internet Explorer pops up the http://www.google.pl/search?hl=pl&q=c%23+pdf+library&lr= page and I can immediately browse search results.

I am used to this feature so much that yesterday I was quite surprised to see this instead of typical results page while searching for "Google Earth":

It seems that Internet Explorer first tries to match the phrase you enter into the address bar with names of some (which exactly?) local files, in this case the "Google Earth.lnk" located on my desktop.

Just a small inconvenience.

Tuesday, September 11, 2007

ASP.NET Web Application in Offline Mode Revisited

Probably most of You are aware of the app_offline.htm functionality. Basically if you put a html file named app_offline.htm in the root of a web application, the application goes to "offline" mode where all incoming requests are redirected to this file.

This seems great at first sight. You can temporarily disable the application while performing some maintenance. Specifically, you can delete all application files except the app_offline.htm and the application still works (even though the web.config is gone!) As long as the maintenance is completed you just remove or rename the app_offline.htm and the application is back from the void.

There is, however, one caveat and one drawback of this feature.

The caveat is that the app_offline.htm file must be longer that 512 bytes otherwise instead of its content you get ... 404 (can someone explain it?).

The drawback is that the offline mode affects everyone including you! Either anyone or noone can access the application. What would be nice, however, is to have the ability to temporarily redirect requests coming from outside but still be able to use the application locally.

The code below shows how to accomplish that requirement with really simple HTTP handler. To use it you have to:

  1. compile the source class
  2. add the handler section to the web.config file and put appoffline.htm (without the _ !) in the root of the application
   1: <httpHandlers>
   2:    <add verb="*" path="*.aspx" type="AppOffline.AppOfflineHandler, AppOffline" />
   3:  </httpHandlers>

 


Handler code:



   1: using System;
   2: using System.Collections.Generic;
   3: using System.Text;
   4: using System.Web;
   5: using System.IO;
   6: using System.Web.UI;
   7:  
   8: /* Wiktor Zychla, 2007 */
   9: namespace AppOffline
  10: {
  11:     public class AppOfflineHandler : IHttpHandler
  12:     {
  13:         string appOffline;
  14:         /// <summary>
  15:         /// appoffline.htm file content as singleton
  16:         /// </summary>
  17:         public string AppOffline
  18:         {
  19:             get
  20:             {
  21:                 if ( appOffline == null )
  22:                     appOffline = OfflineFileContent;
  23:  
  24:                 return appOffline;
  25:             }
  26:         }
  27:  
  28:         /// <summary>
  29:         /// Path to the appoffline.htm file
  30:         /// </summary>
  31:         public string OfflineFilePath
  32:         {
  33:             get
  34:             {
  35:                 return context.Server.MapPath( "~/appoffline.htm" );
  36:             }
  37:         }
  38:  
  39:         /// <summary>
  40:         /// appoffline.htm file content
  41:         /// </summary>
  42:         public string OfflineFileContent
  43:         {
  44:             get
  45:             {
  46:                 return File.ReadAllText( OfflineFilePath );
  47:             }
  48:         }
  49:  
  50:         /// <summary>
  51:         /// Is the application in the offline mode?
  52:         /// 
  53:         /// Yes - if the appoffline.htm exists
  54:         /// </summary>
  55:         public bool IsOffline
  56:         {
  57:             get
  58:             {
  59:                 return File.Exists( OfflineFilePath );
  60:             }
  61:         }
  62:  
  63:         HttpContext context;
  64:  
  65:         #region IHttpHandler Members
  66:         public bool IsReusable
  67:         {
  68:             get
  69:             {
  70:                 return true;
  71:             }
  72:         }
  73:  
  74:         public void ProcessRequest( HttpContext context )
  75:         {
  76:             this.context = context;
  77:  
  78:             // offline mode and remote request?
  79:             if ( !context.Request.IsLocal &&
  80:                  IsOffline
  81:                 )
  82:             {
  83:                 context.Response.Clear();
  84:                 context.Response.Write( AppOffline );
  85:  
  86:                 context.Response.End();
  87:             }
  88:             else
  89:                 // redirect to the default processing pipe
  90:                 PageParser.GetCompiledPageInstance( 
  91:                     context.Request.Path, 
  92:                     context.Request.PhysicalPath, 
  93:                     context ).ProcessRequest( context );             
  94:         }
  95:         #endregion
  96:     }
  97: }

This approach has its own drawback - it is not "maintenance" aware in a sense that when for example some crucial modules are gone from the /bin subfolder, the application will not load at all because of non-existent modules beeing referenced from within the web.config file.


The nice thing is that you can test the application locally but all remote requests are gracefully redirected to the appoffline.htm. I suggest then to use my feature side by side with the ASP.NET app_offline.htm.

Thursday, September 6, 2007

Query String Tampering

Query String Tampering is one of the easiest and most common Internet attacks. Each time a web application uses QueryString to pass sensitive information between pages, there is a risk that tampering is possible and would reveal otherwise unavaiable information.

Suppose that I am allowed to see the list of my order (567), and clicking on the application-generated link I am supposed to see the details.

http://the.address.com/details.aspx?orderid=567

 

What if I try to manually alter the parameter value and see an order from someone else?

http://the.address.com/details.aspx?orderid=568

 

If it works then we can say that the application is vulnerable to QST.

There are two possible solutions to this issue:

  1. sign query string parameters with additional signature. You can read about it here
  2. encrypt the whole query string so that there is only single encrypted parameter visible on the client-side. You can read about it here

 

From these two possibilities I prefer the latter since it seems to be slightly more ellegant. Custom implementation is easy and straightforward.