Visual Studio 2010’s Publish Web Site command offers the option “Allow this precompiled site to be updatable”. When used and your application has User Control (.ascx) files, the published application no longer contains your user controls! Instead, the \bin folder contains new files ending in the .compiled extension, one for each User Control. Your User Controls still work though, just through some hidden behaviors that I will discuss here.

First, the Page.LoadControl() method still works. LoadControl() creates a User Control on demand. In the past, it had to build it from the User Control’s file contents. Without the file present, it knows about the missing files through those new files in the .bin folder.

In my case, I was calling LoadControl after checking for a specific User Control file via a FileExists() function. I used System.Web.Hosting.VirtualPathProvider.FileExists() function. It turns out that VirtualPathProvider.FileExists() does not support the .compiled files and always returns false.

The solution comes from how ASP.NET Dynamic Data v4 does it: Use the System.Web.Compilation.BuildManager.GetObjectFactory() method. Always pass false for the ThrowException parameter and test the result for null. If not null, the file exists.

Here is a method that replaces the Page.LoadControl() method, loading a User Control if it exists. It works with the Publish Web Site feature as discussed.

public System.Web.UI.WebControls.UserControl LoadUserControlIfExists(string pVirtualFilePath)
{
    if (System.Web.Compilation.BuildManager.GetObjectFactory(pVirtualFilePath, false) != null)
        return Page.LoadControl(pVirtualFilePath);
    return null;
}

One idiosyncrasy of the ModalPopupExtender is that its HTML content is actually visible while the page is loading, until the $create() call initializes it. If you have a lengthy load time, as I did with a ListView full of records, the visibility is noticeable.

One simple solution to this is to assign the style=”display:none” to the Control containing your content.

<asp:Panel id="AdvancedFiltersContainer" runat="server" CssClass="AdvSch_PopupControl" Style="display:none;" >
    content
</asp:Panel>
<act:ModalPopupExtender id="Modal1" runat="Server" PopupControlID="AdvancedFiltersContainer" >
</act:ModalPopupExtender>

However, I’ve learned that once an Ajax Control Toolkit ComboBox control is present, IE 9 (and presumably earlier browsers) reports a JavaScript error as the Combobox is being initialized. When the HTML for the combobox is hidden, its elements have an offsetWidth, offsetHeight, clientWidth, and clientHeight of 0. The ComboBox calculates a value based on offsetWidth, subtracts the size of the border, and assigns the result to a Style.width object.

Here is a snippet from the function, _getOptionListWidth, where the issue resides.

// first, default to the clientWidth of the container
var bestWidth = this.get_comboTableControl().offsetWidth;
 
// subtract borders from the offsetWidth
bestWidth -= (leftBorder + rightBorder);
 
// in order for ths list's scrollWidth to be correct, must set its width
var originalWidth = style.width;
style.width = bestWidth + 'px';

If you are paying attention, you’ll notice that when style.width is set, the value it gets is a negative number (0 – border width). That throws a JavaScript exception in IE 9. I’ve found that even assigning style.width to “0px” throws this exception.

As a result, you cannot use style=”display:none;” to fix the problem I’ve raised, at least on IE 9.

Here’s my simple solution.

Assign style=”position:absolute;left:100000px;” instead. This will leave the ModalPopup visible, but way off screen. When ModalPopup initializes itself, it overwrites both position and left styles to fit its needs.

<asp:Panel id="AdvancedFiltersContainer" runat="server" CssClass="AdvSch_PopupControl" Style="position:absolute;left:100000px;" >
    content
</asp:Panel>
<act:ModalPopupExtender id="Modal1" runat="Server" PopupControlID="AdvancedFiltersContainer" >
</act:ModalPopupExtender>

I was using the ComboBox control of Microsoft’s Ajax Control Toolkit and ran into two issues.

  • When its initially hidden (its inside something with a style of display:none) and you make it visible, the toggle button and actual dropdown list are incorrectly sized to be almost meaningless.
    how it should look:
    ComboBox_OnShow_Correct
    how it actually looks
    ComboBox_OnShow_Incorrect
    Notice the toggle button has virtually disappeared. That grey underline is the dropdown list.

  • When its in a ModalDialog created by the ModalPopupExtender, the dropdown lists appear far away from where they belong.
    Here the Category dropdown toggle was clicked. The list should be flush with the bottom of the Category’s textbox.
    ComboBox_InMDE

