Governor Technology Blog

05 February 2009 by Administrator

ControlParameter supports “__Page” as ControlId, but not “__UserControl”

Click here to get the code for this post.

Introduction

If you’ve worked with ObjectDataSource parameters, you may know you can bind parameter values to properties of the current page. You may have also found that when the data source is declared inside a user control, there’s no mechanism for finding property values inside that control. Below we look at the background behind this, and present two possible solutions.

Background

The Asp.Net DataSource controls (ObjectDataSource, SqlDataSource etc) provide a mechanism for transferring data to and from Asp.Net 2.0 data-bound controls.

Typically, you configure parameters for the select, insert update and delete commands of the data source by specifying a Parameters collection for the command in question. Each Parameter instance inside the collection tracks a particular value in the environment outside the control. Different subclasses of Parameter track data in a particular location such as the query string, session, cookies collection, etc. In addition, you can easily create your own Parameter subclasses by deriving from Parameter and overriding a single method.

One particular implementation, called ControlParameter, tracks the value of a property of another control in the page. Typically this is used in master/details scenarios, where the ControlParameter tracking the selected value of a grid or drop-down list is added to the select parameters of the data source, for example, filtering a list of arcade games by year (see the attached code samples for implementation of the Game class and select methods):

<asp:DropDownList runat="server" ID ="ddlYears" 
DataSourceID = "odsYears" AutoPostBack="true" />
 
<asp:ObjectDataSource runat="server" ID = "odsYears" 
TypeName="Game" SelectMethod= "GetYears" />
 
<hr />
 
<asp:GridView runat="server" ID="gvGames" DataSourceID="odsGames" />
 
<asp:ObjectDataSource runat="server" ID="odsGames" 
TypeName="Game" SelectMethod="GetByYear">
    <SelectParameters>
        <asp:ControlParameter ControlID="ddlYears" 
        Type="Int32" PropertyName="SelectedValue" Name="Year" />
    </SelectParameters>
</asp:ObjectDataSource>

The "__Page" Control Id

Although less commonly used, ControlParameter can also be used to track a property declared in the current page itself. This turns out to be handy to get some kind of non-trivial parameter value which is readily available from within the context of page execution. Without this, you would have to create a dedicated custom parameter type to perform that function, or add the logic to the select method or stored/procedure providing the underlying data.

In the following example, the data source select command takes the year parameter from a property on the page, which for the purposes of clarity is just hard coded to 1986:

<script runat="server">
 
    public int YearProperty
    {
        get
        {
            return 1986;
        }                           
    }
 
</script>
 
<asp:GridView runat="server" ID="gvGames" DataSourceID="odsGames" />
 
<asp:ObjectDataSource runat="server" ID="odsGames" 
TypeName="Game" SelectMethod="GetByYear">
 
    <SelectParameters>
        <asp:ControlParameter ControlID="__Page" Type="Int32" 
        PropertyName="YearProperty" Name="Year" />
    </SelectParameters>
 
</asp:ObjectDataSource>

Notice that the property has to be public rather than protected. Additionally you must refer to a bona fide property rather than field.

How does this work – and what is the magical significance of the "__Page" constant? Internally, the ControlParameter, like all other Parameters, has it’s Evaluate() method called when the parameter value is required:

protected override object Evaluate(HttpContext context, Control control)
{ ...}

The control reference passed into the method refers to the data source control. By having a reference to this, the ControlParameter is able to navigate up through the control tree of the page it’s declared in, looking for a control with an Id matching its ControlId property. At the top of the control tree is the Page object itself, which also derives from Control. And yes, you’ve guessed it, in Asp.Net, the Page always has an hard-coded ID of "__Page".

What about user controls?

Since there’s only one page in each request, and since all controls have to have an Id, the Page’s ID has been set to a constant value which you can refer to. Often, however, you may decide to place the data source control inside a user control. And often, you’ll want to use properties of the user control as parameters for the data source. So it would be really handy to be able to refer to the user control in which the data source is situated in the same way as we did for the page.

User controls however, are given arbitrary IDs by the developer when they are placed on the page, so you won’t be able to refer to this ID using a constant inside the control. There are plenty of ways you could do this however. I’m just going to consider two of them here.

Set the value manually

Firstly you could just handle OnSelecting and set the value yourself:

<script runat="server">
 
    public int YearProperty
    {
        get
        {
            return 1987;
        }
    }
 
    protected void odsGames_Selecting(object sender, 
                                    ObjectDataSourceSelectingEventArgs e)
    {
        e.InputParameters.Add("Year", YearProperty);
    }
 
</script>
 
<asp:GridView runat="server" ID="gvGames" DataSourceID="odsGames" />
 
<asp:ObjectDataSource runat="server" ID="odsGames" TypeName="Game" 
    SelectMethod="GetByYear" OnSelecting="odsGames_Selecting" />

That’s pretty simple, and as long as you’re happy to keep writing event handlers.

Create a Custom Parameter

In this solution, we create a new class derived from ControlParameter, and invent a constant called "__UserControl" to represent the user control in which the data source might be situated. Then we override Evaluate() to implement our logic. If the ControlID property is the special constant, we’ll try and find the nearest user control in the control tree above the data source. If the ControlId property is anything else, we’ll just delegate the responsibility back to the base ControlParameter implementation:

public class ExtControlParameter : ControlParameter
{
 
 
    protected override object Evaluate(HttpContext context, Control control)
    {
 
        string controlID = this.ControlID;
        //control is the data source holding the parameter
        //UserControlReference is the constant
        //If it makes sense to search up from the data source control
        //to its parent user control, and the controlId is our special constant
        //try and find the user control. 
        //When we've got it, swap the user control's Id
        //with the constant and let the ControlParameter base class do the rest
        if (controlID == UserControlReference && control != null)
        {
            UserControl userControl = TryFindUserControl(control);
            if (userControl != null)
                this.ControlID = userControl.ID;
        }
 
        return base.Evaluate(context, control);
    }
 
 
 
    private UserControl TryFindUserControl(Control Control)
    {
        //match
        if (Control is UserControl)
            return Control as UserControl;
 
        //the page is the top level, so no point looking further
        if (Control != Control.Page)
        {
            Control namingContainer = Control.NamingContainer;
 
            //recurse upward if applicable
            if ((namingContainer != null) && (namingContainer != Control.Page))
            {   
                return TryFindUserControl(namingContainer);
 
            }
        }
        return null;
    }
}

And that’s it – this control parameter can be used anywhere the standard one can, but has the additional feature of being able to refer to a user control parent. Once again, check out the associated code samples for all the source code featured in this discussion

3 comment(s) for “ControlParameter supports “__Page” as ControlId, but not “__UserControl””

  1. D Says:

    There's a much easier way. Assuming you have a ControlParameter in the SelectParameters tag on a DataSource, then in the Page_Load of the user control, do this:

    ((ControlParameter)MyDataSource.SelectParameters["ParamName"]).ControlId = this.ID;

    Now it will always bind to the current instance of the user control.

    D

  2. Neil Dodson Says:

    Yep, that's true. But then you'd be better off just adding a normal asp:Parameter and setting its value in Page_Load(). I guess it depends on the amount of re-use you need.

  3. vaibhav Says:

    using a variation of ((ControlParameter)MyDataSource.SelectParameters["ParamName"]).ControlId = this.ID;


    very helpful. thanks


Leave comment:


(not shown)


(optional - remember http://)