3. The AccessDataSource Class
The AccessDataSource control is a data source control that represents a connection to an Access database. It is based on the SqlDataSource control and provides a simpler, made-to-measure programming interface. As a derived class, AccessDataSource inherits all members defined on its parent and overrides a few of them. In particular, the control replaces the ConnectionString and ProviderName properties with a more direct DataFile property. You set this property to the .mdb
database file of choice. The data source control resolves the file path
at run time and uses the Microsoft Jet 4 OLE DB provider to connect to
the database.
Note
AccessDataSource actually inherits from SqlDataSource, and for this reason it can’t make base members disappear, as hinted at earlier. AccessDataSource doesn’t really replace the ConnectionString and ProviderName
properties; it overrides them so that an exception is thrown whenever
someone attempts to set their value. Another property overridden only
to throw exceptions is SqlCacheDependency. This feature, of course, is not supported by the Access database. |
Working with an Access Database
The following code shows how to use the AccessDataSource
control to open an .mdb file and bind its content to a drop-down list
control. Note that the control opens Access database files in read-only
mode by default.
<asp:AccessDataSource runat="server" ID="MyAccessSource"
DataFile="~/App_Data/nwind.mdb"
SelectCommand="SELECT * FROM Customers" />
Select a Customer:
<asp:DropDownList runat="server" DataSourceId="MyAccessSource" />
Several features of the AccessDataSource control are inherited from the base class, SqlDataSource.
In fact, the Access data source control is basically a SQL data source
control optimized to work with Access databases. Like its parent
control, the AccessDataSource control supports two distinct data source modes—DataSet and DataReader,
depending on the ADO.NET classes used to retrieve data. Filtering can
be applied to the selected data only if the fetch operation returns a DataSet. Caching works as on the parent class except for the SqlCacheDependency feature.
Updating an Access Database
The AccessDataSource
can also be used to perform insert, update, or delete operations
against the associated database. This is done using ADO.NET commands
and parameter collections. Updates are problematic for Access databases
when performed from within an ASP.NET application because an Access
database is a plain file and the default account of the ASP.NET process
(ASPNET or NetworkService, depending on the host operating system)
might not have sufficient permission to write to the database file. For
the data source updates to work, you should grant write permission to
the ASP.NET account on the database file. Alternatively, you can use a
different account with adequate permission.
Note
Most
Internet Service Providers normally give you one directory in which
ASPNET and NetworkService accounts have been granted the write
permission. In this case, you just place your Access file in this
directory and you can read and write seamlessly. In general, though,
Access databases are plain files and, as such, are subject to the
security rules of ASP.NET. |
4. The ObjectDataSource Control
The ObjectDataSource
class enables user-defined classes to associate the output of their
methods to data-bound controls. Like other data source controls, ObjectDataSource supports declarative parameters to allow developers to pass page-level variables to the object’s methods. The ObjectDataSource
class makes some assumptions about the objects it wraps. As a
consequence, an arbitrary class can’t be used with this data source
control. In particular, bindable classes are expected to have a default
constructor, be stateless, and have methods that easily map to select,
update, insert, and delete semantics. Also, the object must perform
updates one item at a time; objects that update their state using batch
operations are not supported. The bottom line is that managed objects
that work well with ObjectDataSource are designed with this data source class in mind.
4.1 Programming Interface of ObjectDataSource
The ObjectDataSource component provides nearly the same programmatic interface (events, methods, properties, and associated behaviors) as the SqlDataSource, with the addition of three new events and a few properties. The events the ObjectDataSource fires are related to the lifetime of the underlying business object the ObjectDataSource is bound to—ObjectCreating, ObjectCreated, and ObjectDisposing. Table 9 lists other key properties of ObjectDataSource.
Table 9. Main Properties of ObjectDataSource
Property | Description |
---|
ConvertNullToDBNull | Indicates whether null parameters passed to insert, delete, or update operations are converted to System.DBNull. This property is set to false by default. |
DataObjectTypeName | Gets or sets the name of a class that is to be used as a parameter for a Select, Insert, Update, or Delete operation. |
DeleteMethod, DeleteParameters | Gets or sets the name of the method and related parameters used to perform a delete operation. |
EnablePaging | Indicates whether the control supports paging. |
FilterExpression, FilterParameters | Indicates the filter expression (and parameters) to filter the output of a select operation. |
InsertMethod, InsertParameters | Gets or sets the name of the method and related parameters used to perform an insert operation. |
MaximumRowsParameterName | If the EnablePaging property is set to true, indicates the parameter name of the Select method that accepts the value for the number of records to retrieve. |
OldValuesParameterFormatString | Gets or sets a format string to apply to the names of any parameters passed to the Delete or Update methods. |
SelectCountMethod | Gets or sets the name of the method used to perform a select count operation. |
SelectMethod, SelectParameters | Gets or sets the name of the method and related parameters used to perform a select operation. |
SortParameterName | Gets or sets the name of an input parameter used to sort retrieved data. It raises an exception if the parameter is missing. |
StartRowIndexParameterName | If the EnablePaging property is set to true, indicates the parameter name of the Select method that accepts the value for the starting record to retrieve. |
UpdateMethod, UpdateParameters | Gets or sets the name of the method and related parameters used to perform an update operation. |
The ObjectDataSource control uses reflection to locate and invoke the method to handle the specified operation. The TypeName property returns the fully qualified name of the assembly that defines the class to call. If the class is defined in the App_Code directory, you don’t need to indicate the assembly name. Otherwise, you use a comma-separated string in the form of [classname, assembly]. Let’s see an example.
Warning
Having too many classes in the App_Code
directory can become a nightmare at development time because any
changes to any files will cause Visual Studio .NET to recompile the
whole set of files in the project. If you prefer, it’s completely
reasonable to encapsulate your business object in a separate assembly
and have your Web application simply reference that assembly. |
4.2 Implementing Data Retrieval
The
following code snippet illustrates a class that can be used with an
object data source. The class represents employees and takes advantage
of two other helper classes (at the very minimum): Employee and EmployeeCollection. The class Employee contains information about the entity being represented; the class EmployeeCollection
represents a collection of employees. The behavior of the entity
“employee” is codified in a bunch of methods exposed out of the gateway
class—Employees.
public class Employees
{
public static string ConnectionString {
...
}
public static void Load(int employeeID) {
...
}
public static EmployeeCollection LoadAll() {
...
}
public static EmployeeCollection LoadByCountry(string country) {
...
}
public static void Save(Employee emp) {
...
}
public static void Insert(Employee emp) {
...
}
public static void Delete(int employeeID) {
...
}
...
}
If you don’t use static methods, the worker class you use with ObjectDataSource must have a default parameterless constructor. Furthermore, the class should not maintain any state.
Warning
Using
static methods in classes that represent the gateway to a database
table is fine from an architectural viewpoint, but it might pose
practical problems with unit testing. What if you test a business class
that calls the data access layer (DAL) internally and the DAL fails?
Can you figure out what really happened? Does the business class work
or not? To effectively test a business layer that
calls into a DAL, you need to focus on the object under test. Mock
objects come to the rescue. Mock objects are programmable polymorphic
objects that present themselves as others. So you can use mock objects
to wrap the DAL and make it always return valid results. In this way,
any anomalies are the result of non-DAL classes. The point is that
toolkits that provide mock objects typically don’t like static methods.
That’s why instance methods might be preferable in gateway classes of a
DAL. |
The worker class must be accessible from within the .aspx page and can be bound to the ObjectDataSource control, as shown here:
<asp:ObjectDataSource runat="server" ID="MyObjectSource"
TypeName="Core35.DAL.Employees"
SelectMethod="LoadAll" />
When the HTTP runtime encounters a similar block in a Web page, it generates code that calls the LoadAll method on the specified class. The returned data—an instance of the EmployeeCollection—is bound to any control that links to MyObjectSource via the DataSourceID property. Let’s take a brief look at the implementation of the LoadAll method:
public static EmployeeCollection LoadAll()
{
EmployeeCollection coll = new EmployeeCollection();
using (SqlConnection conn = new SqlConnection(ConnectionString)
{
SqlCommand cmd = new SqlCommand("SELECT * FROM employees", conn);
conn.Open();
SqlDataReader reader = cmd.ExecuteReader();
HelperMethods.FillEmployeeList(coll, reader);
reader.Close();
conn.Close();
}
return coll;
}
Although it’s a bit
oversimplified so that it can fit in this section, the preceding code
remains quite clear: you execute a command, fill in a custom collection
class, and return it to the data-bound control. The only piece of code
you need to write is the worker class—you don’t need to put any code in
the code-behind class of the page.
<asp:DataGrid ID="grid" runat="server"
DataSourceID="MyObjectSource" />
The DataGrid receives a collection of Employee objects defined as follows:
public class EmployeeCollection : Collection<Employee>
{
}
Binding is totally seamless, even without ADO.NET container objects. (See the companion code for full details.)
The method associated with the SelectMethod property must return any of the following: an IEnumerable object such as a collection, a DataSet, a DataTable, or an Object. Preferably, the Select method is not overloaded, although ObjectDataSource doesn’t prevent you from using an overloaded method in your business classes.