I created the following class to solve both of these problems. It was developed with v3.0.30512.1 of the AjaxControlToolkit.dll. (Get it as a zip file here)

   1: /// <summary>
   2: /// These static methods fix bugs related to the AjaxControlToolkit's ComboBox.
   3: /// </summary>
   4:    public class ComboBoxFixer
   5:    {
   6: /// <summary>
   7: /// Use this when adding ACT Comboboxes that may be initially hidden when loaded.
   8: /// It registers all comboboxes into a client-side array. If the user writes
   9: /// scripts that make the ACT Comboboxes visible, they should call
  10: /// ActComboBoxMadeVisible_All(), a client-side method.
  11: /// </summary>
  12:       public static void RegisterComboBox(AjaxControlToolkit.ComboBox pComboBox)
  13:       {
  14:          ClientScriptManager vClientScript = pComboBox.Page.ClientScript;
  15:          if (!vClientScript.IsClientScriptBlockRegistered("ActComboBoxFixer"))
  16:          {
  17:             string vScript =
  18:                "function ActComboBoxMadeVisible_All()\r\n" +
  19:                "{\r\n" +
  20:                 "   if (window.gActComboboxes)\r\n" +
  21:                 "      for (var vI = 0; vI < window.gActComboboxes.length; vI++)\r\n" +
  22:                 "      {\r\n" +
  23:                 "         var vCB = $find(window.gActComboboxes[vI]);\r\n" +
  24:                 "         if (vCB)\r\n" +
  25:                 "         {\r\n" +
  26:                 "            ActComboBoxMadeVisible(vCB);\r\n" +
  27:                 "         }\r\n" +
  28:                 "      }\r\n" +  
  29:                "}\r\n" +
  30:                "function ActComboBoxMadeVisible(pCB)\r\n" +
  31:                "{\r\n" +
  32:                 "  if (pCB && !pCB._optionListItemHeight)\r\n" +
  33:                 "  {\r\n" +
  34:                 "     var vBtn = pCB.get_buttonControl();\r\n" +
  35:                 "     vBtn.style.width = '';\r\n" +
  36:                 "     vBtn.style.height = '';\r\n" +
  37:                 "     pCB.initializeButton();\r\n" +
  38:                 "     pCB._optionListHeight = null;\r\n" +
  39:                 "     pCB._optionListWidth = null;\r\n" +
  40:                 "     pCB._optionListItemHeight = 21;\r\n" +
  41:                 "     pCB._getOptionListWidth();\r\n" +
  42:                 "     pCB._getOptionListHeight();\r\n" +
  43:                 "  }\r\n" +
  44:                "}\r\n";
  45:             vClientScript.RegisterClientScriptBlock(typeof(Page), "ActComboBoxFixer", vScript, true);
  46:          }
  47:          pComboBox.Page.ClientScript.RegisterArrayDeclaration("gActComboboxes", "'" + pComboBox.ClientID + "'");
  48:       }
  49:  
  50: /// <summary>
  51: /// Helps ModalDialogExtenders work with AjaxControlToolkit.ComboBox. Call for each ModalDialogExtender
  52: /// that may have ComboBoxes. If you have more than one and they may appear simulateously, add them
  53: /// in a specific order where the topmost one is added before those below it.
  54: /// </summary>
  55: /// <remarks>
  56: /// <para>Requires each ComboBox is passed to ComboBoxFixer.RegisterComboBox.</para>
  57: /// </remarks>
  58: /// <param name="pModalExtender"></param>
  59:       public static void RegisterModalPopupExtender(AjaxControlToolkit.ModalPopupExtender pModalExtender)
  60:       {
  61:          ClientScriptManager vClientScript = pModalExtender.Page.ClientScript;
  62:          vClientScript.RegisterArrayDeclaration("gACTModalDEIDs", "'" + pModalExtender.ClientID + "'");
  63:  
  64:          if (!vClientScript.IsClientScriptBlockRegistered("ActComboBoxInMDE"))
  65:          {
  66:    // The basic idea: Replace AjaxControlToolkit._popupShown with ActComboBoxInMDE_PopupShown.
  67:    // ActComboBoxInMDE_PopupShown is a clone of _popupShown, but inserts code to change x,y
  68:    // when a ModalDialogExtender is visible. 
  69:    // MDEs are registerd in the client-side array gACTModalDEIDs.
  70:    // This allows multiple MDEs and will only evaluate the first whose content element (called _foregroundElement)
  71:    // is visible. So if there are nested MDEs, register the topmost one first and bottommost last.
  72:             string vScript =
  73:             "function ActComboBoxInMDE_Init()\r\n" +
  74:             "{\r\n" +
  75:             "   if (window.gActComboboxes)\r\n" +
  76:             "      for (var vI = 0; vI < gActComboboxes.length; vI++)\r\n" +
  77:             "      {\r\n" +
  78:             "         var vCB = $find(gActComboboxes[vI]);\r\n" +
  79:             "         if (vCB.InitedMDE) continue;\r\n" +
  80:             "         vCB._popupShown = ActComboBoxInMDE_PopupShown;\r\n" +
  81:             "         vCB._popupShownHandlerFix = Function.createDelegate(vCB, ActComboBoxInMDE_PopupShown);\r\n" +
  82:             "         vCB._popupBehavior.add_shown(vCB._popupShownHandlerFix);\r\n" +
  83:             "         vCB.InitedMDE = 1;\r\n" +
  84:             "      }\r\n" +
  85:             "}\r\n" +
  86:             "function ActComboBoxInMDE_PopupShown() {\r\n" +
  87:             "\r\n" +
  88:             "   this.get_optionListControl().style.display = 'block';\r\n" +
  89:             "\r\n" +
  90:             "   // check and enforce correct positioning.\r\n" +
  91:             "   var tableBounds = Sys.UI.DomElement.getBounds(this.get_comboTableControl());\r\n" +
  92:             "   var listBounds = Sys.UI.DomElement.getBounds(this.get_optionListControl());\r\n" +
  93:             "   var textBoxBounds = Sys.UI.DomElement.getBounds(this.get_textBoxControl());\r\n" +
  94:             "   var y = listBounds.y;\r\n" +
  95:             "   var x;\r\n" +
  96:             "\r\n" +
  97:             "   if (this._popupBehavior.get_positioningMode() === AjaxControlToolkit.PositioningMode.BottomLeft\r\n" +
  98:             "      || this._popupBehavior.get_positioningMode() === AjaxControlToolkit.PositioningMode.TopLeft) {\r\n" +
  99:             "      x = textBoxBounds.x;\r\n" +
 100:             "   }\r\n" +
 101:             "   else if (this._popupBehavior.get_positioningMode() === AjaxControlToolkit.PositioningMode.BottomRight\r\n" +
 102:             "      || this._popupBehavior.get_positioningMode() === AjaxControlToolkit.PositioningMode.TopRight) {\r\n" +
 103:             "      x = textBoxBounds.x - (listBounds.width - textBoxBounds.width);\r\n" +
 104:             "   }\r\n" +
 105:             "\r\n" +
 106:             "   if (window.gACTModalDEIDs)\r\n" +
 107:             "      for (var vI = 0; vI < gACTModalDEIDs.length; vI++)\r\n" +
 108:             "      {\r\n" +
 109:             "         var vMDE = $find(gACTModalDEIDs[vI]);\r\n" +
 110:             "         if (vMDE._foregroundElement.style.display == '')\r\n" +
 111:             "         {\r\n" +
 112:             "            var vMDBounds = Sys.UI.DomElement.getBounds(vMDE._foregroundElement);\r\n" +
 113:             "            x = x - vMDBounds.x;\r\n" +
 114:             "            y = (textBoxBounds.y + textBoxBounds.height) - vMDBounds.y;\r\n" +
 115:             "            break;\r\n" +
 116:             "         }\r\n" +
 117:             "      }\r\n" +
 118:             "   Sys.UI.DomElement.setLocation(this.get_optionListControl(), x, y);\r\n" +
 119:             "\r\n" +
 120:             "   // enforce default scroll\r\n" +
 121:             "   this._ensureHighlightedIndex();\r\n" +
 122:             "   this._ensureScrollTop();\r\n" +
 123:             "\r\n" +
 124:             "   // show the option list\r\n" +
 125:             "   this.get_optionListControl().style.visibility = 'visible';\r\n" +
 126:             "\r\n" +
 127:             "}\r\n";
 128:             vClientScript.RegisterClientScriptBlock(typeof(Page), "ActComboBoxInMDE", vScript, true);
 129:  
 130:             vClientScript.RegisterStartupScript(typeof(Page), "ActComboBoxInMDEInit",
 131:                "Sys.Application.add_load(ActComboBoxInMDE_Init);\r\n", true);
 132:  
 133:             // on MDE popup, also fix comboboxes
 134:             vScript =
 135:             "function ActComboBoxInMDE_MDEPopupInit()\r\n" +
 136:             "{\r\n" +
 137:             "   if (window.gACTModalDEIDs)\r\n" +
 138:             "      for (var vI = 0; vI < gACTModalDEIDs.length; vI++)\r\n" +
 139:             "      {\r\n" +
 140:             "         var vMD = $find(gACTModalDEIDs[vI]);\r\n" +
 141:             "         vMD.add_shown(ActComboBoxInMDE_MDEPopupShown);\r\n" +
 142:             "      }\r\n" +
 143:             "}\r\n" +
 144:             "function ActComboBoxInMDE_MDEPopupShown(sender, args)\r\n" +
 145:             "{\r\n" +
 146:             "   if (window.ActComboBoxMadeVisible_All)\r\n" +
 147:             "      ActComboBoxMadeVisible_All();\r\n" +
 148:             "}\r\n";
 149:             vClientScript.RegisterClientScriptBlock(typeof(Page), "ActComboBoxInMDE_MDEPopupInitBlock", vScript, true);
 150:             vClientScript.RegisterStartupScript(typeof(Page), "ActComboBoxInMDE_MDEPopupInit",
 151:                "Sys.Application.add_load(ActComboBoxInMDE_MDEPopupInit);\r\n", true);
 152:          }
 153:       }
 154:    }

