Sun, Jan 8 2006 11:47 PM Erwyn van der Meer

Adding input validation to declarative query string parameters in ASP.NET 2.0

Scott Guthrie is blogging a lot of useful ASP.NET 2.0 articles lately. Today I looked at his DataListPaging sample. It shows how you can create a real word application with not a lot of code.

For instance, it declaratively binds query string parameters for a database query through the ObjectDataSource control. The Products.aspx page in the sample has the following code:

    <SelectParameters>
        <asp:QueryStringParameter Name="CategoryId" QueryStringField="CategoryId" DefaultValue="0" />
        <asp:QueryStringParameter Name="PageIndex" QueryStringField="PageIndex" DefaultValue="0" />
        (…)
    </SelectParameters>

A URL requesting the page might look like products.aspx?categoryId=6&pageIndex=2.

When trying out the sample, the hacker in me changed the value of the categoryId parameter to xxx. This made the application crash with a FormatException because the value xxx cannot be converted to Int32. Not so good. I wondered how much effort it would take to add parameter validation.

After digging through the MSDN documentation it quickly became obvious that there is no declarative way to do this. You can provide a default value for the parameter for when it is absent of empty. And you can even specify that a parameter should be of type Int32, but the page crashes nonetheless. I found in the MSDN docs that the appropriate way to add parameter validation is by handling events like Selecting on the ObjectDataSource.

I noticed that Scott explicitly fetched the parameters from the query string in his ProductDataSource_Selected method in the code-behind file for the page. This duplicates the declarative way of reading these parameters. This method also used casts to Int32 without error handling.

I added a event handler

            <asp:ObjectDataSource (…) OnSelecting="ProductDataSource_Selecting">

to the code-behind for the page:

    protected void ProductDataSource_Selecting(object sender, ObjectDataSourceSelectingEventArgs e)
    {
        EnsureParameterIsInteger(e.InputParameters, "pageIndex", out _pageIndex);
        EnsureParameterIsInteger(e.InputParameters, "categoryId", out _categoryId);
    }

This event handler calls this method to do the dirty work:

    private void EnsureParameterIsInteger(IOrderedDictionary inputParameters, string parameterName, out int parameterValue) 
    {
        bool isInteger = int.TryParse( (string) inputParameters[parameterName], out parameterValue);
        if (!isInteger)
        {
            inputParameters[parameterName] = ProductDataSource.SelectParameters[parameterName].DefaultValue;
            parameterValue = int.Parse( (string) inputParameters[parameterName]);
        }
    }

When the parameter value cannot be cast to an Int32 I use the default parameter value specified declaratively (see above). Notice I added two private fields to the page to store the values of the parameters so that the ProductDataSource_Selected method doesn’t have to read them from the query string:

    private int _pageIndex;
    private int _categoryId;

This also saves me from having to pass the values  explicitly to the UpdateNextPrevLinks and UpdatePagerLocation methods. With this both the declarative code and the code in the code-behind benefit from the parameter validation.

So fortunately it doesn’t take a lot of effort to add parameter validation. At first I feared it couldn’t be done when using declarative parameters, ASP.NET 2.0 proves to be flexible enough to do it.

For completeness, here is the entire content of the adapted code-behind Products.aspx.cs:

using System;
using System.Data;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Collections.Specialized;
 
public partial class Products : System.Web.UI.Page
{
    private int _pageIndex;
    private int _categoryId;
 
    protected void ProductDataSource_Selected(object sender, ObjectDataSourceStatusEventArgs e)
    {
        // Retrieve output parameter values returned from calling the "ProductsTableAdapter.GetProductsByCategoryId" 
        // method invoked by the ObjectDataSource control
        int productCount = (int)e.OutputParameters["CategoryProductCount"];
        string categoryName = (string)e.OutputParameters["CategoryName"];
 
        // Retrieve pageSize pulled from ObjectDataSource parameter
        int pageSize = Int32.Parse(ProductDataSource.SelectParameters["NumRows"].DefaultValue);
 
        // Update various page elements with data values
        UpdateTitles(categoryName);
        UpdatePagerLocation(pageSize, productCount);
        UpdateNextPrevLinks(pageSize, productCount);
    }
 
