Common PostbackManager
Common_PostbackManager_CSharp\PostbackManagerWebSite\GetAttributeTable.aspx.cs
// Copyright 2011 ESRI
// 
// All rights reserved under the copyright laws of the United States
// and applicable international laws, treaties, and conventions.
// 
// You may freely redistribute and use this sample code, with or
// without modification, provided you include the original copyright
// notice and use restrictions.
// 
// See the use restrictions.
// 

public partial class GetAttributeTable : System.Web.UI.Page
{
    #region Page Member Variables

    // Whether attributes are to be retrieved for the clicked layer
    private bool _getLayerAttributes = false;
    // The number of records displayed at one time on the attribute table
    private int _pageSize = 20;

    #endregion

    #region ASP.NET Page Life Cycle Event Handlers - Page_Load

    // Wires event handlers
    protected void Page_Load(object sender, System.EventArgs e)
    {
        PostbackManager1.RequestReceived += new PostbackManager_CSharp.RequestReceivedEventHandler(PostbackManager1_RequestReceived);
        Toc1.NodeClicked += new ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNodeClickedEventHandler(Toc1_NodeClicked);
        Toc1.NodesPopulated += new System.EventHandler(Toc1_NodesPopulated);
    }

    #endregion

    #region Web ADF Control Event Handlers - Toc1_NodesPopulated, Toc1_NodeClicked

    // Fires once the TOC's nodes have been initialized
    void Toc1_NodesPopulated(object sender, System.EventArgs e)
    {
        // Find which nodes correspond to a layer and mark them with an HTML attribute
        foreach (ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode node in Toc1.Nodes)
            this.MarkLayerNodes(node);
    }

    // Fires when a TOC node is clicked
    void Toc1_NodeClicked(object sender, ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNodeEventArgs args)
    {
        // Check whether layer attributes are to be retrieved.  This value is set in 
        // PostbackManager1_RequestReceived
        if (!_getLayerAttributes)
            return;

        // Get the TocLayer corresponding to the clicked node
        ESRI.ArcGIS.ADF.Web.TocLayer selectedLayer = args.Node.Data as ESRI.ArcGIS.ADF.Web.TocLayer;

        // Get the data frame node that contains the layer node
        ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode dataFrameNode = 
            this.GetParentDataFrameNode(args.Node);

        // Create a data table containing the clicked layer's attributes
        System.Data.DataTable attributeTable = 
            this.GetLayerAttributeData(Map1, dataFrameNode.Text, selectedLayer.ID);

        // Apply the layer's LayerFormat to the attribute table.  This will set properties such as field
        // visibilities and display names.
        ESRI.ArcGIS.ADF.Web.UI.WebControls.LayerFormat layerFormat =
            ESRI.ArcGIS.ADF.Web.UI.WebControls.LayerFormat.FromMapResourceManager(MapResourceManager1,
            dataFrameNode.Text, selectedLayer.ID);
        layerFormat.Apply(attributeTable);

        // Store the table in session for quick retrieval during paging or sorting
        this.Session["AttributeTable"] = attributeTable;

        // Calculate the number of pages the displayed table will have and store the number in session
        int pageCount = (int)System.Math.Round(((double)attributeTable.Rows.Count / 
            (double)_pageSize) + .5);
        this.Session["PageCount"] = pageCount;

        // Display the table's first page
        this.ShowAttributeTable(1);

        // Copy the callback results of the floating panel containing the table to the TOC so the
        // results that will display the table are processed on the client.
        Toc1.CallbackResults.CopyFrom(FloatingPanel1.CallbackResults);

        // Pass the name of the clicked layer back to the client via PostbackManager's CustomResults property.
        // This will be used to update the floating panel's title.
        PostbackManager1.CustomResults = args.Node.Text;
    }

    #endregion

    #region Custom Control Event Handlers - PostbackManager1_RequestReceived

    void PostbackManager1_RequestReceived(object sender, PostbackManager_CSharp.AdfRequestEventArgs args)
    {
        // Get the request's arguments and extract EventArg
        System.Collections.Specialized.NameValueCollection requestArgs =
            ESRI.ArcGIS.ADF.Web.UI.WebControls.Utility.ParseStringIntoNameValueCollection(args.RequestArguments, true);
        string eventArg = requestArgs["EventArg"];

        // Check whether the request was initiated by the TOC
        if (args.CallingControl.ID == Toc1.ID)
        {
            // Check whether the request includes the custom argument indicating that layer attributes are to be
            // retrieved.  This argument is added to the request on the client via onAdfRequest, which is a handler for
            // the PostbackManager's client-side invokingRequest event.
            if (requestArgs["getAttributes"] != null)
                _getLayerAttributes = true;
        }
        // Check whether the request was initiated by paging or sorting the attribute table
        else if (eventArg == "nextPage" || eventArg=="previousPage" || eventArg=="firstPage" || eventArg=="lastPage" || eventArg=="sort")
        {
            // Get the current page number
            int pageNumber = (int)this.Session["PageNumber"];
            switch (eventArg)
            {
                // Set the page number if the request is for paging.  If it is for sorting, call the SortAttributeTable method
                case "nextPage" :
                    pageNumber++;
                    break;
                case "previousPage" :
                    pageNumber--;
                    break;
                case "firstPage" :
                    pageNumber = 1;
                    break;
                case "lastPage" :
                    pageNumber = (int)this.Session["PageCount"];
                    break;
                case "sort" :
                    this.SortAttributeTable(requestArgs["Column"]);
                    break;                    
            }
            // Update the displayed table to show the specified page
            this.ShowAttributeTable(pageNumber);

            // Copy the callback results of the floating panel containing the attribute table to the PostbackManager so the
            // updates to the table display are processed on the client.
            PostbackManager1.CallbackResults.CopyFrom(FloatingPanel1.CallbackResults);
        }
    }