You can retrieve a zip file with C# and VB versions of this code here.

Here is how to use this class:

  • Call ComboBoxFixer.RegisterComboBox(combobox) for each ComboBox that is initially hidden. Generally this is done in Page_Load or if inside of a Databound control like ListView or FormView, from their ItemCreated event.
  • Call ComboBoxFixer.RegisterModalPopupExtender(modalpopupextender) for each ModalPopupExtender that contains ComboBoxes. If you have nested ModalDialogs, register the topmost first.
  • If you have a button that makes the comboboxes visible, add this javascript to its client-side onclick event. It must be run after the comboboxes are visible.
    ActComboBoxMadeVisible_All();

A little background on what these scripts do:

  1. The ComboBox initializes the sizes of its toggle button and list when the page is first loaded. If this happens when the control is hidden, its calculations do not get the correct sizing information as the clientWidth and clientHeight properties of DOM elements are usually 0 in this case. The ActComboBoxMadeVisible(cb) function resets properties that determine width. It explicitly calls a function to recalculate the button size and lets the ComboBox’s _popupShowing() method use the rest to know to recalculate.
  2. The ComboBox’s _popupShown() method calculates the x and y coordinates for the dropdownlist using the offset of its textbox from the upper left of the browser window. It uses style=”position:absolute;top:x;left:y;” to position it. This works well until the ModalPopupExtender gets involved. The ModalPopupExtender uses style=”position:fixed” and this appears to impact positioning. _popupShown() needs to position from the upper left of the <div> using that style=”position:fixed”.
    To fix this, _popupShown() is replaced by a clone generated by ComboBoxFixer.RegisterModalPopupExtender(). This new function inserts code that detects if a ModalPopupExtender is visible and adjusts the x and y coordinates based on its location.

Lizamoon is the name of a SQL Injection attack heavily reported in the press since the end of March 2011. It inserts a <script> tag into your database that when returned, redirects to another site (sometimes with the domain name Lizamoon). That site uses social engineering to entice the user to download what they think is antivirus software, and later to get them to provide credit card information to pay for it. At that point, the hackers have stolen their credit card information.

You can read more with these articles:

pcworld, April 1: “Millions of Sites Hit with Mass-Injection Cyberattack”

eweek, April 1, “'LizaMoon' Mass SQL Injection Attack Escalates Out of Control”

Based on this thread from last September amongst Stackoverflow.com users, I think this attack has existing long before the March 28 date reported in the press:

Attack on ASP site that uses a SQL server database (starts on Sept 10, 2010)

Users of Peter’s Input Security (a module within my Peter’s Data Entry Suite) have the tools to defend against this attack. But just having the tools, doesn’t mean you are protected. As a rule, you must evaluate any inputs of each page to determine if they are secure or not. A web page has these “inputs” which hackers use to attack: data entry controls like textboxes, cookies, hidden fields, and query string parameters. (They also can use web services.)

This post discusses how to check your Peter’s Input Security settings to defend against the Lizamoon attack.

If you are on a DES 4 version below 4.0.6, get to DES 4.0.6 and apply a hotfix. Future releases will have the hotfix incorporated. The hotfix introduces a new feature that further assists in protecting you, but is not required. If you are using VAM or do not want to use the hotfix, I’ve included instructions for you below.

Getting v4.0.6 and the hotfix

Note: This hotfix has a minor breaking change to Peter’s Input Security that is described in step 5 of “Protecting yourself with the DetectDatabaseElementNamesInQueryString property”, below.

  1. You must use DES 4.0.6 to apply this hotfix.
    If you do not have it installed, go to http://www.peterblum.com/getupdate.aspx, login and download it. If you do not know your login, get your serial number and contact Peter at PLBlum@PeterBlum.com to request it.
    Run the .msi to install 4.0.6.
    Run the v4.0.6 Web Application Updater with the option "Update a web application (service release)". This application is found in your Start menu and the folder where you installed DES.
  2. Get the hotfix at http://www.peterblum.com/hotfixes/PeterBlum.DES4_0_6_5000.zip.
    Note: This hotfix rolls up all previous hotfixes for v4.0.6.
    Unzip the file.
    Replace the PeterBlum.DES.dll in your web application. Be sure that your Visual Studio project has a reference to this new assembly.
    Clear your IE9 RC1 browser's cache if you were already using 4.0.6.