    void UpdateTitles(string title)
    {
        ProductHeader.Text = title;
        Page.Title = "Products: " + title;
    }
 
    void UpdatePagerLocation(int pageSize, int productCount)
    {
        int currentStartRow = (_pageIndex * pageSize) + 1;
        int currentEndRow = (_pageIndex * pageSize) + pageSize;
 
        if (currentEndRow > productCount)
            currentEndRow = productCount;
 
        PagerLocation.Text = currentStartRow + "-" + currentEndRow + " of " + productCount + " products";
    }
 
    void UpdateNextPrevLinks(int pageSize, int productCount)
    {
 
        string navigationFormat = "products.aspx?categoryId={0}&pageIndex={1}";
 
        PreviousPageNav.HRef = String.Format(navigationFormat, _categoryId, _pageIndex - 1);
        PreviousPageNav.Visible = (_pageIndex > 0) ? true : false;
 
        NextPageNav.HRef = String.Format(navigationFormat, _categoryId, _pageIndex + 1);
        NextPageNav.Visible = (_pageIndex + 1) * pageSize < productCount ? true : false;
    }
 
    protected void ProductDataSource_Selecting(object sender, ObjectDataSourceSelectingEventArgs e)
    {
        EnsureParameterIsInteger(e.InputParameters, "pageIndex", out _pageIndex);
        EnsureParameterIsInteger(e.InputParameters, "categoryId", out _categoryId);
    }
 
    private void EnsureParameterIsInteger(IOrderedDictionary inputParameters, string parameterName, out int parameterValue)
    {
        bool isInteger = int.TryParse((string)inputParameters[parameterName], out parameterValue);
        if (!isInteger)
        {
            inputParameters[parameterName] = ProductDataSource.SelectParameters[parameterName].DefaultValue;
            parameterValue = int.Parse((string)inputParameters[parameterName]);
        }
    }
}
Filed under:

# re: Adding input validation to declarative query string parameters in ASP.NET 2.0

Saturday, August 26, 2006 10:39 PM by martin harvey

this was exactly what i have been tring to do but i have had trouble trying to convert the code to vb I converted this to: Protected Sub ProductDataSource_Selecting(ByVal sender As Object, ByVal e As ObjectDataSourceSelectingEventArgs) EnsureParameterIsInteger(e.InputParameters, "pageIndex", out _pageIndex) EnsureParameterIsInteger(e.InputParameters, "countryId", out, _categoryId) End Sub Private Sub EnsureParameterIsInteger(ByVal inputParameters As IOrderedDictionary, ByVal parameterName As String, ByRef parameterValue As Integer) Dim isInteger As Boolean = Integer.TryParse(CType(inputParameters(parameterName),out parameterValue), String) If Not isInteger Then inputParameters(parameterName) = ObjectDataSource1.SelectParameters(parameterName).DefaultValue parameterValue = Integer.Parse(CType(inputParameters(parameterName), String)) End If End Sub I am getting four blue limes with the following error messages 1) out, _pageIndex..........Error Messages 1) Out is not declared 2)(_pageIndex) comma,parentheses or valid expression expected. 2) out, _categoryId..........Error Messages 1) Out is not declared 2)(_categoryId) comma,parentheses or valid expression expected. 3) parameterValue............Error Messages 1) parentheses expected 4) If Not isInteger Then........Error Messages 1) isinteger is not declared Can you tell me the correct syntax for these four mistakes. Many thanks Martin

# re: Adding input validation to declarative query string parameters in ASP.NET 2.0

Sunday, August 27, 2006 9:06 AM by Erwyn van der Meer

Martin,

VB.NET does not support output parameters like C# does. In VB you can't specify out on a parameter when calling a method which has an output parameter. So remove the out keyword from your calls to the EnsureIsInteger method.

Regards,

Erwyn

# re: Adding input validation to declarative query string parameters in ASP.NET 2.0

Tuesday, August 07, 2007 8:47 AM by Will Shaver

Erwyn -

I've improved on this tutorial with a more global approach to the problem. The tutorial is located here:

www.primedigit.com/automatic-parameter-type-checking