Home   |   QuickStart Welcome   |   ASP.NET   |   Web Services   |   How Do I...?   
  |   I want my samples in...      

ASP.NET 2.0 Quickstart Tutorials

Binding to Objects

The previous section demonstrated binding controls to SqlDataSource, which supports properties for the specifying connection string, SQL statements or stored procedures used to query and modify the database. While this is suitable for a most small scale personal or hobbyist sites, storing SQL statements directly in the presentation pages of an application can quickly become unmaintainable for larger scale enterprise-level applications. These applications typically require a more encapsulated data model using a middle-tier data access layer or business component. Fortunately, the ASP.NET data source control model supports this approach using the ObjectDataSource control.

The ObjectDataSource control object model is similar to the SqlDataSource control. Instead of a ConnectionString property, ObjectDataSource exposes a TypeName property that specifies an object type (class name) to instantiate for performing data operations. Similar to the command properties of SqlDataSource, the ObjectDataSource control supports properties such as SelectMethod, UpdateMethod, InsertMethod, and DeleteMethod for specifying methods of the associated type to call to perform these data operations. This section describes techniques for building data access layer and business logic layer components and exposing them through an ObjectDataSource control.

Binding to a Data Access Layer

A data access layer component encapsulates the ADO.NET code to query and modify a database through SQL commands. It typically abstracts the details of creating ADO.NET connection and commands and instead exposes these through methods that can be called with appropriate parameters. A typical data access layer component might be exposed as follows:
public class MyDataLayer {

  public DataView GetRecords();
  public DataView GetRecordsByCategory(String categoryName);
  public DataView GetRecordByID(int recordID);

  public int UpdateRecord(int original_recordID, String recordData);
  public int DeleteRecord(int original_recordID);
  public int InsertRecord(int recordID, String recordData);
The ObjectDataSource can be associated to this type in the following manner:
  <asp:ObjectDataSource TypeName="MyDataLayer" SelectMethod="GetRecords" UpdateMethod="UpdateRecord" 
    DeleteMethod="DeleteRecord" InsertMethod="InsertRecord" runat="server"/>
The ObjectDataSource requires a very specific design pattern for the objects it can work with. These restrictions are mostly imposed by the stateless environment under which Web application requests are executed. Because objects are typically created and destroyed to serve each request, the objects bound through object data source are expected to be stateless as well. By default, ObjectDataSource assumes a default constructor (no arguments) for the type specified by the TypeName property, although it is possible to instantiate this type on behalf of the ObjectDataSource by handling the ObjectCreating event to create a custom object instance and assign it to the ObjectInstance property of the event arguments. The object method associated with the SelectMethod property can return any Object or IEnumerable list, collection, or array. In the above data access layer example, the DataView object implements IEnumerable. As we'll discuss in the following section, these methods might also return a strongly type collection or object.

GetProducts() -> ProductCollection
GetProductsDataSet() -> DataSet
GetProduct (int productId) -> Product
The Update, Insert, and Delete methods should take individual data item fields as parameters, or they can optionally take an aggregate class object with public properties for data item fields.
UpdateProduct (int original_id, String name, double price, bool inStock)
UpdateProduct (Product p)  // p.ID, p.Name, p.Price, p.InStock �
DeleteProduct (int original_id)
Like the SqlDataSource example, the parameter names or properties of the data item passed to the Update, Insert, and Delete methods must match the names of the data item fields returned by the SelectMethod in order for GridView/DetailsView automatic updates/deletes/inserts to work. Just like SqlDataSource, parameters to ObjectDataSource methods can be associated to data parameter objects assigned to the SelectParameters, FilterParameters, UpdateParameters, DeleteParameters, or InsertParameters collections.

The following example demonstrates an ObjectDataSource control that exposes data from a data access layer component named AuthorsDB. The class file for this type is placed in the application's App_Code directory, which is dynamically compiled by ASP.NET at runtime.

VB Binding to a Data Access Layer
Run Sample View Source

Binding to a Business Logic Layer

An important thing to note about a data access layer is that because the SelectMethod returns the result of executing the query as a DataView, it still exposes the schema of the underlying database to the presentation pages. Another thing to note is that there are no business rules in the data access layer; it simply executes queries and returns results. To insulate the presentation from database schema and introduce business rules or validation, the data access layer is typically wrapped in a business logic layer.

A business logic layer is similar to a DAL in that it exposes stateless methods to ObjectDataSource for binding controls in Web pages. However, instead of returning ADO.NET results directly, it typically returns strongly-typed objects that represent the business entities used by the application. This decouples the presentation layer from the schema of the underlying data store, making it easier to maintain the data access portion of your site separately from the pages that consume the data. With a properly architected middle-tier, you could change the underlying data store to a completely different schema without having to update the individual pages in your application.

An example business logic layer is given below.
public class MyBusinessLayer {

  public RecordCollection GetRecords();
  public RecordCollection GetRecordsByCategory(String categoryName);
  public RecordCollection GetRecordByID(int recordID);
  public String GetRecordName(int recordID);
  public Object GetRecordData(int recordID);

  public int UpdateRecord(Record r);
  public int DeleteRecord(Record r);
  public int InsertRecord(Record r);