Understanding the new feature of the hotfix

The new feature is the DetectDatabaseElementNamesInQueryString property on the PageSecurityValidator manager.  When this property is true (the default), the PageSecurityValidator evaluates every query string parameter for any value defined in the <databaseelementnames> node of the [web app]\DES\Security Config Files\master.config file and report an error if found.

This feature detects lizamoon because the attack incorporates the names of tables and columns from you database, previously extracted through another hack directed at your databases’s master tables. Here is its SQL attack pattern:

update [your table name] set [your field name] = REPLACE(…)

The <databaseelementnames> section of master.config is intended to hold names of tables and important column names that you do not want to appear within your inputs. It works best when your table and column names are not spoken languages, such as using “tbl_client” instead of “client” for a table of clients. Here is an example of this node. (The file is here: [web app]\DES\Security Config Files\master.config)

<databaseelementnames>
   <item action="add" >tbl_products</item>
   <item action="add" >tbl_orders</item>
   <item action="add" >tbl_categories</item>
</databaseelementnames>

Protecting yourself with the DetectDatabaseElementNamesInQueryString property

By default, DetectDatabaseElementNamesInQueryString is true, which means the feature is active. Yet, it does nothing if you have not setup the page correctly.

1. If you are new to Peter’s Input Security, follow the directions of the Input Security Installation Guide. This will enable loading the various config files, have you describe key elements about your app, and install a logging feature to capture attacks.

2. If you have not setup the page for Peter’s Input Security, follow the steps in the “Securing a Page” section of the Input Security User’s Guide. There are many actions to lock down all inputs. Be prepared to spend some time protecting each page.

3. At this point, your page has a PageSecurityValidator with the DetectDatabaseElementNamesInQueryString set to true (the default). If you do nothing else, this attack will be prevented.

4. Query string parameters that accept string values may have legal values blocked by the DetectDatabaseElementNamesInQueryString property. Review those parameters against the values in the <databaseelementnames> node of [web app]\DES\Security Config Files\master.config. If there is a legal value, set DetectDatabaseElementNamesInQueryString to false. Then add a ParameterRule object to the QueryStringRules object with its DataType=String and DetectInjection=true (the default). Also adjust the SQLDetectionLevel property, which defaults to the highest protection.

<des:PageSecurityValidator ID="PageSecurityValidator1" runat="server" DetectDatabaseElementNamesInQueryString="false" >
   <QueryStringRules>
      <des:ParameterRule Name="search" DataType="String" DetectInjection="true" SQLDetectionLevel="MediumLow" />
   </QueryStringRules>
</des:PageSecurityValidator>
5. The hotfix also introduced a breaking change by changing the default of the DetectInjection property from false to true on the HiddenFieldRule, ParameterRule, and QueryStringRule objects. This breaking change only applies to Rules that:
  • do not set DetectInjection, thus using the default.
  • has its DataType property set to Ignore or String, or does not set DataType (which defaults to Ignore)

Look through your PageSecurityValidators to locate Rules that are now using DetectInjection=true. If you do not want that behavior, set its DetectInjection property to false.

Protecting yourself with VAM or without the DES hotfix

This topic helps users who have Professional Validation and More (“VAM”) as well as DES without the hotfix.

1. If you are new to Peter’s Input Security, follow the directions of the Input Security Installation Guide. This will enable loading the various config files, have you describe key elements about your app, and install a logging feature to capture attacks.

2. If you have not setup the page for Peter’s Input Security, follow the steps in the “Securing a Page” section of the Input Security User’s Guide. There are many actions to lock down all inputs. Be prepared to spend some time protecting each page.

3. Review the QueryStringRules collection of each PageSecurityValidator to be sure that every query string parameter used by this page is listed, with the appropriate rules. When a ParameterRule has its DataType property set to Ignore or String, or it does not declare the DataType property are candidates for this attack. Your defense is to set DetectInjection to true.

<des:PageSecurityValidator ID="PageSecurityValidator1" runat="server" >
   <QueryStringRules>
      <des:ParameterRule Name="search" DataType="String" DetectInjection="true" />
   </QueryStringRules>
</des:PageSecurityValidator>    

Index to this series of articles

Business logic is applied to your Entity classes (objects that describe individual tables where columns are the properties) through metadata. Typically this metadata is defined by applying attributes from the System.ComponentModel.DataAnnotations namespace.

To successfully let business logic drive the user interface, you need an extensive library of business rules. Here are the additional business rules available as attributes. In my “Peter’s Soapbox”, you will find attributes that I feel need to be provided to complete the library.

  • DefaultValueAttribute – Supplies the initial value for the column when creating a new record. Generally this value is assigned to the user interface to establish a default. ASP.NET Dynamic Data supports it in the FieldValue property of FieldTemplateUserControl class. FieldValue is generally assigned to the data entry control when generating the HTML output. If in Insert mode, it uses the default. Otherwise it uses the value previously stored.
  • DisplayFormatAttribute – Column values that need to be displayed as strings use the DisplayFormatAttribute to assist in converting from its native type to the string.
  • DisplayColumnAttribute – Applied to the Entity class definition, it specifies which column to display in lists, such as those shown in filters or in foreign key links. For example, you may want to show the Last Name column in lists. By default, Dynamic Data uses the first column of type string that it finds. It also lets you change the default sorting, to specify a different column and sort order.
  • EditableAttribute - Introduced in .net 4, it's a bit like a security oriented attribute. It tells the client application whether a column can be made editable or not. It also tells the client application whether insert mode allows an entry. Typically its used to define a read only column and perhaps to express that the user can still enter a value in insert mode.
  • KeyAttribute – When your Entity class is not generated by Entity Framework or LINQ to SQL, use this attribute to identify columns that are primary keys. Primary keys have special behaviors in the user interface.

Peter’s Soapbox

