This tutorial gives a brief overview of how to use the ASP.NET Input Validation Controls.
Back when we had only ASP, developers who had to write webpages for forms knew that the most tedious part is writing code to validate the user input. User input had to be validated so that malicious use of the pages couldn't be achieve. User input had to be validated so that an incorrect piece of information would not be entered. User input had to be validated so that the information stored was standardized. Yeah, some people had libraries of ASP functions to validate common things such as postal codes (zip codes for you Americans), e-mail addresses, phone numbers, etc. The developers of ASP.NET saw the tedium in always having to check user input. They decided that to simplify our life by including validation controls. ASP.NET validation controls also provide two ways of validation: Server-side or Client-side. The nice thing about these Validation controls is that it will preform client-side validation when it detects the browser is able (unless client-side validation has been disabled). Thus reducing roundtrips. And it will preform server-side where necessary. This client-side/server-side detection and validation is done without extra work by the developer!
With ASP.NET, there are six(6) controls included. They are:
The RequiredFieldValidation Control
The CompareValidator Control
The RangeValidator Control
The RegularExpressionValidator Control
The CustomValidator Control
Validator Control Basics All of the validation controls inherit from the base class BaseValidator so they all have a series of properties and methods that are common to all validation controls. They are:
ControlToValidate - This value is which control the validator is applied to.
ErrorMessage - This is the error message that will be displayed in the validation summary.
IsValid - Boolean value for whether or not the control is valid.
Validate - Method to validate the input control and update the IsValid property.
Display - This controls how the error message is shown. Here are the possible options:
None (The validation message is never displayed.)
Static (Space for the validation message is allocated in the page layout.)
Dynamic (Space for the validation message is dynamically added to the page if validation fails.)
The RequiredFieldValidation Control The first control we have is the RequiredFieldValidation Control. As it's obvious, it make sure that a user inputs a value. Here is how it's used:
ErrorMessage="* You must enter a value into textbox1" Display="dynamic">*
</asp:RequiredFieldValidator>
>
In this example, we have a textbox which will not be valid until the user types something in. Inside the validator tag, we have a single *. The text in the innerhtml will be shown in the controltovalidate if the control is not valid. It should be noted that the ErrorMessage attribute is not what is shown. The ErrorMessage tag is shown in the Validation Summary (see below).
The CompareValidator Control Next we look at the CompareValidator Control. Usage of this CompareValidator is for confirming new passwords, checking if a departure date is before the arrival date, etc. We'll start of with a sample:
ErrorMessage="* You must enter the same values into textbox 1 and textbox 2"
Display="dynamic">*
</asp:CompareValidator>
>
Here we have a sample where the two textboxes must be equal. The tags that are unique to this control is the ControlToCompare attribute which is the control that will be compared. The two controls are compared with the type of comparison specified in the Operator attribute. The Operator attribute can contain Equal, GreterThan, LessThanOrEqual, etc. Another usage of the ComapareValidator is to have a control compare to a value. For example:
ErrorMessage="* You must enter the a number greater than 50" Display="dynamic">*
</asp:CompareValidator>
>
The data type can be one of: Currency, Double, Date, Integer or String. String being the default data type.
The RangeValidator Control Range validator control is another validator control which checks to see if a control value is within a valid range. The attributes that are necessary to this control are: MaximumValue, MinimumValue, and Type. Sample:
Enter a date from 1998:
<asp:textbox id="textbox1"runat="server"/>
<asp:RangeValidatorid="valRange"runat="server"
ControlToValidate="textbox1"
MaximumValue="12/31/1998"
MinimumValue="1/1/1998"
Type="Date"
ErrorMessage="* The date must be between 1/1/1998 and 12/13/1998" Display="static">*</asp:RangeValidator>
>
The RegularExpressionValidator Control The regular expression validator is one of the more powerful features of ASP.NET. Everyone loves regular expressions. Especially when you write those really big nasty ones... and then a few days later, look at it and say to yourself. What does this do? Again, the simple usage is:
The CustomValidator Control The final control we have included in ASP.NET is one that adds great flexibility to our validation abilities. We have a custom validator where we get to write out own functions and pass the control value to this function.
Field: <asp:textbox id="textbox1"runat="server">
<asp:CustomValidatorid="valCustom"runat="server"
ControlToValidate="textbox1"
ClientValidationFunction="ClientValidate"
OnServerValidate="ServerValidate"
ErrorMessage="*This box is not valid" dispaly="dynamic">*
</asp:CustomValidator>
We notice that there are two new attributes ClientValidationFunction and OnServerValidate. These are the tell the validation control which functions to pass the controltovalidate value to. ClientValidationFunction is usually a javascript funtion included in the html to the user. OnServerValidate is the function that is server-side to check for validation if client does not support client-side validation. Client Validation function:
<script language="Javascript">
<!--
/* ... Code goes here ... */
-->
</script>
Server Validation function:
Sub ServerValidate (objSource As Object, objArgs As ServerValidateEventsArgs)
' Code goes here
End Sub
Validation Summary ASP.NET has provided an additional control that complements the validator controls. This is the validation summary control which is used like:
The validation summary control will collect all the error messages of all the non-valid controls and put them in a tidy list. The list can be either shown on the web page (as shown in the example above) or with a popup box (by specifying ShowMessageBox="True")
Now you know how to use the Validator Controls in ASP.NET! Have fun! I will also upload a sample of all the validator controls to the code sample section.
Acknoledgment: Professional ASP.NET (published by Wrox) was used a reference. It's a good book!
Tips to remember
If you are doing server-side validation, make sure the button onclick method has a Page.IsValid if statement or it will look like your validators aren't doing anything
Don't forget to wrap everything in the <form runat=server> tag.
Retrieving Schema Information from the Data Source
Schema information can be retrieved from a data source using the FillSchema( ) method, which retrieves the schema information for the SQL statement in the SelectCommand. The method adds a DataTable to the DataSet and adds DataColumn objects to that table. Finally, it configures the AllowDBNull, AutoIncrement, MaxLength, ReadOnly, and Unique properties of the DataColumn, based on the data source. While it configures the AutoIncrement property, it doesn't set the AutoIncrementSeed and AutoIncrementStep properties. The FillSchema( ) method also configures the primary key and unique constraints for the DataTable. It doesn't configure the DefaultValue property.
In addition to an argument specifying the DataSet argument, the FillSchema( ) method takes an argument specifying whether the schema is transformed by the table mappings for the data adapter. Mapping tables and columns is discussed in more detail later in this chapter
If the FillSchema( ) method is used with a table that already has schema defined, the original schema isn't overwritten. Rather, new columns are added if they are part of the schema retrieved but don't exist in the table.
Finally, if a query returning multiple result sets is specified in the SelectCommand, only the schema from the first result set is used. To fill schemas based on queries with multiple result sets, use the Fill( ) method with the MissingSchemaAction set to AddWithKey.
The following example demonstrates the FillSchema method:
SqlDataAdapter da = new SqlDataAdapter(selectSql, connString);
// create a new DataSet to receive the table schema
DataSet ds = new DataSet();
// read the schema for the Orders table from the data source and
// create a table in the DataSet called "Orders" with the same schema
da.FillSchema(ds, SchemaType.Source, "Orders");
// create a new DataTable to receive the schema
DataTable dt = new DataTable("Orders");
da.FillSchema(dt, SchemaType.Source);
As with the Fill( ) method, the DataAdapter connection must be valid, but doesn't have to be open. If it is closed when FillSchema( ) is called, it is automatically opened
to retrieve the data and then closed. If it is open when FillSchema( ) is called, it is left open after the data is retrieved.
Updating the Data Source//imp
The Update( )method can submit DataSet changes back to the data source. It uses the statements in the DeleteCommand, InsertCommand, and UpdateCommand objects to attempt to update the data source with records that have been deleted, inserted, or updated in the DataSet. Each row is updated individually and not as part of a batch process. Furthermore, the order in which the rows are processed is determined by the indexes on the DataTable and not by the update type. Figure below illustrates how the DataAdapter is used both to reconcile changed data in the DataSet with the data source using the Update() method and to retrieve data from the data source using the Fill() method.
Figure. Retrieving and updating data using the DataAdapter
The delete, insert, and update statements can be automatically generated using the CommandBuilder object, but this is probably not the best approach for production systems. Alternatively, custom update logic can be used where the DeleteCommand, InsertCommand, and UpdateCommand are each defined. Compared with using the CommandBuilder, custom logic can significantly improve performance and can implement solutions to complex updating and conflict-resolution scenarios.
The following example demonstrates the Update( ) method. For simplicity, a CommandBuilder generates the update logic.
The overloaded constructor for the DataAdapter allows four different ways to create the data adapter, of which two are most commonly used. The following example creates a DataAdapter specifying the SELECT statement and connection string in the constructor.
SqlDataAdapter da = new SqlDataAdapter(selectSql, connString);
While this approach is common, it is awkward when using parameterized queries or stored procedures. The following example creates a DataAdapter specifying a Command object for the SelectCommand property of the DataAdapter in the constructor:
SqlDataAdapter da = new SqlDataAdapter(selectCmd);
It should be noted that there is no best way to create a DataAdapter, and it makes no real difference how it is created.
Retrieving Data from the Data Source
The Fill( ) method of the DataAdapter retrieves data from the data source into a DataSet or a DataTable. When the Fill( ) method for the data adapter is called, the select statement defined in the SelectCommand is executed against the data source and retrieved into a DataSet or DataTable. In addition to retrieving data, the Fill( ) method retrieves schema information for columns that don't exist. This schema that it retrieves from the data source is limited to the name and data type of the column. If more schema information is required, the FillSchema( ) method can be used. The following example shows how to use the Fill( ) method to retrieve data from the Orders table in the Northwind database:
SqlDataAdapter da = new SqlDataAdapter(selectSQL, connString);
// create a new DataSet to receive the data
DataSet ds = new DataSet();
// read all of the data from the orders table and loads it into the
// Orders table in the DataSet
da.Fill(ds, "Orders");
A DataTable can also be filled similarly:
// ... code to create the data adapter, as above
// create the DataTable to retrieve the data
DataTable dt = new DataTable("Orders");
// use the data adapter to load the data into the table Orders
da.Fill(dt);
Notice that a connection object is never opened and closed for the data adapter. If the connection for the data adapter isn't open, the DataAdapter opens and closes it as required. If the connection is already open, the DataAdapter leaves the connection open.
The same set of records can be retrieved more efficiently using a stored procedure. Stored procedures have a number of benefits over SQL statements:
·Stored procedures allow business logic for common tasks to be consistently implemented across applications. The stored procedure to perform a task can be designed, coded, and tested. It can then be made available to any client that needs to perform the task. The SQL statements to perform the task need to be changed in only one place if the underlying business logic changes. If the parameters for the stored procedure don't change, applications using the stored procedure will not even need to be recompiled.
·Stored procedures can improve performance in situations where a group of SQL statements are executed together with conditional logic. A stored procedure allows a single execution plan to be prepared for the SQL statements together with the conditional logic. Rather than having the client submit a series of SQL statements based on client-side conditional logic, both the SQL statements and conditional logic are executed on the server, requiring only one round trip. Additionally, when a stored procedure is executed, only the parameters need to be transmitted to the server rather than the entire SQL statement.
·Stored procedures are more secure. Users can be granted permission to execute stored procedures that perform required business functions rather than having direct access to the database tables.
·Stored procedures provide a layer of abstraction for the data, making performing business function more intuitive and, at the same time, hiding database implementation from the users.
There are several options available to load more than one table into the same DataSet using a DataAdapter:
·The Fill( ) method can be called several times on the same DataAdapter, specifying a different DataTable in the same DataSet. The SelectCommand is modified to select the records for a different table each time Fill( ) is called.
·Multiple DataAdapter objects, each returning one table, can be created. Fill( ) is called on each DataAdapter, specifying the appropriate DataTable in the same DataSet.
·Either a batch query or a stored procedure that returns multiple result sets can be used.
In the last option, the DataAdapter automatically creates the required tables and assigns them the default names Table, Table1, Table2, if a table name isn't specified. If a table name is specified, for example MyTable, the DataAdapter names the tables MyTable, MyTable1, MyTable2, and so on. The tables can be renamed after the fill, or table mapping can map the automatically generated names to names of the underlying tables in the DataSet. The following example shows how to use a batch query with a DataAdapter to create two tables in a DataSet:
SqlDataAdapter da = new SqlDataAdapter(selectSql, connString);
// create and fill the DataSet
DataSet ds = new DataSet();
da.Fill(ds);
The DataSet is filled with two tables named Table and Table1, respectively, containing data from the Customers and the Orders tables in data source.
Finally, the DataAdapter provides an overloaded Fill( ) method that retrieves a subset of rows from the query and loads them into the DataSet. The starting record and maximum number of records are specified to define the subset. For example, the following code statement retrieves the first 10 records and inserts them into a DataTable named Categories:
da.Fill(ds, 0, 10, "Categories");
It is important to realize that this method actually performs the original query and retrieves the full set of results. It then discards those records that aren't in the specified range. As a result, this approach performs poorly when selecting from large result sets. A better approach is to limit the amount of data that must be transferred over the network and the work that must be performed by the data source by fine-tuning a SQL SELECT statement using a TOP n or WHERE clause.
The AcceptChanges( ) and RejectChanges( ) methods either accept or reject the changes that have been made to the DataSet since it was last loaded from the data source or since AcceptChanges( ) was last called.
The AcceptChanges( ) method commits all pending changes within the DataSet. Calling AcceptChanges( ) changes the RowState of Added and Modified rows to Unchanged. Deleted rows are removed. The Original values for the DataRow are set to the Current values. Calling the AcceptChanges( ) method has no effect on the data in the underlying data source.
The AcceptChanges( ) method is implicitly called on a row when the DataAdapter successfully updates that row back to the data source when the Update( ) method is called. As a result, when a DataAdapter is used to update the data source with the changes made to the DataSet, AcceptChanges( ) doesn't need to be called. Calling AcceptChanges( ) on a DataSet filled using a DataAdapter effectively removes all information about how the DataSet has been changed since it was loaded. This makes it impossible to reconcile those changes back to the data source using the Update( ) method of the DataSet.
The following example demonstrates the AcceptChanges( ) method:
ds.AcceptChanges();
The RejectChanges( ) method cancels any pending changes within the DataSet. Rows marked as Added are removed from the DataSet. Modified and Deleted rows are returned to their Original state. The following example demonstrates the RejectChanges( ) method:
ds.RejectChanges();
The following example illustrates the concepts just explained:
The DataTable and DataRow objects also expose an AcceptChanges( ) method and a RejectChanges( ) method. Calling these methods on the DataSet implicitly calls these methods for all DataRow objects in the DataSet.
HasChanges and GetChanges
The HasChanges( ) method of the DataSet indicates whether the DataSet has changes, including Added, Deleted, or Modified rows. The method accepts an optional DataRowState argument that causes the method to returns a value from the DataSetRow enumeration if the DataSet has changes:
// check if there are any changes to the DataSet
Boolean hasChanges = ds.HasChanges();
// check if there are modified rows in the DataSet
The GetChanges( ) method creates a copy of the DataSet containing all the changes that have been made since it was last loaded or since AcceptChanges( ) was called. The method takes an optional DataRowState argument that specifies the type of row changes the DataSet should include. The GetChanges( ) method can select the data that has been modified in a DataSet so that only the changed data rather than the entire DataSet is returned. This subset of the DataSet that contains just the changed data can improve performance of disconnected applications by reducing the amount of information that needs to be transmitted between different application domains.
The HasChanges( ) method can be called first to determine whether GetChanges( ) needs to be called. The following example show how to use the HasChanges( ) and GetChanges( ) methods:
// check to see whether GetChanges needs to be called
if (ds.HasChanges())
{
// create a DataSet containing all changes made to DataSet ds
DataSet dsChange = ds.GetChanges();
// create a DataSet containing only modified rows in DataSet ds
The DataSet contains a PropertyCollection that is exposed through the ExtendedProperties property. This collection allows a user to add custom information to the DataSet such as the date and time when the DataSet should be refreshed. The following example sets an extended property indicating that the DataSet should be refreshed in 20 minutes:
ds.ExtendedProperties.Add("RefreshDateTime",
DateTime.Now.AddMinutes(20).ToString());
The following code can then check that value to see if the DataSet needs to be refreshed:
Extended properties must be of type String, or else they will not persist when the DataSet is written as XML.
The DataTable, DataColumn, DataRelation, and Constraint objects also have a similar collection of extended properties.
Cloning the Schema
The Clone( ) method creates a new DataSet with the same structure, including table schemas and relations, as the original but containing none of the data in the original DataSet. The following example uses the Clone( ) method to create a new DataSet:
// create a DataSet object variable to receive the clone
DataSet cloneDs;
cloneDs = ds.Clone();
Cloning the Schema
The Clone( ) method creates a new DataSet with the same structure, including table schemas and relations, as the original but containing none of the data in the original DataSet. The following example uses the Clone( ) method to create a new DataSet:
// create a DataSet object variable to receive the clone
DataSet cloneDs;
cloneDs = ds.Clone();
Copying the DataSet
The Copy( ) method of the DataSet creates a new DataSet with the same structure, including tables schemas and relations, and data as the original DataSet. The following example uses the Copy( ) method to create a new DataSet:
// create a DataSet object variable to receive the copy
DataSet copyDs;
copyDs = ds.Copy();
Removing All Data
The Clear( )method removes all rows from all tables in the DataSet:
ds.Clear();
Removing All Data
The Clear( ) method removes all rows from all tables in the DataSet:
ds.Clear();
Committing and Discarding Changes
When a DataRow is modified, ADO.NET marks the row as having a changes and sets the RowState of the DataRow to Added, Modified, or Deleted, as appropriate. ADO.NET also maintains version information by storing both Original and Current versions of each row. Together, this information allows ADO.NET or an application to identify the rows and columns that have been changed.
Relations belonging to the DataSet are stored as DataRelation objects in a DataRelationCollection object and are accessed through the Relations property of the DataSet. Each DataRelation object represents the relationship between a parent and child table in the DataSet. This section examines some methods and properties of the DataRelationCollection.
Relations are added to the DataSet using the Add( ) method of the DataRelationCollection, as shown in the following example: