Design and Implementation Guidelines for Web Clients
|This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies.
This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.
Microsoft .NET Framework
Summary: This chapter describes the correct way for components in the user interface (UI) to access, present, update, and validate data input, and how the UI participates in maintaining data integrity.
This chapter describes how to manage data in the presentation layer. The chapter includes the following sections:
- Accessing and Representing Data
- Presenting Data Using Formatters, Data Binding, and Paging
- Supporting Data Updates from the Presentation Layer
- Validating Data in the Presentation Layer
Most business applications involve some degree of data processing. Typically, the presentation layer must display data retrieved from a data store, such as a relational database, and accept user input of data. This chapter describes best practices for accessing, presenting, updating, and validating input of data in the presentation layer.
For recommendations specific to data access, see the.NET Data Access Architecture Guide on MSDN (http://msdn.microsoft.com/library/en-us/dnbda/html/daag.asp).
You must take into account a number of considerations when designing the data access functionality of an application. These considerations include:
- Choosing the representation format for data passed between application layers
- Working with transactions in the presentation layers
- Determining which layers should access data
This section addresses each of these considerations.
Choosing the Representation Format for Data Passed Between Application Layers
Data can be represented in a number of formats as it is passed internally between the components and layers of a distributed application. The following formats are generally used to represent data in distributed Microsoft .NET Framework applications:
- Data set
- Typed data set
- Data reader
- Custom "business entity" objects
It is a good idea to choose the most appropriate data representation format for your requirements, and use it consistently throughout your application. For recommendations about choosing representations for your business data and for passing data across tiers, see Designing Data Tier Components and Passing Data Through Tiers on MSDN (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/boagag.asp)
There are two main ways to access data from the presentation layer:
- Using disconnected dataIn this scenario, you pass data such as data sets, custom objects, or XML documents to the presentation layer. These objects and documents may or may not represent data in a database, and they do not imply any connection to a data store.
- Using streaming dataIn this scenario, you use an object such as a data reader (SqlDataReader, OracleDataReader, or OleDbDataReader) to access streaming data in a data store, typically in read-only, forward-only manner.
The following sections describe how to use disconnected data and streaming data in the presentation layer.
Using Disconnected Data
The term "disconnected data" refers to data that you retrieve from a database and then you close the database connection or leave the scope of the current transaction. After you close the database connection or exit the transaction, you continue to use the data even though you have no current connection to the database.
It is a good idea to use disconnected data structures in your user interface layers in any scenario where you have to handle, populate, or modify data outside the scope of a database access or transaction, or if you cannot receive data from a direct connection to the database from the physical tier that contains the presentation layers.
When using disconnected data, you have to:
- Choose the type of object you will use to represent the data. Possible representation formats include XML, data sets, and custom "business entity" objects.
- Determine how you will manage concurrency. For example, you must create a policy for dealing with overlapping updates to data in the data store by different users.
- Decide how you will retain access to the data, especially in Web scenarios. Possible solutions include the use of session state to store per-user data, or application state to store per-application data.
These issues are addressed in the guide described earlier, Designing Data Tier Components and Passing Data Through Tiers. The rest of this section discusses these issues from the perspective of the presentation layers.
Using Streaming Data
The term "streaming data" refers to data that you obtain through a data reader. You use the data reader to pull information from the database in a read-only, forward-only manner while a connection is kept open.
Use streaming data from the user interface in the following scenarios:
- You are consuming data in a read-only, forward only manner.
- You require access to the data in a physical tier that can access the database directly.
- You are outside the scope of a transaction.
Streaming data is typically more appropriate in Web applications than in smart-client applications, because Web applications are more likely to have access to the database server. A typical scenario is for a Web application to use a data reader to populate controls with read-only data from the data store.
Note Data readers implement the System.Collections.IEnumerable interface; a subset of .NET Framework user interface controls can use this for data binding. In contrast, data sets implement System.Collections.IList; this allows data sets to be data bound to a wider range of user interface controls. For more information, see "Presenting Data Using Formatters, Data Binding, and Paging" later in this section.
Working with Transactions in the Presentation Layer
This section describes how to manage transactions in the presentation layer. There are two distinctly different kinds of transactions:
- Atomic transactionsAtomic transactions are intended to encapsulate small, brief units of work that occupy few resources and complete quickly. A typical example is the transfer of money; this locks the accounts being updated for a short time while they are updated, and then releases the locks after the updates are complete.
Atomic transactions have just two potential outcomes—success or failure—and provide ACID (atomicity, consistency, isolation, and durability) guarantees.
- Business transactionsBusiness transactions encapsulate operations that can last several minutes, hours, days, or even weeks. An example is the exchange of documents between two businesses that have to follow a particular protocol. The documents may require manual processing or authorization; these operations could take a lot more time than atomic transactions. In these circumstances, it is not advisable to keep resources locked for the duration of the transaction, so an alternative strategy has to be used instead.
Business transactions may have many potential outcomes, including compensation activities to handle various transaction failure scenarios.
In distributed systems (unlike client-server applications), the presentation layer should not initiate, participate in, or vote on atomic transactions for the following reasons:
- Atomic transactions typically represent a business operation that should be handled by a business component. The business component should be isolated from how data is displayed to the user.
- If you initiate an atomic transaction in the presentation layers, the physical computer where the presentation layers are deployed becomes part of the transaction. This means that an extra node and set of communication links are required to coordinate the transaction; this adds failure points and may add security risks because of the extra channels involved.
- If you initiate or vote on atomic transactions in the presentation layers, you risk exposing a situation that requires user interaction between the transaction initiation and its completion. During this time span, all resource managers participating in the transaction have to keep locks to provide ACID guarantees; scalability is drastically reduced because other activities have contention on these locks.
To prevent presentation layers from initiating, participating in, or voting on atomic transactions, follow these guidelines:
- Do not use the Transaction attribute on ASP.NET pages.
- If your controller classes are hosted in COM+ applications, they must not have the Supported, Required, or Requires New, transactional attributes.
The implication for distributed systems of not initiating transactions in the presentation layers is that all data in the presentation layers exists outside the scope of transactions; this implies the data might be stale. For information about mechanisms for managing data staleness and optimistic concurrency, see the guide described earlier, Designing Data Tier Components and Passing Data Through Tiers.
Determining Which Layers Should Access Data
When designing a layered application, you sometimes have to choose between "strict layering" and "loose layering":
- Strict layering means a component only interacts with components in the same layer, or with components in the layer directly beneath it.
- Loose layering means a component is allowed to interact with components in any layer, not just those in the layer directly beneath it.
The choice between strict layering and loose layering arises because of potential tradeoffs between the maintainability that strict layering provides by letting you change and extend the behavior in the future, and the productivity gain that you can get by letting a layer access layers other than the one directly beneath it.
For data access, the data source itself should be accessed only by data access logic components or data access helper components in the data access layer. The main design decision is whether to allow the presentation layer to access the data access layer directly or force all data access requests to pass through the business layer. There are three different approaches to consider:
- Using message-based business processes
- Invoking business objects
- Invoking data access logic components directly from the presentation layer
Note This section describes the relationship between the logical user interface layers and other logical layers. It does not describe how to distribute these layers in a multi-tiered environment.
Using Message-based Business Processes
In the message-based approach to data access, data is accessed by exchanging messages between the user interface process components in the presentation layer and business workflows in the business layer. The reliance on purely message-based communication means that it is the easiest way for some applications to convert smart-client user interfaces to offline mode.
The processes-based approach is shown in Figure 4.1. In the illustration, the solid lines represent requests for data and the dashed lines represent the returned data.
Figure 4.1. Using message-based business processes for data access
This approach is not straightforward and relies on a careful analysis of your requirements.
To access data by using message-based business processes
- Analyze your application use cases to determine the data flow back and forth.
- Define or use an existing business process as an exchange of messages.
- Design coarse-grained messages or Web services for each data flow. Coarse-grained communication increases efficiency and establishes a document-based information exchange that can be reused by other clients (not necessarily user interfaces) using the same business process.
- Write code in your user interface process layer controllers to invoke the service interfaces that access the business workflow or business components.
This approach may be cumbersome if the presentation and business layers are built together as part of the same application. Also, if the business process was not originally designed to be consumed from a user interface, it might rely on messages being sent to your presentation layers. For example, your user interface might have to react to incoming messages or Web service calls. Such notification architecture for your presentation layer necessitates additional infrastructure and code and is outside the scope of this guide. You can build notification as part of your application, or rely on external infrastructures, such as e-mail, MSN® Messenger, or Windows Messenger.
Invoking Business Objects
The invocation of business objects is probably the most used approach when business logic exposed by an application is designed to service the user interface. For example, the presentation layer can invoke business components through .NET Framework remoting or Web services to retrieve data, and then invoke other business components. This approach does not rely on business workflows to control the process.
Figure 4.2 shows how to invoke business objects from the presentation layer.
Figure 4.2. Invoking business objects from the presentation layer
To access data by invoking business objects
- Design methods in your business components that acquire the reference data the user interface requires and return it to the presentation layer. Write coarse-grained methods that return a set of data all at the same time, such as a whole Order object or a data set. The use of course-grained methods increases communication efficiency. Optimistic concurrency management is also simplified, because related data is acquired in one call and is therefore easier to time stamp.
- Design methods in your business components that take coarse grained sets of data, encapsulate transactions, and update data.
This approach is appropriate for most applications and enforces strict layering in the application. However, if the methods exposed by the business components are just wrappers for methods provided by data access logic components and provide no additional business logic, you might prefer to bypass the business layer and access the data access logic components directly from the presentation layer.
Invoking Data Access Logic Components Directly from the Presentation Layer
It is a good idea to allow your presentation layer components to directly access the data access layer when:
- You do not mind tightly coupling your data access semantics with your presentation semantics. This coupling involves joint maintenance of presentation layer changes and data schema changes. Evaluate this option if you have not encapsulated all data access and entity-specific business logic into business entity components.
- Your physical deployment places the data access layer and presentation layer components together; in this scenario you can retrieve data in streaming formats (such as a data reader) from data access logic components, and bind the data directly to user interface elements for performance. If your presentation layer components and data access logic components are deployed on different servers, this functionality is not available.
There are two variations of this approach:
- Invoke data access logic components from user interface process "controller" components.
- Invoke data access logic components from user interface components.
The following sections describe how and when to use each technique.
Invoking Data Access Logic Components from User Interface Process Controllers
If you do not require the maintainability and growth capabilities afforded by strict layering, you might decide to acquire data, and maybe even update it directly, using the data access logic layer from the controller functions in the user interface process layer. This is a useful approach in small data-intensive applications that have predictable areas of growth.
The user interface process layer uses data access logic component methods to read reference data that is to be displayed to the user, and it uses the layering style described in the previous section for operations that might require additional business transaction logic at some future point. Figure 4.3 shows this approach.
Figure 4.3. Invoking data access logic components from the user interface process layer
To invoke data access logic components from the user interface process layer
- Decide the kinds of operations that will be permitted from the user interface process layer to the data access layer. For example, you might choose to allow only operations that read data, or you might also allow operations that update the data.
- Choose which methods or use cases will have direct access to the data access layer. It is a good idea to choose read methods that are unlikely to require data aggregation or some other type of logic in the future, and write methods that are unlikely to grow into business transactions with more implications than a data write.
Consider the following issues when deciding whether to allow the user interface process layer to directly invoke the data access layer:
- You have to expose the data access logic to the user interface process layer. In smart client scenarios, or Web scenarios with remote application tiers, this means configuring .NET Framework remoting or Web services to enable remote access from the user interface process components. Consider the security and maintainability implications of exposing such fine-grained operations. Keep in mind that it may not add complexity to make remote calls from the user interface process layer with controller functions if business components already require remote access.
- Business components functions that return data typically do so in disconnected formats (such as a data set), whereas data access logic component frequently return streaming data (through a data reader). If you originally use data access logic components to get data as a data reader, and then you have to upgrade your logic to use a business component instead, you will have to change your presentation layer code to use data sets (or another disconnected format). To avoid costly code rewrites, plan ahead and return disconnected data for complex queries that might evolve into more complex operations.
Invoking Data Access Logic Components from User Interface Components
It is a good idea to access the data access layer directly from the user interface components (forms, pages, controls) only in specific cases where:
- You have to encapsulate logic for accessing the data in the user interface component.
- The user interface component requires read-only access to reference data, and the use case embodied in the user interface process is agnostic to the data.
- The data, or its source, does not vary depending on the use case.
- Bypassing the controller functions does not negatively affect maintainability in the long term.
For example, you can develop a custom Countries list box control that knows how to populate itself with country data from a data access logic component. The benefit of this approach is that the developer building the user interface does not have to know how to retrieve or load the data. The drawback is the tight coupling that this introduces between the user interface controls and data design and the affect it may have on scalability if multiple controls get their data independently from a remote tier. It is not recommended to use this approach for update operations because this would be equivalent of allowing views to update model data in the Model-View-Controller (MVC) pattern.
Figure 4.4 shows user interface components directly using data access logic components.
Figure 4.4. Invoking data access logic components from user interface components
A slightly different design with a potential for better maintainability and scalability is to encapsulate functions in the data-intensive controls that are specifically intended to be controller function helpers, so that the user interface process controller methods can invoke them when appropriate. For example, the Countries list box control can have a method that invokes a data access logic component and places the data in the state of the current user interface process, the control, or both. This way the developer has more control over what data is displayed and when the queries are performed.
One of the main functions of the presentation layer is to present data to the user. There are a number of architectural considerations for data presentation that you must take into account. They are:
- What format should you use to display the data the user?
- How should you bind user interface controls to data in the data source?
- What pagination strategy should you use if there is a large amount of data to display to the user?
This section addresses each of these issues.
You frequently have to format data for display. For example, you might want to display the database value "1.2300000" as "1.23." The .NET Framework provides several format specifiers that you can use to format strings in your application. Other basic types must be converted to strings using their ToString method before formatting can be applied. The format specifiers include:
- NumericThe .NET Framework provides many standard numeric format strings, such as currency, scientific notation, and hexadecimal, for formatting numbers. For a complete list of the numeric format strings, see "Standard Numeric Format Strings" in the.NET Framework Developer's Guide on MSDN (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconstandardnumericformatstrings.asp).
- Date and TimeWhen displaying date and time information to a user, you frequently want to display a simpler representation than the complete contents of the DateTime data type. For a complete list of the date and time format strings, see "Standard DateTime Format Strings" in the.NET Framework Developer's Guide on MSDN (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconstandarddatetimeformatstrings.asp).
- EnumerationWhen you have an enumeration, you can use ToString to create a numeric, hexadecimal, or string representation of the enumeration value. For more information, see "Enumeration Format Strings" in the.NET Framework Developer's Guide on MSDN (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconenumerationformatstrings.asp).
- CustomIf none of the built-in format strings fully meet the formatting functionality your application requires, you can create a base type that accepts a custom format string or create a format provider class to provide formatting for an existing type. For more information, see "Customizing Format Strings" in the.NET Framework Developer's Guide on MSDN (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconcustomizingformatstrings.asp).
For an example of how to define a custom formatter to format business entity objects, see "How to: Define a Formatter for Business Entity Objects" in Appendix B of this guide.
With each of the format specifiers, you can supply a culture to localize the string format. For more information, see "Formatting for Different Cultures" in the.NET Framework Developer's Guide on MSDN (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconformattingfordifferentcultures.asp).
Web Forms and Windows Forms allow you to display information by binding controls to a source of data.
However, because of the nature of Web Forms and the architecture of Web programming, there are some significant differences between data binding in Web Forms and Windows Forms. The most significant difference is that the data-binding architecture of Web Forms does not perform updates—that is, it does not write data from the control back to the data source; you must perform this logic.
You can bind to any data source that implements the IEnumerable interface; this includes collection objects, data reader objects, data set objects, DataView objects, and DataTable objects. All these objects (except data readers) also implement the IList interface; this supports data binding to a wider range of Windows Forms controls.
This difference is because of the type of scenario each object type is designed for. Data set and DataTable objects provide a rich, disconnected structure suited to both Windows Forms applications and Web applications. Data readers, on the other hand, are optimized for Web applications that require optimized forward-only data access.
For an example of how to perform data binding, see "How to: Perform Data Binding in ASP.NET Web Forms" in Appendix B of this guide.
When you have to retrieve a large amount of data, it is a good idea to consider using data paging techniques to avoid scalability problems. Generally, follow the simple rule of not retrieving any more data than you require at any one time. For example, if you have to display 1,000 rows of data in a grid with 20 rows per page, implement data retrieval logic that retrieves 20 rows at a time.
Data paging techniques help to reduce the size of data sets, and to avoid expensive and unnecessary heap allocations that are not reclaimed until the process is recycled. For more information about data paging, see the.NET Data Access Architecture Guide on MSDN (http://msdn.microsoft.com/library/en-us/dnbda/html/daag.asp).
In addition to viewing data, many applications must allow users to make updates, insertions, or deletions. There are a number of considerations to keep in mind when implementing data update functionality in the presentation layer. These considerations include the following:
- Is it appropriate to perform batched updates?
- How should you implement optimistic concurrency?
- Do you have to define data maintenance forms to support CRUD (Create, Read, Update, Delete) operations?
This section addresses each of these issues.
The purpose of batching updates is to improve performance, scalability, and integrity of data. This technique groups related operations and submits them as a unit, so that they occur in one network roundtrip, or so that they can be encapsulated in a transaction.
To batch updates you can use two techniques:
- Make changes to data in a data set, and then bind the data set to a DataAdapter object in the data access logic components.
- Store data for your changes in your custom business objects, and invoke the appropriate data access logic methods from a business object.
The first technique is easier to implement, but it does not offer much flexibility in how the resulting changes are sent back. For example, the data set has to be bound to a DataAdapter that is specific to a database connection.
Using Optimistic Concurrency
When using optimistic concurrency, a row in the data source is not locked when a user reads it. Because the row of data is not locked, other users can read or update the row after the original user reads it. When the original user tries to update the row, the system must check whether the data has been modified by another user in the intervening period.
There are various techniques for identifying whether the data has been modified. For example, you can use timestamps to indicate the last-modification time for the row. Another approach is to keep a copy of the original data for the row and compare it against the current data for the row when you perform an update.
For guidance on implementing optimistic concurrency strategies, see the section titled "Using Optimistic Concurrency" in Designing Data Tier Components and Passing Data Through Tiers on MSDN (http://msdn.microsoft.com/library/en-us/dnbda/html/BOAGag.asp).
Designing Data Maintenance Forms to Support Create, Read, Update, and Delete Operations
Many applications require Create, Read, Update, and Delete (CRUD) forms to allow administrators and other users to perform day-to-day data maintenance tasks. However, data maintenance is only a part of the user interface of the application; most applications also provide forms to support specific business use cases and to perform reporting tasks.
Note If a use case for updating data works mostly with other services, instead of being just an update, it is not likely to be built as a distinct CRUD form. For example, checkout pages in an e-commerce site are generally not designed as CRUD forms.
Data Maintenance Principles
The primary motivation for designing CRUD forms is to maintain simple data related to business entities. Typically, the design and implementation of CRUD forms is strongly driven by the relational data storage design of the application.
One of the key assumptions of CRUD data maintenance forms is that the actions are relatively predictable on the data of the application; this can lead to some optimizations. For example, you can assume that adding a product category generally results in a new row in a table of product categories.
Data maintenance operations are also constrained to simple entities that are normalized, pretty much in the same way as the database. This predictability leads to opportunities to use caching, and thereby reduce roundtrips to remote servers.
Typically, many data maintenance forms are built around the following base elements:
- The business entity that you want to maintain
- A mechanism to display a list of business entities, where the user selects one from the list
- A mechanism to view, edit, or delete a single business entity
The business entity you are maintaining does not have to be complex. In fact, maintaining complex business entities typically involves departing from conventional CRUD mechanisms. The business entities you deal with can be expected to have the following set of characteristics:
- A set of attributes that together represent the business entity data
- A set of validation rules for the individual data members in the business entity, and for the business entity as a whole
- A set of fields that identify the business entity, such as a primary key
- Fields that are references to other business entities, such as a RegionID field in a customer entity.
To display a set of business entities in a control, such as a DataGrid or a DataList, you typically have to consider the following questions:
- What business entities are being displayed? Business entities are typically filtered in some way, or they are loaded on specific user gestures such as paging through a large set of entities.
- What attributes of the business entities have to be displayed?
- What actions are permitted on the business entities (such as editing and deleting) and what action triggers them (for example, right-clicking on a DataList, selecting a shortcut menu option, and clicking a hyperlinked attribute)?
To display and allow data entry on a business entity, consider the following questions:
- What attributes are shown in "new" or "edit" modes?
- How do you make sure there is integrity in new or edited business entities?
- What controls are used to display the attributes, and how do they proactively enforce integrity? For example, you can use a drop-down list to select the region of a customer, or a calendar control for picking a date. You also require extra information, such as default values, and how the reference data for drop-down lists is retrieved.
Using Different Visual Styles for Data Maintenance
There are many visual styles for data maintenance. The most common are:
- Implementing separate forms for the list and entity displayCreate separate forms to display a list of business entities and a single business entity. For example, you might display a form that shows a list of all geographical regions to the user. When the user selects a specific region, display a new form that shows the details for the selected region.
- Implement list and details in the same formCreate a single master-detail form. The master-section of the form displays a list of all the business entities. When the user selects one of the entities, its details are displayed in the detail-section of the form.
- Implement inline editing in grids or specialized controlsCreate a form that contains grids or other specialized controls to allow inline editing of the data.
When deciding which style to use, consider issues such as usability and the complexity of the business entity being maintained. Simple business entities (for example, reference data consisting of an ID and a small set of fields) can be represented in a grid, whereas more complex entities generally have separate forms for the display. Practical issues such as development effort and maintainability are important factors. Consistency is also an important consideration, because it increases usability and therefore reduces training costs.
The following sections describe each of the techniques listed earlier in this section. If you examine the diagrams closely, you will see that each technique uses the same functionality in the controllers and performs the same interaction with the server. Therefore, if you implement these techniques correctly, they can all be equally scalable. However, you also have to consider the impact of the user interface on how the data is consumed. For example, if you show the details for customers just below a list of all known customers, a user may click each customer in the list, causing the application to perform many data retrievals from the database.
For an example that illustrates each of the techniques listed above, see "How to: Design Data Maintenance Forms to Support Create, Read, Update, and Delete Operations" in Appendix B of this guide.
Implementing Separate Forms for the List and Entity Display
When implementing separate forms to display a list of business entities and individual business entity details, it is a good idea to use different controller classes for the list view and the single-entity view, effectively creating different user interface "processes." This simplifies reuse of the business entity details form for insert and edit operations.
Figure 4.5 illustrates a solution that uses separate entity list and entity details forms. The interaction with the server from the controller functions is illustrated as a call into a service agent to reduce clutter. For CRUD operations, calls typically go to the data access layer.
It is a good idea to use one schema, type, or data set for the data that appears in the list (the list might contain aggregated, computed, or formatted data), and another schema, type, or data set for the single entity.
Implementing List and Details in the Same Form
When you have to edit only one entity at a time, you might consider implementing the entity list and details user interface elements on the same form. Reusability is reduced in this scenario because the single-entity view is embedded in the same view as the list, and programming the state machine in the forms and controls can be more difficult because users can change selection in the list while editing the attributes of the active entity. For example, if you use a System.Windows.Forms.DataGrid control to display an entity list in a Windows Forms-based application, you must decide how to respond to user actions, such as moving to another row or re-selecting the current row. For each user action, you must make decisions such as whether to accept the data that currently appears or to cancel the update.
When using this approach, you are advised to encapsulate the single-entity view in a containing control; this allows you to enable or disable the control instead of opening a new form when an entity is to be edited.
You are also recommended to use different controller classes for the list view and the single-entity view, as described in the previous section. If you use the same controller class for the list view and the single-entity view, there will be increased coupling as shown in Figure 4.6. The dotted lines indicate that the list view might capture user actions that inform the single-entity view to accept or discard changes on the current entity.
Figure 4.6. Using a single form to display a list of business entities and an individual business entity
If a user clicks on many rows while browsing through data, the properties that appear in the single-entity view must be updated in fast succession. To address this issue, it may be efficient to preload as much of this information as possible.
Implementing Inline Editing in Grids or Specialized Controls
An inline editing approach is suitable in Windows Forms-based applications where you allow users to edit data in a grid directly. In this approach, a set of entities appears in the user interface and facilitates editing inline so that a separate view is not required. All data insertions and updates are performed on the display of the entity list itself.
The inline editing approach is best suited for simple entities that have relatively few fields and do not require specialized user interfaces to edit or create. It is assumed that the user can act directly on the data in the grid, so special interactions are not required with the business or data layers to get the entity data. This approach is also appropriate when updates to multiple rows can be batched, for example by taking a modified data set and sending it back to the business and data layers for processing.
You can develop an inline editing user interface manually with any grid that supports editing. Follow these guidelines:
- Capture the appropriate events from the controls that receive user actions, and tie the events to the controller functions described earlier in this chapter.
- Use data binding with feature-rich grid controls that handle all the user actions relating to navigation, editing, and inserting new rows; this allows you to respond only to events to confirm row deletion. To update the data source, take the modified data set and use batched updates, or iterate through the data set and invoke appropriate single-entity methods on the server.
Figure 4.7 shows an inline editing user interface design.
Figure 4.7. Using inline editing to display a modifiable list of entities in a grid control
Advanced Data Maintenance Implementation Techniques
If you have a recurrent structure in your code that depends only on the description of the entities and information such as how they are displayed and validated, you can use metadata-based techniques to automatically generate CRUD user interfaces. With this approach, the development team does not have to perform tedious, repetitive tasks for each different entity in your application.
You use metadata to create a simple meta-model for your application; the meta-model describes the structure of classes and their inter-relationships. For example, you can write an XML document containing appropriate elements to help with things such as automatic generation of CRUD-related components, pages, and forms. Figure 4.8 shows a fragment of an example of such a model.
Figure 4.8. Fragment of a data maintenance meta-model
The first step in using a metadata-based approach is to model the metadata you require. Generally, you require information about the entities, how to display them, and how to allow data entry. The best way to determine what metadata you require is to analyze a small set of CRUD implementations that follow the same structure, and that express the variability points you believe will be required across all the entities.
Table 4.1 shows some common pieces of metadata that are required and some sources where that metadata can already be found, or sources that can be used to capture metadata.
Table 4.1: Common sources of metadata for data maintenance user interfaces
|DataSet Schema||Visual Studio .NET Designer|
|Attributes and data types||x||x|
|Validation rules for attributes||x||x|
|Validation rules for entities||x||x||x|
|Default values for attributes||x||x|
|Controls to use for attributes||x|
|Layout of controls||x|
|Friendly names for attributes and entities||x|
|Binding of view, controller, and components||x|
|Names of forms and controls||x||x|
|Names of assemblies that perform insert, read, update, and delete operations||x|
|Names of controller classes||x|
There are two main ways to implement a metadata-based approach to building a data maintenance user interface:
- Using metadata for code generationUse metadata at design time, in the tool, or during the build process to automatically generate all data maintenance user interface code. This approach is appropriate if you use code generation as a starting point for the development process but expect to change or customize the generated code after it is working, or if the effort of acquiring and interpreting the metadata at runtime is costly in terms of security, performance, and initial development effort.
To use this approach, you have to know:
- The implementation structure you are trying to repeat.
- Whether it relies on reusable components (for example, a validation framework or a specialized ListView).
- The templates for the code you have to generate.
- The schema for the metadata required to generate the code.
- The sources for the metadata.
- Interpreting metadata at run timeUsing metadata at run time can enhance maintainability. Maintainability is improved because only the metadata must be changed to modify the application behavior; the source code is not affected. This means that modifications can be implemented by business users if they are provided with appropriate editing tools.
However, this approach might require:
- Extra effort in design and development of the code that acquires and interprets the metadata. For example, you have to load assemblies at run time, create controls on a form and display them at run time, and bind events.
- Careful analysis of performance degradation. For example, performance might suffer if the metadata is interpreted many times across users.
- Security reviews and testing. For example, the use of metadata at run time might open the door to elevation of privileges and component injection techniques.
To use this approach, you have to know:
- How to load controls, bind events, and call components at run time
- Whether the framework that interprets the metadata has sufficient extension points to grow with the requirements
- Whether the dynamic behavior relies on techniques that require full trust security privileges, for example by using reflection
- The sources for the metadata that can be used at run time
You can use the Configuration Management Application Block to store the metadata in a secure way. You can download this block from MSDN (see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/cmab.asp).
Figure 4.9 shows a metadata-based approach to data maintenance user interface design.
Figure 4.9. A meta-data based approach to data maintenance user interface design
Metadata techniques apply mostly to enterprise-level application developers who require CRUD forms for many entities, and to ISVs who want to build very flexible and rapidly customizable applications.
Validation is an important issue in the presentation layer. There are two kinds of validation:
- Continuous validationContinuous validation occurs each time a form or page is submitted. The validator controls provided by the .NET Framework provide continuous validation.
- One-time validationOne-time validation occurs only once. To perform one-time validation of a control, you must write some code.
This section describes why validation is important, and it provides guidance on how to perform validation in the presentation layer.
It is a good idea to never trust user input. Validating the data entered in your application can produce the following benefits:
- IntegrityValidation enables you to prevent integrity errors in the data in your application.
- UsabilityValidation permits you to format user-entered data (for example, a user enters a phone number as "1234567890" and you want it to display it as "123-456-7890").
- SecurityValidation on incoming data helps prevent security threats such as cross-site scripting attacks and code-injection.
- ResponsivenessThe built-in validator controls (RegularExpressionValidator and RequiredFieldValidator) enable you to give users immediate, client-side feedback.
- Client-side and server-side validationThe concept of "is this page valid" is nicely abstracted on both the client and server. The System.Web.UI.Page class has a Validators property that returns a collection of validator controls contained on the requested page, and an IsValid property that indicates whether page validation succeeded.
Data validation is important because it protects the integrity of your application data and improves the usability of the application.
Choosing a Validation Strategy
Any input coming from a user must be validated before being used. The .NET Framework provides a rich set of validator controls that you can use in ASP.NET Web applications, to handle this task.
There are five main types of validator controls:
- ComparisonValidatorVerifies that a user's input is of the correct type, or that the input matches a specific pre-defined value.
- RequiredFieldValidatorVerifies that the user has entered a value for a particular control.
- RangeValidatorVerifies that a user has entered a value within a permissible range. For example, you can test that the amount a user wants to withdraw from his or her checking account is between $0 and the total balance.
- RegularExpressionValidatorVerifies that user input matches a specific pattern. For example, you can test that a social security number matches the patter "nnn-nn-nnnn," where "n" is a number between 1 and 9.
- CustomValidatorIf none of the built-in validator controls suit your validation requirements, you can write a custom validation function that performs server-side validation of user input.
By using the validation controls provided in the .NET Framework, you can prevent many of the problems associated with invalid data entry in ASP.NET Web applications.
Note The validator controls are only available in ASP.NET Web applications. In Windows Forms-based applications, you must write your own code to perform these validation tasks.
Using Validation Controls
Consider the following issues when using validation controls:
- You might have to enable or disable some validator controls in response to user actions or if previous validator controls return as invalid. For example, you might have to validate the Province field only if the user selects Canada in the Country field. This must be done programmatically, and it can involve fairly complex logic.
- There are scenarios where the standard validator controls do not provide sufficient functionality and you must resort to the CustomValidator. Moreover, if you require client-side validation, you must write your own custom script code.
- There is no RequiredFieldValidator that works with a CheckBoxList control. To perform validation on this control, you must create your own validator class that inherits from BaseValidator.
Handing Validation Errors
There are several options for handling validation errors:
- Raise an exception. This might not be a useful action in the presentation layer. However, you might want to raise (or log) an exception if one of your validator controls detects some sort of attack.
- Display individual error messages, indicating the reason for the validation error and allowing the user to re-enter data accordingly.
- Use the ValidationSummary control to display a summary of all validation errors in a single location.
Whatever action you decide to perform when validation fails, you must make sure that the user is clearly notified about what is wrong and is given an opportunity to correct the data entry.
Accessing, presenting, modifying, and validating data are fundamental to most applications. You must make sure that you carefully plan how data will be accessed by the presentation layer, and in particular whether presentation layer components should have direct access to data access logic components. You must consider the impact of your data presentation approach on scalability, and you must carefully consider how you will implement data maintenance forms that allow users to view and modify data. Finally, it is a good idea to always implement at least a minimal level of data input validation to protect the integrity of your application's data and to improve usability.
This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.