Is that it? A handful of Validation attributes, 2 DataTypeAttributes, and the rest mentioned in the last few postings? I’ve already mentioned in those posts where I felt the attributes were lacking. Here’s some more.

Security roles attributes

Role-based security restrictions to tables and columns is industry standard stuff. Where’s the support?

My next release of Peter’s Data Entry Suite introduces the ColumnRestrictionAttribute and TableRestrictionAttribute. For example:

[DES.ColumnRestriction("Admin", DES.DenyAccess.None)]
[DES.ColumnRestriction("Sales", DES.DenyAccess.Insert | DES.DenyAccess.Edit)]
[DES.ColumnRestriction("Support", DES.DenyAccess.View)]
public object Photo { get; set; }

Dependency attributes

Often on field is required based on the state of another. For example, when the column “CustomerType” has a value of “Other”, the OtherDescription field should require a value. The user interface would enable and disable both the textbox and validator associated with the OtherDescription field based on the setting of the CustomerType DropDownList. It’s a pretty standard thing and not too difficult to create attributes to describe.

This is already supported in my Peter’s Data Entry Suite.

[DES.RequiredDependency(ColumnNames="PrinterType|FaxType",
MultipleMode=PeterBlum.DES.MultipleRequiredControlsMode.AtLeastOne)]
public object OutputHeading {get; set; }

Column is sortable attribute

The column headers of the ListView and GridView can usually be clicked to sort them. Another user interface would be to offer a dropdownlist of sortable fields. These user interfaces should be driven by the business logic through a SortableColumnAttribute. It not only determines if sorting is active, but also specifies the Sort Expression to apply.

Again, already supported in my Peter’s Data Entry Suite.

[DES.SortableColumn(false)]
public object Notes { get; set; }

Using the CategoryAttribute

The System.ComponentModel.CategoryAttribute can be used in creative ways. Take a look at my earlier posting on the subject: The CategoryAttribute and Dynamic Data.

Text entry attributes

Often the only difference between string column values is the pattern and character set permitted. Both can be used by textboxes to enhance entry. In addition, they are used by validators. Here’s how.

  • Pattern. Think of the “masked textbox” concept. Let’s suppose that you declare:
    [TextMask("999-999-9999")]
    public object SocialSecurityNumber { get; set; }

    The Text_Edit.ascx Field Template can use this to establish the MaskedEdit Extender control on it’s textbox. With a converter routine, it can also be converted into a regular expression that is applied to the RegularExpressionValidator in the Field Template. (Or the Field Template can create a MaskedEditValidator.)

    I think the TextMaskAttribute should be a subclass of DataTypeAttribute so there can be a standard Field Template called TextMask_Edit.ascx.

  • Character set. Many strings don’t have a pattern, but have a limited character set. For example, a person’s first name may be limited to upper and lower case letters. Again the Field Template establishes the necessary javascript to apply the character set, and sets up the RegularExpressionValidator to limit to those characters.
    [CharacterSet(Digits=true, Othercharacters="-")]
    public object SocialSecurityNumber { get; set; }

     

Injection attack attributes

Business logic determines what’s legal within strings. Hackers employ SQL Injection and Cross Site Scripting attacks through your web form’s inputs. String type fields should determine what HTML patterns and SQL statements are allowed or rejected.

Field Templates should use these attributes to generate a CustomValidator control that invokes your Injection detection code (server side only). What? You don’t have one? There’s one in my Peter’s Data Entry Suite called Peter’s Input Security.

Once again: “Is that all?”

I highly doubt that’s all of the business rules for entity objects. Feel free to describe your own cases in the comments.

 

 

Index to this series of articles

Index to this series of articles

Business logic is applied to your Entity classes (objects that describe individual tables where columns are the properties) through metadata. Typically this metadata is defined by applying attributes from the System.ComponentModel.DataAnnotations namespace.

Scaffolding is a term that describes letting the business logic determine the list of columns and tables displayed in your user interface. You see it in action when using Dynamic Data’s Page Templates. Yet, it is fine to explicitly define the list of columns in your user interface code, especially when you need flexibility in your layout from column to column.

The ScaffoldColumnAttribute and DisplayAttribute are both used to identify if a column is included or excluded. ScaffoldColumnAttribute was introduced first and has fewer capabilities. DisplayNameAttribute is new to .net 4, adding the Order and GroupName properties to the scaffolding rules.

[ScaffoldColumn(true)]
public object Picture { get; set; }

 

or

[Display(AutoGenerateField=true)]
public object Picture { get; set; }

 

You can read about my complaints of the DisplayAttribute’s design in the previous post. See the “Peter’s Soapbox” section.

When no attribute is supplied

When neither of these attributes are assigned, the Dynamic Data scaffolding engine determines if the field shows based on the following rules:

  • Included: A Field Template name is specified in the UIHintAttribute.
  • Excluded: MetaColumn.IsForeignKeyComponent = true (Part of a ForeignKeyColumn)
  • Excluded: MetaColumn.IsGenerated = true (The column’s data is generated by the database)
  • Included: MetaColumn.IsPrimaryKey = true (The column is a primary key)
  • Excluded: MetaColumn.IsCustomProperty = true (The column was created in the EntityTable class, but does not exist in the database)
  • Included: MetaColumnType is one of these: string, char, integer, floating point, bool, DateTime, TimeSpan, or DateTimeOffset.
  • Excluded: Anything not listed above.

Ordering the scaffolded columns

By default, Dynamic Data shows the columns in the same order they appear in the Entity class. If you want to change the order, use the DisplayAttribute. Its Order and GroupName properties are used by the scaffolding engine to sort the columns.

Scaffolding Tables

Like columns, Dynamic Data can provide a specific list of tables to show. Use the ScaffoldTableAttribute to exclude a table. When initializing Dynamic Data in Application_Start(), use the ScaffoldAllTables property to include all tables that are not set to [ScaffoldTableAttribute(false)].

DefaultModel.RegisterContext(typeof(NorthwindModel.Entities), 
new ContextConfiguration() { ScaffoldAllTables = true });

 

