Data Management for Controls

Using the Nitobi ASP.NET Library, there are four ways to manage data for a control:

  1. Bind the data to the control and send it to the browser in the page's content. This is the equivalent of local mode in our open source JavaScript components.
  2. Define a GetDataHandler on the control that points to a different URL than the page containing the control. During Ajax calls for data, the handler cannot access other controls on the page, but it can access the HttpRequest object containing relevant information.
  3. Define a GetDataHandler on the control that points to the same page containing the control. This handler will have access to other controls on the page, which will act as though a standard post back has occurred. This approach gives full flexibility (you can access all of the other controls on the page - this can be useful for filtering the data in another component, for example), although it is slower than an external GetDataHandler in some cases (an external page will not have process all of the components on every postback). If you're just using one component on a page, an external GetDataHandler is the better option.
  4. Provide the data in an XML data island (either local or remote) and bind the controls to that data island. This approach works with the ComboBox control, and is ideal when you have multiple Combos using the same data set. Using a shared data source minimizes the amount of data that needs to be transferred, and will improve the performance of your page.

Which approach you use depends on your application design - the larger and more flexible your data source, the more likely you are to need to a remote data source.

Providing Data for the Controls

All of the column-based controls have a Data property, which exposes an XmlDataHandler object, used to access the data for the component. The XmlDataHandler contains a collection of data providers, which obtain the data and convert it to the Nitobi compressed XML format. The Grid/TreeGrid control is the only one that may need multiple data providers, for child column sets or Combo columns (i.e. listboxeditors and lookupeditors in our open source components).

The type of data that the XmlDataHandler can access includes collections of custom objects, ASP.NET DataTable objects containing rows of data, and classes that implement the ITypedList interface.

Using a Data Event Handler

The simplest way to provide data to your control is through an ASP.NET event handler for the data provider, which is called when the control needs its data. Here is some sample code to set up a Grid control's main data provider in this way:

g.Data.provider().GetData += new Nitobi.GetDataHandler(GridBoundToListOfObjects_GetData);
public object GridBoundToListOfObjects_GetData(HttpRequest request, 
                                        Nitobi.AjaxGetDataHandlerEventArgs args)
{
    return Session["customers"];
}

Writing a Custom Data Provider Class

Rather than using the GeneralDataProvider Class used when adding event handlers, as discussed above, you can write a new class to implement the IDataProvider interface. Using data event handlers, as in the above example, exposes the work of getting the data source to the GetDataHandler (on the same page as the controls, or on a separate one). If multiple pages have the same type of data, or if non-trivial data processing is required for the data source, writing your own data provider class may be more convenient. Here is a sample of such a class:

g.Data.addProvider(new CustomerDataProvider());
public class CustomerDataProvider : IDataProvider
{
    public string DataId
    {
        get { throw new NotImplementedException(); }
    }
    public object getDataSource(HttpRequest request, AjaxGetDataHandlerEventArgs info)
    {
        throw new NotImplementedException();
    }
    public void saveData(HttpRequest request, AjaxSaveDataHandlerEventArgs info)
    {
        throw new NotImplementedException();
    }
    public int getTotalDataSize(HttpRequest request, AjaxGetDataHandlerEventArgs info)
    {
        throw new NotImplementedException();
    }
}

Writing a Custom XmlDataHandler Class

When using a Grid/TreeGrid with multiple Combo Column, you may wish to build a class that inherits from XmlDataHandler and sets up the data providers itself. This simplifies the code needed on a given ASPX or ASHX page.

g.Data = new CustomDataHandler();
public class CustomDataHandler : XmlDataHandler
{
    // any private members necessary for your page
    public CustomDataHandler()
    {
        // set up data providers
    }
}

At the minimum, your constructor should set-up the data providers in the constructor. This allows your web form to set the Data property of the column and use the new class. You can also take over the generation of the Nitobi compressed XML by overriding the processGetDataRequest method, and control any updates by overriding the processSaveDataRequest method.

Sample Data Handler

Here is an example of how one would go about implementing a standard data handler for a Grid in an external handler script. This is taken directly off the samples bundled in CUI ASP.NET Beta 2009 (This is the LiveScrolling Grid sample).

Grid Declaration

First, let's set up a basic Grid definition:

<ntb:Grid id="g" runat="server" Mode="LiveScrolling" PageSize="15" Width="640" Height="100" AllowDeleteRow="false" 
        AllowAddRow="true" Theme="Flex" Effect="Shade" GroupOffset="40" 
        LiveScollingMode="Peek" MinHeight="300" MinWidth="400"
        Resizable="HeightOnly" GetDataUrl="Editors.ashx" SaveDataUrl="Editors.ashx" ShowToolbar="True">
        <Columns>
                <ntb:BoundColumn DataField="ContactID" Visible="true" HeaderText="ID" />
                <ntb:TextColumn TextMode="SingleLine" MaxLength="255" HeaderText="Name" DataField="ContactName" Width="200" ReadOnly="false" />
                <ntb:TextColumn TextMode="SingleLine" MaxLength="255" HeaderText="E-mail" DataField="ContactEmail" Width="200" ReadOnly="false" />
                <ntb:TextColumn TextMode="SingleLine" MaxLength="255" HeaderText="Job Title" DataField="JobTitle" Width="200" ReadOnly="false" HorizontalAlign="Right" />
           </Columns>
</ntb:Grid>