  public int UpdateRecordData(int original_ID, String Data);
  public int UpdateRecordName(int original_ID, String Name);

public class Record {
  public int ID { get; set; }
  public String Name { get; set; }
  public Object Data { get; set; }
The main difference between the business logic layer and data access layer is that it returns a strongly-typed RecordCollection of Record objects, rather than a DataView. It also allows for Update, Insert, and Delete operations that take this Record object as a parameter. The ObjectDataSource DataObjectTypeName property allows you to configure the ObjectDataSource to pass this type to methods instead of individual field values. In the business logic layer method implementations, you can include custom logic for validating business rules. For example, you could ensure that only Records in the "In Review" category can be updated or only Administrators can insert new records. You can also include validation logic to ensure the data types and values supplied as arguments are correct before inserting or modifying data in the database. Note that validation logic in business layer is not a substitute for input validation at the presentation layer, which helps guide the end user to enter the correct values before submitting the update.

The example below shows a simple business logic layer called AuthorsComponent. Internally, this BLL calls through to the DAL to actually perform database operations. For simplicity, this BLL does not include any business rules or validation, although it would in a real-world application. Also note that instead of writing our own custom collection class to return strongly-typed records, this example takes advantage of a new language feature in the .NET Framework 2.0 called "Generics" to create a collection of Author objects. Using strongly-typed collections allows ObjectDataSource to infer the schema of your business objects at design-time (in Visual Studio and other tools).

VB Binding to a Business Logic Layer
Run Sample View Source

The illustration below shows the interaction between the GridView, ObjectDataSource, and business logic layer. The ObjectDataSource is configured to call a GetContacts method on the ContactsList type, which return a collection of Contact objects. The GridView enumerates these Contacts objects and binds directly to the properties (ID, Name) of this type to produce it's columns. Note that the SelectMethod can return either an IEnumerable of Contact objects, or it could return a singleton Contact object. The ObjectDataSource will always wrap the result of the SelectMethod in an IEnumerable if it doesn't implement IEnumerable itself.

Like SqlDataSource, the ObjectDataSource control supports sorting when the SelectMethod returns a DataSet, DataView, or DataTable object. Internally, the ObjectDataSource relies on the DataView.Sort property to perform sorting in this case. ObjectDataSource also supports custom sorting in the SelectMethod implementation, which is useful if the method doesn't return a DataSet, DataView, or DataTable. Custom sorting is configured by setting SortParameterName property to the name of a method parameter that accepts the SortExpression from the data source. When the SelectMethod is called, ObjectDataSource will pass this expression to your method and you can implement your own sorting logic using this expression. The preceding example demonstrates custom a custom sorting implementation in the AuthorsComponent class.

ObjectDataSource also supports custom paging in the SelectMethod implementation. This is configured using the StartRowIndexParameterName, MaximumRowsParameterName, and SelectCountMethod properties and is discussed in more detail in the Advanced Paging and Sorting section of this tutorial.

Binding to a Visual Studio DataSet

Building a data access layer can be tedious, because often the ADO.NET code to execute SQL statements or stored procedures is the same or similar for different methods of the DAL. Although you can write your own DAL using custom ADO.NET code using the techniques described above, Visual Studio also provide a convenient way to generate a data access layer for you, based on inputs to a simple wizard. The data access layer in this case is a strongly-typed DataSet object. The DataSet contains TableAdapter types that expose methods for returning strongly-typed DataTable objects. These methods are suitable for binding to an ObjectDataSource directly, or for calling from your business logic layer components.

To add a DataSet to your Visual Studio project, right-click the Solution Explorer and choose "Add New Item...", then select the "DataSet" project item type. Visual Studio may prompt to add this to the App_Code directory; answer "Yes" to this prompt to proceed. Visual Studio adds a DataSet.xsd file to the App_Code directory and opens the DataSet designer, launching the TableAdapter wizard by default. Stepping through the TableAdapter wizard, you can specify SQL statements or stored procedures from your database, and then name the methods associated to these queries/commmands on the final page of the wizard.

The TableAdapter can expose two types of methods: Fill methods that are suitable for filling an existing DataSet, and Get methods that return an already filled DataTable object. The former is more suitable for a Windows client (where the DataSet is kept in memory for the lifetime of the application), whereas the latter Get methods are more suitable for ObjectDataSource. The TableAdapter wizard can also automatically generate Update, Insert, and Delete methods for your provided SQL select statements (requires the primary key to be selected). After completing the wizard, Visual Studio adds a new DataTable and TableAdapter type to the DataSet designer.

A TableAdapter represents the schema from a single result set and the Select, Update, Insert, or Delete operations over that schema. You can add multiple TableAdapters in a DataSet by right-clicking in the DataSet designer. You can also add additional queries, provided they return the same schema (fields), to a TableAdapter by right-clicking the TableAdapter box in the designer. For example, you could have a single TableAdapter with methods GetAuthors() and GetAuthorsById(int id), but you would need a new TableAdapter to add a GetTitles() method. The figure below shows the DataSet designer with several TableAdapters added:

Once you've finished designing the DataSet, save the DataSet.xsd file (this causes the types to be background compiled by the designer to make them available to your pages). You can now see that these types are exposed to page code:
protected void Page_Load(object sender, EventArgs e)
    DataSetTableAdapters.PhotosTableAdapter adapter = new DataSetTableAdapters.PhotosTableAdapter();
However, you do not need to call these methods directly from your own code. You can bind the ObjectDataSource to these methods instead:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
    <asp:QueryStringParameter Name="albumID" QueryStringField="id" Type="Int32"/>
The following example shows an ObjectDataSource bound to DataSet.TableAdapter methods. We will use this DataSet in several other examples later in the tutorial to demonstrate how you can use ASP.NET data controls to implement a simple Photo Album application. Note that the DetailsView in this example is using a new field type called ImageField to display the photo images. Also note the use of ConvertNullToDBNull on the ObjectDataSource to cause null parameter values to convert to DbNull before passing to TableAdapter methods (required).

VB Binding to a Visual Studio DataSet
Run Sample View Source