This attribute impacts two features of Dynamic Data:

  • The “entry point” web form, which is Default.aspx, initially, can show this table. It internally uses the MetaModel.VisibleTables property to get a list of tables. Be aware that this list is ordered by how the Tables were defined by the ModelProvider class to the MetaModel class. If you want to control the ordering yourself, create a list of MetaTables in the desired order instead of using MetaModel.VisibleTables.
  • Url Routing will prevent Urls that contain table names that are not scaffolded.

Scaffolding for Filters

List oriented interfaces often have filters to let the user modify the query used to generate the list. Your business logic can assist your user interface in building filters with these two attributes:

  • DisplayAttribute – Use the AutoGenerateFilter property to include or exclude the column from your filter interface.
  • FilterUIHintAttribute – Specifies an alternative Filter Template file (a violation of the separation of concerns).

ASP.NET Dynamic Data uses this with its QueryableFilterRepeater control to generate a user interface based on business logic.

When no DisplayAttribute is supplied

When the DisplayAttribute is not assigned, the Dynamic Data scaffolding engine determines if the filter shows based on the following rules:

  • Included: A Filter Template name is specified in the FilterUIHintAttribute.
  • Excluded: The column is not scaffolded (see the earlier scaffolding rules for fields)
  • Excluded: MetaColumn.IsCustomProperty = true (The column was created in the EntityTable class, but does not exist in the database)
  • Included: MetaColumnType is one of these: bool, enumerated type
  • Excluded: Anything not listed above.

 

Index to this series of articles

Index to this series of articles

Business logic is applied to your Entity classes (objects that describe individual tables where columns are the properties) through metadata. Typically this metadata is defined by applying attributes from the System.ComponentModel.DataAnnotations namespace.

Each column has a name taken from the database. Yet there are many ways to display text about the column which does not match the column’s name. The DisplayAttribute (introduced in ASP.NET 4) provides the following properties that are available to your user interface:

  • Name – The column’s name corrected to reflect language and formatting issues.
  • ShortName – A shorter version of the Name.
  • Description – A description that may appear in a hint, tooltip or help label.
  • Prompt – A description that works well as a prompt, such as in a wizard or the label preceding a textbox.

This attribute supports resource based localization.

[Display(Name="Category", Prompt="Enter the category name")]
public object CategoryName { get; set; }

If you are still using ASP.NET 3.5, you can define the Name and Description from the System.ComponentModel.DisplayNameAttribute and System.ComponentModel.DescriptionAttribute classes. The rest are not available.

Naming for tables

Like a column, each table has a name taken from the database. If you want to display the table name, it may need an alternative version that reflects language and formatting issues.

[DisplayName("Categories")]
public class Category
{
}

Peter’s Soapbox

I have mixed feelings about the DisplayAttribute introduced in .net 4. I feel it mixes too many concepts together and these concepts already had their own attributes.

  • Text – Already in DisplayNameAttribute and DescriptionAttribute. No support for ShortName and Prompt in the past though.
  • Scaffolding (the “AutogenerateField” property) – Allows business logic to dictate if the column appears. There already exists ScaffoldColumnAttribute, although this new version introduces the Order and GroupName properties to determine the column’s position.

    I think that the new properties should have been added to the ScaffoldColumnAttribute. I foresee are additional rules for scaffolding which should appear in the framework, such as defining a list of columns that match a specific category. The ScaffoldColumnAttribute makes sense for this kind of growth.

  • Filter scaffolding (the “AutoGenerateFilter” property). Its another aspect to scaffolding that impacts generating a list of filters in the QueryableFilterRepeater control. This probably belongs in the ScaffoldColumnAttribute so it can share the Order and GroupName properties.

    Again, there are probably other rules for scaffolding to come for filters. One is already defined in a separate attribute, FilterUIHintAttribute, which helps select the user interface used for filtering, such as setting up a range or a checkboxlist.

 

 

 

Index to this series of articles

Index to this series of articles

Business logic is applied to your Entity classes (objects that describe individual tables where columns are the properties) through metadata. Typically this metadata is defined by applying attributes from the System.ComponentModel.DataAnnotations namespace.

The DataTypeAttribute impacts the “data type” of a column. When defined in the database or a programming language, data types are integers, decimals, strings, etc. We use these to hold all kinds of real world types. Examples:

  • Integers: percentage, enumerated type, age
  • Decimals: currency, duration, distance, weight
  • Strings: phone number, postal code, email address, URL
  • DateTime: Date, time, Month/Year (credit card expiration), Birthday (day and month).

Each of these real-world data types have differences in their business rules. The user interface should respect these differences by using different data entry controls (like DateTextBox, Calendar, IntegerTextBox, etc) and validators.

Use the DataTypeAttribute to specify the real world type. Its parameter takes either a value from System.ComponentModel.DataAnnotations.DataType or a string. When the DataType enumerated list lacks the desired type, specify it as a string.

[DataType(DataType.Date)]
public object BirthDate { get; set; }
[DataType("Longitude")]
public object Longitude { get; set; }

 

Making DataTypeAttribute impact validation

Validation is a very important feature of business rules, and DataTypeAttribute is heavily involved. That’s why DataTypeAttribute is subclassed from System.ComponentModel.DataAnnotations.ValidationAttribute. Yet DataTypeAttribute’s implementation of the IsValid() method is to return true every time.

I recommend subclassing DataTypeAttribute for each data type where validation is needed and implementing the IsValid() method. For example:

public class DateAttribute : DataTypeAttribute
{
public override bool IsValid(object instance)
{
if (instance is DateTime)
return true;
if (instance is string)
{
DateTime vTemp;
return DateTime.TryParse((string)instance, out vTemp)
}
return false;
}
}
public class EmailAddressAttribute : DataTypeAttribute
{
public override bool IsValid(object instance)
{
if (instance is string)
{
return Regex.IsMatch((string) instance,
"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$");
}
return false;
}
}

How Dynamic Data uses the DataTypeAttribute

ASP.NET Dynamic Data uses the DataTypeAttribute to select a Field Template. Field Templates are User Control files that define the user interface for a specific data type in a specific situation. The situation includes the mode – read-only, edit, or insert – and other ways the user interface may change. Field Templates for edit and insert mode should create Validation web controls based on the business logic.