The key attributes set here are the GetDataUrl and SaveDataUrl attributes. These attributes tell the Grid where to send the 'Get' and 'Save' data requests. Once we have set this up on a page, we have to go and write the 'Editors.ashx' script that we've assigned to the DataUrl attributes. Additionally, we've explicitly declared a set of columns in the <Columns> element. This can be omitted, but then the Grid will, by default, bind every field returned by the data handler to a column automatically. This usually leads to a generally undesirable side-effect of displaying the database record primary key (so it is good practice to explicitly define the columns).

Writing the Data Handler Script

Here are the internals of the Editors.ashx script:

public class DataHandler : IHttpHandler, IRequiresSessionState
    {
        public void ProcessRequest(HttpContext context)
        {
            XmlDataHandler da = new editors.EditorsDataHandler();
            da.checkForAndProcessAjaxRequest(context.Request, context.Response);
        }
        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }

The key is the ProcessRequest method, which instantiates our own implementation of the XmlDataHandler class that we've sub-classed into 'EditorsDataHandler' (more on that in the next section). Then you call 'checkForAndProcessAjaxRequest' on this XmlDataHandler and pass the context request and response. The XmlDataHandler class will handle encoding your data into the appropriate format, so that the Grid can decode your data properly.

Writing a Data Provider Method

The EditorsDataHandler class we mentioned in the previous section is the class that actually retrieves the data. As detailed in the first few overview sections of this article, there are a variety of ways to provide data to the data-handling Complete UI components. This example establishes a connection to a SQL Server, creates and populates a standard ASP.NET DataSet object and returns it. From there, the XmlDataHandler class will handle packaging the data and sending it back to the Grid. Here's the (somewhat truncated code) for the EditorsDataHandler:

public class EditorsDataHandler : XmlDataHandler
    {
        public EditorsDataHandler()
        {
            provider().GetData += new GetDataHandler(EditorsDataProvider.default_GetData);
            provider().SaveData += new SaveDataHandler(EditorsDataProvider.default_SaveData);
            provider().GetTotalRowCount += new GetDataHandler(EditorsDataProvider.default_GetTotalRecordCount);
        }
    }

The three lines set up the basic functionality for the default data source provider for the EditorsDataHandler class: retrieving data (via GetDataHandler), saving data (via SaveDataHandler) and retrieving row counts (required for TreeGrids and LiveScrolling Grids, via a GetDataHandler as well). Finally, here is the code for the actual data retrieval method:

SqlDataAdapter dAdapter;
SqlConnection dbConn = new SqlConnection(localConnectionString);
dbConn.Open();
// Create a DataSet object - we will populate it full of data from our server and return that to the Grid.
DataSet ds = null;          
try
{
    string tableName = "people", query = "";
    SqlCommand cmd = new SqlCommand("select count(*) from " + tableName, dbConn);
    int count = (int)cmd.ExecuteScalar();
    int pageSize = args.PageSize;
    if (pageSize <= 0)
        pageSize = 10;
    if (count > 0)
    {
        string sortField = "ContactID";
        Nitobi.SortOrder sortOrder = Nitobi.SortOrder.Asc;
        if (!Cmn.IsEmpty(args.SortColumn))
        {
            sortField = args.SortColumn;
            sortOrder = args.SortDirection;
        }
        string ReverseDirection;
        if (sortOrder == Nitobi.SortOrder.Asc) ReverseDirection = Nitobi.SortOrder.Desc.ToString(); else ReverseDirection = Nitobi.SortOrder.Asc.ToString();
        // The reason for this overly complicated SQL query is due to the fact that MDB does not support proper paging.
        // Using a server such as Oracle or MySql would eliminate the complexity of this query, and most of the 
        // preceeding code.
        string sort = " ORDER BY " + sortField + " " + sortOrder;
        query = "SELECT TOP " + pageSize + " * FROM (";
        query += "SELECT ROW_NUMBER() OVER (" + sort + ") AS Row, * FROM "+tableName+") AS X WHERE Row > " + args.StartRecordIndex.ToString();
        if (!Cmn.IsEmpty(args.SearchString))
        query += " AND JobTitle LIKE '%" + args.SearchString + "%'";            
    }
    // Fill the tables from the dataset with the data from the database.
    ds = new DataSet();
    ds.Tables.Add(tableName);
    if (count > 0)
    {
        dAdapter = new SqlDataAdapter(query, dbConn);
        dAdapter.Fill(ds.Tables[tableName]);
    }
}
finally
{
    dbConn.Close();
}
return ds;

This method uses a static string (localConnectionString) to define a connection string. For the samples, we store this string in the web.config file. The most complicated part of this method is the obtuse SQL query that is generated based on the arguments passed in such as Page Size, Row Index, Search Strings, and other Grid / Combo functionality. SQL Server (more specifically, T-SQL, or Microsoft's implementation of the SQL standard) does not support a standard paging operation or syntax, so we need to run the above nested query to simulate paging results. This query is necessary for good performance. Other than actually building the query, the rest of the code is quite standard .NET data handling: create a SQL Data Adapter, populate a DataSet object with a filled Data Table, and finally return the DataSet. The CUI.NET XmlDataHandler class will handle the rest of the data transaction from there.

The save data method can be found in the samples - very much like the data retrieval method, except that it iterates over the args enumerable and creates a SQL update/insert/delete action based on each IRow's ACTION type. Check out the samples for code details.

page_revision: 8, last_edited: 1249412388|%e %b %Y, %H:%M %Z (%O ago)
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License