    #endregion

    #region Private Page Methods

    #region TOC Node Manipulation - MarkLayerNodes, GetParentDataFrameNode

    // Finds TOC layer nodes that are descendants of the passed-in node and adds an HTML attribute to 
    // them marking them as such
    private void MarkLayerNodes(ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode node)
    {
        // Check whether the passed-in node is a layer node and add an "IsLayer" attribute to it if so
        if (node.Data is ESRI.ArcGIS.ADF.Web.TocLayer)
            node.Attributes.Add("IsLayer", "true");

        // Recursively call the method on the passed-in node's child nodes
        foreach (ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode childNode in node.Nodes)
            this.MarkLayerNodes(childNode);
    }

    // Retrieves the TOC data frame node that is an ancestor of the passed-in node
    private ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode GetParentDataFrameNode(
        ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode node)
    {
        // Declare a node variable to hold the function's return value
        ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode dataFrameNode = null;

        // If the current node is a data frame node, store it in the return variable.  Otherwise, call the
        // method recursively on the node's parent.
        if (node.Data is ESRI.ArcGIS.ADF.Web.TocDataFrame)
            dataFrameNode = node;
        else
            dataFrameNode = this.GetParentDataFrameNode(node.Parent as
                ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode);

        return dataFrameNode;
    }

    #endregion

    #region Attribute Table Manipulation - ShowAttributeTable, PopulateAttributeTable, SortAttributeTable

    // Updates the AttributeTable control to display attributes on the specified page of the current layer
    private void ShowAttributeTable(int pageNumber)
    {
        // Retrieve the number of attribute pages and validate the requested page number
        int pageCount = (int)this.Session["PageCount"];
        if (pageNumber == 0 || pageNumber > pageCount)
            return;

        // Update the current page number
        this.Session["PageNumber"] = pageNumber;

        // Get the attribute DataTable for the current layer
        System.Data.DataTable attributeTable = this.Session["AttributeTable"] as System.Data.DataTable;

        // Calculate the first and last indexes of the rows to be displayed
        int startIndex = (pageNumber - 1) * _pageSize;
        int stopIndex = (pageNumber * _pageSize) - 1;
        if (stopIndex >= attributeTable.Rows.Count)
            stopIndex = attributeTable.Rows.Count - 1;

        // Update the attribute table display
        this.PopulateAttributeTable(attributeTable, startIndex, stopIndex);

        // Update the paging text
        PageLabel.Text = string.Format("Current Page: {0} of {1}", pageNumber, pageCount);

        // Make sure the floating panel containing the attribute table is visible, then refresh it to
        // apply the updates to the attribute table control
        FloatingPanel1.ShowFloatingPanel();
        FloatingPanel1.Refresh();
   } 