Here are some guidelines for creating Field Templates which respect DataTypeAttributes:

  • The file name of the Field Template should be the same as the value passed into the DataTypeAttribute’s parameter, plus “_Edit” or “_Insert” for the desired mode. For example, DataTypeAttribute(DataType.EmailAddress) should have a Field Template named “EmailAddress_Edit.ascx”.
  • The data entry control should be suitable for the data type. Generally you will use a TextBox for string-types, although its not a requirement.
  • When using a textbox, always define a Validator web control that will enforce the data type. Typically you will add the CompareValidator, RegularExpressionValidator, or CustomValidator.
    • The CompareValidator works for non-string types. Set its Operator property to the desired type. If the Operator doesn’t match the type you need, use one of the other two validators.
    • The RegularExpressionValidator works for string types that have a pattern. Your business logic may specify the RegularExpressionAttribute to deliver a regular expression to this validator, letting Dynamic Data automatically connect the two. As a result, there will be two attributes: DataTypeAttribute and RegularExpressionAttributes. It would be nice to combine these two.
    • The CustomValidator handles everything else.

The EnumDataTypeAttribute

The EnumDataTypeAttribute is a DataTypeAttribute subclass for one very common case: Enumerated types. It can be assigned to properties whose type is an enumerated type, a string, or integer.

The EnumDataTypeAttribute maps a list of strings to the value stored in the data. It gets those strings from the actual enumerated type definition. For example:

public enum Movement
{
Stop,
Slow,
Normal,
Fast,
Excessive
}

[EnumDataType(typeof(Movement))]
public int VehicleMovement { get; set; }

Dynamic Data provides the Enum.ascx and Enum_Edit.ascx Field Templates to display the list of strings from this attribute. In edit mode, it gives you a DropDownList.

Peter’s soapbox

While its easy to setup, I dislike supplying the list of strings from the enumerated type. Values in the type are usually not correct for the language and lack formatting. In my Peter’s Data Entry Suite, I created an EnumeratedAttribute to handle this. While it can be setup using the enumerated type, you can also define strings explicitly, including culture specific strings.

Index to this series of articles

Index to this series of articles

Business logic is applied to your Entity classes (objects that describe individual tables where columns are the properties) through metadata. Typically this metadata is defined by applying attributes from the System.ComponentModel.DataAnnotations namespace.

Validation attributes focus on evaluating data to report errors. The base class is System.ComponentModel.DataAnnotations.ValidationAttribute. It defines the IsValid() method, which is always overriden to evalutate the data. It also supplies the ErrorMessage property, where you can describe the error reported. (I have complaints about using the error message here. See Where does this go? Applying SoC to dynamic data – Part 2.)

The namespace defines the following attributes to validate your data.

  • RequiredAttribute – Marks the field as required. If your database already defines the column as “not nullable”, Dynamic Data knows that the field is required. Yet, since your Entity classes may be used outside of Dynamic Data, always consider assigning this.
    [Required()]
    public object Name { get; set; }

     

    Concerns

    This attribute is limited to testing text returned from the browser and is generally mapped to the RequiredFieldValidator web control.

    It is further limited to considering only the empty string is invalid, whereas you can use the InitialValue property on RequiredFieldValidator to recognize another case (a watermark in the textbox).

  • RegularExpressionAttribute – Evaluates text against a regular expression. Dynamic Data maps this attribute to the RegularExpressionValidator web control.
    [RegularExpression("^\d{5}$")]
    public object ZipCode { get; set; }

     

    Concerns

    Like with the RegularExpressionValidator, this attribute has no way to set the “ignore case” or “multiline” options, which are useful in developing regular expressions.

    When this expression is used with server side validation, it will use the .net framework’s regular expression engine. When it is used within the browser, it uses javascript’s regular expression engine. Javascript has a simpler syntax. Take care to define your expressions with the Javascript syntax when using Dynamic Data. See Javascript RegExp class guide.

  • StringLengthAttribute – Evaluates the length of text to confirm it has not exceeded a maximum value. If your database already defines the length on a textual column, Dynamic Data has access to that value. Yet, since your Entity classes may be used outside of Dynamic Data, always consider assigning this.
    [StringLength(10)]
    public object SocialSecurityNumber { get; set; }

     

    Concerns

    None of the Field Templates actually include a validator web control for this attribute. Those that have single-line textboxes (like Text_Edit.ascx) set their TextBox control’s MaxLength property. If you are using a multiline textbox, you will need to introduce a validator. I recommend using the RegularExpressionValidator with its expression programmatically set in the Field Template’s Page_Load method to a pattern like this:

    ^[\s\S]{0,#}$

    where # is replaced by the value from the Field Template’s Column.MaxLength property. Do NOT attach your validator to the FieldTemplateUserControl’s class by using the SetupValidator() method as that try to associate it with the RegularExpressionAttribute. It will either have its expression updated or be disabled.

    Instead, I recommend creating a method in the Field Template that sets it based on the StringLengthAttribute and if not found, sets the Validator’s Enabled property to false.

  • RangeAttribute – Confirms that the value is between a low and high value of a range. Dynamic Data maps this attribute to the RangeValidator web control.

    If you only want to compare greater than or equal to a value, just set the low (the Minimum property). Similarly set Maximum to compare less than or equal to the high value.

    [Range(0, 100)]
    public object Percentage{ get; set; }

     

  • CustomValidationAttribute – (requires ASP.NET 4). Lets you add a method to your Entity class which will be invoked to validate the column in some way. It can also evaluate multiple columns of an entity. The CustomValidationAttribute only validates on the server side. Dynamic Data uses its DynamicValidator web control to report errors.

    See my earlier posting, “The CustomValidationAttribute”.

While Dynamic Data installs validator web controls into the user interface, your Entity class should never assume that validation was handled elsewhere. It should always run a final validation against the validation rules that you have created. Use the System.ComponentModel.DataAnnotations.Validator class to run that validation. Typically you will call its TryValidateObject() method. It throws an exception for the first error

In this example, the Products table has a method called OnValidate() which invokes it. (OnValidate() is defined in Linq to SQL Entities automatically, but the call to TryValidateObject is not.)

public class Product
{
public void OnValidate(System.Data.Linq.ChangeAction pAction)
{
List<ValidationResult> vErrors = new List<ValidationResult>();
Validator.TryValidateObject(this,
new ValidatorContext(this, null, null), vErrors);
if (vErrors.Count > 0)
throw new Exception(vErrors[0].ErrorMessage)
}
}


Peter’s Soapbox

As many of you know, I have been developing an enhanced validation framework for ASP.NET for years. I’ve found and fixed numerous limitations. Not surprisingly, I find that many of my concerns remain unresolved within the Validation Attributes. I have worked to fix all of the complaints listed in this posting in my Peter’s Data Entry Suite. You’ll learn more here.

Field Templates cannot setup “compare two field” validators

One of the most common validation rules is not available as a ValidationAttribute: comparing two fields. For example, BirthDate < HireDate. This expression is easily represented with the CompareValidator in the web form level:

<asp:CompareValidator id="CompareBirthToHire" runat="server" 
ControlToValidate="BirthDateTextBox" ControlToCompare="HireDate"
Operator="LessThan" Type="Date" />

Field Templates are supposed to automatically setup Validator web controls like this based on the ValidationAttributes. I’d like to see something like:

[CompareColumn(OtherColumn="HireDate", Operator="LessThan")]
public DateTime BirthDate { get; set; }

 

Without this, you can still use the CustomValidatorAttribute on the class definition, but it is strictly server side. Field Templates do not manage a validator web control associated with it.

There are a few issues to fixing this problem, including:

  • Property-level validation only passes in the value of the property itself, not the overall object, to the IsValid() property. Therefore IsValid cannot see the second property’s value. Yet this is fixable because IsValid actually can be passed a second value, the entire instance, in the ValidationContext property. This issue requires a change to the DynamicValidator class. More importantly, if the goal is to convert the CompareColumnAttribute into a CompareValidator web control, we really don’t care about the IsValid method.
  • The two columns are converted into HTML using separate Field Templates. Each is a User Control, which means it uses a separate Naming Container. As a rule, properties on web controls take an ID to controls in the same naming container. So the CompareValidator cannot locate the two textboxes. My own Validator classes allow attaching to the textbox object as an alternative. You can learn more about my philosophy here: Improving ASP.NET: Finding Controls.
There are many more common validation rules

My validation suite includes around 25 Validator web controls. There are so many reusable ways to validate. In my Dynamic Data solution, I’ve created numerous ValidationAttributes.

Field Templates may not be coded to handle every ValidationAttribute

The Field Template must create a Validator web control for each ValidationAttribute declared on the column. Its easy to introduce a new ValidationAttribute on your business logic without it impacting the Field Template for which it was intended. For example, the default field template for strings, Text_Edit.ascx, lacks a validator for the StringLengthAttribute. Your Entity class should still catch errors prior to saving by using the System.ComponentModel.DataAnnotations.Validator class. But the Validator class is strictly server side.

In my solutions, I chose to abandon defining Validator web controls in the Field Template. Instead, the user drops in a new control called ColumnValidationManager. This control evaluates the list of ValidationAttributes on the column and creates all validators it needs. To work, the ValidationAttribute class must help out. My extensions to the ValidationAttribute class include methods to return an instance of the web control that will be used. If you do the same, be sure to also add the DynamicValidator in your validator generator code.

Here’s my version of Text_Edit.ascx Field Template:

<%@ Control Language="C#" AutoEventWireup="true" Inherits="PeterBlum.DES.DynamicData.TextEditFTUC" %>
<%@ Register assembly="PeterBlum.DES.DynamicData" namespace="PeterBlum.DES.DynamicData" tagprefix="desDD" %>
<script runat="server">
   1:  
   2:    protected void Page_Init(object sender, EventArgs e)
   3:    {
   4:       SetUpEditableDataControl(TextBox1);
   5:       SetUpColumnValidatorManager(ColumnValidatorManager1);
   6:    }
</script>
<des:FilteredTextBox ID="TextBox1" runat="server" ></des:FilteredTextBox>
<desDD:ColumnValidatorManager ID="ColumnValidatorManager1" runat="server" />

 

 

 

 

Index to this series of articles

This is the index to a series of articles, to be published periodically, about the elements of ASP.NET Dynamic Data. It focuses on specific technologies, especially the web controls you use to build a Dynamic Data-based web form. As the author of Peter’s Data Entry Suite, I have identified limitations and gaps in Dynamic Data which I will also discuss. My commercial solution resolves many of those limitations and gaps.

  • Data Source controls to interact with your business logic – Discusses which DataSource web controls are best for good separation of concerns between UI and business logic.
  • Validation attributes for business logic. A look at the Validation attributes in System.ComponentModel.DataAnnotations.
  • DataTypeAttribute for business logic.
  • Textual attributes for business logic. The many ways to associate text with column and table elements.
  • Scaffolding attributes for business logic. What is scaffolding and how to manage it.
  • Other attributes for business logic. Includes my thoughts on what business logic attributes are missing.
  • Field Templates – Pending. The user interfaces for each data type.
  • DynamicControl and DynamicField – Pending. The mainstay web control for a user interface of a column in your business logic.
  • DynamicValidator control – Pending. Displays errors generated by your business logic.
  • Entity Templates – Pending. A user interface with webcontrols (including DynamicControls) and HTML to describe typical situation, such as a two column row in a detail record, an email entry, and a street address entry.
  • DataBound controls – Pending. ListView, FormView, GridView, and DetailsView are DataBound controls. Dynamic Data requires one as a container for any DynamicControl or DynamicField.
  • DynamicDataManager control – Pending. Connects Dynamic Data technology to the DataBound control.
  • DynamicFilter, FilterRepeater, and DynamicFilterRepeater – Pending. For building filtering user interfaces.
  • DataPager control – Pending. More than just for Dynamic Data, but central to managing list-based DataBound controls.
  • QueryExtender – Pending. Connects DynamicFilterRepeater with the DataSource control.
  • Page Templates – Pending. Provides nearly instant applications completely driven by scaffolding and other business logic attributes.
  • DynamicHyperlink control – Pending. Delivers a hyperlink that defines its URL based on URL routing rules.
  • What’s missing – Pending. Other elements that I’ve implemented to make Dynamic Data more robust.
More Posts Next page »