    // Displays the data specified by the method parameters on the AttributeTable control
    private void PopulateAttributeTable(System.Data.DataTable dataTable, int startIndex, int stopIndex)
    {
        // Remove all rows from the display table
        AttributeTable.Rows.Clear();
        AttributeTable.GridLines = System.Web.UI.WebControls.GridLines.Both;

        // Create a row for the header row and add it to the display table
        System.Web.UI.WebControls.TableRow row = new System.Web.UI.WebControls.TableRow();
        AttributeTable.Rows.Add(row);
        System.Web.UI.WebControls.TableCell cell = null;

        // Add the name of each visible field to the header row
        foreach (System.Data.DataColumn column in dataTable.Columns)
        {
            // Check whether the current column is visible
            if (System.Convert.ToBoolean(column.ExtendedProperties[ESRI.ArcGIS.ADF.Web.Constants.ADFVisibility]))
            {
                // Create a cell for the column header and apply a bold font to it
                cell = new System.Web.UI.WebControls.TableCell();
                cell.Font.Bold = true;
                
                // Set the header text to either the field's display name or its database name if a display name is 
                // not specified
                string headerText = (string.IsNullOrEmpty(column.Caption)) ? column.ColumnName : column.Caption;

                // Create a hyperlink for the header - we do this to make the table sortable by clicking a header cell
                System.Web.UI.HtmlControls.HtmlAnchor sortLink = new System.Web.UI.HtmlControls.HtmlAnchor();
                sortLink.InnerText = headerText;

                // Set the header link to issue an asynchronous request to the server via PostbackManager's doAsyncRequest
                sortLink.HRef = string.Format(
                    "javascript:ESRI.ADF.Samples.PostbackManager.doAsyncRequest('EventArg=sort&Column={0}');", headerText);
                cell.Controls.Add(sortLink);
                row.Cells.Add(cell);
            }
        }

        // Add the data rows to the display table
        for (int i = startIndex; i <= stopIndex; i++)
        {
            // Add a row to the display table
            row = new System.Web.UI.WebControls.TableRow();
            AttributeTable.Rows.Add(row);

            // Get the current row from the data table
            System.Data.DataRow dataRow = dataTable.Rows[i];

            // Add a new cell for each visible column and populate it with that column's value
            foreach (System.Data.DataColumn column in dataTable.Columns)
            {
                // Check whether the current field is visible
                if (System.Convert.ToBoolean(column.ExtendedProperties[ESRI.ArcGIS.ADF.Web.Constants.ADFVisibility]))
                {
                    // Set the cell's text to be the value of the current column in the current row
                    cell = new System.Web.UI.WebControls.TableCell();
                    cell.Text = System.Convert.ToString(dataRow[column.ColumnName]).Trim();

                    // Set a tab as the cell's text if there is no current value.  Without this, the cell borders do
                    // not render in this situation.
                    if (string.IsNullOrEmpty(cell.Text))
                        cell.Text = "&nbsp;";
                    row.Cells.Add(cell);
                }
            }
        }
    }

    // Sorts the display table on the specified column
    private void SortAttributeTable(string columnName)
    {
        // Get the current data table from session
        System.Data.DataTable attributeTable = this.Session["AttributeTable"] as System.Data.DataTable;

        // If the column has a display name specified, retrieve the database field name
        if (!attributeTable.Columns.Contains(columnName))
        {
            foreach (System.Data.DataColumn column in attributeTable.Columns)
            {
                if (column.Caption == columnName)
                {
                    columnName = column.ColumnName;
                    break;
                }
            }
        }

        // Get the current sort direction
        string currentSort = this.Session["CurrentSort"] as string;

        // Create a sort expression that will sort on the passed-in column in the opposite
        // direction of the current sort
        string sortExpression = (currentSort == string.Format("{0} ASC", columnName)) ? 
            string.Format("{0} DESC", columnName) : string.Format("{0} ASC", columnName);

        // Update the current sort expression stored in session
        this.Session["CurrentSort"] = sortExpression;

        // Apply the sort
        attributeTable.DefaultView.Sort = sortExpression;

        // Replace the current data table with the sorted table
        this.Session["AttributeTable"] = attributeTable.DefaultView.ToTable();
    }

    #endregion

    // Retrieves attribute data for the specified layer
    private System.Data.DataTable GetLayerAttributeData(ESRI.ArcGIS.ADF.Web.UI.WebControls.Map adfMap, 
        string resourceName, string layerID)
    {
        // Get the map functionality for the resource
        ESRI.ArcGIS.ADF.Web.DataSources.IMapFunctionality mapFunctionality =
            adfMap.GetFunctionality(resourceName);

        // Get the resource and make sure it supports querying
        ESRI.ArcGIS.ADF.Web.DataSources.IGISResource gisResource = mapFunctionality.Resource;
        bool supportsQueries = gisResource.SupportsFunctionality
            (typeof(ESRI.ArcGIS.ADF.Web.DataSources.IQueryFunctionality));
        if (!supportsQueries)
            return null;

        // Get query functionality for the resource
        ESRI.ArcGIS.ADF.Web.DataSources.IQueryFunctionality queryFunctionality =
            gisResource.CreateFunctionality(typeof(ESRI.ArcGIS.ADF.Web.DataSources.IQueryFunctionality),
            null) as ESRI.ArcGIS.ADF.Web.DataSources.IQueryFunctionality;

        // Initialize a query filter.  We do not specify a where clause so all features are returned. 
        // We also specify that geometry not be returned since only the attribute data will be used and
        // retrieving geometry is a relatively expensive operation.
        ESRI.ArcGIS.ADF.Web.QueryFilter adfQueryFilter = new ESRI.ArcGIS.ADF.Web.QueryFilter();
        adfQueryFilter.ReturnADFGeometries = false;
        adfQueryFilter.MaxRecords = 10000;

        // Execute the query, returning the result
        return queryFunctionality.Query(mapFunctionality.Name, layerID, adfQueryFilter);
    }

    #endregion
}