 MSDN Magazine August 2003

MSDN Magazine August 2003

ASP.NET: Jump Start Your Web Site Development with the ASP.NET Starter Kits

If you're building an ASP.NET Web site you could probably use a good, solid code foundation to start with and build upon. Wouldn't it be nice to start with a complete site, make a few tweaks and customizations, and go live? The ASP.NET Starter Kits are packaged solutions that let you do just that. The five kits—Community, Reports, Commerce, Portal, and Time Tracker—supply full, reusable code that can be easily customized. In addition, there are a number of ISPs that support automatic deployment of ASP.NET Starter Kit Web sites, leaving you with little left to do when you have to get there fast. Here, the author introduces the ASP.NET Starter Kits and builds a community Web site with lots of advanced features such as ratings, user polls, upload quotas, change notifications, and themes.

Paul Litwin DataGrid: Tailor Your DataGrid Apps Using Table Style and Custom Column Style Objects

One of the most enduring challenges in writing user interfaces is figuring out how to display large amounts of data efficiently and intuitively without bewildering the user. The problem becomes particularly thorny when the interface must reflect hierarchical relationships within the data that the user needs to modify. The Windows Forms DataGrid control gives developers a powerful and flexible tool to meet this challenge. This article explains its basic operations and shows how to extend the DataGrid to display columns of data in an application-appropriate manner.

Kristy Saunders XSLT: Simplify Development and Maintenance of Microsoft .NET Projects with Code Generation Techniques

Code generation techniques using technologies such as XSLT are playing an increasingly important part in software projects as they support the development of a rapidly maintainable code base. This article discusses some of the benefits and possible applications of code generation.To demonstrate these techniques the author develops a Web Forms application that supports the maintenance of records in a SQL Server database, using the database's own metadata to drive the generation process. The SQL Server database schema is extracted using SQLXML 3.0 data access and processed through XSLT stylesheets that generate both a database access layer and a Web Forms user interface with query and update pages.

Peter Ashley DCOM Interop: Generate Custom Managed C++ Wrappers for Easier COM Interoperation Using DCOMSuds

Now that you're writing managed code, you'll certainly want to use your existing COM components, but you can't simply call them directly. Instead, you have to wrap the COM component in a runtime-callable wrapper that acts as a proxy between the component and your managed code. While the CLR provides wrapper classes for this purpose, there will be times when you'll want custom objects to wrap your COM components. One way to get the low-level access you need to precisely control resource cleanup, pass security information, and get access to CLR features is to write your own wrapper class in managed C++. This article shows you how.

Vishwas Lele Web Services: Extend the ASP.NET WebMethod Framework with Business Rules Validation

In an earlier article the authors showed how to build a custom WebMethods extension that provides XML Schema validation, a function that is lacking in ASP.NET. In the process they established a foundation for enforcing business rules during the deserialization of XML data. The technique, which is described in this article, uses declarative XPath assertions to test business rule compliance.In building this business rules validation engine, the authors integrate the validation descriptions into the WSDL file that is automatically generated by the WebMethod infrastructure. Finally, they demonstrate how to extend wsdl.exe, the tool that generates WebMethod proxy/server code from WSDL files, to make use of their extensions.

Aaron Skonnard and Dan Sullivan Editor's Note: Tech•Ed 2003 Offers Revealing Glimpse into Dev

When we arrived in Dallas for Tech•Ed in June, there was reason to worry. For a moment, we felt that familiar unease we once had as adolescents sent off to visit the grandparents down in Miami. How else to explain the "Happy 100th Birthday, Irving!" signs on the highway exit from DFW?.

New Stuff: Resources for Your Developer Toolbox

Dubbeldam Software has released HtmlTweak, a tool that allows Web developers and designers to locate and inspect HTML elements loaded in Microsoft® Internet Explorer, and modify HTML, Cascading Style Sheet rules, and attributes to test changes.

Theresa W. Carey Web Q&A: Who Called the Script?, Concatenating Binary Files, and More

This month find out which part of an HTML document has called a script, how to concatenate binary files, find a node, reference one script from another, build a GUI database front end in Access, and compare XML files.

Edited by Nancy Michell Cutting Edge: Creating a Multi-table DataGrid in ASP.NET

If you bind a multi-table DataSet to a DataGrid, only the first table is recognized. Here Dino Esposito writes a custom solution the the multi-table problem.

Dino Esposito The XML Files: XSLT Keys, Select vs. Match, Conflict Resolution, and More

This month Aaron Skonnard covers how keys work in XSLT, XSLT match and select attributes, SelectNodes order, finding <script> tags, XSLT pattern matching, xsl:include and xsl:import, xsl:if, and xsl:output.

Aaron Skonnard Advanced Basics: Data Binding in Visual Basic .NET

Ken Spencer introduces data binding in Visual Basic .NET.

Ken Spencer Security Briefs: Hashing Passwords, The AllowPartiallyTrustedCallers Attribute

Keith Brown describes how yo can hash passwords when you want to store them in your own custom database, and when to use the AllowPartiallyTrustedCallers attribure on your assembly.

Keith Brown Basic Instincts: Naming and Building Assemblies in Visual Basic .NET

Learn what the different parts of an assembly name mean, how to create a stronly named assembly, and get other assembly security tips.

Ted Pattison C++ Q&A: Finding a Win32 Handle, HTML in CHtmlCtrl

This month Paul DiLascia discusses how to find windows with GetLastChild and outputting HTML with a C++ procedure equivalent to document.write.

Paul DiLascia Resource File: Creating Privacy-aware Web Sites

Privacy issues are of primary concern to those involved in Internet commerce. Some consumers are hesitant to provide information to Web sites without clearly understanding how their data will be used and with whom it will be shared.


ASP.NET: Jump Start Your Web Site Development with the ASP.NET Starter Kits

ASP.NET

Jump Start Your Web Site Development with the ASP.NET Starter Kits

Paul Litwin
This article assumes you're familiar with Web Development
Level of Difficulty 1 2 3
Code download available at: StarterKits.exe (141KB)
SUMMARY

If you're building an ASP.NET Web site you could probably use a good, solid code foundation to start with and build upon. Wouldn't it be nice to start with a complete site, make a few tweaks and customizations, and go live? The ASP.NET Starter Kits are packaged solutions that let you do just that. The five kits—Community, Reports, Commerce, Portal, and Time Tracker—supply full, reusable code that can be easily customized. In addition, there are a number of ISPs that support automatic deployment of ASP.NET Starter Kit Web sites, leaving you with little left to do when you have to get there fast. Here, the author introduces the ASP.NET Starter Kits and builds a community Web site with lots of advanced features such as ratings, user polls, upload quotas, change notifications, and themes.

Contents
The Commerce and Portal Starter Kits
The Time Tracker Starter Kit
The Reports Starter Kit
The Community Starter Kit
Creating a Community
The Community Starter Kit Architecture
Working with Themes
Creating a New Module
Conclusion

A
SP.NET and Visual Studio® .NET have certainly made Web application development simpler. However, creating an ASP.NET Web site still involves some effort on your part. You still need to design a sound application and develop it with solid and reusable code and thorough testing. In the midst of this process you might ask yourself, "Hasn't anyone else ever tackled this problem before? Am I just reinventing the wheel?" Some scenarios are common to most ASP.NET developers, like putting together a community site, tracking time on a project, or creating a portal for your company. It would be nice to build upon a well-designed foundation rather than have to design the application from scratch even if it means customizing and extending the solution. That's exactly the idea behind the ASP.NET Starter Kits.

The ASP.NET Starter Kits are a set of five packaged sample applications from Microsoft that you can download from http://www.asp.net/starterkits. They include an e-commerce storefront app, a community Web site, a portal site, sample reports, and a project time-tracking app. The purpose of the starter kits is to provide several useful starter Web sites and some solid ASP.NET code samples that you can learn from and build upon. All five starter kits come with complete source code, work with SQL Server™ or Microsoft® Data Engine (MSDE) as the data store, and are available in Visual Basic®, C#, and J# versions. Additionally, the starter kits are available in both Visual Studio .NET versions (with separate .aspx and codebehind pages) and SDK versions (without codebehind pages). Thus, you can use your favorite development environment to work with the starter kits, whether it be Visual Studio .NET, the ASP.NET Web Matrix Project, or some other editor. In this article, I will introduce the five ASP.NET Starter Kits. Then I will focus on the Community Starter Kit in order to show you how you can extend it for your particular needs.

The Commerce and Portal Starter Kits

The Commerce Starter Kit creates an e-commerce storefront application for a fictitious spy company, IBuySpy. The application employs a three-tier architecture, caching, forms authentication, and a shopping cart. The Commerce Starter Kit also includes a B2B Web Service component. This is a great application to study and learn from, especially if you need to build an e-commerce application of your own.

The Portal Starter Kit creates the portal application for IBuySpy, the same fictitious spy company featured in the Commerce Starter Kit. The Portal Starter Kit features 10 portal modules: announcements, contacts, discussion, events, html/text, image, links, quicklinks, xml/xsl, and a custom module.

The Time Tracker Starter Kit

The Time Tracker Starter Kit (see Figure 1) is essentially a time and billing intranet application without the billing component. It is pretty straightforward. It has three tabs: Log, Reports, and Administration. You use the Administration tab to configure projects, the Log tab for logging hours, and the Reports tab for generating reports by project or individual.

Figure 1 Log Tab in Time Tracker Starter Kit
Figure 1 Log Tab in Time Tracker Starter Kit

The Time Tracker Starter Kit shows off a number of interesting ASP.NET architectural features including a three-tier design, Windows® and forms authentication, dynamic chart generation using GDI+, and a mobile front end.

The Reports Starter Kit

The Reports Starter Kit differs from the other starter kits in that it is not a single cohesive application. Rather, it's a series of eight reports that you can read about, run against sample data, and examine the source code from. In addition, you can copy and paste the source into your own applications. This kit works similarly to the ASP.NET QuickStarts that ship with the Microsoft .NET Framework SDK and Visual Studio .NET. The reporting examples include simple tabular reports, two styles of hierarchical drill-down reports, cross tabs, and several GDI+ charts.

The Community Starter Kit

The Community Starter Kit may well be the star of the show. Without much effort or knowledge of how it works, you can use it to quickly put together a community site for your user group, club, family, or community. You could also use it to put together a simple news and information site. In some ways, it overlaps with the functionality provided by the Portal Starter Kit, but it focuses more on the noncommercial organization and is easier to set up and manage.

The Community Starter Kit supports a number of different types of content, including articles, photo galleries, and event listings. In addition, it supports a number of optional features that are standard on many community sites, including advertisements, moderation, upload quotas, ratings, comments, user polls, content change notifications, automatic transformations, topics, Web boxes, syndication, newsletters, skins, and security.

Advertising is handled by a couple of AdRotator controls, one at the standard banner size of 468×60 pixels and the other at 125×125 pixels. If you enable moderation for a section, the content must be approved by a moderator before it is displayed on the site. You can set upload quotas for each user to limit the number of images and files that can be uploaded to the site. The Community Starter Kit also supports an upload quota for the entire community. This allows an ISP to host a number of communities on a single box without letting one site hog all the available disk space.

You have the option to turn on user ratings and comments for a particular type of content, such as books or articles. Users can rate a content item on a scale of 1 to 5 or, if they prefer, they can enter a comment instead of or in addition to the rating.

The Community Starter Kit supports the creation and reporting of user polls. Thus, you can poll users with questions like "What is your favorite .NET-targeted development language?" or "What was the name of Led Zeppelin's original drummer?" You can also notify users automatically when new content becomes available in a section. You can even specify transformations so that foul language is automatically cleaned up from submissions and comments without requiring any intervention on your part.

The Community Starter Kit has an option that allows you to create a list of topics so that users are required to categorize contributions as they are added. Thus, your .NET Framework-based news site might have topics such as ASP.NET, managed C++, Web Services, and so on.

The Community Starter Kit lets you add highlights of content in the margins of pages by enabling various Web boxes. For example, you could use a Web box to display the most popular or newest articles, links, downloads, or photos on the home page. You can also use the quote Web box to display random quotes on a page.

The Community Starter Kit supports content replication via two mechanisms: service subscriptions and Web Service boxes. Using a service subscription, you can aggregate content from a section of another Community Starter Kit site and display it on your site. For example, you might subscribe to the discussion section of another community site and display it on your site as if it were your own discussion section. Like Web boxes, Web Service boxes display excerpted information in the margins of a page; however, Web Service boxes can obtain their content from other sites. You can subscribe to another Community Starter Kit site that has published its content as an XML Web Service or you can subscribe to RSS services such as http://msdn.microsoft.com/msdnmag/rss/rss.aspx. You can publish any section of your site as a community service employing XML Web Services and the SOAP protocol (but not currently the RSS protocol). Published sections can be consumed as either a service subscription or a Web Service box.

You can send newsletters to community members. The newsletter component supports templates and the embedding of special tags that you can use to include fields such as Username, FullName, and Password, as well as content-specific fields like NewArticles, NewBooks, and NewDownloads.

The Community Starter Kit engine supports the concept of skins or themes, letting you radically change the look of the site by choosing a different theme from the admin page. Skins and themes are discussed later in this article.

The Community Starter Kit supports an extensive security system that classifies members according to their roles: anonymous, authenticated, moderator, and administrator, or according to your own custom roles. You can also secure any section of the site so that only certain types of users can view, add, edit, or delete content.

Figure 2 Community Web Site Example
Figure 2 Community Web Site Example

Figure 2 illustrates a number of the community features supported by the Community Starter Kit. This community, a fictitious user group called Ballard .NET, uses the Professional theme. Notice the user poll displayed at the top of the page and the community service feed box entitled ".NET Tips & Tricks" at the lower-right side of the page.

Creating a Community

After downloading and installing the Community Starter Kit, the first thing you will want to do is remove the sample site data. You can do this by clicking the Login link at the bottom of the navigation bar and logging in as user Admin with a password of Admin. (Change this password as soon as possible, since it's a security risk.) After successfully logging in, a new link (Admin) will appear on the navigation bar. Click this link to jump to the administration portion of the site and scroll down to find the Delete Content link.

When you return to the community home page after deleting content, you will notice that some sample content remains. To replace the home page content with your own, you will need to return to the admin page, click Edit Sections, click the Home section, and provide a site title, description, and logo. You can also use this Edit Sections option to change the basic layout of the home page, choose a theme, or enable a number of optional features (such as the display of Web boxes or Web Service boxes) for the home page.

After laying out the home page, you can add sections to your site. A site can have an unlimited number of sections. The various types of sections (or modules) you can create are listed in Figure 3.

Figure 3 Community Starter Kit Modules (Close Figure 3)

Section Type | Description
Articles | Content of articles, news, lists of FAQs, and tips.
Books | Information on books. Supports book images and a number of supporting fields including author, ISBN, publisher, and price.
Custom Modules | Contents of a user control.
Discuss | Discussion forum. Supports pinned posts, locked posts, and announcements.
Downloads | Files available for download.
Events | Information on community meetings and other events. You can specify a visibility date so an event can be hidden until you are ready to display it.
HTML Page | Contents of a single HTML page. For example, you might use this for a "Contact Us" page.
Links | Hyperlinks.
Parent Section | Container for other sections. The default page is a parent section, but you can add additional parent sections.
Photo Gallery | A gallery of images. Supports automatic thumbnail generation of images.

To add a section, click the Edit Sections link from the admin page. You can create multiple sections of the same type. For example, the community shown in Figure 2 includes two article sections, News and Articles. You can use a parent section to help organize a group of similar sections. For example, if you wanted to separate your downloads by general category, you could create a parent section that contained several download sections, such as User Contributions, Controls, and Speaker Presentations.

From the Edit Sections page, you can enable various optional features that apply to the particular section, such as moderation or e-mail notifications. You can also opt to publish the contents of the section as a community Web Service.

The Community Starter Kit Architecture

While many Web sites are data-driven and dynamically generated, most are still largely static. While that's not necessarily a bad thing, the more content that's dynamically generated on a site, the more customizable it is. The Community Starter Kit is completely data-driven. Not counting the pages used for site administration, the Community Starter Kit contains exactly one .aspx page, which is devoid of content. All content is stored in a SQL Server or MSDE database. A single installation of the Community Starter Kit supports multiple communities which can be configured using the special ISPAdmin account.

When a request for a Community Starter Kit page comes into the Web server, an HTTPModule named CommunitiesModule handles the request and determines the community, section, page, and user associated with the request, populating the CommunityInfo, SectionInfo, PageInfo, and UserInfo objects, respectively. These objects are members of the Items collection of the Context object. Because the Context object is persisted across all modules and handlers that participate in a request, this data will be available to the page that handles the request (for more details on the Context object, see "ASP.NET: Nine Options for Managing Persistent User State in Your ASP.NET Application" by Steven Smith in the April 2003 issue of MSDN® Magazine). The CommunitiesModule then redirects the request to the communityDefault.aspx page, which is responsible for building the response dynamically using the aforementioned objects, a number of user controls, supporting classes, and style sheets.

Right out of the box, the Community Starter Kit probably doesn't do everything you might want, but the kit was built with extensibility in mind. If you don't like how it does something, you can easily change it. After all, it ships with the complete source code in Visual Basic .NET, C#, and J#. The themes and modules elements of this kit are particularly suited for customization.

Working with Themes

The design of the Community Starter Kit is based on the ASP.NET Forums application, and like the Forums application, it uses themes (also referred to as skins) to separate the user interface from the code. A theme is quite a bit more than a stylesheet. In fact, without themes there would be no content because themes contain all the controls that are displayed on the site. A theme is made up of the elements listed in Figure 4.

Figure 4 Elements of a Theme (Close Figure 4)

Element | Location of Files | Description
PageSkins | Themes\theme\Skins\PageSkins | Defines the layout of the home page. Change this skin to change the basic layout of the home page, including the number of columns and the placement of user controls.
ContentSkins | Themes\theme\Skins\ContentSkins | Layout of specific pages, such as the article page, the articles section page, the add articles page, the download page, or the download section page.
ControlSkins | Themes\theme\Skins\ControlSkins | Layout of controls used across pages, such as the Web Service box or the display comments control.
TemplateSkins | Themes\theme\Skins\TemplateSkins | Layout of templates used by Repeater and DataList controls, such as the ItemTemplate for the photo gallery DataList control.
Stylesheet | Themes\theme\Styles | All of the generic styles used across pages, such as SectionMenu, as well as the module-specific styles, such as Article_IntroCell. Most themes (but not all) have only one stylesheet, named default.css. In addition to the stylesheets, the Styles folder contains several images used by the stylesheets.
Images | Themes\theme\Images | Images used by the user controls.

To create a new theme, you need to add a folder underneath the Communities\Common\Themes folder. The name of the theme is derived from the folder name. Beneath this folder, create the folders shown in Figure 4. Fortunately, you don't have to populate every folder for a theme because all themes inherit from the base theme, Default. Thus, if you don't supply a skin that is needed by a theme, the Community Starter Kit uses a skin from the Default theme.

Although the Default theme contains more than 75 files, you can create a basic theme by providing just a few files. I created a basic theme, named Small, which is a knock-off of the Default theme. The basic differences from the Default theme are smaller fonts, a color change from blue to red, and a different layout for portions of the default page. My new theme consists of the six files that are listed here:

  • Communities\Common\Themes\Small\Images\ redshiny3dsquarebullet_7x7.jpg
  • Communities\Common\Themes\Small\Skins\ControlSkins\ Sections_SectionMenu.ascx
  • Communities\Common\Themes\Small\Skins\PageSkins\Default.ascx
  • Communities\Common\Themes\Small\Styles\an_red_arrow.gif
  • Communities\Common\Themes\Small\Styles\Default.css
  • Communities\Common\Themes\Small\Styles\redyellowstar.gif

Of course, the more elements you supply, the more complete your theme will be. For example, if you drill down into one of the sections using the Small theme, you will still see controls with a blue background. If I hadn't changed the color scheme of the theme, however, no one would be the wiser. The easiest way to create a new theme is to model your theme on an existing one and then modify its skins and styles.

Creating a New Module

While it's a bit more involved than creating a theme, you can also create a new module for your community. For example, I created a module named Jobs, which tracks job postings.

To create a new module, first add a new table to the CommunityStarterKit database and name it Community_module (where module is the name of the new module) that will be used to track the new module items. The primary key of this table should be named module_ContentPageID, along with the names of additional fields you want to collect for the module.

You'll need stored procedures that can be used to add a new module item, edit an exiting item, retrieve a list of items, and retrieve all of the fields for a single item. The stored procedures for the Jobs module are named Community_JobsAddJob, Community_JobsEditJob, Community_JobsGetJobs, and Community_JobsGetJob. I copied the stored procedures for the Articles module and used them as a model for the Jobs stored procedures, adding the additional fields needed to track job postings. Community_JobsGetJobs is used to retrieve the list of jobs for the Jobs_Section page. This module calls the Community_GetPagedSortedContent function to paginate the output (see Figure 5).

Figure 5 Community_JobsGetJobs Stored Procedure (Close Figure 5)
CREATE PROCEDURE Community_JobsGetJobs
(
  @communityID int,
  @username NVarchar(50),
  @sectionID int,
  @pageSize int,
  @pageIndex int,
  @sortOrder NVarchar(50)
)
AS

DECLARE @currentDate DATETIME
SET @currentDate = GetUtcDate()

— Get the Articles
SELECT
    Job_Company,
    Job_TempStatus,
    Job_TempMonths,
    Job_StartDate,
    Job_Location,
    Job_Education,
    Job_Experience,
    Job_Salary,
    Job_ContactName,
    Job_ContactPhone,
    Job_ContactEmail,
    Job_ContactWeb,
    Content.*
FROM
    dbo.Community_GetPagedSortedContent
    (
        @communityID,
        @username,
        @sectionID,
        @currentDate,
        @sortOrder,
        @pageSize,
        @pageIndex,
        default
    ) Content
    JOIN Community_Jobs
        ON Job_ContentPageID = ContentPage_ID
ORDER BY
    IndexID

Create a new folder under Engine\Modules that will contain the module code. Under this folder, you should have three subfolders named Components, Content, and Controls. For the Jobs module, I created the Engine\Modules\Jobs\Components, Engine\Modules\Jobs\Content, and Engine\Modules\Jobs\Controls folders. The Components folder should contain two class libraries, the first containing a class that derives from the ContentInfo class, and a second containing a utility class that is responsible for calling each of the module's stored procedures.

Using the Visual Basic .NET version of the Community Starter Kit, I created a class library named JobInfo.vb, which contains a single class (JobInfo). I used the ArticleInfo.vb class library as a model for my class library. The JobInfo constructor takes a single parameter, a DataReader, with which it grabs each of the fields from the Jobs table and places them into private variables which are then exposed as a series of properties of the JobInfo class (see the code in Figure 6).

Figure 6 JobInfo Class (Excerpted) (Close Figure 6)
Imports System
Imports System.Data.SqlClient

Public Class JobInfo
    Inherits ContentInfo

    Private _company As String
    Private _tempStatus As Boolean
    Private _tempMonths As String = String.Empty
    Private _startDate As String = String.Empty
    Private _location As String = String.Empty
    Private _education As String = String.Empty
    Private _experience As String = String.Empty
    Private _salary As String = String.Empty
    Private _contactName As String = String.Empty
    Private _contactPhone As String = String.Empty
    Private _contactEmail As String = String.Empty
    Private _contactWeb As String = String.Empty

    Public Sub New(ByVal dr As SqlDataReader)
        MyBase.New(dr)

        ' Populate required fields
        _company = CStr(dr("Job_Company"))
        _tempStatus = CStr(dr("Job_TempStatus"))

        ' Populate optional fields
        If Not (dr("Job_TempMonths") Is DBNull.Value) Then
            _tempMonths = CStr(dr("Job_TempMonths"))
        End If
        If Not (dr("Job_StartDate") Is DBNull.Value) Then
            _startDate = CStr(dr("Job_StartDate"))
        End If
        If Not (dr("Job_Location") Is DBNull.Value) Then
            _location = CStr(dr("Job_Location"))
        End If
        If Not (dr("Job_Education") Is DBNull.Value) Then
            _education = CStr(dr("Job_Education"))
        End If
        If Not (dr("Job_Experience") Is DBNull.Value) Then
            _experience = CStr(dr("Job_Experience"))
        End If
        If Not (dr("Job_Salary") Is DBNull.Value) Then
            _salary = CStr(dr("Job_Salary"))
        End If
        If Not (dr("Job_ContactName") Is DBNull.Value) Then
            _contactName = CStr(dr("Job_ContactName"))
        End If
        If Not (dr("Job_ContactPhone") Is DBNull.Value) Then
            _contactPhone = CStr(dr("Job_ContactPhone"))
        End If
        If Not (dr("Job_ContactEmail") Is DBNull.Value) Then
            _contactEmail = CStr(dr("Job_ContactEmail"))
        End If
        If Not (dr("Job_ContactWeb") Is DBNull.Value) Then
            _contactWeb = CStr(dr("Job_ContactWeb"))
        End If
    End Sub 'New

    Public Property Company() As String
        Get
            Return _company
        End Get
        Set(ByVal Value As String)
            _company = Value
        End Set
    End Property

' ... continues with the remaining properties

The JobUtility class provides the interface between the Jobs module and the Jobs stored procedures. It contains a single class, JobUtility, with four methods: AddJob, EditJob, GetJobs, and GetJobInfo. The GetJobs method is responsible for calling the Community_JobsGetJobs stored procedure and returning an ArrayList of JobInfo objects (see Figure 7).

Figure 7 GetJobs Method of the JobUtility Class (Close Figure 7)
Public Shared Function GetJobs(ByVal username As String, _
 ByVal sectionID As Integer, ByVal pageSize As Integer, _
 ByVal pageIndex As Integer, ByVal sortOrder As String) As ArrayList
   Dim conPortal As New _
    SqlConnection(CommunityGlobals.ConnectionString)
   Dim cmdGet As New SqlCommand("Community_JobsGetJobs", conPortal)
   cmdGet.CommandType = CommandType.StoredProcedure
   cmdGet.Parameters.Add("@communityID", CommunityGlobals.CommunityID)
   cmdGet.Parameters.Add("@username", username)
   cmdGet.Parameters.Add("@sectionID", sectionID)
   cmdGet.Parameters.Add("@pageSize", pageSize)
   cmdGet.Parameters.Add("@pageIndex", pageIndex)
   cmdGet.Parameters.Add("@sortOrder", sortOrder)

    Dim colJobs As New ArrayList()

    conPortal.Open()
    Dim dr As SqlDataReader = cmdGet.ExecuteReader()
    While dr.Read()
        colJobs.Add(New JobInfo(dr))
    End While
    conPortal.Close()
    Return colJobs
End Function 'GetJobs

The Content folder should contain one class library for each basic type of page that's supported by the module. These classes will become the code behind the user controls that you will create later. At a bare minimum, your module needs to supply classes for each of the following types of pages: add item, edit item, display a list of items (for the section page), and display an individual item (for the item drilldown page). These classes need to derive from the ContentAddPage, ContentEditPage, ContentListPage, or ContentItemPage, respectively. As you may have noticed, the Content classes tend to have a one-to-one correspondence with the stored procedures that they call.

I created four class libraries, AddJob.vb, EditJob.vb, JobSection.vb, and Job.vb, each modeled on the Articles content class libraries. The content classes are the glue between the JobInfo class and the user control skins. The Job class from the Job.vb class library is responsible for displaying the complete information for a particular job listing (see Figure 8).

Figure 8 Job.vb Class Library (Close Figure 8)
Imports System
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports ASPNET.StarterKit.Communities

Namespace Jobs
    Public Class Job
        Inherits ContentItemPage

        Private _skinFileName As String = "Jobs_Job.ascx"

        Private _getContentItem As New _
         GetContentItemDelegate(AddressOf JobUtility.GetJobInfo)

        Public Sub New()
            SkinFileName = _skinFileName
            GetContentItem = _getContentItem
        End Sub 'New
    End Class 'Job
End Namespace

The Controls folder will contain custom server controls for each of the job items displayed on the Job and JobSection pages. I created the following server controls for the Job page: JobCompany, JobTempStatus, JobTempMonths, JobStartDate, JobLocation, JobEducation, JobExperience, JobSalary, JobContactName, JobContactPhone, JobContactEmail, JobContactWeb, and JobEditContent. For the JobSection page, I created the ItemJobLocation control. The JobCompany custom control is shown in Figure 9.

Figure 9 The JobCompany.vb Custom Server Control (Close Figure 9)
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.ComponentModel
Imports ASPNET.StarterKit.Communities.Jobs

<Designer(GetType(ASPNET.StarterKit.Communities.CommunityDesigner))> _
 Public Class JobCompany
    Inherits WebControl

    Private _text As String

    Public Sub New()
        CssClass = "jobOtherFields"

        ' Get ContentInfo object
        If Not (Context Is Nothing) Then
            Dim objJobCompany As JobInfo = _
             CType(Context.Items("ContentInfo"), JobInfo)
            If Not (objJobCompany Is Nothing) Then
                _text = objJobCompany.Company
            End If
        End If
    End Sub 'New

        Public Property [Text]() As String
        Get
            Return _text
        End Get
        Set(ByVal Value As String)
            _text = value
        End Set
    End Property

    Protected Overrides Sub RenderContents( _
     ByVal writer As HtmlTextWriter)
        writer.Write(HttpUtility.HtmlEncode(_text))
    End Sub 'RenderContents
End Class 'JobCompany

Now you need to create user controls that represent the content skins for each of the basic module pages that correspond to the SkinnedCommunityControl classes created in the previous step. At a minimum, you must provide one set of content skin user controls for the Default theme. You may also provide content skin user controls for additional themes.

I created three user controls under the Themes\Default\Skins\ContentSkins folder: Jobs_AddJob.ascx, Jobs_JobSection.ascx, and Jobs_Job.ascx. There is no user control for editing a job because the Jobs_AddJob.ascx control does double duty, providing support for both adding and editing jobs. An excerpt of the Jobs_Job.ascx user control is shown in Figure 10. Notice the references to the custom server controls named JobCompany and JobTempStatus from the Engine\Modules\Jobs\Controls folder.

Figure 10 The Jobs_Job.ascx User Control (Close Figure 10)
<%@ Control %>
<%@ Register TagPrefix="Community"
Namespace="ASPNET.StarterKit.Communities"
Assembly="ASPNET.StarterKit.Communities" %>

<table cellPadding=5 width=520>
  <tr>
    <td align=right colSpan=2><community:displaytopic id=topicPreview
      runat="Server"></community:DisplayTopic></TD></TR>
  <tr>
    <td class="Job_Label" colSpan=2><community:title id=lblPreviewTitle
      CssClass="Job_Title" Runat="Server"></community:Title></TD></TR>
  <tr>
    <td class="Job_Label">Description: </TD>
    <td><community:briefdescription id=lblPreviewBriefDescription
      CssClass="Job_BriefDescription" Runat="Server">
      </community:BriefDescription></TD></TR>
  <tr>
    <td class=Job_Label>Company:</TD>
    <td><community:jobcompany id=lblPreviewCompany
      CssClass="Job_OtherFields" Runat="Server"></community:JobCompany>
    </TD></TR>
  <tr>
    <td class="Job_Label">Temporary Job?</TD>
    <td><community:jobtempstatus id=lblPreviewTempStatus
      CssClass="Job_OtherFields" Runat="Server">
      </community:JobTempStatus></TD></TR>
<!-- .. ascx page continues ... -->

At this point, I added styles to the default style sheet (Themes\theme\ Styles\default.css) for the new content controls. Several of these new styles are shown in Figure 11.

Figure 11 Subset of New Styles for Jobs Module (Close Figure 11)
.JobSection_SectionTitle {font-weight: bold; font-size: 17px;
    font-variant: small-caps;}
.JobSection_SectionDescription {font-family: verdana, arial, helvetica,
    sans-serif; font-size: 12px; font-weight: normal;}

.Job_Table {border: none;}
.Job_IntroCell {background-color:#EEEEEE;border:#CCCCFF solid 1px;}
.Job_BodyCell {background-color:#FFFFFF;}
.Job_Title {font-weight: bold; font-size: 17px; font-variant:
    small-caps;}
.Job_BriefDescription {font-size: 12px; font-style: italic}
.Job_Label {font-size: 12px; font-weight: bold; font-style: italic;
    background-color:#FFFFFF;}
.Job_OtherFields {font-size: 12px; color: #333333}

Finally, I added entries for the item and item section pages to the Community_PageTypes and Community_NamedPages tables in the database. This step is relatively easy thanks to the stored procedure named Community_ MaintenanceInitializeJobs that I patterned after the existing Community_MaintenanceInitializeArticles stored procedure. Running this stored procedure created the necessary table entries.

Figure 12 JobSection Page
Figure 12 JobSection Page

Figure 12 shows the JobSection page for the Ballard .NET community site. If you click on the Read More link under a job posting, the Job page will display as shown in Figure 13.

Figure 13 Job Page
Figure 13 Job Page

Download a copy of the files that make up the Jobs module (as well as the Small theme) from the link at the top of this article.

Conclusion

The ASP.NET Starter Kits provide several prepackaged sample applications that offer both functional Web site templates and handy sample code in Visual Basic .NET, C#, and J#. The kits represent the most common Web apps, including community, commerce, portal, reporting, and project time-tracking sites. This article discussed the installation and use of the starter kits, as well as ways to customize and extend the Community Starter Kit through code (even if you don't own a copy of Visual Studio .NET). If you're looking for a way to jump start your ASP.NET site, these kits might be the set of jumper cables you're looking for. It's also worth mentioning that several Web hosting companies are offering automatic deployment of the starter kits along with account registration. See http://www.asp.net/hosters for details.

Thanks to Stephen Walther for his help with this article.

For background information see:
ASP.NET Starter Kits
http://msdn.microsoft.com/aspnet

Paul Litwin is the CEO and a senior trainer for Deep Training, a .NET training company. He is the conference chair for Microsoft ASP.NET Connections and has authored and co-authored a number of books, including ASP.NET for Developers (SAMS, 2001). He is currently working on a new book about ASP.NET. You can reach Paul at paull@deeptraining.com.

© 2007 Microsoft Corporation and CMP Media, LLC. All rights reserved; reproduction in part or in whole without permission is prohibited.


DataGrid: Tailor Your DataGrid Apps Using Table Style and Custom Column Style Objects

DataGrid

Tailor Your DataGrid Apps Using Table Style and Custom Column Style Objects

Kristy Saunders
This article assumes you're familiar with SQL Server and Windows Forms
Level of Difficulty 1 2 3
Code download available at: DataGrids.exe (424KB)
SUMMARY

One of the most enduring challenges in writing user interfaces is figuring out how to display large amounts of data efficiently and intuitively without bewildering the user. The problem becomes particularly thorny when the interface must reflect hierarchical relationships within the data that the user needs to modify. The Windows Forms DataGrid control gives developers a powerful and flexible tool to meet this challenge. This article explains its basic operations and shows how to extend the DataGrid to display columns of data in an application-appropriate manner.

Contents
The Timesheet Application
Loading the DataSet
Displaying Client Data in a DataGrid Control
Parent-child Relationships in a DataSet
Customize the DataGrid with Properties
DataGridTableStyle and DataGridColumnStyle
Implementing a Custom Column Style
Windows Forms Data Binding in a Nutshell
The DataGridComboBoxColumn Class
The DataGridComboBoxColumn Column Style
Conclusion

T
he DataGrid is one of the most powerful Windows® Forms controls. By using default DataGrid property settings you can easily bind a DataSet to a DataGrid to create an impressive Windows Forms user interface—one that allows users to drill down through multiple levels of hierarchical data and sort and reorder columns. While it is easy to get started with the DataGrid control, leveraging its more sophisticated capabilities—for example, creating custom column displays—remains lightly documented and requires some trial and error. In this article I will explain how to bind to a DataGrid, illustrate the basic features of the control, and show how to exercise extensive control over the appearance and behavior of tables and columns. Finally, I'll demonstrate how you can extend the DataGrid control with custom column style objects by implementing a type that displays column values in a dropdown combobox.

The Timesheet Application

To illustrate various features of DataGrid controls, this article will walk through the implementation of a timesheet application for a fictional software consulting firm. Take a look at Figure 1 to see the complete application in action.

Figure 1 The Timesheet App
Figure 1 The Timesheet App

The UI is a Windows Forms application that contains three DataGrid controls (see Figure 2). The first control provides a read-only listing of all clients. The second provides a read-only listing of all projects associated with the selected client. The third DataGrid control displays an employee's timesheet entries billable to the selected project. This DataGrid allows a user to input and edit time entries, but the user cannot modify items in the client and project DataGrid controls.

Figure 2 Relationships
Figure 2 Relationships

Loading the DataSet

The DataGrid controls in the timesheet applications bind to a single DataSet object. While DataGrids can be bound to a variety of types, DataSets are their most common data source. The timesheet DataSet object is populated from a database using four SqlDataAdapter objects. The creation and configuration of the SqlDataAdapter objects is trivial when using the DataAdapter Configuration Wizard in Visual Studio® .NET. Note that I use stored procedures with the SqlDataAdapter objects to access rows from the Client, Project, TimeEntry, and Activity tables, and to insert, update, and delete rows from the TimeEntry table. This is because the timesheet app uses database-generated identity columns for primary keys—a common practice. After an insert operation, this database-generated value must be mapped back to the corresponding column in the DataTable for subsequent operations on that row. The most reliable method of retrieving this value is to use an INSERT stored procedure that returns it as an output parameter.

Code in the form's Load event handler, shown in Figure 3, populates the DataSet when the app starts. Note that the MissingSchemaAction property of the time entry SqlDataAdapter object is set to MissingSchemaAction.AddWithKey. This causes the return of more schema info, such as primary key information, constraint information, and column properties including whether null values are allowed. By default, only column name metadata is retrieved by the Fill method. When the application allows users to add and update rows in a DataTable object, additional schema information will help detect invalid values and constraint violations.

Figure 3 Loading Four Tables into a DataSet (Close Figure 3)
private void Form1_Load(object sender, System.EventArgs e)
{
    try
    {
        // Use client and project DataAdapters to fill
        // first two tables with clients and projects
        sqlDataAdapterClients.Fill(dataSetTimeTracker, "Client");
        sqlDataAdapterProjects.Fill(dataSetTimeTracker, "Project");

        // Hardcode EmployeeID parameter for _GetTimeEntries sproc
        sqlSelectCommand3.Parameters["@EmployeeID"].Value = 1;
        // Set AddWithKey to get primary key restraints
        sqlDataAdapterEntries.MissingSchemaAction =
           MissingSchemaAction.AddWithKey;
        // Use entries DataAdapter to fill 3rd table in DataSet
        sqlDataAdapterEntries.Fill(dataSetTimeTracker, "TimeEntry");

        // Use activities DataAdapter to fill 4th table in DataSet
       sqlDataAdapterActivities.Fill(dataSetTimeTracker, "Activity");
    }
    catch (System.Exception ex)
    {
        MessageBox.Show(ex.Message, Text);
    }
}

Displaying Client Data in a DataGrid Control

Once you have populated a DataSet object, it is simple to get a DataGrid to display the data. To do this, you use the SetDataBinding method of the DataGrid to specify which DataSet, and which DataTable within that DataSet, to use. The following code binds the Client table in the DataSet to the dataGridClients object:

// Bind the Client table to the client's DataGrid
dataGridClients.SetDataBinding(dataSetTimesheet, "Client");
Figure 4 shows the DataGrid for the Client table.

Figure 4 Client Table DataGrid
Figure 4 Client Table DataGrid

Once the DataTable object is bound to the DataGrid, the DataGrid uses the column metadata for the Client table to set the column headers, then uses the data in the Client table to populate the DataGrid. At this point, you can click on column headers to sort by column. Though this is fairly basic, you will soon see how to perform more sophisticated tricks with a DataGrid.

Parent-child Relationships in a DataSet

You saw in Figure 2 that a parent-child relationship exists between the Client and Project tables based on the ClientID column. In addition, the ProjectID column defines a parent-child relationship between the Project and TimeEntry tables. Even though the DataSet tables contain the ClientID and ProjectID columns, the DataSet is unaware of these relationships. To relate tables in a DataSet you must explicitly define the relationship by adding a DataRelation object to the DataSet's Relations collection. The timesheet app uses these two statements to create the Client-Project and Project-TimeEntry relationships:

dataSetTimesheet.Relations.Add("Client2Projects",
dataSetTimesheet.Tables["Clients"].Columns["ClientID"],
dataSetTimesheet.Tables["Projects"].Columns["ClientID"]);

dataSetTimesheet.Relations.Add("Project2TimeEntries",
dataSetTimesheet.Tables["Projects"].Columns["ProjectID"],
dataSetTimesheet.Tables["TimeEntry"].Columns["ProjectID"]);

To add a relationship, you provide a name for the DataRelation ("Client2Projects" and "Project2TimeEntries" in the example given) and specify the column in both the parent and child table that will relate the two tables.

Figure 5 DataGrid with Relations
Figure 5 DataGrid with Relations

Figure 5 shows the DataGrid control after these DataRelation objects have been defined. Notice that the name of the child relationship ("Client2Projects") displays as a Web link beneath each row when the plus sign (+) is expanded. Clicking the first link navigates to the children of the client Woodgrove Bank in the Projects table (see Figure 6).

Figure 6 Children in Projects
Figure 6 Children in Projects

You can click the back button in the DataGrid caption to return to the parent table. The right-most button allows you to toggle the display of the parent row. Notice that "Project2TimeEntries" Web links are displayed when a "+" icon is expanded.

While the built-in navigation support is useful, the drill-down model might not provide the ideal display of hierarchical information for your app. One alternative is to use multiple DataGrids to display related data. To display a table containing just the child rows of a parent that has been selected in another DataGrid control, you can bind the child DataGrid control to a relationship name rather than to a DataTable object. You specify a relationship by providing the parent table name followed by the relationship name, delimited by a period. Note that you can bind to multiple levels of DataRelations. For example, in the timesheet application, "Client.Clients2Project" specifies the Clients2Project DataRelation object and "Client.Clients2Project.Project2TimeEntries" specifies the Project2TimeEntries DataRelation object. Binding to this relationship will return the child rows of the project that was selected in the selected client.

The following code binds the first timesheet DataGrid to the Clients table, the upper-right DataGrid to the "Clients.Clients2Project" relationship, and the bottom DataGrid to Clients.Clients2Project.Project2TimeEntries:

// Bind the Client table to the client's DataGrid
dataGridClients.SetDataBinding(dataSetTimesheet,
   "Client");
// Bind Client2Project to projects datagrid
dataGridProjects.SetDataBinding(dataSetTimesheet,
   "Client.Client2Projects");
// Bind Project2TimeEntries to tasks datagrid
 dataGridEntries.SetDataBinding(dataSetTimesheet,
   "Client.Client2Projects.Project2TimeEntries");

Setting the AllowNavigation property to false for each DataGrid disables the built-in navigation shown in Figure 5, Figure 6, and Figure 7. Setting the ReadOnly property of the clients and project DataGrids to true ensures that the rows displayed in these DataGrids cannot be edited. See Figure 7 for these changes.

Figure 7 Three Grids
Figure 7 Three Grids

Notice that the project DataGrid control displays only those child rows of the client row that are selected in the client DataGrid. Likewise, the time entry rows displayed in the time entry DataGrid are only those that are children of the row selected in the project DataGrid. When a different client or project is selected, the project and time entry DataGrid controls are automatically refreshed to reflect the corresponding child rows. In the time entries DataGrid control, users can delete a row by selecting it and hitting the Delete key and they can add a row by entering values at the last row, marked by an asterisk.

Customize the DataGrid with Properties

The timesheet app is now fully functional—users can make entries and persist changes to the database using the time entries SqlDataAdapter object. But the UI needs work.

Various DataGrid properties allow you to customize the DataGrid appearance. These properties are listed in Figure 8. Within Visual Studio .NET, you can choose from a number of pre-formatted schemes from the Auto Format dialog—much like you can auto-format cells in a Microsoft® Excel spreadsheet. This dialog is accessible from a link at the bottom of the DataGrid Properties window. I am partial to the "Professional 3" scheme, so I chose it here. Selecting this scheme sets the properties listed in Figure 8.

Figure 8 DataGrid Properties that Affect Appearance (Close Figure 8)

Property | Description
AllowSorting | Indicates whether the grid can be re-sorted by clicking on a column header
AlternatingBackColor | Background color of alternating rows for a ledger appearance
BackColor | Background color of the grid
BackgroundColor | Color of the non-row area of the grid
BorderStyle | Grid's border style
CaptionBackColor | Background color of the caption area
CaptionFont | Font of the grid's caption
CaptionForeColor | Foreground color of the caption area
CaptionText | Text of the grid's window caption
CaptionVisible | Indicates whether the grid's caption is visible
ColumnHeadersVisible | Indicates whether the column headers in a table are visible
FirstVisibleColumn | Index of the first visible column in a grid
FlatMode | Value indicating whether the grid displays in flat mode
GridLineColor | Color of the grid lines
GridLineStyle | Line style of the grid
HeaderBackColor | Background color of all row and column headers
HeaderFont | Font used for column headers
HeaderForeColor | Foreground color of headers
LinkColor | Color of the text that you can click to navigate to a child table
LinkHoverColor | Color a link changes to when the mouse pointer moves over it
ParentRowsBackColor | Background color of parent rows
ParentRowsForeColor | Foreground color of parent rows
ParentRowsLabelStyle | Display style for parent row labels
ParentRowsVisible | Indicates whether the parent rows of a table are visible
PreferredColumnWidth | Default width of the grid columns in pixels
PreferredRowHeight | Preferred row height for the SystemWindowsFormsDataGrid control
RowHeadersVisible | Specifies whether row headers are visible
RowHeaderWidth | Width of row headers
SelectionBackColor | Background color of selected rows
SelectionForeColor | Foreground color of selected rows

DataGridTableStyle and DataGridColumnStyle

Next I want to make some tables and columns read-only, hide some columns, show numeric columns in a particular format, label columns with different text, and choose appropriate default column widths. These changes require the use of DataGridTableStyles and DataGridColumnStyles objects.

With DataGridTableStyle objects, you can set appearance properties and selectively omit or reorder displayed columns. By adding multiple DataGridTableStyle objects to the TableStyle collection of a DataGrid, you can create a number of distinct appearances. This makes it possible to programmatically toggle between display schemes. In addition, when a single DataGrid control is structured to display multiple tables, you can map a different DataGridTableStyle object to each table.

With DataGridColumnStyle objects you can affect column appearance, behavior, and data formatting. DataGridColumnStyle is an abstract class for which the Microsoft .NET Framework provides two implementations: the DataGridTextBoxColumn class and the DataGridBoolColumn class. When you bind to a DataGrid without providing DataGridColumnStyle objects, the DataGrid automatically creates them. The type created depends on the data type of the associated column. For example, in the timesheet app, the Approved column is a System.Boolean. The DataGrid creates a DataGridBoolColumn to display this column (notice the checkbox in Figure 7). All other columns in the application are displayed using DataGridTextBoxColumn objects.

Next I'll use DataGridTableStyle and DataGridColumnStyle objects to refine the timesheet's appearance by:

  • Omitting the display of the database identity columns ClientID, ProjectID, and TimeEntryID in the DataGrid controls. Because these columns are identifiers, meaningful only to the database, this is okay.
  • Adjusting column widths in each DataGrid.
  • Omitting the display of the EmployeeID column in the time entries DataGrid control. It is not necessary to display the EmployeeID because all time entries will always belong to the same employee. The application will set the DefaultValue property of the related DataColumn to the current user's employee ID, giving new rows a value for this column.
  • Formatting the Duration column in the time entries DataGrid control such that the column values display with two digits behind the decimal point.
  • Ensuring that the Approved column in the time entries DataGrid control is read-only.

You can use the Properties window associated with a DataGrid control to create DataGridTableStyle and DataGridColumnStyle objects. Figure 9 shows the DataGridTableStyle Collection Editor dialog, accessible by clicking the TableStyles property in the DataGrid Properties window.

Figure 9 DataGridTableStyle Properties
Figure 9 DataGridTableStyle Properties

When you add a DataGridTableStyle object, you associate it with one of the tables bound to the DataGrid control data source by setting the MappingName property to the name of the table. Figure 10 shows code generated by Visual Studio .NET to configure a DataGridTableStyle object and DataGridColumnStyle object in the timesheet application.

Figure 10 DataGridTableStyle and DataGridColumnStyle (Close Figure 10)
//
// dataGridClients
//
•••
dataGridClients.TableStyles.AddRange(new
System.Windows.Forms.DataGridTableStyle[] {
   dataGridTableStyleClients});
•••
//
// dataGridTableStyleClients
//
dataGridTableStyleClients.DataGrid = dataGridClients;
dataGridTableStyleClients.GridColumnStyles.AddRange(new
    System.Windows.Forms.DataGridColumnStyle[] {
    dataGridTextBoxColumnClients});
dataGridTableStyleClients.HeaderForeColor =
    System.Drawing.SystemColors.ControlText;
dataGridTableStyleClients.MappingName = "Client";
//
// dataGridTextBoxColumnClients
//
dataGridTextBoxColumnClients.Format = "";
dataGridTextBoxColumnClients.FormatInfo = null;
dataGridTextBoxColumnClients.HeaderText = "Clients";
dataGridTextBoxColumnClients.MappingName = "ClientName";
dataGridTextBoxColumnClients.Width = 200;

By adding a DataGridTextBoxColumn or DataGridBoolColumn object to the GridColumnStyle collection of the DataGridTableStyle object, you indicate which columns you want to display within the table style. To associate a particular DataGridColumnStyle object to a data source column, you set its MappingName property to the name of the column. Other important DataGridColumnStyle properties include the HeaderText, Width, ReadOnly, and Format properties. The HeaderText property controls the text to display in the column header; the default is the name of the column. You can set the Format property of DataGridTextBoxColumn objects to a .NET Framework format-specifier to display the text in a particular format. For example, setting the Format property of the DataGridTextBoxColumn object mapped to the Duration column in the TimeEntry table to "#.00" causes the value to display with two digits behind the decimal point.

There is one problem though—the display schemes selected with AutoFormat are lost. They are only applied to the DataGrid itself, not to any associated DataGridTableStyle objects. Because it's convenient to use AutoFormat to design the display and DataGridTableStyle objects to customize column display, I added the method in Figure 11 to copy default DataGrid properties to a DataGridTableStyle object.

Figure 11 CopyDefaultTableStyle Method (Close Figure 11)
// Copy the display-related properties of the given DataGrid
// to the given DataGridTableStyle
private void CopyDefaultTableStyle(DataGrid datagrid,
                                   DataGridTableStyle ts)
{
    ts.AllowSorting = datagrid.AllowSorting;
    ts.AlternatingBackColor = datagrid.AlternatingBackColor;
    ts.BackColor = datagrid.BackColor;
    ts.ColumnHeadersVisible = datagrid.ColumnHeadersVisible;
    ts.ForeColor = datagrid.ForeColor;
    ts.GridLineColor = datagrid.GridLineColor;
    ts.GridLineStyle = datagrid.GridLineStyle;
    ts.HeaderBackColor = datagrid.HeaderBackColor;
    ts.HeaderFont = datagrid.HeaderFont;
    ts.HeaderForeColor = datagrid.HeaderForeColor;
    ts.LinkColor = datagrid.LinkColor;
    ts.PreferredColumnWidth = datagrid.PreferredColumnWidth;
    ts.PreferredRowHeight = datagrid.PreferredRowHeight;
    ts.ReadOnly = datagrid.ReadOnly;
    ts.RowHeadersVisible = datagrid.RowHeadersVisible;
    ts.RowHeaderWidth = datagrid.RowHeaderWidth;
    ts.SelectionBackColor = datagrid.SelectionBackColor;
    ts.SelectionForeColor = datagrid.SelectionForeColor;
}

To copy the display-related properties of a DataGrid control to a DataGridTableStyle object that will be used by the DataGrid, you can use the CopyDefaultTableStyle method. The following code shows how to copy the grid defaults to the associated table styles:

// Copy data grid defaults to associated table styles
CopyDefaultTableStyle(dataGridClients, dataGridTableStyleClients);
CopyDefaultTableStyle(dataGridProjects, dataGridTableStyleProjects);
CopyDefaultTableStyle(dataGridEntries, dataGridTableStyleEntries);
Figure 12 shows the result of applying the table and column styles.

Figure 12 Result of Styles
Figure 12 Result of Styles

Implementing a Custom Column Style

Applying table and column styles has improved the data display, but before calling it a day, take a look at the Activity column in Figure 12. Notice back in Figure 2 that the ActivityID column of the TimeEntry table is a foreign key into the Activity table. Using the associated activity description would be more helpful than showing this identifier, but the stored procedures that insert into and update the database require the identifier.

A typical solution is to display valid descriptions in a combobox control. By deriving a column style class from the abstract DataGridColumnStyle, you can create a column style that uses a combobox control to allow selection of an arbitrary group of column values. The last section of this article implements a custom combobox column style class. However, understanding it will require a basic understanding of Windows Forms data binding.

Windows Forms Data Binding in a Nutshell

The concepts you must understand include how both simple and complex data binding work, how Windows Forms handles synchronization between multiple controls bound to the same data source, and how binding contexts are used.

First, Binding objects are used to bind a control property to a single data element. A Binding object describes an association between a control property and a data source. You can, for example, create a Binding object to bind the Text property of a textbox control to the value of a particular column in a DataTable. Simple binding refers to a control's ability to bind to a single data element.

All Windows Forms controls expose a DataBindings property that represents a collection of Binding objects. An application typically binds a data source to the control property that displays some data to the user, such as the Text property of a textbox. However, the Binding class allows you to bind any control property to a data source member. For example, this adds Binding objects to the DataBindings collection of two textbox controls:

textBox1.DataBindings.Add("Text", userList, "Name");
textBox1.DataBindings.Add("BackColor", userList, "FavoriteColor");
textBox2.DataBindings.Add("Text", userList, "PhoneNumber");

In this example, userList is an ArrayList instance populated with objects of type User. The User type exposes Name, FavoriteColor, and PhoneNumber properties. The first statement binds the Text property of a textbox control to the Name property of an object in an ArrayList named userList. The second statement binds the BackColor property of the same textbox control to the FavoriteColor property. The last statement binds the Text property of a second textbox control to the PhoneNumber property.

Second, the DataSource and DataMember properties of the DataGrid, combobox, listbox, and ErrorProvider controls are used to support complex data binding. Controls that support complex data binding have the ability to bind to and concurrently display a set of items, such as all the rows in a DataTable. Set the DataSource property to a data source such as a DataTable or an ArrayList. When the object referenced by DataSource is a type that can contain multiple lists, mainly the DataSet, you can set the DataMember property to indicate which list is the binding target.

The DataGrid control includes a SetDataBinding method that sets the DataSource and DataMember properties. The timesheet application uses this method:

// Bind the Clients table to the clients datagrid
dataGridClients.SetDataBinding(dataSetTimesheet, "Clients");

Third, binding manager objects manage synchronization between multiple controls bound to the same data source. A data source is usually an instance of an ADO.NET type such as DataSet, DataTable, DataView, or DataViewManager, or another type such as ArrayList, that implements the IList interface. None of these types implement internal mechanisms to denote an item in the list as current. In applications that use data binding, the lack of such an internal mechanism presents a problem: how do multiple controls bound to the same data source manage to stay synchronized so that all bound Windows Forms controls display a value from the same row or item? In the previous example, how is the application assured that the Name and PhoneNumber that is displayed in the textbox controls will always belong to the same user in the ArrayList?

Windows Forms data binding solves this problem with two types that derive from the abstract BindingManagerBase class—the CurrencyManager class and the PropertyManager class. The first time any control binds to a particular data source, a BindingManagerBase object is automatically created. This object is shared by controls that subsequently bind to the same data source.

CurrencyManager objects are used to set the current position in list-based data sources. For example, the following code sets the Position property of the CurrencyManager associated with the userList given in the first point to set the next item as current:

myCurrencyManager = (CurrencyManager)BindingContext[userList];
myCurrencyManager.Position += 1;
This causes the bound textbox control properties to change to property values given by the next User item in userList. I will explain the BindingContext object next.

Controls usually bind to list-based types such as the data sources I just mentioned. However, any class that exposes public properties can be databound. You could, for example, bind the Text property of a textbox control to a single instance of the User class just described, instead of to an ArrayList of User items. PropertyManager objects are used to manage data sources that are not list-based. PropertyManager objects have a Position property, but setting it has no effect. Finally, CurrencyManager and PropertyManager objects exist within a BindingContext. A BindingContext is simply a collection of CurrencyManager and PropertyManager objects. More than one BindingContext can be created for a single form, but by default a single BindingContext is automatically created for each Windows Form. Each control can only be associated with a single BindingContext.

Each Windows control has a BindingContext property that returns the BindingContext to which the control belongs. You can use this property to retrieve the CurrencyManager or PropertyManager related to the data source bound to the control. How you retrieve the CurrencyManager associated with the control's data source is shown in the following line of code:

CurrencyManager cm = (CurrencyManager)(BindingContext[DataSource]);
By using the List property of the CurrencyManager, you can then access the bound data source, in this case a DataView object:
DataView dataview = ((DataView)cm.List);

The ability to access the data source by using a control's BindingContext property can be useful. You will see how the implementation of the DataGridComboBoxColumn uses this technique to access the data source bound to the combobox control member.

The DataGridComboBoxColumn Class

Recall that the timesheet application requires a combobox column style with the following characteristics:

  • When the DataGrid cell in the column is edited, it displays a dropdown combobox control within the cell bounds. When not being edited, the cell displays the selected description.
  • Applications that use the column style class can bind a combobox class member to any DataTable object. By setting the DisplayMember and ValueMember combobox properties, the application can specify which DataTable column contains descriptions to display and which column contains a corresponding identifier to return to the DataGrid control.
  • Because this is a common need in DataGrid applications, the derived column style should be generic and reusable.

This section will provide an overview of a combobox column style implementation. The complete implementation is shown in Figure 13. The class derives from the DataGridTextBoxColumn class instead of from the DataGridColumnStyle class. This lets the column style class borrow useful DataGridTextBoxColumn functionality, such as the ability to easily display text when the DataGrid cell is not being edited.

Figure 13 DataGridComboBoxColumn Class (Close Figure 13)
// Derive class from DataGridTextBoxColumn
public class DataGridComboBoxColumn : DataGridTextBoxColumn
{
    // Hosted combobox control
    private ComboBox comboBox;
    private CurrencyManager cm;
    private int iCurrentRow;

    // Constructor - create combobox,
    // register selection change event handler,
    // register lose focus event handler
    public DataGridComboBoxColumn()
    {
        this.cm = null;

        // Create combobox and force DropDownList style
        this.comboBox = new ComboBox();
        this.comboBox.DropDownStyle = ComboBoxStyle.DropDownList;

        // Add event handler for notification when combobox loses focus
        this.comboBox.Leave += new EventHandler(comboBox_Leave);
    }

    // Property to provide access to combobox
    public ComboBox ComboBox
    {
        get { return comboBox; }
    }

    // On edit, add scroll event handler, and display combobox
    protected override void Edit(System.Windows.Forms.CurrencyManager
       source, int rowNum, System.Drawing.Rectangle bounds, bool readOnly,
       string instantText, bool cellIsVisible)
    {
        base.Edit(source, rowNum, bounds, readOnly, instantText,
            cellIsVisible);

        if (!readOnly && cellIsVisible)
        {
            // Save current row in the DataGrid and currency manager
            // associated with the data source for the DataGrid
            this.iCurrentRow = rowNum;
            this.cm = source;

            // Add event handler for DataGrid scroll notification
            this.DataGridTableStyle.DataGrid.Scroll
                += new EventHandler(DataGrid_Scroll);

            // Site the combobox control within the current cell
            this.comboBox.Parent = this.TextBox.Parent;
            Rectangle rect =
                this.DataGridTableStyle.DataGrid.GetCurrentCellBounds();
            this.comboBox.Location = rect.Location;
            this.comboBox.Size =
                new Size(this.TextBox.Size.Width,
                         this.comboBox.Size.Height);

            // Set combobox selection to given text
            this.comboBox.SelectedIndex =
                this.comboBox.FindStringExact(this.TextBox.Text);

            // Make the combobox visible and place on top textbox control
            this.comboBox.Show();
            this.comboBox.BringToFront();
            this.comboBox.Focus();
        }
    }

    // Given a row, get the value member associated with a row.  Use the
    // value member to find the associated display member by iterating
    // over bound data source
    protected override object
    GetColumnValueAtRow(System.Windows.Forms.CurrencyManager source,
        int rowNum)
    {
        // Given a row number in the DataGrid, get the display member
        object obj =  base.GetColumnValueAtRow(source, rowNum);

        // Iterate through the data source bound to the ColumnComboBox
        CurrencyManager cm = (CurrencyManager)
        (this.DataGridTableStyle.DataGrid.BindingContext[this.comboBox.DataSource]);
        // Assumes the associated DataGrid is bound to a DataView or
        // DataTable
        DataView dataview = ((DataView)cm.List);

        int i;

        for (i = 0; i < dataview.Count; i++)
        {
            if (obj.Equals(dataview[i][this.comboBox.ValueMember]))
                break;
        }

        if (i < dataview.Count)
            return dataview[i][this.comboBox.DisplayMember];

        return DBNull.Value;
    }

    // Given a row and a display member, iterate over bound data source to
    // find the associated value member.  Set this value member.
    protected override void
        SetColumnValueAtRow(System.Windows.Forms.CurrencyManager source,
        int rowNum, object value)
    {
        object s = value;

        // Iterate through the data source bound to the ColumnComboBox
        CurrencyManager cm = (CurrencyManager)
        (this.DataGridTableStyle.DataGrid.BindingContext[this.comboBox.DataSource]);
        // Assumes the associated DataGrid is bound to a DataView or
        // DataTable
        DataView dataview = ((DataView)cm.List);
        int i;

        for (i = 0; i < dataview.Count; i++)
        {
            if (s.Equals(dataview[i][this.comboBox.DisplayMember]))
                break;
        }

        // If set item was found return corresponding value,
        // otherwise return DbNull.Value
        if(i < dataview.Count)
            s =  dataview[i][this.comboBox.ValueMember];
        else
            s = DBNull.Value;

        base.SetColumnValueAtRow(source, rowNum, s);
    }

    // On DataGrid scroll, hide the combobox
    private void DataGrid_Scroll(object sender, EventArgs e)
    {
        this.comboBox.Hide();
    }

    // On combobox losing focus, set the column value, hide the combobox,
    // and unregister scroll event handler
    private void comboBox_Leave(object sender, EventArgs e)
    {
        DataRowView rowView = (DataRowView) this.comboBox.SelectedItem;
        string s = (string) rowView.Row[this.comboBox.DisplayMember];

        SetColumnValueAtRow(this.cm, this.iCurrentRow, s);
        Invalidate();

        this.comboBox.Hide();
        this.DataGridTableStyle.DataGrid.Scroll -=
            new EventHandler(DataGrid_Scroll);
    }
}

The DataGridComboBoxColumnStyle class contains a private combobox member and a read-only property to access it. The class constructor also registers an event handler to receive notification when focus leaves the combobox.

By overriding the Edit method, the class can track when editing begins by saving the current row and CurrencyManager object. The code then registers an event handler for the DataGrid Scroll event. Finally, this method shows the combobox control within the bounds of the DataGrid cell, selecting the display value associated with the cell value.

The Leave event handler tracks when editing ends. This method calls the SetColumnValueAtRow method to save the value associated with the currently selected item in the combobox control. To do this, it uses the CurrencyManager object and row saved in the Edit method. Then this method hides the combobox control and unregisters the Scroll event handler.

The base class GetColumnValueAtRow and SetColumnValueAtRow methods retrieve and save a value to a DataGrid cell. By overriding the GetColumnValueAtRow method, the DataGridComboBoxColumnStyle code can translate a display value to a corresponding value to save. When used by the timesheet application, this method will switch the selected Activity description for an ActivityID value. To do this, the overridden method calls the base class method to get the DataGrid cell value. It uses the BindingContext property of the DataGrid to get the DataView object that is associated with the bound data source. The DataView object is used to look up and return the display member column associated with the cell value.

Overriding the SetColumnValueAtRow method does the opposite—a saved DataGrid cell value is translated to a value to display. A DataView object, retrieved using the BindingContext property of the DataGrid, is used to find the value member column associated with the display member. The base class SetColumnValueAtRow is then called to set the underlying DataGrid cell value to the found value member. Finally, the Scroll event handler hides the combobox when the DataGrid is scrolled.

The DataGridComboBoxColumn Column Style

To use the combobox in the time entries DataGrid, three changes to the existing form are required. First, all references to the existing DataGridTextBoxColumn object for the ActivityID column are replaced with a reference to the DataGridComboBoxColumn object in the form's InitializeComponent method.

Second, the following code is added to the form's Load event handler to bind the combobox to the Activity table data source:

// Bind activities to comboBoxActivities
dataGridComboBoxColumnActivity.ComboBox.DataSource =
    dataSetTimesheet.Tables["Activity"];
dataGridComboBoxColumnActivity.ComboBox.DisplayMember = "Description";
dataGridComboBoxColumnActivity.ComboBox.ValueMember = "ActivityID";
The second and third statements instruct the combobox to display the Description column while using the value in the corresponding ActivityID column.

Finally, setting the row height for the DataGrid control to the height of the combobox plus space for a small margin around it guarantees that the combobox will display completely within the DataGrid control cells.

// Set task datagrid preferred height to combobox height
dataGridEntries.PreferredRowHeight =
   dataGridComboBoxColumnActivity.ComboBox.Height + 1;

Conclusion

Built-in navigation, plus the ability to bind to the all-powerful DataSet and tailor all aspects of its appearance, make the DataGrid a compelling tool for Windows Forms data-centric applications. And the extensible architecture of column styles make them one of the most powerful features of the DataGrid. This article has provided a simple example of a custom column style. By expanding on this idea to implement your own column styles, you can allow users to view and edit data within the DataGrid in any manner you think is appropriate and intuitive to solve your data display and access problems.

For related articles see:
Windows Forms
Introduction to the Windows Forms DataGrid Control
Windows Forms Data Architecture

For background information see:
Introducing ADO+: Data Access Services for the Microsoft .NET Framework
Using ADO.NET
Data Points: Establishing Relationships Between Rowsets with ADO.NET

Kristy Saunders is a trainer and consultant for SoftSource Consulting (http://www.sftsrc.com). She is based in Portland, Oregon, and can be reached at kristy@sftsrc.com.


XSLT: Simplify Development and Maintenance of Microsoft .NET Projects with Code Generation Techniques

XSLT

Simplify Development and Maintenance of Microsoft .NET Projects with Code Generation Techniques

Peter Ashley
This article assumes you're familiar with XML and XSLT
Level of Difficulty 1 2 3
Code download available at: CodeGeneration.exe (224KB)
SUMMARY

Code generation techniques using technologies such as XSLT are playing an increasingly important part in software projects as they support the development of a rapidly maintainable code base. This article discusses some of the benefits and possible applications of code generation.

To demonstrate these techniques the author develops a Web Forms application that supports the maintenance of records in a SQL Server database, using the database's own metadata to drive the generation process. The SQL Server database schema is extracted using SQLXML 3.0 data access and processed through XSLT stylesheets that generate both a database access layer and a Web Forms user interface with query and update pages.

Contents
Getting to the Metadata
Prototyping the Architecture
Loading and Storing Records
Running Transformations
Stylesheets
Stored Procedure Stylesheet
Web Forms User Interface
Analysis
Implementation Recommendations
Conclusion

D
id you ever have one of those days where you seem to be writing the same code over and over again? If the answer is yes, then congratulations! You've met a key requirement for employing one of the most powerful and underutilized technologies in software development—code generation. The key prerequisite for generating code is the ability to recognize the recurring software patterns that take place while you're programming. Developers usually attempt to abstract these generalized programming constructs into reusable functions, but frequently this approach fails because the code patterns are not regular enough for a general function. Also, devising the right design for a functional style generalization is often more difficult or less type-safe than just generating code, once you're comfortable with the technique.

Code generation is increasingly used for all aspects of programming. Developers employ code generation indirectly almost every day, through compilers, wizards, XSLT, C++ templates, application servers, and so on. Enterprise product developers use code generation techniques to produce mountains of code. But how can developers comfortably harness code generation for their own Microsoft® .NET Framework projects? Even those with slight familiarity with these techniques can reduce tedious coding and attain a much more flexible code base. See the sidebar "Benefits and Costs of Code Generation" for more on this topic.

Fundamentally, the code generation process involves three ingredients. First, you need a concrete model or schema that will be used as input to the process. For example, SQL Server™ schemas, exports of UML models, Web Services Description Language (WSDL) documents, XML Metadata Interchange (XMI) documents, and IDL interfaces all define metadata that can drive a code generation process. Second, you need a .NET Framework code example or prototype that shows what the output code will look like; you will convert this into a generic template. Third, you need a processing facility that can convert modeled data into your desired output code.

Figure 1 Search Criteria
Figure 1 Search Criteria

XSLT is one example of a processing facility that can generate output dynamically based on an abstract pattern. This process is much easier to understand using a concrete example, so I will walk through a sample application that searches database records (partially shown in Figure 1 and Figure 2) to illustrate these principles.

Figure 2 Customer Search Results
Figure 2 Customer Search Results

Getting to the Metadata

In starting this project, my interest was in developing a technique to rapidly construct data access layers that connect directly to an existing SQL Server database. The DataSet-based tools provided in the .NET Framework are great in many situations, but sometimes you want the architectural control provided by direct database access. The SQL Server visual design tools can be used to design the schema on a new project quickly, and an existing project will already have a database, thus using SQL Server schemas seemed like a very good starting point.

I also wanted to use XSLT for my transformation engine because of its wide availability and familiarity to many developers. This project would also provide a challenging use of XSLT to thoroughly burn in my understanding of its syntax. XSLT requires an XML input source, so first I needed to extract my SQL Server schema into an XML format. This operation needs to be automatic, so that I can update the whole system for additions or changes in the database.

To extract the XML model for my code generation process, I wrote a stored procedure that accesses SQL Server system tables. The procedure outputs metadata on tables, views, and procedures, and their associated columns and parameters. The SQL XML capabilities in SQL Server (available as a download for SQL Server 7.0 and built into SQL Server 2000), with the appropriate query as shown in Figure 3, allowed the direct generation of this document in XML format.

Figure 3 Stored Procedure (Close Figure 3)
CREATE PROCEDURE  up_DatabaseXML
/* ***Stored Procedure: up_DatabaseXML ****
* Purpose: Stored Procedure that builds an XML document describing the user
* created tables, views and stored procedures in the database.  A view is
* output as a table, but with a false IsUpdateable attribute.  This
* procedure is highly dependent upon the internal structure of SQL Server
* system tables and metadata, reverse engineered from stored procedures in
* the master database. */
AS SET NOCOUNT ON
/* First select fetches table document elements and defines document format
* through SQLXML explicit format column names. */
SELECT distinct 2 as tag, NULL  as parent,
     syso.name as [Table!2!Name],
     [Table!2!IsUpdateable] = CASE syso.xtype
       WHEN 'u' THEN 1
       WHEN 'v' THEN 0 END,
     NULL as [Column!3!Name],
     NULL as [Column!3!DataType],
     NULL as [Column!3!Length],
     NULL as [Column!3!IsNullable],
     NULL as [Column!3!ID],
     NULL as [Column!3!Description],
     NULL as [Column!3!IsIndexed],
     NULL as [Column!3!IsPrimaryKey],
     NULL as [Column!3!IsIdentity],
     NULL as [Procedure!4!Name],
     NULL as [Parameter!5!Name],
     NULL as [Parameter!5!DataType],
     NULL as [Parameter!5!Length],
     NULL as [Parameter!5!IsNullable],
     NULL as [Parameter!5!ID],
     NULL as [Parameter!5!IsOutParam],
     NULL as [Parameter!5!Description]
FROM sysobjects syso
WHERE syso.name != 'dtproperties'
 AND syso.name != 'meta'
 AND syso.name != 'sysconstraints'
 AND syso.name != 'syssegments'
 AND (syso.xtype = 'u' or syso.xtype = 'v')

/* select procedure elements */
UNION ALL SELECT distinct 4 as tag, NULL  as parent,
     NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
     syso.name, NULL, NULL, NULL, NULL, NULL, NULL, NULL
FROM sysobjects syso
WHERE (syso.xtype = 'p') AND (syso.category = 0)

/* select table column elements */
UNION ALL SELECT DISTINCT 3 as tag, 2 as parent,
     syso.name,
     NULL,
     syscol.name,
     syst.name,
     syscol.length,
     syscol.isnullable,
     syscol.colid,
     isnull(sysp.value, ''),
     isnull((select top 1 1 from sysindexkeys sys
       WHERE syscol.id = id and syscol.colid = colid
       AND sysk.keyno = 1), 0),
       isnull((select top 1 1 from sysindexkeys sysk, sysindexes sysi
       Where syscol.id = sysk.id and syscol.colid = sysk.colid
       AND sysk.id = sysi.id and sysk.indid = sysi.indid
       and (sysi.status & 2048)<>0), 0),
     (syscol.status & 128)/128,
     NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
FROM sysobjects syso
 LEFT JOIN syscolumns syscol ON syso.id = syscol.id
 LEFT JOIN sysindexkeys sysk ON syscol.id = sysk.id
    and syscol.colid = sysk.colid
 LEFT JOIN systypes syst on syscol.xtype = syst.xtype
 LEFT JOIN sysproperties sysp ON syscol.id = sysp.id
 AND syscol.colid = sysp.smallid
WHERE syso.name != 'dtproperties'
 AND syso.name != 'meta'
 AND syso.name != 'sysconstraints'
 AND syso.name != 'syssegments'
 AND (syso.xtype = 'u' or syso.xtype = 'v')
 AND syst.name != 'sysname'

/* select procedure parameter elements */
UNION ALL SELECT 5 as tag, 4 as parent,
     NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
     syso.name,
     syscol.name,
     syst.name,
     syscol.length,
     syscol.isnullable,
     syscol.colid,
     syscol.IsOutParam,
     isnull(sysp.value, '')
FROM sysobjects syso
 LEFT JOIN syscolumns syscol ON syso.id = syscol.id
 LEFT JOIN systypes syst on syscol.xtype = syst.xtype
 LEFT JOIN sysproperties sysp ON syscol.id = sysp.id
 AND syscol.colid = sysp.smallid
WHERE (syso.xtype = 'p') AND (syso.category = 0)
 AND syst.name != 'sysname'

/* organize elements properly in parent, child order */
ORDER BY [Table!2!Name],
     [Column!3!ID],
     [Procedure!4!Name],
     [Parameter!5!ID]
     for xml explicit

Two aspects of the procedure in Figure 3 are especially tricky: the OPTION EXPLICIT column formatting to get the correct XML document structure, and the WHERE clauses that use some lower-level SQL Server system table information. The key elements for the OPTION EXPLICIT formatting are the leading tag and parent query fields, the field names, such as [Table!2!Name], specified in the first query, and the ORDER BY clause. OPTION EXPLICIT formatting is explained in detail in SQL Server Books Online, under the heading "Using EXPLICIT Mode." To understand the system table information, I examined the code of built-in SQL Server stored procedures in the master database, such as sp_MShelpindex and sp_columns.

Fortunately, the XML document produced by this procedure (see Figure 4) is much easier to understand. It was derived from the Northwind database that ships with SQL Server. XML elements are shown for a stored procedure and a table. The procedure elements contain parameter elements and the table elements contain column elements. Views are exported as non-updateable tables. The XML attribute information for the columns and parameters provided the information I needed to fill in the code patterns. The column data types, name, and length are important for implementing the data access code and the UI. Identity columns can't be updated. The search user interface shown in Figure 1 indicates the indexed columns CustomerID, CompanyName, and so on.

Figure 4 XML (Close Figure 4)
<?xml version="1.0" encoding="utf-8"  ?>
<Database>
  <Procedure Name="Sales by Year">
    <Parameter Name="@Beginning_Date" DataType="datetime" Length="8"
        IsNullable="1" ID="1" IsOutParam="0" Description=""/>
    <Parameter Name="@Ending_Date" DataType="datetime" Length="8"
        IsNullable="1" ID="2" IsOutParam="0" Description=""/>
  </Procedure>
•••
  <Table Name="Shippers" IsUpdateable="1">
    <Column Name="ShipperID" DataType="int" Length="4" IsNullable="0"
      ID="1" Description="" IsIndexed="1" IsPrimaryKey="1"
      IsIdentity="1"/>
    <Column Name="CompanyName" DataType="nvarchar" Length="80"
      IsNullable="0" ID="2" Description="" IsIndexed="0" IsPrimaryKey="0"
      IsIdentity="0"/>
    <Column Name="Phone" DataType="nvarchar" Length="48" IsNullable="1"
      ID="3" Description="" IsIndexed="0" IsPrimaryKey="0"
          IsIdentity="0"/>
  </Table>
•••
</Database>

To extract the XML document from SQL Server, I first installed the stored procedure in the Northwind database using Query Analyzer, then set up HTTP access to the database using the Microsoft Management Console (MMC) snap-in wizard. To start the wizard, select the "Configure SQL XML Support in IIS" item in the SQL Server program group. Note though that the SQLXML ISAPI handler is not installed by default during the installation of SQL Server 2000. Rather, it's part of the client tools. To create a virtual directory, select the default Web site and choose Actions | New | Virtual directory from the context menu. Work through the tabs and select Allow URL Queries on the Settings tab. This allows you to run the stored procedure from Microsoft Internet Explorer using a URI such as http://localhost/northwind?sql=EXEC%20up_DatabaseXML&root=Database. In this case, the virtual directory, called northwind, was created on my machine and the root XML document element containing the <Procedure> and <Table> elements was called <Database>. I then used the File | Save As menu item in Internet Explorer to save the XML document to the hard drive for further processing.

Prototyping the Architecture

Next, I developed a prototype of the data access wrapper procedures using one table with a couple of columns. For my data access layer, a database details class holds the contents of a single row of each table and view in the database. I wanted this class to have type-safe member accessors, such as public String GetCustomerDesc for column values. The members are stored internally in the details class as objects rather than C# value types because value types cannot be null, whereas the database allows null values for types such as int.

Each member's dirty property was tracked so that the update procedure can avoid unnecessarily updating indexed columns, thus reducing lock contention and increasing the probability of an in-place update. To assist in dirtiness tracking, I declared two variables for each member, one of which held the old value. The load methods must initialize the original member values and the set methods must protect the member values from being erroneously overwritten with objects:

private Object _CustomerTypeID; // member value
private Object _CustomerTypeIDOrig; // original member value

In addition to the benefit of dirtiness tracking, storing old and new values enables you to write storage-time validation code, though not in the member set method itself. This code can reject illegal value transitions, or allow the update code to be written in a SQLXML diffgram.

By creating overloaded member set methods that take object and string parameters my loading and UI code was easier to write. This enabled the database code and UI layers to specify values in a convenient format, where the set method does the work to properly handle the values. For example, it's easiest for the database code to send in an object, which will have a runtime type in the System.Data.SqlTypes namespace.

Under these conditions, the object's set methods must handle a number of issues, such as:

  • Allowing the member to be set to null.
  • Handling native SQL-specific datatypes like System.DBNull and System.Data.SqlTypes.SqlString.
  • Rejecting input objects that are equal in value to the existing member to avoid unnecessary updates.
  • Accepting Strings and native objects that are statically dispatched by the compiler to the object interface. Even if a set method takes a String parameter and the target object is a String, the String will be dispatched to the Object set method if the variable passing the String is typed as Object.
  • Telling you the type of the source class that can't be legally cast to your specific value type.

The code in Figure 5 includes a typical member set method that handles these issues.

Figure 5 Member Set Method (Close Figure 5)
/// <summary>Object modifier for EmployeeID</summary>
/// <param name="Int32Value">member value to set
public void SetEmployeeID(Object value) {

  // Object may be provided from SQL row/cell in dataset or other sources
  // Handle known types based on runtime type test of incoming object
  if (value == null || value is System.DBNull) {
    _EmployeeID  = null;
  }
  // handle db type
  else if (value is SqlInt32) {
    SetEmployeeID((Int32)((SqlInt32)value).Value);
  }
  else if (value is String) {
    SetEmployeeID((String) value);
  }
  // cast using native type
  else {
    try {
      SetEmployeeID((Int32) value);
    }
    catch (Exception e) {
      throw new
         ApplicationException("unable to cast from
           "+value.GetType().ToString(), e);
    }
  }
}

Loading and Storing Records

Once the basic member accessor methods were developed, the wrapper classes needed an easy-to-use Load and Store API—something that enabled you to modify and store a record:

EmployeesDetails Employee = EmployeesDetails.Load("EmployeeID=1",
    null, null);
Employee.SetBirthDate("1965/09/23");
Employee.Store(null, null);

The "EmployeeID=1" parameter to the WHERE clause used by the Load method is not type-safe, but will work with tables with integer or unique identifier row IDs, as well as tables with multicolumn primary keys. If you're designing your own tables, you should add a LoadByID method based on your chosen row ID scheme. The flexibility that the current load method provides will work with all kinds of databases.

The Load and Store database interaction code I developed uses dynamic SQL based on samples in the .NET Framework documentation (see Figure 6). Optional connection and transaction parameters in the API calls facilitate explicit transaction management. Some repetitive code was extracted into procedures in an associated Util class.

Figure 6 Load and Store Database Interaction (Close Figure 6)
/// <summary>
/// Details class for records in table CustomerDemographics.
/// </summary>
public class CustomerDemographicsDetailsBase
{

  /// <summary>
  /// Load record specified by optional where clause.
  /// null where clause causes creation of a new record.
  /// throws ArgumentOutOfRangeException if where clause is invalid
  /// </summary>
  public static CustomerDemographicsDetails Load(String where,
   SqlConnection myConnection, SqlTransaction myTrans)
  {
    CustomerDemographicsDetails result = new CustomerDemographicsDetails();
    if (where == null) {
      result.PostLoad(myConnection, myTrans);
      result.ResetDirtyStatus(); // reset dirty flags affected by sets
      return result; // no where = new record
    }
    DataSet dSet = Utils.GetDataSet("CustomerDemographics",
      "2", where, null, myConnection, myTrans);
    // check results
    if (dSet == null || dSet.Tables[0].Rows.Count == 0)
    {
      throw new ArgumentOutOfRangeException(
        "where clause, "+where+", resulted in retrieval of no records");
    }
    if (dSet.Tables[0].Rows.Count != 1)
    {
      throw new ArgumentOutOfRangeException(
        "where clause, "+where+", resulted in retrieval of multiple
         records");
    }

    // select first and only row
    DataRow dRow = dSet.Tables[0].Rows[0];
    result.SetCustomerTypeID(dRow["CustomerTypeID"]);
    result.SetCustomerDesc(dRow["CustomerDesc"]);
    result._loadNew = false;
    result._loadWhere = where;
    result.PostLoad(myConnection, myTrans);
    result.ResetDirtyStatus(); // reset dirty flags affected by sets
    return result;
  }

  /// <summary> Store record which was previously loaded.  </summary>
  public void Store(SqlConnection myConnection, SqlTransaction myTrans)
  {
    // use load history to whether to create new record or update existing.
    PreStore(myConnection, myTrans);
    if (_loadNew) {
      Create(myConnection, myTrans);
    }
    else {
      Update(myConnection, myTrans);
    }
  }

  /// <summary>Delete record which was previously loaded. </summary>
  public void Delete(SqlConnection myConnection, SqlTransaction myTrans)
  {
    // if this is a new record, then delete is a no-op
    if (!_loadNew)
    {
      Utils.Delete("CustomerDemographics", _loadWhere, myConnection,
                   myTrans);
    }
  }

  /// <summary>Get dataset for table</summary>
  public static DataSet GetDataSet(String topLimit, String where,
    String orderBy, SqlConnection myConnection, SqlTransaction myTrans)
  {
    return Utils.GetDataSet("CustomerDemographics", topLimit,
      where, orderBy, myConnection, myTrans);
  }

  /// <summary>create new record</summary>
  private void Create(SqlConnection myConnection, SqlTransaction myTrans)
  {
    bool manageConnection =
      Utils.GetConnectionIfNeeded(ref myConnection, myTrans);
    bool needComma = false;
    StringBuilder cmdStr = new StringBuilder("INSERT [CustomerDemographics]
      (");

    if (IsCustomerTypeIDDirty())
    {
      needComma = Utils.InsertCommaIfNeeded(needComma, cmdStr);
      cmdStr.Append("CustomerTypeID");
    }
    if (IsCustomerDescDirty())
    {
      needComma = Utils.InsertCommaIfNeeded(needComma, cmdStr);
      cmdStr.Append("CustomerDesc");
    }

    needComma = false;
    cmdStr.Append(") VALUES (");

    if (IsCustomerTypeIDDirty())
    {
      needComma = Utils.InsertCommaIfNeeded(needComma, cmdStr);
      cmdStr.Append(Utils.PrepForSQL(GetCustomerTypeIDObject()));
    }
    if (IsCustomerDescDirty())
    {
      needComma = Utils.InsertCommaIfNeeded(needComma, cmdStr);
      cmdStr.Append(Utils.PrepForSQL(GetCustomerDescObject()));
    }
    cmdStr.Append(")");

    String cmd = cmdStr.ToString();
    SqlCommand myCommand = new SqlCommand(cmd, myConnection, myTrans);
    try {
      myCommand.ExecuteNonQuery();
    }
    finally {
      Utils.CloseConnectionIfNeeded(ref myConnection, manageConnection);
    }
    ResetDirtyStatus();
  }
•••

A number of features in the Load and Store code warrant further discussion. Let's start with one of the most important topics: error handling. I didn't try to catch exceptions in the data access layer. Many coding examples show exception handling in low-level code where an adequate decision as to what to do about the error can't be made. Sometimes these well-intentioned catch methods throw away information by returning status Booleans or even by swallowing the exception entirely. Pushing exception handling up the stack makes code more reusable. The business logic using the database wrapper code can handle the error in different ways depending upon the situation. The database access code is wrapped with try...finally blocks to close database connections in case a runtime database error occurs, preventing a resource leak in the form of database connections. Do not rely on the common language runtime (CLR) garbage collector for all your database resource management.

The Load method is the only public way to get your hands on a database wrapper class. Load returns a different class than the CustomerDemographicsDetailsBase class declaring the Load method, namely, CustomerDemographicsDetails. The class CustomerDemographicsDetails will be hand-coded to extend the CustomerDemographicsDetailsBase. This hand-coded class will be the place to implement storage field validation logic, additional business logic methods, and methods that use stored procedures. Some template method hooks, such as PreStore, are built into the CustomerDemographicsDetailsBase methods as a convenient place to put business validation logic without the need to override the entire Load or Store method. Here's an example CustomerDemographicsDetails class:

  /// <summary>Class for adding hand code</summary>
  public class CustomerDemographicsDetails :
  CustomerDemographicsDetailsBase
  {

    /// <summary>hook to simplify override access in hand-coded class
    /// </summary>
    override protected void PreStore(SqlConnection myConnection,
       SqlTransaction myTrans)
       {
        if(_CustomerTypeID == null) {
    throw new ApplicationException("null CustomerTypeID not allowed");
      }
    }
  }

Further improvements could be made in this basic data access pattern. I didn't bother to parameterize the SQL statements, which should improve performance considerably by increasing the likelihood of the execution plan being cached by SQL Server. No update contention detection occurs in my basic system. This would be easiest to add by creating a last-updated timestamp field on your tables, with comparison logic to reject the update if another user has updated the table in the database since the time that the record was retrieved for display.

Running Transformations

With a working code prototype, the code can be reworked into an XSLT stylesheet that generates this same code. First I need to be able to run the XSLT transformation. By downloading msxsl.exe from the command-line transformation utility (XSLT Utilities and Samples You Can Download), I am able to create a batch file with commands to run the transformations, rather than creating code to do it. The batch file allows integration of the transformation process into my build. The following is an example batch file command line using msxsl.exe:

msxsl Database.xml DBWrapperTemplate.xslt -o DBWrapper.cs
Database.xml is the input file, DBWrapperTemplate.xslt is the stylesheet, and DBWrapper.cs is the output code.

Stylesheets

If you're not familiar with stylesheets, you should review one of the background information articles, such as "XSL Transformations: XSLT Alleviates XML Schema Incompatibility Headaches," in the August 2000 issue of MSDN® Magazine, which has an excellent overview of the main concepts. I won't be repeating all the usual details here, but I will discuss some of the troubles that I had creating my code generation templates. Figure 7 contains the complete DBWrapperTemplate stylesheet used to generate the database wrappers for tables and views.

Figure 7 DBWrapper (Close Figure 7)
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:import href = "CommonTemplates.xslt" />
<xsl:output method="html"/>
<xsl:template match="/">
using System;
using System.Data;
using System.Text;
using System.Data.SqlClient;
using System.Xml;
using System.Data.SqlTypes;

namespace DBWrapper
{
<xsl:for-each select="//Table">
  <!-- whitespace not tolerated in function and class names -->
  <xsl:variable name="rawTableName"><xsl:value-of
  select="@Name"/></xsl:variable>
  <xsl:variable name="tableName"><xsl:value-of
  select="translate($rawTableName,'&#x20;','')"/></xsl:variable>

  /// <summary>Details class for records in<xsl:value-of
  /// select="@Name"/>.</summary>
  public class <xsl:value-of select="$tableName"/>DetailsBase
  {

    /// <summary>Load record specified by optional where clause.
    /// null where clause causes creation of a new record.
    /// throws ArgumentOutOfRangeException if where clause invalid.
    /// </summary>
    public static <xsl:value-of select="$tableName"/>Details Load(String
        where, SqlConnection myConnection, SqlTransaction myTrans)
    {
      <xsl:value-of select="$tableName"/>Details result =
        new <xsl:value-of select="$tableName"/>Details();
      if (where == null) {
        result.PostLoad(myConnection, myTrans);
        result.ResetDirtyStatus(); // reset dirty flags affected by sets
        return result; // no where = new record
      }
      DataSet dSet = Utils.GetDataSet("<xsl:value-of select="@Name"/>",
          "2", where, null, myConnection, myTrans);
      // check results
      if (dSet == null || dSet.Tables[0].Rows.Count == 0)
      {
        throw new ArgumentOutOfRangeException(
        "where clause, "+where+", resulted in retrieval of no records");
      }
      if (dSet.Tables[0].Rows.Count != 1)
      {
        throw new ArgumentOutOfRangeException(
        "where clause, "+where+", resulted in retrieval of multiple
        records");
      }

      // select first and only row
      DataRow dRow = dSet.Tables[0].Rows[0];
<xsl:for-each select="Column">
      <xsl:variable name="datatype">
       <xsl:call-template name="convert_datatype"/></xsl:variable>
      result.Set<xsl:value-of select="@Name"/>(dRow[
      "<xsl:value-of select="@Name"/>"]);</xsl:for-each>
      result._loadNew = false;
      result._loadWhere = where;
      result.PostLoad(myConnection, myTrans);
      result.ResetDirtyStatus(); // reset dirty flags affected by sets
      return result;
    }

<!-- some APIs not applicable for views -->
<xsl:if test="@IsUpdateable='1'">
    /// <summary>
    /// Store record which was previously loaded.
    /// </summary>
    public void Store(SqlConnection myConnection, SqlTransaction myTrans)
    {
      // use load history to whether to create new record or update
      // existing.
      PreStore(myConnection, myTrans);
      if (_loadNew) {
        Create(myConnection, myTrans);
      }
      else {
        Update(myConnection, myTrans);
      }
    }

    /// <summary>Delete record which was previously loaded.</summary>
    public void Delete(SqlConnection myConnection, SqlTransaction myTrans)
    {
      // if this is a new record, then delete is a no-op
      if (!_loadNew)
      {
        Utils.Delete("<xsl:value-of select="@Name"/>", _loadWhere,
        myConnection, myTrans);
      }
    }

</xsl:if>

    /// <summary>Get dataset for table</summary>
    public static DataSet GetDataSet(String topLimit, String where, String
        orderBy, SqlConnection myConnection, SqlTransaction myTrans)
    {
      return Utils.GetDataSet("<xsl:value-of select="@Name"/>", topLimit,
          where, orderBy, myConnection, myTrans);
    }

<xsl:if test="@IsUpdateable='1'">
    /// <summary>Create new record</summary>
    private void Create(SqlConnection myConnection, SqlTransaction myTrans)
    {
      bool manageConnection = Utils.GetConnectionIfNeeded(ref myConnection,
        myTrans);
      bool needComma = false;
      StringBuilder cmdStr = new
        StringBuilder("INSERT [<xsl:value-of select="@Name"/>] (");
<!-- identity column value assigned by SQL Server -->
<xsl:for-each select="Column[@IsIdentity='0']">
        if (Is<xsl:value-of select="@Name"/>Dirty())
      {
        needComma = Utils.InsertCommaIfNeeded(needComma, cmdStr);
        cmdStr.Append("<xsl:value-of select="@Name"/>");
      }
      </xsl:for-each>
      needComma = false;
      cmdStr.Append(") VALUES (");
<xsl:for-each select="Column[@IsIdentity='0']">
      if (Is<xsl:value-of select="@Name"/>Dirty())
      {
        needComma = Utils.InsertCommaIfNeeded(needComma, cmdStr);
        cmdStr.Append(Utils.PrepForSQL(Get<xsl:value-of
            select="@Name"/>Object()));
      }</xsl:for-each>
      cmdStr.Append(")");

      String cmd = cmdStr.ToString();
      SqlCommand myCommand = new SqlCommand(cmd, myConnection, myTrans);
      try {
        myCommand.ExecuteNonQuery();
      }
      finally {
        Utils.CloseConnectionIfNeeded(ref myConnection, manageConnection);
      }
      ResetDirtyStatus();
    }
•••
</xsl:for-each>
}

</xsl:template>
</xsl:stylesheet>

I had a couple of common templates in my stylesheets, primarily for converting from SQL datatypes such as nvarchar in the database.xml document to C# ValueTypes such as String. These templates are removed to a separate stylesheet, CommonTemplates.xslt, and are included with one of the first elements in the DBWrappersTemplate.xslt stylesheet, <xsl:import href = "CommonTemplates.xslt" />.

Another important element in Figure 7 is the <xsl:output method="html"/> output method. I started with text as my output method, which was disappointing because non-XSL elements in comments, such as <summary>my method</summary>, were stripped from the output. By explicitly inserting the less-than character with the xsl:text element (for example, <xsl:text>&lt;</xsl:text>), I could get the desired output with the text method, but this seemed like too much work and not very elegant. With the HTML output method, non-XSL elements are included in the output. However, other side effects occur, like single quotes being converted to double quotes and the <param> comment element being output as an empty element because <param> is defined as empty in the HTML spec. Given the choice, I would stick with the text output method for non-HTML code.

The stylesheet really gets rolling, though, with the first xsl:for-each element, as shown here:

<xsl:for-each select="//Table">
  <!-- whitespace not tolerated in function and class names -->
  <xsl:variable name="rawTableName"><xsl:value-of
    select="@Name"/></xsl:variable>
  <xsl:variable name="tableName"><xsl:value-of
    select="translate($rawTableName,'&#x20;','')"/></xsl:variable>
This XSL code repeats all the code within the for-each loop for every table (and view) in the database.xml file. Within the for-each block, the XSL context is set to the table, so attribute accessors such as @Name will provide the name of the current table in each transform iteration.Benefits and Costs of Code Generation

Code generation can reduce all the time it takes to type repetitive code, especially once you're comfortable with the technique. The time you save can be used for other important things like validating your architecture, refining requirements, performance testing, and so on.

The most important benefits occur in the maintenance cycle. If you need to change your architecture, upgrade your technology, or add a technology, this becomes much easier in a template-generated system. Change your templates and you're mostly finished. Since changes aren't being manually applied, testing is reduced because you don't need to look for human error throughout the code base. On one of my projects, I decided to completely change the transaction management, object API, and component automation strategies because the performance overhead of the existing architecture was too high. This only took a couple of weeks and resulted in almost no new bugs because of the high level of generation in the system.

Another big benefit of code generation, which was a great help in the previously mentioned code overhaul, is the ease of creating type-safe code. After changing my system APIs, a lot of code that was written by hand broke. The compiler helped find all the places that needed to be fixed, as opposed to finding problems at run time during an extended testing phase.

Constructing type-safe code by hand can be a lot more time consuming than designing general techniques or design patterns that are not type safe. The temptation for developers who are unassisted by code generation to use object-based APIs, untyped property bags, and so on, can be high. Imagine two different possible implementations for a factory class. The generated API might be defined as public static CustomerOrder CreateCustomerOrder, and an ungenerated API might be defined as public static Object CreateObject(String ObjectType). Make a typo using the generated API and the compiler complains, unlike when you make a typo in the string or you make a casting error with the hand-coded API.

Code generation also enables you to make performance improvements. Type-safe generated code runs much faster for many patterns than hand-coded equivalents that might need to use runtime type identification, type conversions such as int to String, or reflection techniques. In order to gain performance improvements you typically will face the task of writing more complex or low-level code, such as when you use parameterized SQL queries as opposed to dynamic SQL queries with inline parameters. Generation of the majority of the low-level code can take away most of the sting in this process.

A code-generated system is more forgiving than a hand-coded system. A designer can become paralyzed making design decisions for a hand-coded system because the architectural decisions, once made, are hard to change. A developer will typically require much more experience to know all the proper design patterns that their system requires up front, compared with the experience required to just dive in with a generated layer. Patterns can be refactored into the templates once the need for them becomes apparent in the developed code base.

Code generation can speed up the development of hand-coded parts of a system since APIs can be made to order for IntelliSense in Visual Studio. IntelliSense will bring CreateCustomerOrder right to your fingertips, whereas with CreateObject, you must remember and correctly type the object type string parameter in order to avoid a runtime error.

The learning curve you will most likely encounter when you first begin to use code generation techniques will be steep, especially if you're a new developer joining an existing project. Plan for training and thorough documentation of the models that drive a code generation process.

Adding handwritten code into a generated code base without modifying generated code can be tricky. One important technique is to generate lower levels of a system, such as the database interaction layer, and then hand-code on top of the generated parts by using class inheritance. Template methods can also be added to lower levels of generated code to simplify insertion of hand-coded business logic.

Sometimes the power of code generation activates hammer-and-nail syndrome within an organization. In other words, if your main tool is a hammer, everything starts to look like a nail. In order to avoid the negative effects of too much one-size fits all code, periodically examine your generated code base to look for opportunities where design patterns or common procedures will simplify things. Carefully consider whether generation is worth the initial cost for each tier and functional aspect of your system.

To manage performance issues in bottleneck areas, you may have to abandon perfectly functional but slow code relying on a generated layer, and craft a customized routine. For example, set oriented operations may be more efficiently handled by a custom-coded stored procedure than by iterative code using a record-oriented database wrapper layer.

The amount of code generated for a project can grow quite large, resulting in an ever increasing memory footprint for an application. Usually the amount of code isn't an issue in itself with application-level code running on modern hardware, but be wary if you're doing development at the system level or for a hardware and memory-deficient platform.

I ran into a problem when first switching to the Northwind database because spaces are embedded in the name of some of Northwind's views. The two xsl:variable elements first pull the table name into a variable, then use the XSL translate function to strip out spaces. Translate replaces spaces (encoded as &#x20;) with nothing, encoded as empty quotes. The tableName variable has all spaces removed, which is better suited for use in class and method names, where spaces aren't allowed.

In Figure 7, you will find that there are several instances of the following XSL element:

<xsl:value-of select="$tableName"/>
This element gets replaced with the name of the current table during the transform. To convert my sample code to a stylesheet, I replaced all instances of the table name with that xsl:value-of element.

The next new structure in the template is in the Load method, where I need to start accessing individual column values. The XSLT in the following sample causes the transform to iterate over all the columns, and output a line of code to set the member data for the column from the corresponding DataRow object:

<xsl:for-each select="Column">
      result.Set<xsl:value-of select="@Name"/>(dRow[
      "<xsl:value-of select="@Name"/>"]);</xsl:for-each>
This XSLT will produce a line of code for each column, such as:
result.SetOrderID(dRow["OrderID"]);
result.SetCustomerID(dRow["CustomerID"]);
result.SetEmployeeID(dRow["EmployeeID"]);
result.SetOrderDate(dRow["OrderDate"]);

Occasionally the for-each column elements are augmented with an XPath query, as shown here:

<xsl:for-each select="Column[@IsIdentity='0']">
This XPath query causes only those columns that have an IsIdentity attribute of 0 to be included in the for-each iteration. Identity columns cannot be updated, so this technique is used to screen these columns out of the update statements.

Another important XSLT technique is the use of templates to establish variables. In the following code snippet, datatype and valuetype variables are being created:

<xsl:for-each select="Column">
    <xsl:variable name="datatype">
        <xsl:call-template name="convert_datatype"/></xsl:variable>
    <xsl:variable name="valuetype">
        <xsl:call-template name="convert_valuetype"/></xsl:variable>
The convert_datatype and convert_valuetype templates in CommonTemplates.xslt supply the variables values. These templates use an xsl:choose block to convert the SQL Server datatypes such as nvarchar into C# ValueTypes such as String and System.Data.SqlTypes such as SqlString.

Figure 8 shows the xsl:choose block for the convert_valuetype template. I put "###DEBUG###" in the xsl:otherwise block so that the compiler will let me know about any problems finding a valid type conversion.

Figure 8 Convert Types (Close Figure 8)
<!--
return System.Data.SqlTypes valuetype corresponding to SQL Server datatype
-->
<xsl:template name="convert_valuetype">
 <xsl:choose >
  <xsl:when test="@DataType='int'">SqlInt32</xsl:when>
  <xsl:when test="@DataType='bit'">SqlBoolean</xsl:when>
  <xsl:when test="@DataType='smallint'">SqlInt16</xsl:when>
  <xsl:when test="@DataType='datetime'">SqlDateTime</xsl:when>
  <xsl:when test="@DataType='ntext'">SqlString</xsl:when>
  <xsl:when test="@DataType='nvarchar'">SqlString</xsl:when>
  <xsl:when test="@DataType='image'">SqlBinary</xsl:when>
  <xsl:when test="@DataType='bigint'">SqlInt64</xsl:when>
  <xsl:when test="@DataType='binary'">SqlBinary</xsl:when>
  <xsl:when test="@DataType='char'">SqlString</xsl:when>
  <xsl:when test="@DataType='decimal'">SqlDecimal</xsl:when>
  <xsl:when test="@DataType='float'">SqlDouble</xsl:when>
  <xsl:when test="@DataType='money'">SqlMoney</xsl:when>
  <xsl:when test="@DataType='nchar'">SqlString</xsl:when>
  <xsl:when test="@DataType='real'">SqlSingle</xsl:when>
  <xsl:when test="@DataType='smalldatetime'">SqlDateTime</xsl:when>
  <xsl:when test="@DataType='smallmoney'">SqlMoney</xsl:when>
  <xsl:when test="@DataType='text'">SqlString</xsl:when>
  <xsl:when test="@DataType='timestamp'">SqlBinary</xsl:when>
  <xsl:when test="@DataType='tinyint'">SqlByte</xsl:when>
  <xsl:when test="@DataType='uniqueidentifier'">SqlGuid</xsl:when>
  <xsl:when test="@DataType='varbinary'">SqlBinary</xsl:when>
  <xsl:when test="@DataType='varchar'">SqlString</xsl:when>
  <xsl:when test="@DataType='variant'">Object</xsl:when>
  <xsl:otherwise>###DEBUG### unhandled convert_valuetype =
      <xsl:value-of select="@DataType" /> </xsl:otherwise>
 </xsl:choose>
</xsl:template>

I also needed to use some xsl:if blocks in the code. For example, I suppressed the output of storage code like Store for views with the element <xsl:if test="@IsUpdateable='1'">. Also, some of the SQL datatypes such as image needed special handling, and xsl:if blocks were used to insert the right code in these cases.

Stored Procedure Stylesheet

Another stylesheet provides an important aspect of the database wrapper layer, namely, the stored procedure wrappers. These wrappers are especially handy when used in conjunction with IntelliSense® in Visual Studio®. Let's consider the following code using a stored procedure wrapper:

DataGrid1.DataSource = SPWrapper.EmployeeSalesbyCountry(new
    DateTime(1980, 1, 1),
    DateTime.Today, null, null);
DataGrid1.DataBind();

When I typed "SPWrapper", IntelliSense instantly produced a list of all the stored procedures in the database along with their result types and descriptive parameters. This is, in my opinion, far simpler than using the Data Access Application Block provided by Microsoft. Calling the same stored procedure requires the developer to remember the procedure name and return type, as well as the parameter names and types. As a developer, I already have enough to remember without committing all this to memory. Here is an example of how the same code to use a stored procedure would typically be written without code generation:

DataGrid1.DataSource =
      SqlHelper.ExecuteDataset(
      connectionString,
      CommandType.StoredProcedure,
      "Employee Sales by Country",
      new SqlParameter("@Beginning_Date",
      new DateTime(1980, 1, 1)),
      new SqlParameter("@Ending_Date",
      DateTime.Today,));
DataGrid1.DataBind();

A current limitation of the database XML metadata document is that when a stored procedure doesn't return an explicit parameter, the metadata document doesn't tell you whether the stored procedure returns a RowSet or nothing. Determining the correct SQL Server metadata to answer this question would take more research into SQL Server internals, so for now I just assumed that any stored procedure that returns no parameter returns a DataSet. This assumption works fine with the Northwind database, but may not work in other scenarios.

Web Forms User Interface

To test the data wrapper layer, I generated a plain Web Forms UI, as shown in Figure 9 and Figure 10. A directory page lists all the record types, a search/browse page displays records in a DataGrid, and an edit page displays a full page form for modifying column values.

Figure 9 Web Forms Edit Page
Figure 9 Web Forms Edit Page
Figure 10 Web Forms Directory Page
Figure 10 Web Forms Directory Page

For these mostly HTML pages, using the HTML output mode from XSL makes sense. XSLT complains about the syntax of the <% %> script escaping used by ASP, which is neither legal XML nor HTML. To output script, I need to enclose script directives in XSL text elements, with the disable-output-escaping attribute set to yes so that greater-than and less-than characters aren't changed into the XML entities &lt; or &gt;. The script directives must further be enclosed in a CDATA section to allow the parser to ignore the angle brackets:

<xsl:text disable-output-
    escaping="yes"><![CDATA[
    <% DrawPage(); %>]]>
</xsl:text>

I began by generating these pages as "single file" Web Forms, but switched to the preferred codebehind model used in Visual Studio .NET because compile-time errors weren't detected until I browsed the page, which is a documented limitation of the single file format. Within the stylesheets that produce the C# code, several changes to the prototype code were needed to produce legal XML syntax. In some of the if statements, I was using && in logical comparisons, which was being parsed as invalid XML, so I flipped them into || operations using DeMorgan's theorem (for example, !(A & B) = !A | !B). In other cases, I used externally defined constants in place of angle brackets in strings to avoid the interpretation of the angle brackets as XML. Within an XML element, the HTML output method caused the conversion of single quotes to double quotes and other things I didn't want. For example, in this statement, Utils.lt is defined as "<" in another, non-XSL file.

Response.Write(Utils.lt+"OPTION
  selected=\"\" /"+Utils.gt);

Analysis

The stylesheet's constraint to valid XML is a bit of a hassle for general code generation. Moving selected code to functions in non-generated classes can reduce these problems. Developers regularly run into character escaping problems when using template languages, so the problem is not unique to XSLT. An advantage of XSLT is its built-in functions such as translate, position, and so on. The requirement that a stylesheet be legal XML also helps you find template programming errors more quickly, since the transformation will alert you to invalid nesting of elements.

If you're working on a large project, you may want to consider code generation technology offered by vendors. There are a variety of products now available from both high-end vendors, and more affordable vendors, providing powerful features that may be more suitable for your needs than straight XSLT.

Implementation Recommendations

Here are some guidelines for using code generation that will maximize the power of the technique.

Don't check in generated code The beauty of code generation is that you can modify your template and instantly update your code base. Modifying code by hand defeats this. Prohibiting check-ins to generated code prevents developers on the team from making this mistake. Use template methods or class inheritance to separate handwritten code from generated code.

Control your code generation technology Using third-party products can save you time, especially in modeling tools or translation engines. But be wary of losing control of your architecture. If a vendor provides your code templates, then you are at the whim of their architecture. What if after your product is implemented, for example, you discover the need for field-level dirtiness control for performance reasons, but your vendor doesn't support this?

Standardize and simplify Generated templates shouldn't try to support every possible construct. For example, when generating a database wrapper you might not want to support complicated scenarios such as stored procedures that return multiple parameters or tables with multiple identity columns. Code the anomalies by hand.

Know when to stop After developers experience the benefits of code generation, they often take it too far, trying to generate business logic that just varies too much to be worth generating.

Pay attention to your architecture Generated code is only as good as your templates. Make sure your architecture has the performance, scalability, and flexibility you need.

Support the hand-coder You should always try to generate APIs that are easy for the hand-coder to use. Nothing speeds up the development process like using a class library with an intuitive API, especially when it is combined with the IntelliSense feature that's found in Visual Studio.

Document and integrate A generated code base can be more challenging for new developers to get up to speed on, so document extensively. Integrate the transformation technology into your build process so generation occurs automatically.

Refactor Reviewing generated code, you will frequently find patterns that could be simplified with a convenience function or by implementing a design pattern. Refactoring will reduce the verbosity of your generated code base and simplify maintenance.

Unit tests Generate unit tests and test harnesses for your code as part of the development process.

Conclusion

Hopefully this article has given you an appreciation for some of the benefits of code generation, and stimulated some ideas on how code generation might benefit your current projects. XSLT can be used for this task in a general way, in addition to some of its more common uses such as generating HTML presentation for XML and translating XML documents between schemas. XSLT knowledge is useful in a variety of situations, whereas knowledge of other more proprietary or less widely used products might be less transportable to new organizations and environments.

For related articles see:
Bug Tracker: Build a Configurable Web-Based Bug Management Tool Using ADO.NET, XML, and XSLT

For background information see:
SQLXML 3.0: Build Data-Driven Web Services with Updated XML Support for SQL Server 2000

Peter Ashley has 15 years of development experience including projects at large computer manufacturers, ERP software vendors, and software startups. Peter is currently a development team lead at IM Logic in Waltham, MA. Please e-mail comments to Peter at MSDN@AshleyFamily.com.


DCOM Interop: Generate Custom Managed C++ Wrappers for Easier COM Interoperation Using DCOMSuds

DCOM Interop

Generate Custom Managed C++ Wrappers for Easier COM Interoperation Using DCOMSuds

Vishwas Lele
This article assumes you're familiar with .NET, C++, and COM
Level of Difficulty 1 2 3
Code download available at: DCOMSuds.exe (296KB)
SUMMARY

Now that you're writing managed code, you'll certainly want to use your existing COM components, but you can't simply call them directly. Instead, you have to wrap the COM component in a runtime-callable wrapper that acts as a proxy between the component and your managed code. While the CLR provides wrapper classes for this purpose, there will be times when you'll want custom objects to wrap your COM components. One way to get the low-level access you need to precisely control resource cleanup, pass security information, and get access to CLR features is to write your own wrapper class in managed C++. This article shows you how.

Contents
Managed C++ Wrappers
A Tool for Auto-generating Managed Wrappers
DCOMSuds Design
Code Generation
Conclusion

I
nteroperation with existing COM/COM+ objects is an important aspect of the Microsoft® .NET Framework that developers need to understand when working with managed code. While the .NET Framework provides rich support for COM interoperability in the form of wrapper classes, there are times, especially involving remote objects, in which you may need to manually create the interoperation code. In this article I'll explain when that's appropriate and how to do that. Note that some of the sample code in this article has been shortened for clarity. The full source code offers additional advisable habits for dealing with exception handling and cross-apartment calls in COM. You can download the complete code from the link at the top of this article. But before I begin, let's take a close look at the wrapper classes provided by the common language runtime (CLR). These classes make it easier for managed and unmanaged objects to interoperate.

Take for example a managed client that needs to invoke a method, Foo, that's exposed by a COM object, ACOMObject.Test. The managed client merely has to interact with the runtime-callable wrapper (RCW), which acts as the proxy for the COM object and is responsible for managing the lifetime of the COM object as well as marshaling data to and from it. This means that the code to call the COM object is no different from code that would call a managed object, as you can see in the following:

ACOMObject.Test objTest = new ACOMObject();
objTest.Foo();
Obviously this is a basic example with no data marshaling. If you want more information on data marshaling and customizing the COM wrapper classes, Don Box discusses migrating native code in his May 2001 House of COM column.

Using the wrapper classes seems simple enough, so why would you ever want to consider moving beyond RCW and write your wrapper classes by hand? Well, what if the COM object in question, ACOMObject.Test, was located on a remote machine? What would you do then?

It turns out that you can use the Activator class of the System namespace for remote access, so the previous example would look like the following:

Type type = Type.GetTypeFromProgId ("ACOMObject.Test", "MachineName");
Object objTest = Activator.CreateInstance (type);
objTest.Foo();
As you can see, DCOM is being used to communicate with the remote object. Unfortunately, this technique is somewhat self-limiting. For example, there is no way to request multiple interfaces as part of one call (and it gets expensive to make separate round-trips to instantiate interfaces). Also, you can't pass security information when instantiating the COM object. In other words, there is no way to access all the features offered by the API function CoCreateInstanceEx.

Now let's consider calling COM+ components remotely. To do so you could use a configured COM object or a ServicedComponent object. Both can leverage COM+ services by virtue of being hosted inside COM+.

To use Activator.CreateInstance to remotely instantiate COM+ components, you will need to host the client assembly inside a COM+ application. You can also remotely invoke a configured object by using the exported COM+ application proxy. Once you have the application proxy installed on the client machine, you use the embedded assembly to invoke the remote object. For the most part, this technique works well, but it has one problem associated with it—the managed client assembly is dependent on types that are not part of the public interface. This means that the CLR will attempt to load the dependent assemblies, which in turn requires them to be installed on the client machine.

For example, if you have a serviced component that relies on a data access assembly, you will need to place this assembly on all client machines. You can view the assembly's dependencies by opening up the managed client assembly using the ILDASM tool and looking for AssemblyRef tokens. AssemblyRef tokens define the external dependencies of an assembly. You can find more information on AssemblyRef tokens in the Common Language Infrastructure Specification Part II at Common Language Infrastructure (CLI).

Another option would be to use .NET Remoting instead of DCOM to call COM+ components. You can use ASP.NET to host COM+ components, which enables the managed clients to talk to ASP.NET using .NET Remoting or SOAP. If you need to pass service contexts (such as transactions) across machines, you would use the DCOM protocol.

Another difference between DCOM and .NET Remoting is that .NET Remoting requires a surrogate process (such as ASP.NET or a custom socket listener) to host a serviced component, while the DCOM protocol is natively supported. While these differences will certainly disappear over time (especially with the advent of WS-Transaction), the DCOM protocol offers the best approach available at the moment.

Another important issue is object lifetime and resource cleanup when using COM wrappers. As I stated earlier, the COM wrapper abstracts the differences between managed and unmanaged environments. This means that managed clients don't have to worry about reference counting; RCW takes care of it for them. RCWs are, in turn, garbage collected like other managed objects. This nondeterministic finalization means that the lifetime of the COM object is not directly controlled by the managed client, which can be a problem when working with resource-intensive COM objects. The way around this is to call the Marshal::ReleaseComObject method, which allows managed clients to explicitly control the lifecycle of a COM object. But this control comes at the price of additional complexity.

The runtime creates exactly one RCW for each COM object, regardless of how many instances of the COM objects exist. As a result, the reference count of the underlying COM object is always one, even if more than one managed client is connected to it. The managed clients need to be aware of this and make sure that they do not leave dangling instances of RCWs on the heap. The order in which the COM objects are released is also very important.

There is another approach besides the one I just described to force the release of the underlying COM object: explicitly invoking the garbage collector using System.GC.Collect will do the trick, but this is a rather extreme step and should be used with caution. Forcing garbage collection can be expensive because it affects all the application domains of a process.

Managed C++ Wrappers

Managed C++ is an extension of the C++ language that allows developers to take advantage of various features of the .NET Framework when using Visual C++®. The most powerful aspect of managed C++ is that it enables managed and unmanaged code to be intermixed. The Visual C++ compiler facilitates this by automatically generating the code needed to flow seamlessly between the managed and unmanaged context.

Since managed and unmanaged code can coexist, you can simply wrap an unmanaged class with a managed wrapper. The unmanaged class has complete access to the Platform SDK API functions such as CoCreateInstanceEx, which can be used to instantiate a COM object. The managed wrapper makes this functionality available to any .NET Framework-compliant language. Figure 1 illustrates this concept. The unmanaged C++ class uses the COM API functions to instantiate a COM component and the managed C++ class wraps the unmanaged C++ class, making the COM object available to the managed client.

Figure 1 Using a Managed C++ Wrapper Class
Figure 1 Using a Managed C++ Wrapper Class

There are several advantages to this approach. First of all, you have finer control over how the COM object is instantiated and released and when needed, CoCreateInstanceEx can be called from the unmanaged class. This enables you to obtain multiple interfaces in one call and supply appropriate security information. You can also explicitly control the lifetime of the COM object by calling the Release method. And in certain cases you'll even get better marshaling performance. For example, a marshaled data structure can be cached and used to invoke multiple managed calls. Yet another advantage is that unlike exported managed client assemblies, you control the external dependencies of the managed wrapper assembly. Finally, you can even handle COM objects that do not always play by the rules of COM and are not handled well by default COM wrappers. As you can see, managed wrappers help alleviate some of the problems that I've discussed in the earlier sections of this article.

Before you run out to build a managed wrapper class, you should know that creating it is not without its share of challenges. First, you will need to use the #import directive to incorporate information from a type library. The contents of the type library are automatically converted into C++ classes. Next, you will need to code the managed wrapper class with proxy methods that mirror the underlying COM object methods. The proxy methods simply forward the incoming request from a managed client to the underlying COM object. Finally, you will need to handle conversions between managed and unmanaged datatypes. As you can see, these are repetitive steps that can be automated.

A Tool for Auto-generating Managed Wrappers

The tool I'm about to create, which I'll call DCOMSuds, automates the generation of managed wrappers. It's not intended to be a commercial-quality tool that takes into account all the COM idioms such as connection points, events, and custom marshalers. The current version of the tool only looks for dual COM interfaces, and the data marshaling support is limited to primitive types and strings. Once you understand the process of creating managed wrappers, you will be able to add other datatypes by making changes to the files that are generated.

As the name suggests, this tool was inspired by the Soapsuds tool, which creates runtime assemblies that communicate with XML-based Web Services. DCOMSuds is similar in function except that it creates managed assemblies that communicate with DCOM-based servers. The other difference is that while the Soapsuds tool reads the metadata from an XML Schema, DCOMSuds reads the metadata from a type library. COM server descriptions can be read in from either a type library file or an assembly file that houses a serviced component, as shown in Figure 2.

Figure 2 DCOMSuds Tool
Figure 2 DCOMSuds Tool

While the DCOMSuds tool can be used for both local and remote interoperations with a COM object, it is particularly well-suited for managed clients remotely calling an existing configured or nonconfigured COM server, or for remotely calling serviced components through the DCOM protocol.

Figure 3 provides the syntax for invoking the tool. As stated earlier, it can take a type library or an assembly as an input. The interface name of the COM object for which the managed wrapper assembly is to be generated also needs to be supplied. By default, an assembly is generated. The option -gc can be used to generate the managed C++ files. For additional details, see the ReadMe.txt file in the download. Once you have the generated managed wrapper assembly, invoke the DCOM-based servers from a remote machine. To do this, copy the generated assembly to the client machine, then add it as a reference to the managed client application. Add the following entry to the application configuration file to pass the location of the server:

<configuration>
    <appSettings>
        <add key="ServerName" value="machineName" />
    </appSettings>
</configuration>
You will see later how the unmanaged C++ class relies on the application configuration file for reading information such as the server location at run time. This is more XCOPY-friendly than an exported .msi file that ties the server location to the machine where the application proxy was generated. Finally, make sure that the COM object or the serviced component being invoked is registered locally using regsvr32 or regasm.

Figure 3 Options for Invoking DCOMSuds (Close Figure 3)

dcomsuds { I -i:interfacename} { I -t:typelibfile I - :assemblyfile} [options]
Option | Description
-gc | Generates code
-i:interfacename | Specifies the COM interface name for which the managed wrapper is to be generated
-t: typelibfile | Specifies the input typelibrary name
-a:assemblyfile | Specifies the input assembly name that hosts a Serviced component

DCOMSuds Design

Now I'd like to explore the DCOMSuds tool. This tool was developed in C# and consists of four primary classes: Controller, TypeLibReader, CodeGenerator, and AssemblyGenerator. Each of these classes is available in the eponymous .cs file in the code download. Figure 4 lists their method and properties. Let's take a look at each individually.

Figure 4 DCOMSuds Classes, Methods, Properties
Figure 4 DCOMSuds Classes, Methods, Properties

Controller This class is responsible for validating the input arguments and loading the input type library. When an assembly is supplied as an input, the controller class uses the ConvertAssemblyToTypeLib function to convert an assembly into a type library. DCOMSuds places one restriction on serviced component assemblies—it requires the use of interfaces to implement the ServicedComponent classes. It does this because the TypeLibReader class does not support class interfaces. A class interface is a COM-accessible interface automatically generated by the CLR as a result of decorating .NET classes with the ClassInterfaceAttribute. The current version of TypeLibReader requires that interfaces be explicitly defined.

TypeLibReader This class does the heavy lifting in this application. It is responsible for traversing through the type library using the UCOMITypeInfo interface of the System.Runtime.InteropServices namespace. UCOMITypeInfo is the managed definition of ITypeInfo interface. Because I wanted to develop this tool in C# (rather than C++), I started looking at UCOMITypeInfo. The article "Improve Your Debugging by Generating Symbols from COM Type Libraries" by Matt Pietrek (MSJ, March 1999) provides a good description of the ITypeInfo interface and the steps involved in traversing a type library. Therefore, I'll skip a similar discussion on UCOMITypeInfo since the two are very similar. However, I would like to point out a couple of challenges you'll encounter in using the UCOMITypeInfo interface.

Consider the following code snippet taken from file TypeLibReader.cs (I've edited the code for clarity):

IntPtr pElemDesc = FuncDesc.lprgelemdescParam;

for ( int cParams = 0; cParams < FuncDesc.cParams; cParams++ )
{
    ElemDesc = (ELEMDESC) Marshal.PtrToStructure  (pElemDesc,
               typeof(ELEMDESC));
    vt = GetValueTypeFromELEMDESC (ElemDesc);
    m_COMInterfaceDeclaration.AddParameter (vt, GetParamType (vt));

    if (cParams != FuncDesc.cParams-1)
    {
        m_COMInterfaceDeclaration.AddParameterSeperator();
    }

    pElemDesc =  new IntPtr(pElemDesc.ToInt64()
                            +Marshal.SizeOf(ElemDesc));
}
The first thing to note in this code is the use of the IntPtr structure. As there are no pointers in C# safe mode, I use the IntPtr structure, which is designed to hold a platform-specific pointer. The variable pElemDesc in the code is of the IntPtr type, which points to an unmanaged array of buffers of the ELEMDESC. To copy a single element of this array to a managed object, I use the Marshal.PtrToStructure method. The other interesting thing about this code relates to incrementing the IntPtr variable. This is accomplished by constructing a new instance of IntPtr by passing a 64-bit signed representation of pElemDesc, summed with the size of the ELEMDESC. Note that using the 64-bit representation is beneficial since it will work on 64-bit versions of Windows® as well.

CodeGenerator This class is responsible for generating various elements of managed C++ wrapper code. You might be familiar with the ICodeGenerator interface, which provides a way to generate code based on the code Document Object Model (CodeDOM) graph. The idea is that an instance of a CodeDOM graph can be converted into source code in any .NET-compliant programming language as long as the language implements the ICodeGenerator interface. Since the code being generated by DCOMSuds is C++ (and mixed mode at that), you cannot utilize the CodeDOM. Note, however, that in Visual Studio .NET 2003 it is possible to use the CodeDOM in this regard. I have attempted to model the CodeGenerator class based on this interface.

Also worth mentioning is the use of the IndentedTextWriter class for writing out C++ code. This class is particularly useful for code generation as it provides methods to control the indentation level of the output.

AssemblyGenerator This class is responsible for generating an assembly from the generated header (.h) and source (.cpp) files. Also part of the CodeDOM feature is the CodeCompiler class that allows for creation of assemblies from CodeDOM graphs and source files. Unfortunately, like code generation functionality, code compilation is not available for compiling C++ source files. As a result, I had to invoke a batch compile as shown here:

//Declare and instantiate a new process component.
ProcessStartInfo pStartInfo = new ProcessStartInfo();
pStartInfo.FileName =  @"C:\Program Files\Microsoft Visual Studio
.NET\Common7\IDE\devenv.exe";
pStartInfo.Arguments = @"/rebuild release .\gen\ManagedWrapper.sln";
pStartInfo.CreateNoWindow = true;
pStartInfo.WorkingDirectory = Thread.GetDomain().BaseDirectory;
pStartInfo.RedirectStandardOutput = true;
pStartInfo.RedirectStandardError = true;
pStartInfo.UseShellExecute = false;
(Process.Start (pStartInfo)).WaitForExit();

Code Generation

Now let's look at the generated managed and unmanaged C++ code. I will start with a COM object defined by the Interface Definition Language (IDL) file shown in Figure 5. Here I am defining a COM object with a component class called CTest and IDispatch interface ITest. ITest defines five methods (f1 through f5) each with a different data marshaling requirement. Study the IDL attributes associated with each parameter for a moment. These attributes will determine the signature of the managed wrapper class methods.

Figure 5 IDL File (Close Figure 5)
// Forward declare all types defined in this typelib
    interface ITest;

    [
      uuid(EBA63FEE-EAE4-461A-85DA-598901C626A2),
      version(1.0),
      helpstring("Test Class")
    ]
    coclass CTest {
        [default] interface ITest;
    };

    [
      odl,
      uuid(BACFE9A1-2142-4B6F-A166-3F6ABC836BC3),
      helpstring("ITest Interface"),
      dual,
      oleautomation
    ]
    interface ITest : IDispatch {
        [id(0x00000001), helpstring("method f1")]
        HRESULT f1([in] BSTR p);
        [id(0x00000002), helpstring("method f2")]
        HRESULT f2([in, out] BSTR* p);
        [id(0x00000003), helpstring("method f3")]
        HRESULT f3([in] short p);
        [id(0x00000004), helpstring("method f4")]
        HRESULT f4([in, out] short* p);
        [id(0x00000005), helpstring("method f5")]
        HRESULT f5([out, retval] short* p);
    };

Figure 6 shows the code from an auto-generated header file. The code defines a managed wrapper class called ManagedCTest that wraps the CTest component class that you saw earlier. This class is part of the MSDNMagNS namespace. The ManagedCTest class also defines a private member, m_pUnMngdObject0, that points to a template class called UnManagedObject that takes two parameters. (You will see the implementation of the template class a little later.) Also make note of the CTest and ITest structs that mirror the IDL definitions of the component class (coclass) and interface. CTest and ITest are passed as parameters to the template class. The ManagedCTest class also defines equivalent wrapper methods for each method defined by the ITest interface. The key difference is that wrapper methods are defined using the Common Type System (CTS) types. The BSTR datatype is represented as String* and the BSTR* datatype is reflected as StringBuilder* in the wrapper methods.

Figure 6 ManagedWrapper.h (Close Figure 6)
namespace MSDNMagNS
{

    struct __declspec(uuid("eba63fee-eae4-461a-85da-598901c626a2"))CTest;

    struct __declspec(uuid("bacfe9a1-2142-4b6f-a166-3f6abc836bc3"))ITest
        : IDispatch
    {
        virtual HRESULT __stdcall f1( BSTR )=0;
        virtual HRESULT __stdcall f2( BSTR* )=0;
        virtual HRESULT __stdcall f3( short )=0;
        virtual HRESULT __stdcall f4( short* )=0;
        virtual HRESULT __stdcall f5( short* )=0;
    };

    public __gc class ManagedCTest: public IDisposable
    {
        public:
            HRESULT f1( System::String* );
            HRESULT f2( System::Text::StringBuilder* );
            HRESULT f3( System::Int16 );
            HRESULT f4( System::Int16* );
            HRESULT f5( System::Int16* );

        private:
            UnManagedObject<ITest,CTest> *m_pUnMngdObject0;
        public:
            ManagedCTest()
            {
                m_pUnMngdObject0= new UnManagedObject<ITest,CTest>();
            }
            void Dispose()
            {
                m_pUnMngdObject0->Release();
            }

    };
}

One last thing to note in this code is that the ManagedCTest class implements the IDisposable interface. IDisposable defines a method, Dispose, that can be used to release allocated resources. The implementation simply calls the Release method on m_pUnMngdObject0. Shortly you'll see how the Dispose method allows a managed client to control the life of the COM object.

Figure 7 shows the auto-generated source file. It provides the definition for the CManagedTest wrapper methods that were in the header file. The thing to note is the data marshaling that takes place when going between an unmanaged and a managed data type. The Marshal class provides a number of useful functions that can help in this transition. The method ManagedCTest::f1 uses Marshal::StringToBSTR to allocate a BSTR and copy the contents of the String to it. This BSTR instance is passed to the COM object. The previously allocated buffer needs to be freed using Marshal::FreeBSTR after the control is returned from the COM method invocation. The method f2 has a different marshaling requirement. As shown in Figure 5, the parameter for method f2 has an [in, out] attribute. This means the COM object can change the content of the BSTR. Since the String class is immutable, I cannot use it here. Instead, I use the StringBuilder class, which represents a mutable string of characters.

Figure 7 ManagedWrapper.cpp (Close Figure 7)
#include "stdafx.h"
#include "objbase.h"
#include "ManagedWrapper.h"

inline HRESULT MSDNMagNS::ManagedCTest::f1( System::String* Param0 )
{
    HRESULT bRet=S_OK;
    try
    {
        // Convert String to BSTR
        IntPtr Ptr0 = Marshal::StringToBSTR(Param0).ToPointer();
        BSTR UParam0=(BSTR)Ptr0.ToPointer();
        bRet = m_pUnMngdObject0->operator->()->f1((BSTR)UParam0);
        Marshal::FreeBSTR(Ptr0);
        return bRet;
    }
    catch(_com_error e)
    {
    }
}
inline HRESULT MSDNMagNS::ManagedCTest::f2( System::Text::StringBuilder*
    Param0 )
{
    HRESULT bRet=S_OK;
    try
    {
        // StringBuilder to BSTR*
        IntPtr Ptr0 = Marshal::StringToBSTR((*Param0).ToString());
        BSTR UParam0=(BSTR)Ptr0.ToPointer();
        bRet = m_pUnMngdObject0->operator->()->f2((BSTR*)&UParam0);
        ((*Param0).Remove(0,(*Param0).Length));
        ((*Param0).Insert(0, Marshal::PtrToStringBSTR (UParam0)));
        return bRet;
    }
    catch(_com_error e)
    {
    }
}

You can tweak these files to make data marshaling more efficient or supply additional data conversion routines. For example, a marshaled instance of a structure can be cached and reused across multiple invocations. Again, the key to any conversion between managed and unmanaged data types is the Marshal class. It provides a collection of methods for doing the actual conversion.

Figure 8 shows the implementation of the UnManagedObject template class. One very powerful aspect of managed C++ is that it allows generic types to be mixed with managed types, despite the fact that parametric polymorphism is not currently supported by the .NET Framework. It would have been difficult to implement the UnManagedObject class without support for templates. Note how the template parameters of the UnManagedObject class correspond to the CLSID and IID of the underlying COM object. The location of the underlying COM object is read from the configuration file using the AppSettings property of the ConfigurationSettings class. The rest of the code is there simply to use the CoCreateInstanceEx/CoCreateInstance API functions to instantiate the COM object. The Release method calls Release on the underlying COM object. Finally, I need to hide the copy constructor and assignment operator.

Figure 8 managedobj.h (Close Figure 8)
//
// managedobj.h: Helper class for DCOMSUDS
//
//
    template <typename T1, typename T2>
    class UnManagedObject
    {
    public:
        T1* m_IP;
        UnManagedObject ()
        {
            HRESULT hr= S_OK;

            LPOLESTR lpolestr;
            IID IID_INTERFACE;
            LPVOID p;
            ZeroMemory(&IID_INTERFACE, sizeof(IID));
            StringFromIID (__uuidof(T1), &lpolestr);
            IIDFromString (lpolestr,&IID_INTERFACE );
            CoTaskMemFree(lpolestr);
            String *Settings[] =
                ConfigurationSettings::get_AppSettings()->
                GetValues("ServerName");
            if (Settings && Settings->Length > 0)
            {

                String *ServerName = Settings[0];
                LPWSTR lpServerName = (LPWSTR)
                    Marshal::StringToHGlobalUni (ServerName).ToPointer();
                // Set the Server Location
                COSERVERINFO srvInfo;
                ZeroMemory(&srvInfo, sizeof(COSERVERINFO));
                srvInfo.pwszName= lpServerName;
                srvInfo.dwReserved1 = 0;
                srvInfo.dwReserved2 = 0;
                srvInfo.pAuthInfo = NULL;

        MULTI_QI qi = {(&IID_INTERFACE) , NULL, S_OK };
        hr= CoCreateInstanceEx (__uuidof( T2 ), NULL,
            CLSCTX_INPROC_SERVER, &srvInfo, 1, &qi);
        if (SUCCEEDED(hr))
        {
            m_IP = (T1*)qi.pItf;
        }
        Marshal::FreeHGlobal(IntPtr((void*)lpServerName));
    }
    else
    {

        hr = CoCreateInstance (__uuidof( T2 ), NULL, CLSCTX_INPROC_SERVER
            , IID_INTERFACE , (LPVOID *)&p);
        if (SUCCEEDED(hr))
        {
            m_IP = (T1*)p;
        }
    }

    if (((HRESULT)(hr)<0))
    {
        throw new COMException ("Failed to create underlying COM object",
                                hr);
    }

 }
 void Release()
{
    m_IP->Release();
}

T1* operator -> () const { return m_IP;}

private:
    // Hide the copy constructor and = operator
    UnManagedObject(const UnManagedObject&);
    operator=(const UnManagedObject&);
};

Figure 9 shows a C# client that leverages the managed wrapper assembly generated by the DCOMSuds tool. Creating the managed wrapper class is simply a matter of instantiating the ManagedCTest object, which in turn causes the underlying COM object to be instantiated. The underlying COM object remains alive until the program exits the scope of the using statement. At that point, the Dispose method is automatically called. The using statement defines a scope at the end of which the object will be disposed. You can utilize the using statement because the ManagedCTest class implements the IDisposable interface. The Dispose method results in the release of the underlying COM object. This is in contrast to COM wrappers where the equivalent code would have simply marked an object for deletion.

Figure 9 C# Managed Client (Close Figure 9)
static void Main(string[] args)
    {
        ManagedCTest mw = new ManagedCTest();
        using (mw)
        {
            try
            {
                short s=10;
                String str = "hello";

                mw.f1(str);

                StringBuilder strBuilder = new StringBuilder("hello");
                mw.f2 (strBuilder);

                mw.f3(s);

                mw.f4(ref s);

                mw.f5(ref s);

            }
            catch (Exception e)
            {
                Console.WriteLine (e.Message);
            }
        }
    }

Conclusion

When RCW and COM-callable wrapper (CCW) classes are not enough, managed C++ provides a powerful option for interoperating with COM objects. The DCOMSuds tool that I've described in the article helps automate the generation of managed C++ wrapper classes. As you saw, these custom classes give you more control and flexibility and let you deal with circumstances, including remote invocation, that require some special treatment.

For related articles see:
C++ Attributes: Make COM Programming a Breeze with New Feature in Visual Studio .NET
Garbage Collection-Part 2: Automatic Memory Management in the Microsoft .NET Framework
Visual C++ .NET: Tips and Tricks to Bolster Your Managed C++ Code in Visual Studio .NET
Understanding Enterprise Services in .NET
Improve Your Debugging by Generating Symbols from COM Type Libraries

For background information see:
.NET and COM: The Complete Interoperability Guide by Adam Nathan (SAMS, 2002)

Vishwas Lele is a Principal Architect at Applied Information Sciences (http://www.appliedis.com), a custom software development company that specializes in the .NET Framework and Win32 technologies. He can be reached at vlele@acm.org.


Web Services: Extend the ASP.NET WebMethod Framework with Business Rules Validation

Web Services

Extend the ASP.NET WebMethod Framework with Business Rules Validation

Aaron Skonnard and Dan Sullivan
This article assumes you're familiar with XML Schemas and Web Methods
Level of Difficulty 1 2 3
Code download available at: BusinessRules.exe (223KB)
SUMMARY

In an earlier article the authors showed how to build a custom WebMethods extension that provides XML Schema validation, a function that is lacking in ASP.NET. In the process they established a foundation for enforcing business rules during the deserialization of XML data. The technique, which is described in this article, uses declarative XPath assertions to test business rule compliance.

In building this business rules validation engine, the authors integrate the validation descriptions into the WSDL file that is automatically generated by the WebMethod infrastructure. Finally, they demonstrate how to extend wsdl.exe, the tool that generates WebMethod proxy/server code from WSDL files, to make use of their extensions.

Contents
Business Rules and XPath
Extending ValidationExtension
AssertAttribute
Extending GetInitializer
Extending ProcessMessage
Documenting Business Rules
WSDL Extensibility Elements
WSDL Extensibility Points
WSDL Generation, SoapExtensionReflector
Consuming WSDL
SoapExtensionImporter
Client-side SoapExtension Processing
Conclusion

L
ast month we showed you how to compensate for the lack of validation in ASP.NET WebMethods by using the extensibility hooks built into ASP.NET (see Web Services: Extend the ASP.NET WebMethod Framework by Adding XML Schema Validation). We showed how to build a custom extension that provides XML Schema validation (ValidationExtension) and gave you a sneak peek at another technique for enforcing business rules during deserialization. In this article, we'll continue that discussion by explaining how to implement an XPath-based business rules engine.

We'll also show you how to integrate the necessary validation descriptions into the Web Services Description Language (WSDL) file that is automatically generated by the WebMethod infrastructure. To complete the topic, we'll show you how to extend wsdl.exe, the tool that generates WebMethod proxy/server code from WSDL files, to include our extensions. The full source code is available for download from the link at the top of this article.

Business Rules and XPath

Business rules typically evolve from corporate specifications that describe actual business constraints. Developers who implement business rules often put comments in their code describing the rule and sometimes even reference the specification. As you might expect, this approach is a nightmare to manage since specifications, code, and comments will be harder to keep in sync. It would be much cleaner to declaratively include business rules in your class definitions, relying on some other piece of infrastructure to validate constraints and generate the documentation.

As we illustrated with the ValidationExtension example in the July 2003 issue, XML Schema validation may give you what you need to enforce certain information constraints. However, XML Schema cannot describe co-occurrence constraints, which are typical of most business rules in the real world, so it's not sufficient for building a generic business rules processing engine.

XPath, on the other hand, is an ideal language for defining additional message constraints. An XPath constraint is simply an expression that acts as an assertion: it must evaluate to true (similar to the type of expression you use in an XPath predicate). For example, the following XPath expression asserts that the size of the length element is greater than the size of the width element in a SOAP request message:

/s:Envelope/s:Body/t:CalcArea/t:length >=
/s:Envelope/s:Body/t:CalcArea/t:width

If you're already using XML Schema to validate the message structure (or you just don't care where the elements exist in the document), you could use this abbreviated expression instead:

//t:length >= //t:width
Likewise, this expression ensures that the area that is calculated here is greater than 100 in all cases:
(//t:length * //t:width) > 100
The following expression verifies that if an optional username element exists, so does the password element:
not(//t:username) or (//t:username and //t:password)
Finally, this expression validates that a price element exists and depending on its value, there may also be a tax element:
//t:price[. < 100][not(following::t:tax)] or
//t:price[. >=100][following::t:tax]

According to this expression, if the price is less than 100, there must not be a tax element, but if it's greater then 100, the tax element is required. These are all examples of constraints that cannot be described using XML Schema but are quite easily described using XPath expressions.

Extending ValidationExtension

We decided that it would be useful to build this type of XPath constraint processing right into the ValidationExtension class. To do so we defined a new attribute class called AssertAttribute that allows you to specify XPath assertions to either an individual method or the entire class (applying to all WebMethods). You can use any XPath expression in the declaration—it will be evaluated as a Boolean.

To evaluate the XPath expressions, the processor needs to know what's actually meant by the prefixes they use. This information is supplied to the processor through an XmlNamespaceManager object. Hence, we also defined an AssertNamespaceBindingAttribute class so users can specify the namespace prefix bindings required by the expressions; this attribute may only be used on the class definition. The code in Figure 1 contains a complete example using these attributes.

Figure 1 Using XPath Assertions in WebMethods (Close Figure 1)
[WebService(Namespace="http://example.org/geometry/")]
[AssertNamespaceBinding("s",
  "http://schemas.xmlsoap.org/soap/envelope/")]
[AssertNamespaceBinding("t",
  "http://example.org/geometry/")]
[Assert(@"//t:width >= 0", "width must be greater than 0")]
[Assert("(//t:length > //t:width)",
  "Length must be greater than width")]
public class Geometry
{
  [WebMethod]
  [Validation]
  [Assert("(//t:length * //t:width) > 100",
    "Area must be greater than 100")]
  [Assert("(//t:length div //t:width) = 2",
    "Length must be exactly twice the width")]
  public double CalcArea(double length, double width)
  {
    return length * width;
  }
  [WebMethod]
  [Validation]
  public double CalcPerimeter(double length, double width)
  {
    return length *2 + width *2;
  }
}

In this example, the class has two asserts that apply to both of the WebMethods: width >= 0 and length >= width. There are two additional asserts defined specifically for CalcArea: the area must be greater than 100 and the length must be twice the width.

We had to modify our GetInitializer implementation to save the compiled XPath expressions and namespace bindings for use in ProcessMessage. In addition, we also had to modify ProcessMessage in order to evaluate the expressions after the schema validation process completes.

Because we wanted to make it possible to handle XPath assertions without requiring XSD validation, we added a property to ValidationAttribute called SchemaValidation, which you can use to disable XSD validation:

[Validation(SchemaValidation=false)]

Now let's look at the new attributes and the changes to GetInitializer and ProcessMessage in more detail.

AssertAttribute

To help you understand how the various pieces fit together, let's first look at the AssertAttribute and AssertNamespaceBindingAttribute classes. AssertAttribute enables the user to provide the rule (such as the XPath expression) along with a human-readable description. It also provides an internal slot for storing the compiled XPath expression for future use in ProcessMessage. Figure 2 shows the complete class definition.

Figure 2 AssertAttribute Definition (Close Figure 2)
// holds the compiled XPath expression & description
[AttributeUsage(AttributeTargets.Class |
  AttributeTargets.Method, AllowMultiple = true)]
public class AssertAttribute : System.Attribute
{
  string _rule;
  string _description;
  XPathExpression _expression;

  public AssertAttribute() : this("", "") { }
  public AssertAttribute(string rule) : this(rule, "") {}
  public AssertAttribute(string rule, string description)
  {
    _rule = rule;
    _description = description;
    _expression = null;
  }

  [XmlElement("expression")]
  public string Rule
  {
    get { return _rule; }
    set { _rule = value; }
  }

  [XmlElement("description")]
  public string Description
  {
    get { return _description; }
    set { _description = value; }
  }

  internal XPathExpression Expression
  {
    get { return _expression; }
    set { _expression = value; }
  }
}

Notice that the attribute may be used on class or method declarations. When it's used on a method, the rule only applies to that method. When it's used on a class, it applies to all methods in the class. You may have also noticed the XmlElement declarations on the Rule and Description properties. These are used to control how the class is mapped to an XML message when it's serialized. Later we'll use XmlSerializer to report the attributes that failed to validate during the generation of the SoapException.

The AssertNamespaceBindingAttribute is much simpler since it just stores the prefix-to-namespace name bindings. See Figure 3 for the class definition. With these attributes in place, devs can provide all the necessary information for processing their business rules: the expressions, descriptions, and namespace bindings (see Figure 1 again). Now let's look at how to harvest this information in GetInitializer and store it for future use in ProcessMessage.

Figure 3 AssertNamespaceBindingAttribute Definition (Close Figure 3)
// Specifies namespace bindings required by the XPath expressions
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class AssertNamespaceBindingAttribute :
  System.Attribute
{
  string _prefix;
  string _ns;

  public AssertNamespaceBindingAttribute(string prefix, string ns)
  {
    _prefix = prefix;
    _ns = ns;
  }
  public string Prefix
  {
    get { return _prefix; }
  }
  public string Namespace
  {
    get { return _ns; }
  }
}

Extending GetInitializer

The first thing we must do to extend our GetIntializer method is to modify the return type. When we were just performing XML Schema validation, we simply returned an XmlSchemaCollection object, but now that we've added XPath assertions we have more information to store. We defined a new class called ValidationContext for holding all of the initialization state that we'll need in ProcessMessage:

internal class ValidationContext
{
  internal XmlNamespaceManager NamespaceManager = null;
  internal AssertAttribute[] AssertAttributes = null;
  internal XPathExpression CompleteRuleExpression = null;
  internal ValidationAttribute ValidationAttribute = null;
  internal XmlSchemaCollection SchemaCollection = null;
}
It still contains an XmlSchemaCollection member, but this class also includes several other members. If the user configured the extension using a ValidationAttribute, we'll save it in the ValidationAttribute field. The other three fields are used for XPath processing: NamespaceManager holds an XmlNamespaceManager object initialized with all of the user-supplied AssertNamespaceBinding attributes, AssertAttributes contains the array of user-supplied Assert attributes, and CompleteRuleExpression contains the cumulative XPath expression generated from all of the individual expressions (this can be used for a quick success/failure test).

We've already discussed how the schema collection is loaded within GetInitializer (see our July 2003 article), but the others require some attention. At the beginning of GetInitializer, we instantiate a ValidationContext object in order to hold everything we're going to need later:

ValidationContext ctx = new ValidationContext();
Then we harvest the AssertNamespaceBinding attributes and save the supplied information into an XmlNamespaceManager object, which is then placed into the ValidationContext object, as shown in the code in Figure 4.

Figure 4 Storing AssertNamespaceBinding Attributes (Close Figure 4)
// temporary navigator for compiling XPath expressions
XmlDocument doc = new XmlDocument();
XPathNavigator nav = doc.CreateNavigator();
// namespace manager for holding all namespace bindings
XmlNamespaceManager ns = new XmlNamespaceManager(nav.NameTable);

// retrieve (user-provided) namespace binding attributes
object[] namespaceAtts = serviceType.GetCustomAttributes(
  typeof(AssertNamespaceBindingAttribute), true);
foreach(AssertNamespaceBindingAttribute nsa in namespaceAtts)
  ns.AddNamespace(nsa.Prefix, nsa.Namespace);
// store namespace manager in context for future use
ctx.NamespaceManager = ns;

Next, we harvest the Assert attributes from both the class and method (if it was configured on the method through an attribute). If both class and method attributes exist, we create a new AssertAttribute array and copy the objects into it. This simplifies the work we have to do in ProcessMessage later on (see Figure 5).

Figure 5 Harvesting the Assert Attributes (Close Figure 5)
// retrieve (user-provided) assertion attributes
AssertAttribute[] allRuleAtts = null;
object[] classRuleAtts = serviceType.GetCustomAttributes(
  typeof(AssertAttribute), true);
if (methodInfo != null) // configured via attribute
{
  // retrieve method
  object[] methodRuleAtts = methodInfo.GetCustomAttributes(
    typeof(AssertAttribute));
  allRuleAtts = new AssertAttribute[methodRuleAtts.Length +
    classRuleAtts.Length];
  methodRuleAtts.CopyTo(allRuleAtts, 0);
  classRuleAtts.CopyTo(allRuleAtts, methodRuleAtts.Length);
}
else
  // just retrieve class-level assertion attributes
  allRuleAtts = (AssertAttribute[])classRuleAtts;
// save into extension context
ctx.AssertAttributes = allRuleAtts;

We then iterate through all of the harvested Assert attributes and build the XPath string expressions. We're wrapping the supplied expression with the XPath Boolean function to ensure that it returns a Boolean value, regardless of the expression. We'll simply apply a logical and operator on all of the Boolean expressions together to make sure that they all succeed when evaluating the complete expression. We also want to cache each individual compiled expression for future use when inspecting errors. The entire process is illustrated in Figure 6. Then, of course, we cache the complete compiled expression so we can do a quick check inside of ProcessMessage during each future invocation.

Figure 6 Building and Compiling the Cache Expressions (Close Figure 6)
// generate, compile, and cache expressions for future use
StringBuilder completeExpression = new StringBuilder();
string and = "";
foreach(AssertAttribute ra in allRuleAtts)
{
  string rule = String.Format("boolean({0})", ra.Rule);
  // cache compiled expression for future use
  ra.Expression = nav.Compile(rule);
  ra.Expression.SetContext(ns);
  completeExpression.Append(and);
  completeExpression.Append(rule);
  and = " and ";
}
if (completeExpression.Length != 0)
{
  ctx.CompleteRuleExpression = nav.Compile(
      completeExpression.ToString());
  ctx.CompleteRuleExpression.SetContext(ns);
}

Extending ProcessMessage

In order to evaluate XPath expressions, ProcessMessage needs to load the request stream into an XPathDocument object. If the user has enabled schema validation, we'll load the XPathDocument object from an XmlValidatingReader; otherwise we'll use an XmlTextReader directly. Once the XPathDocument is loaded, we know its schema is valid because the load operation didn't throw an exception. Then we're ready to evaluate the complete XPath expression, as illustrated here:

XPathDocument doc = new XPathDocument(reader);
XPathNavigator nav = doc.CreateNavigator();

if (false == (bool)nav.Evaluate(_context.CompleteRuleExpression))
{
// generate error response here
}
If the expression results in true, the extension allows the processing to continue onto the WebMethod; otherwise the extension throws a SoapException.

To help the caller figure out what went wrong, we want to identify each business rule that failed. Inside the if block, we'll iterate through the ValidationContext.AssertAttributes array and evaluate each one to see where the error occurred. While doing so, we'll generate a custom detail element to use the SoapException (detailElement), as shown here:

SoapException error =
  new SoapException("Business rules failed validation",
    SoapException.ClientFaultCode,
    HttpContext.Current.Request.Url.AbsoluteUri,
    detailElement);

If you'd like more information on how detailElement was generated, see the sample code. Figure 7 shows a typical SOAP response that contains the custom detail element specifying exactly which business rules failed.

Figure 7 SOAP Response for Failed Rules (Close Figure 7)
<soap:Envelope
   xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
 <soap:Body>
  <soap:Fault>
   <faultcode>soap:Client</faultcode>
   <faultstring>Business rules failed validation</faultstring>
   <faultactor>http://localhost/tests/test2.asmx</faultactor>
   <detail>
    <dm:failedAssertions
     xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
     xmlns:t="http://www.example.org/tests/test2"
     xmlns:dm="http://www.develop.com/web/services/">
     <dm:assert>
      <dm:expression>(//t:length >//t:width)</dm:expression>
      <dm:description>The length must be greater than the width
        </dm:description>
     </dm:assert>
    </dm:failedAssertions>
   </detail>
  </soap:Fault>
 </soap:Body>
</soap:Envelope>

With all of these enhancements in place, we have the makings of a declarative business rule engine that is flexible and easy to use. Although clients will find out through the SOAP Fault details when a given business rule fails, we need a way to inform developers ahead of time about our business rule requirements.

Documenting Business Rules

We now have a way to declaratively add business rules by decorating classes and methods with [Validate] and [Assert], and we know that the underlying code will automatically enforce the rules. Anyone who reads our code can see what constraints apply to the WebMethod without having to understand the code that implements it. Most XML Web Service clients, however, won't have access to the source code, so they need an alternate form of documentation. Most developers provide this in human-readable form, such as an HTML page or Microsoft® Word file. Keeping a manually produced document in sync with the code is difficult and prone to errors.

WSDL provides a standard language for describing Web Services. A WSDL document contains all the details necessary to implement or consume a Web Service. Most of today's Web Services toolkits can read a WSDL file and generate the code required to consume or implement a Web Service. Wsdl.exe provides this functionality in the Microsoft .NET Framework SDK and Apache's WSDL2Java (Axis) provides equivalent functionality for programmers working in Java.

The elements that WSDL uses to describe a Web Service fall into two major categories: WSDL language constructs and extensibility elements. WSDL elements come from the WSDL namespace (http://schemas.xmlsoap.org/wsdl) and are used to describe only the most generic, abstract parts of a Web Service. Extensibility elements (from a namespace outside the WSDL namespace) provide more concrete information about the Web Service's behavior. Figure 8 shows a WSDL fragment with both types of elements.

Figure 8 WSDL Components
Figure 8 WSDL Components

The service element (from the WSDL namespace) doesn't tell us much other than there is a Web Service named ComplexMath. It is used as a reference to the rest of the information about the ComplexMath Web Service. The address element (from the WSDL/SOAP namespace) is an extensibility element that tells us that we can communicate with ComplexMath at the http://math.com/convolve endpoint.

The WSDL specification makes it possible for developers to add ad hoc extensibility elements whose meaning is mutually understood by both the implementer and consumer of a Web Service. We want to leverage this flexibility to add extensibility elements that describe our validation attributes.

Once we've done this, we can extend the WebMethod infrastructure to automatically generate the WSDL file with our extensibility elements included, guaranteeing that it will be in sync with our original class definition. Then we want to ensure that the tools that generate proxies take into account our extensibility elements so that they are also automatically in sync with the Web Service.

WSDL Extensibility Elements

Mapping our attributes to extensibility elements is fairly straightforward. The extensibility elements should contain all of the information that is provided by our validation attributes. We will qualify our extensibility elements with the http://www.develop.com/web/services/ namespace.

We have three attributes to map to WSDL extensibility elements: AssertNamespaceBindingAttribute, AssertAttribute, and ValidationAttribute. The first one can only be applied to a class, but more than one can be applied. A single extensibility element that acts as a container can be used to hold elements for each namespace binding, as shown in Figure 9. The validationNS element contains a namespaces element which holds a namespace element for each AssertNamespaceBindingAttribute.

Figure 9 Namespace Attributes/Extensibility Mapping (Close Figure 9)

The ValidationAttribute and multiple AssertAttributes can be applied to a class or method. These can be combined into a single extensibility element named validation, as shown in Figure 10.

Figure 10 Validation Attribute/Extensibility Mapping (Close Figure 10)

WSDL Extensibility Points

Now that we have extensibility elements that capture the information in our attributes, we must decide where to use them in the WSDL file itself. A full explanation of the WSDL language is beyond the scope of this article, but to give you an idea, Figure 11 lists the five primary WSDL elements that are used to describe a Web Service.

Figure 11 Primary WSDL Elements (Close Figure 11)

WSDL Element | Description
<service> | Enumerates endpoints (to access service)
<binding> | Binds operations to access protocol
<portType> | Associates operations with messages
<message> | Associates messages with types
<types> | Type definitions for messages

The binding element describes the concrete details surrounding how to use a set of operations (such as portType). Optionally, each operation in the binding may have a single input or output message, and any number of fault messages associated with it. The binding itself says nothing about what the messages look like and only enumerates the fact that they exist. Figure 12 shows a simple WSDL binding element and its extensibility points.

Figure 12 WSDL Binding Element
Figure 12 WSDL Binding Element

This binding associates a single operation, Add, with the HTTP transport protocol. It tells us that there is an input and output message associated with the Add operation. It does not tell us the format of these messages, but it does tell us that they will be defined by some XML Schema.

The WSDL binding element has an extensibility point that comes just before the first operation element in the binding. You can include any number of elements (not from the WSDL namespace) here. Extensibility elements included at this location apply to all operations in the binding. The WSDL input element also contains an extensibility point. It may contain any number of elements (again, not from the WSDL namespace). Extensibility elements included here only apply to the input element. The output element has a corresponding extensibility point.

With this information we know where to place our extensibility elements. Figure 13 shows the equivalences between a WebMethod class and the WSDL binding element. For simplicity, the examples shown in this article do not have any return attributes. In practice, however, it might be a good idea to use return attributes to ensure the implementation produces proper results.

Figure 13 Class Attributes/Binding Extensibility
Figure 13 Class Attributes/Binding Extensibility

Let's look at a complete example. Figure 14 shows a typical class that implements a Web Service. The ValidationAttribute and AssertAttributes can be applied to either a class or to individual methods, but in the end they cause validation/assertion processing to occur on the methods within a class. Applying them to the class causes validation/assertion processing to occur for all the methods in the class. To simplify the implementation, ValidationAttribute and AssertAttribute declarations on the class will simply be added to the declarations on each method. That way, there is a single extensibility point in the input element of the binding element. Figure 15 shows the binding element for this class.

Figure 15 Binding for Typical Web Service Class (Close Figure 15)
<binding name="SimpleTestsSoap" type="s0:SimpleTestsSoap"
  xmlns="http://schemas.xmlsoap.org/wsdl">
  <soap:binding
   transport="http://schemas.xmlsoap.org/soap/http"
   style="document" />
   <validatioNS xmlns="http://www.develop.com/web/services/">
     <namespaces>
       <namespace>
         <prefix>t</prefix>
         <namespace>
           http://www.example.org/tests/test2
         </namespace>
         </namespace>
         <namespace>
         <prefix>s</prefix>
         <namespace>
           http://schemas.xmlsoap.org/soap/envelope/
         </namespace>
      </namespaces>
   </validationNS>
   <operation name="CalcArea2">
      <soap:operation style="document"
         soapAction="http://www.example.org/tests/test2/
         CalcArea2" />
      <input>
        <soap:body use="literal" />
        <validation
          xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
          xmlns:t="http://www.example.org/tests/test2"
          xmlns="http://www.develop.com/web/services/"
          SchemaValidation="true">
          <assertions>
            <assert>
              <expression>//t:width &gt;= 0</expression>
              <description>
               width must be greater than 0
              </description>
            </assert>
            <assert>
              <expression>
               s:Envelope/s:Body/t:CalcArea2/t:length &gt;=
               /s:Envelope/s:Body/t:CalcArea2/t:width
              </expression>
              <description>
               length must be greater than or equal to width
               </description>
            </assert>
            <assert>
              <expression>
               (//t:length * //t:width) &gt; 100
              </expression>
              <description>
               Area must be greater than 100
              </description>
            </assert>
          </assertions>
        </validation>
      </input>
</binding>
Figure 14 Web Service Class (Close Figure 14)
[AssertNamespaceBinding("s",
  "http://schemas.xmlsoap.org/soap/envelope/")]
[AssertNamespaceBinding("t",
  "http://www.example.org/tests/test2")]
[Assert(@"//t:width > 0", "width must be greater than 0")]
public class SimpleTests : System.Web.Services.WebService
{
  [WebMethod]
  [Validation]
  [Assert("/s:Envelope/s:Body/t:CalcArea2/t:length >=
    /s:Envelope/s:Body/t:CalcArea2/t:width",
    "length must be greater than or equal to width")]
  [Assert("(//t:length * //t:width) > 100",
    "area must be greater than 100")]
  public double CalcArea2(double length, double width)
  {
    return length * width;
  }
}

WSDL Generation, SoapExtensionReflector

We have extended WSDL to include our validation attribute information. In order to make this really useful we need to hook into the WebMethod infrastructure that automatically creates WSDL and include our extensibility elements.

You can hook into the ASP.NET WSDL generator by configuring it to use a class derived from SoapExtensionReflector. Simply add an entry to the soapExtensionReflectorTypes element in the web.config file, as shown here:

<system.web>
   <webServices>
      <soapExtensionReflectorTypes>
        <add type=
        "DevelopMentor.Web.Services.ValidationExtensionReflector,
         DevelopMentor.Web.Services, Version=1.0.0.0,
         PublicKeyToken=09381596d5226568, Culture=neutral"/>
      </soapExtensionReflectorTypes>
   </webServices>
</system.web>

When ASP.NET creates a WSDL file, it first checks this section of web.config and makes an instance of each SoapExtensionReflector class listed. It then calls the SoapExtensionReflector.ReflectMethod once for each WebMethod. The ReflectMethod implementation has access to the WSDL object model as well as the MethodInfo for the particular method being reflected.

Hence, we can write a ReflectMethod implementation that adds our new WSDL extensibility elements to the WSDL object model based on the validation attributes found for the reflected method. In order to design the SoapExtensionReflector class, we create a class that derives from SoapExtensionReflector and override the ReflectMethod. When ReflectMethod is invoked, we use the ReflectionContext property of the base class in order to gain access to the MethodInfo and binding element for the method being processed, as shown in Figure 16.

Figure 16 Gaining Access to the MethodInfo (Close Figure 16)
public class MyReflector : SoapExtensionReflector
{
  public override void ReflectMethod()
  {
     // MethodInfo for method being processed
     MethodInfo mi = ReflectionContext.Method;
     // binding for method being processed
     Binding binding = ReflectionContext.Binding;
     // input operation for method being processed
     OperationBinding input =
       ReflectionContext.OperationBinding.Input;

         // Write code to reflect against method, find
         // validation attributes, and insert custom
         // extensibility elements
  }
  •••
}

In ReflectMethod we can use Binding.Add and Input.Add to add extensibility elements at the appropriate location. However, the extensibility elements that we add must be derived from ServiceDescriptionFormatExtension and must be properly marked with the XmlFormatExtensionAttribute to identify their usage. Consider the class shown here:

[XmlFormatExtension("MyExstension", "urn:myNamespace",
  typeof(Binding)]
public class MyExtensibilityElement :
  ServiceDescriptionFormatExtension
{
       // include data members here that will be
       // part of your extensibility element
}
XmlFormatExtension is applied to the MyExtensibilityElement class, which represents a WSDL extensibility element. Its arguments identify the name of the extensibility element as "MyExtension" from the urn:myNamespace. It also marks this extensibility element for use within a WSDL binding element.

The XML format of the extensibility element is the result of using XmlSerializer against the ServiceDescriptionFormatExtension-derived object. You have to add XmlSerializer attributes to get the exact XML format that you desire. Look at the ValidationFormatExtension and NSFormatExtension classes in the complete sample project to see the details of how the XmlSerializer was configured to get the extensibility element formats we require.

Consuming WSDL

Our WSDL file now has all the validation information that was contained in the original source file. The last step of the development process is to build a proxy that accesses the Web Service. Both wsdl.exe and the Visual Studio® .NET "Add Web Reference" feature use .NET Framework classes to read WSDL and generate proxy classes. These classes also have hooks that enable their behavior to be modified.

Getting attributes into proxy code completes the documentation loop; that is, all components involved with the Web Service now contain the same documentation, which is automatically synchronized. We can even carry the process one step further by forcing the proxy code to perform the same validation checks on the messages before it sends them to the Web Service.

The SoapExtensionAttributes that are applied to WebMethods can also be applied to proxy classes generated by wsdl.exe or Visual Studio .NET. The effect is the same since the SoapExtension classes get to take part in the processing of the messages the proxy sends. This means we can use the same classes, with some minor modifications, to extend the client's behavior.

SoapExtensionImporter

To modify the behavior of wsdl.exe and Visual Studio .NET, we must design a SoapExtensionImporter-derived class and configure the client systems to use it. There is only one method that we must override: ImportMethod. During proxy generation it is called once for each operation found in the WSDL binding element.

When ImportMethod is called, we have access to two important pieces of information: a modifiable collection of attributes (in the proxy code being generated) and an object model of the WSDL file that is being processed:

public class MyReflector : SoapExtensionReflector
{
  public override ImportMethod(
    CodeAttributeDeclarationCollection metadata)
  {
     SoapProtocolImporter importer = ImportContext;
     // implementation follows
  }
  •••
}

ImportContext, which comes from the SoapExtensionImporter base class, gives access to the WSDL file through its Service property and to the generated class through its CodeTypeDeclaration property. The code that implements the method can be accessed through the supplied metadata parameter.

To configure client systems to use our SoapExtensionImporter we must modify the machine.config file. We do this by adding the class name to the system.web/webServices/soapExtensionImporterTypes element, as shown here:

<configuration>
  <system.web>
    <webServices>
     <soapExtensionImporterTypes>
      <add type=
        "MyImporter, MyAssembly, Version=1.0.0.0, locale=neutral,
        PublicKeyToken=a4...9/>
     </soapExtensionImporterTypes>
    </webServices>
  </system.web>
</configuration>

There are two important things to keep in mind when registering a SoapExtensionImporter. One is that a full assembly name must be used and the assembly itself must be added to the Global Assembly Cache (GAC) which requires that it's signed. The second thing to remember is that every time wsdl.exe runs, your extension will be used. This means your implementation better be well thought out and forgiving.

When MethodImport is invoked, there are two places where you might want to add attributes: in the method for the operation being processed and in the class that contains the methods for all of the operations. Since only MethodImport is invoked, you will have to be able to detect when the first method is being processed and use it in order to add attributes to the proxy class.

When MethodImport is called, you can inspect the object model of the WSDL to determine what extensibility elements have been added to the binding being processed, since each binding has a collection of extensibility elements associated with it. The collection itself has a Find method which can be used to quickly reference an extensibility element. The following code illustrates how to obtain the "MyExtension" extensibility element from the "urn:myNamespace" namespace:

•••
XmlElement extensibility =
  ImportContext.Binding.Extensions.Find
   ("MyExtension", "urn:myNamespace");
•••

Once you've determined that the method has an extensibility element, you can add the corresponding attributes to the proxy class. To do this you must create an instance of the CodeAttributeDeclaration class. The constructor requires the name of the attribute you will be adding:

•••
CodeAttributeDeclaration attr =
  new CodeAttributeDeclaration(typeof(MyAttribute).FullName);
•••

Once you have constructed a CodeAttributeDeclaration you will need to add any necessary arguments. You can use this class' Arguments collection property and its corresponding Add method to provide additional instances of CodeAttributeArgument. The constructor for the CodeAttributeArgument class requires the name of the argument and its value. Once all of the arguments have been added to the CodeAttributeDeclaration, it can be added to the method in the proxy. This is accomplished by using the CustomAttribtutes property of the CodeTypeDeclaration property, as shown here:

ImportContext.CodeTypeDeclaration.CustomAttributes(attr);
For more information, take a look at the complete implementation of the ValidationExtensionImporter class in the sample project.

Client-side SoapExtension Processing

Applying SoapExtensionAttributes to the methods of a proxy class has the same effect as applying them to a WebMethod. These attributes make it possible to take part in the processing of the messages sent to and from a Web Service. In order to be able to check the messages against the client-side [Validation] and [Assert] attributes, we need to be able to inspect the message just before it's sent over the wire.

To perform the actual validation and assert checking, we need to trap the message stream and process it in ProcessMessage. Our SoapExtension class gives us a way to do this through the overridable ChainStream method. Each time a proxy method is invoked, ChainStream is called twice by the runtime: once just before it begins to build the request message, and once just before it begins to process the response message returned by the Web server.

The ChainStream method allows us to substitute our own stream for the one that the runtime would have used. We will provide a MemoryStream for the one being used to build the request message. This way, when the Framework creates the request message, it will write it to our stream. We can then perform validation on the MemoryStream (inside ProcessMessage). If it passes validation, we'll forward the contents of our stream onto the stream originally used by the runtime.

To do all this, our ChainStream implementation constructs a memory stream, returns a reference to it, and saves the original stream that was passed when ChainStream was called. Since ChainStream is called twice, it will have to keep track of which call it's processing. It should only substitute the stream on the first call, which is the one that happens just before it builds the request message (see Figure 17).

Figure 17 Overriding ChainStream (Close Figure 17)
class public ValidationExtension : SoapExtension
{
  MemoryStream requestStream;
  bool firstChain = true;

  •••// other methods omitted for brevity

  public override Stream ChainStream(Stream stream)
  {
      if(firstChain)
      {
        firstChain=false;
        requestStream = new MemoryStream();
        return requestStream;
    }
    else
    {
      return stream;
    }
  }
}

Once the request has been captured, it can be processed and copied to the original requestStream. With this mechanism in place, the proxy object performs validation and XPath assertion processing on the stream in ProcessMessage (just like with WebMethods on the server side), before actually sending the message. If the message passes validation, it's allowed to pass through; otherwise the proxy automatically generates a SoapException without wasting a round-trip. You can see the full implementation of the ValidationExtension in the complete sample project.

Conclusion

The WebMethod infrastructure provides a powerful extensibility framework for adding additional user-defined behavior. We were able to add XML Schema validation and XPath assertions to any WebMethod through various extension classes. We also made it possible to export the validation descriptions through WSDL, allowing the documentation to stay completely in sync with the code. Using these techniques makes it possible to enforce real-world business rules in the heterogeneous environment of XML-based Web Services.

For related articles see:
Web Services: Extend the ASP.NET WebMethod Framework by Adding XML Schema Validation

For background information see:
Web Services Developer Center

Aaron Skonnard is an instructor and researcher at DevelopMentor, where he develops the XML and Web Service-related curriculum. Aaron coauthored Essential XML Quick Reference (Addison-Wesley, 2001) and Essential XML (Addison-Wesley, 2000). Reach him at http://staff.develop.com/aarons.

Dan Sullivan is an independent consultant, and an instructor and course author at DevelopMentor. He has worked in all facets of computing, from designing processors to writing applications and system software. Dan now focuses on XML and developing Web Services and can be contacted at dsullivan@net1plus.com.


Editor's Note: Tech•Ed 2003 Offers Revealing Glimpse into Dev

Editor's Note
Tech•Ed 2003 Offers Revealing Glimpse into Dev

When we arrived in Dallas for Tech•Ed in June, there was reason to worry. For a moment, we felt that familiar unease we once had as adolescents sent off to visit the grandparents down in Miami. How else to explain the "Happy 100th Birthday, Irving!" signs on the highway exit from DFW?

This weird feeling in the pit of our stomachs was more than just the prospect of spending time with family, however. As you probably know, the past couple of years have brought waves of caution and pessimism to our industry. There's no question that times have been tough out there. The slowdown has meant fewer available projects as companies tried to maintain what they have instead of funding new research.

So what does this have to do with Tech•Ed 2003, you may ask? We've watched the Microsoft .NET Framework from its introduction to its second rev, and our belief that it represents the future of computing has been reinforced by our coverage in the magazine over the past three years.

Meanwhile, we've looked for signs of renewed growth in the industry. At this year's Tech•Ed, we found many such signs. From the moment we hit the exhibitor area, we noticed a background level of buzz that had been missing from recent events. People sounded very upbeat and excited about everything that was going on at the show.

These feelings were echoed by many people we talked to during the show. New consulting projects that were taking months to land are suddenly going to contract in just a couple of weeks. Most importantly (to us), we saw a lot of activity around .NET. Companies that were just talking about experimenting with the Framework a year ago are now committing their resources to .NET-based projects. We were struck by the level of large-scale .NET deployment that's going on today, and planned for the near future.

One of the great things about a show like Tech•Ed is that it gives a good picture of where development is headed as a whole. You can always tell from the track overviews what's going to be on everyone's radar screens in the next year. For instance, security has been a much-discussed topic in the industry, and it was one of the major focuses at this year's show. Microsoft Office 2003 has many new features for programmers. Messaging is gaining momentum. There was some discussion about Yukon, the codename for the next version of SQL Server.

However, the biggest areas of interest we saw at the show weren't necessarily related to a single product. We regularly keep in touch with our readers at shows like Tech•Ed through both focus groups and on-floor discussions. In Dallas, we asked for and received many points of view about the magazine and the state of the software industry. One of the biggest topics on everyone's minds, without question, was architecture and best practices. Time and time again, people told us that they've spent a couple of years learning about the basics of the .NET Framework—properties, methods, and interfaces. With things starting to look up in the world of computing, it's now project time. There's no longer a question of what you can do with the Framework; the focus is now on the best way to do those things.

If you're working in the enterprise, building distributed and tiered apps, or creating high-traffic Web sites, you already know that 1/1000 second per call can make all the difference in a program. Security, performance, deployment, and robustness are all key considerations in your code, now more than ever. In the years to come, these factors are what will set your work apart from others.

Anyway, the show's highlight was the snack table setup between sessions. The luscious snack choices gave us the opportunity to cram our mouths full of creme-filled goodness, uncountable varieties of real fruit-flavored filling, and delectable preservatives. In case you're wondering about the photo, that is a smattering of real apples behind all the good stuff. Fortunately, those Granny Smiths were for appearance's sake only, as they were hollowed out and stuffed with Tootsie Rolls. Mmmmm. Tootsie apples. Isn't that really what the Developer Life is all about?

—J.T.

Active Directory, ActiveX, IntelliSense, Microsoft, MSDN, MS-DOS, Visual Basic, Visual C++, Visual Studio, Windows, Windows NT, and Win32 are registered trademarks of Microsoft Corporation. Windows Server is a trademark of Microsoft Corporation. Other trademarks or tradenames mentioned herein are the property of their respective owners.

MSDN Magazine does not make any representation or warranty, express or implied with respect to any code or other information herein. MSDN Magazine disclaims any liability whatsoever for any use of such code or other information.


New Stuff: Resources for Your Developer Toolbox

New Stuff
Resources for Your Developer Toolbox
Theresa W. Carey

Contents
Tweak HTML Elements
Trap Hackers with a Honeypot
High-powered Site Analytics
File Transfer for Enterprises
Track and Stomp Bugs
Visual Basic Code Generator
Personalize Dynamically Assembled PDF Documents
The Bookshelf

Tweak HTML Elements

Dubbeldam Software has released HtmlTweak, a tool that allows Web developers and designers to locate and inspect HTML elements loaded in Microsoft® Internet Explorer, and modify HTML, Cascading Style Sheet rules, and attributes to test changes. Instead of having to tweak Web pages by changing the source HTML and reloading the page, you can point to any spot in the browser window and the program will highlight the HTML element at that location. It shows the exact boundaries of the element, the relations in the HTML hierarchy, the style properties, and the style rules that apply to the element.

You can change the HTML, change embedded style rules and style attributes interactively, and see the result immediately without having to reload the page. The software is based on the Microsoft .NET Framework and runs on Windows® XP, Windows 2000, Windows Me, Windows 98, and Internet Explorer 5.5 and higher.

http://www.dubbeldam.com

Trap Hackers with a Honeypot

KeyFocus has launched KFSensor, a host-based intrusion detection system which acts as a honeypot to attract and detect hackers by simulating vulnerable system services and trojans. The system features detailed logging, analysis of attack, and security alerts. The honeypot approach to security has a number of advantages that complement other security systems. It produces a lower number of false positive alerts and provides more detail on an attack than other forms of security. KFSensor is compatible with Windows 98, Windows Me, Windows 2000, and Windows XP.

http://www.keyfocus.net/kfsensor

High-powered Site Analytics

Omniture recently released SiteCatalyst 9.0, which introduces three features designed to enable enterprise companies to gain insight into the behavior of their online customers, make educated decisions, and thereby improve business processes and their return on investment.

The first feature is VISTA (Visitor Identification, Segmentation, and Transformation Architecture), which allows you to create real-time segmentation of all online data. Customers can define rules to partition data into segments and view all SiteCatalyst reports for that segment. For example, segments can be designed to display data only about visitors who made a purchase or who viewed a flash demonstration before completing registration.

The second new feature is True Fall-out Reporting, which provides conversion and fall-out reporting between user-defined checkpoints, irrespective of the users' navigation between those points. By understanding the flow of traffic through these checkpoints, Omniture's customers can now have a greater handle on how their users get from one point to another without confining the definition of success to the limits of a predefined process.

Finally, A/B Comparison Framework helps customers collect and manage data associated with an A/B testing process. This method of testing simplifies design changes by comparing site performance across different segments, versions of Web sites, or time frames. By displaying data side-by-side and calculating the percent change, the time required to gauge the effectiveness of incremental design changes can be reduced.

Also introduced in this version is Optimum Path ClickMap, which superimposes clickstream and page view data on the actual pages of a site. This allows you to analyze customer response to content, placement, and design.

http://www.omniture.com

File Transfer for Enterprises

SoftArtisans has released FileUp Enterprise Edition (FileUpEE) to address the needs of upload or download applications. Multigigabyte files, even those in excess of 100GB, are now securely transferable within a Web farm environment. FileUpEE scales to the most demanding Web sites consisting of numerous server farms and multiple levels of security perimeters. Content is secured at both the Web server and file server levels, ensuring high availability and redundancy.

FileUpEE File Servers can be isolated and protected. In addition, large volume transfers use SOAP/MIME with future versions of FileUpEE supporting other emerging standards such as the Microsoft Direct Internet Message Encapsulation (DIME) initiative. Extending the simple file upload functionality built into ASP.NET, FileUpEE has no restrictions on file size or Web farm support.

http://fileup.softartisans.com

Track and Stomp Bugs

elementool recently unveiled their latest upgrade, Bug Tracking 4.0, which allows project managers to manage all of their elementool accounts through Bug Tracking 4.0's centralized control panel. The account-admittance function offers access to many projects by logging in only once. Previously, managers working on multiple projects were required to provide passwords for each additional account in order to keep data separate and secure.

New reporting capabilities enable customers to define the filters for which they want to secure information. The Quick Reports feature allows users to set up a report template that incorporates specific data into every report, making it unnecessary to define the filters each time that a report is generated.

Customers can select from three standard levels of service—Basic, Advanced, or Pro-Package—based on their specific software tracking needs. All Bug Tracking 4.0 packages allow for an unlimited number of users for each account and features Web-based accounts with downloadable databases, automatic e-mail notification for account users, and HTML-based customer service management.

Each package features special functions, which include storage of up to 200 issues at the Basic level. Advanced-level users have additional reporting capabilities, customization of forms, and storage of up to 2000 issues. At the Pro level, users can take advantage of features such as file attachments, history trail, full customization of forms, and unlimited storage.

http://www.elementool.com

Visual Basic Code Generator

VBeXpress.Com recently introduced VBeXpress.NET, a code generator for developers using the Microsoft .NET Framework. It can generate entire Windows Forms and Web Forms applications from an existing SQL Server™ or Microsoft Access database. It contains over 150 code generation templates and has support for over seven different Windows Forms grids.

The code generation engine uses Visual Studio® for Applications, which means that code generation is controlled by fully customizable scripts using the VBeXpress.NET built-in template editor. The latest version includes support for Visual Studio .NET 2003.

http://www.vbexpress.com

Personalize Dynamically Assembled PDF Documents

Appligent Inc. has announced the release of AppendPDF Pro 3.0, which enables businesses to dynamically assemble sections from PDF documents to build a completely new version with a choice of personalized features.

AppendPDF Pro 3.0 includes support for the XML format to create parameter files. In addition, it offers the option of supporting linearization of a PDF, sometimes called optimization, so you can view the first page of a PDF document while it is still being downloaded from the Internet. For instance, if you decide the information needed is on Page 25, you can quickly link to it while the download process continues in the background. AppendPDF Pro also supports 40-bit and 128-bit encryption as well as setting user permissions on PDF documents.

A server-based application, AppendPDF Pro can run from a command line on multiple platforms. It is thread safe and can run in a high-volume, multithreaded environment. It can also be integrated with other Appligent utilities and applications like StampPDF Batch, FDFMerge, and SecurSign to build automated solutions for document processing. AppendPDF Pro 3.0 is available for Windows NT®, Windows 2000, Windows XP, Solaris, Red Hat Linux, AIX, and HP-UX, as well as Mac OS X.

http://www.appligent.com

The Bookshelf

Addison-Wesley has published two new guides for Windows Server™ 2003: Inside Windows Server 2003, by William Boswell and The Ultimate Windows Server 2003 System Administrator's Guide, by Robert Williams and Mark Walla. The books offer two approaches to deploying and administering Windows Server 2003. Both are specifically written for systems administrators, architects, and designers who need to design, manage, or administer Windows Server 2003.

Inside Windows Server 2003 offers task-based coverage of the .NET Server, including hundreds of new features and thousands of tips and recommendations.

The Ultimate Windows Server 2003 System Administrator's Guide explains the new features of Windows Server 2003 and offers continued support for Windows 2000 to the experienced programmer. The book addresses changes in security, Active Directory®, Microsoft Management Console, and integration with the .NET Framework 1.1. It speaks to both the Microsoft server community as well as users of the Linux/Unix operating systems.

http://www.awprofessional.com

Sybex has published a whole stack of books for Windows Server 2003. Mastering Windows Server 2003, by Mark Minasi with Christa Anderson, Michele Beveridge, C.A. Callahan, and Lisa Justice, is an in-depth guide.

Mastering Active Directory for Windows Server 2003, by Robert R. King, includes a detailed look at Active Directory-related network traffic, as well as related network traffic of DNS, DHCP, the Login process, and Windows Internet Name Service. This book also covers the backup and recovery tools included in Windows Server 2003.

Mark Minasi's Windows XP & Server 2003 Resource Kit, by Mark Minasi, Peter Hipson, and Robert R. King, is a compilation of more than 4,000 pages in four volumes, providing the reader with Windows XP and Windows Server 2003 coverage in one definitive resource collection.

http://www.sybex.com

Send your New Stuff to Theresa at newstuff@microsoft.com.

Theresa W. Carey is a freelance writer who lives in California. Her byline has appeared in Barron's and PC World.


Web Q&amp;A: Who Called the Script?, Concatenating Binary Files, and More

Web Q&A
Who Called the Script?, Concatenating Binary Files, and More
Edited by Nancy Michell

Q Is there a way to determine from which part of an HTML document a script was called, by getting, say, the ID of an element?

A Yes, there is. Take a look at the example in Figure 1. There are two buttons, each of which have script code that calls into a function named MyFunction. Inside of MyFunction is event.srcElement, which retrieves the particular element that triggered the initial event. Once you have this, you can get the caller's ID.

Figure 1 Who Called the Script? (Close Figure 1)
<html>
<head>
 <script>
  function MyFunction()
  {
   alert( window.event.srcElement.id );
  }
 </script>
</head>
<body>
 <button id="1" onclick="MyFunction();">Button with id 1</button>
 <button id="2" onclick="MyFunction();">Button with id 2</button>
</body>

</html>

Q I am writing a script which needs to concatenate binary files together into one file. What I need is essentially the same functionality as the MS-DOS® command "copy /b file1+file2 file3".

The File System Object (FSO) doesn't seem to be much help. I happened to try it with MP3 files, but they got corrupted.

A The FSO model supports only text files, not binary files. System administrators and script writers most commonly use text files to store information because they can easily read and write to these files. So the FSO model doesn't include any objects, methods, or properties to work with these types of files. If you want to manipulate any type of binary file you would need to use byte arrays or pointers to a memory address. Although adding them to the FSO model is possible, it would add an unpleasant level of complexity.

The sensible solution is to use the shell command:

WShell.Run "cmd /c copy /b file1+file2 file3"
That does exactly what you want, it does it from script, it's one line of code, and it's guaranteed to work.

Q Suppose I have some XML and somewhere deep inside there is a node "mynode" that I'm looking for. This node is in namespace abc. It could be declared many ways:

<root xmlns:myns="abc">...<myns:mynode/>...</root>
<root xmlns="abc">...<mynode/>...</root>
<root>...<parent xmlns:myns="abc"><myns:mynode/><parent/>...</root>
<root>...<myns:mynode xmlns:myns="abc"/>...</root>
<root>...<m:mynode xmlns:m="abc"/>...</root>
I don't know in advance where the namespace alias is defined or how it is going to be called. So if I have an IXMLDOMNode pointer to <root> and I want to find the node called "mynode" in namespace abc, how do I do that using the Document Object Model? I cannot use an XPath query such as //mynode, //myns:mynode, or //*:mynode.

A To perform the XPath query you need to know the namespace uri, not the prefix that represents it on the doc. Prefix aliases in the docs are not related to the ones you should use on the XPath except by the fact that they point to the same namespace. With MSXML 4.x, to specify prefixes used on an XPath you set the SelectionNamespaces property. This sample is taken directly from the MSXML 4.x documentation:

var xmlDoc = new ActiveXObject("Msxml2.DOMDocument.4.0");
var objNodeList;
xmlDoc.async = false;
xmlDoc.load("hello.xsl");
xmlDoc.setProperty("SelectionNamespaces",
    "xmlns:xsl='http://www.w3.org/1999/XSL/Transform'");
xmlDoc.setProperty("SelectionLanguage", "XPath");
objNodeList = xmlDoc.documentElement.selectNodes("//xsl:template");
alert(objNodeList.length);

Q In my XML, I have some HTML, but MSXML doesn't see HTML. So, when transforming XML via XSL, is there a way to grab an element's inner text as text rather than as XML?

A First, is your HTML making the XML not well formed? If so, then the transformation won't work unless you wrap the contents of the root in a CDATA section and use "disable-output-escaping='yes'":

<Root>
<![CDATA[
     <p>this is a <b>para</b></p><hr>
]]>
</Root>
Then your XSL would be:
<xsl:template match="Root">
    <xsl:value-of select="." disable-output-escaping="yes"/>
</xsl:template>
If your XML is well formed, then simply use <xsl:output method="html"> and <xsl:copy-of> to output the HTML content as is and your result will be HTML.
<xsl:template match="Root">
    <xsl:copy-of select="." />
</xsl:template>

However, if your XML looks like this

<Root>
<![CDATA[
     <p>this is a <b>para</b></p><hr>
]]>
</Root>
and you have control over the format, you will have a better XSLT experience if you make the HTML XML-compliant:
<Root>
     <p>this is a <b>para</b></p><hr/>
</Root>
Then you will have the ability to query on tags and content within your HTML and change the format easily.

Q How can I reference an external VBScript file from an existing VBScript file which will be run at the command prompt?

A The best way is to write a Windows® Script Host File.

<job>
  <script language="VBScript" src="c:\library.vbs"/>
  <script language="VBScript">
  ' whatever
  </script>
</job>

You might wonder why there is no #include, like in C. That's because #include would have to mean something different in every host. In Microsoft® Internet Explorer you have to use the Internet Explorer security model to ensure that scripts aren't being included across domains and that they support URL paths. In ASP, you have to use vroot paths. In Windows Script Host you would use file system paths, and so on down the line. It gets too complicated.

Q I've been storing my department's user info (phone numbers, passwords, cubicle locations, and so on) in various Microsoft Excel spreadsheets, which have become difficult to keep current. I want to design a database with Microsoft Access 2000 to keep track of all the information and in turn match each user to a cubicle. The problem is that the employees get moved around a few times each year, and trying to remember who is where is not feasible. I want to use a floorplan as the UI for the database so I can click on a cubicle to bring up the associated record and edit it. I'd like to display the current user names as labels in the cubicles.

Here's an example: Bob is in cubicle 1001A. When the database is opened, a floorplan with names is displayed, and I can click on Bob and see all his associated information (login, phone extension, and so forth). However, if Bob moves to cubicle 1234, all that should be necessary is to update the cubicle number, and the floorplan should change to reflect the new location (so now cubicle 1001A will show as empty). How can I do this?

A You could use a form with button controls. Set up the structure of the database, and in the relevant table (let's call it User Info) call the fields "Jack" for the phone jack and "User" for the employee. When you look up a phone jack in the table you want the label set to the value of the employee who has that number. Once the label is set properly, you can add a click event handler to open up another form for that record.

All you need to do is to pull the field value and assign it to the button caption (see Figure 2). The Open event calls SetUserButtons with "Me" as the argument. The SetUserButtons code takes every control in the form, and if it's a command button, looks up the object's Name in the User_Info table (where it matches a field called Location). Then it sets the caption of the object to be the user name, his phone extension, and his cubicle location. The only other non-wizard code in there opens the appropriate form:

Private Sub Ctl1003a_Click()
OpenSubForm "1003a"
End Sub

Sub OpenSubForm(button As String)
Dim frm As String, rs As Recordset
frm = "SelectItem"

DoCmd.OpenForm frm, , , "Location = '" & button & "'"

End Sub

Figure 2 Put User Name on Button (Close Figure 2)
Private Sub Form_Open(Cancel As Integer)
    SetUserButtons Me
End Sub

Sub SetUserButtons(frm As Form)
Dim rs As Recordset, ctl As Control

For Each ctl In frm.Controls
    If ctl.ControlType = acCommandButton Then
        With ctl
            Set rs = CurrentDb.OpenRecordset("select * from
                user_info where location = '" & ctl.Name & "'")
            Do Until rs.EOF
                ctl.Caption = rs!User & " - #" & _
                rs!Extension &  " - " & rs!Location
                rs.MoveNext
            Loop
        End With
    End If
Next ctl

End Sub

Right now the button names are hardcoded into the click events. When clicked, the button passes the hardcoded string to the DoCmd.Openform command, which filters by location.

Everything else in the database is generic wizard code, including the searches. In this database, all the tables have relationships, so editing can be performed right in the form. The Search buttons aren't affected by the SetUserButtons code because the entire map and tab-buttons set is a subform within the form you see, and the open event is only triggered on the subform.

Obviously, you could port all of this to a Web page front end so you can access it from anywhere and let users make changes.

Figure 3 User by Cubicle
Figure 3 User by Cubicle

You'll also need to add error handling, but otherwise you're all set. Figure 3 shows the application running in Access. The center of the figure shows the cubicles with their corresponding buttons.

Q I need a tool that compares two XML files. The tool should say the files are equal if they are identical except for different ordering of elements and attributes. Also, can I have different XML files in a single file, like a .chm file, which has multiple indexed .htm files?

A Try Microsoft XML Diff and Patch—a set of tools for comparing two XML documents. XML Diff detects additions, deletions, and other changes made between two XML documents. It also detects structural changes like the move of an XML subtree. Check it out at http://apps.gotdotnet.com/xmltools/xmldiff.

You can have different XML files in one file as long as the complete XML is well formed, but it is inadvisable because the uniformity of the data is lost, the schema information becomes complex or makes no sense, and it becomes hard to maintain and transform. Also, if there are nodes with similar names they may clash in the queries unless you separate them with unique namespaces.

Got a question? Send questions and comments to webqa@microsoft.com.

Thanks to the following Microsoft developers for their technical expertise: Earl Beaman, Jonas Brandel, Manuel Rebello de Andrade, Derek Denny-Brown, Michael Gibson, Lei Guy, Brian Heitt, Dion Houston, Dmitry Kakurin, Pranav Kandula, Michael Kaplan, Prashant Kudli, Krishna Kumar, Eric Lippert, Jonmichael Presley, Nate Sternberg, Bruce Taimana, Konrad Tupaj, Michael Whalen, Federico Winkel. Also, special thanks to reader Mike Young.


Cutting Edge: Creating a Multi-table DataGrid in ASP.NET

Cutting Edge
Creating a Multi-table DataGrid in ASP.NET
Dino Esposito

Code download available at: CuttingEdge0308.exe (116KB)
Contents
Binding a Multi-table DataSet
The New TabStrip Component
The DataSetGrid Component
A Real-world Example
A Master/Detail DataGrid Control
Conclusion

Like many other data-bound Web controls, the ASP.NET DataGrid can be bound to a variety of data sources, including the ADO.NET DataTable and DataView objects. The grid can also be bound to custom collection classes and arrays, even though in these cases you might want use template columns to gain better control over the output. Unlike the Windows® Forms DataGrid (which is discussed in "DataGrid: Tailor Your DataGrid Apps Using Table Style and Custom Column Style Objects" by Kristy Saunders in this issue), the ASP.NET DataGrid control cannot be bound directly to a DataSet object. You can bind it to a DataSet object through the DataGrid's DataSource property, but you also need to set the DataMember property to specify which table in the DataSet to bind to. As a result, the ASP.NET DataGrid control works on a per-table basis and displays the contents of a single DataTable object, or the contents of a view built atop a particular table.

Figure 1 Table Links
Figure 1 Table Links

The Windows Forms DataGrid control doesn't suffer from the same design limitation. If you bind a Windows Forms DataGrid to a multi-table DataSet, the control automatically adapts its user interface to allow for navigation between the tables (see Figure 1). The control displays a list of links, each one corresponding to a particular table in the DataSet. You click on the table and the grid gets populated with the table's content. This just doesn't happen with Web grids. This column will discuss a way to enrich the user interface of the ASP.NET DataGrid control with a tabbed menu so that the developer can bind a DataSet to the grid and the user can select a particular table within the DataSet to be displayed.

Binding a Multi-table DataSet

If you bind a multi-table DataSet to a Web Forms DataGrid only the first table found in the Tables collection is noticed; all the others are ignored. The DataMember property can be used to select a particular table in the grid, but you're limited to this one table. Nothing in the default DataGrid programming interface allows for a multi-table representation of the data. The pager bar is the only user interface element that can be used as a table selector. The pager bar can also be used to select pages of data from the currently displayed table. If you adapt it to work as a table selector, you lose the ability to page through content of the current table. There is a way to adapt the pager bar to work as a menu with all the available tables. You simply need to hook up the ItemCreated event, change the default text displayed for page links, and write an appropriate handler for the PageIndexChanged event to physically control the process of table selection.

Figure 2 Tabbed Control Idea
Figure 2 Tabbed Control Idea

If you don't want to modify the pager bar—and you normally shouldn't—the only viable alternative for multi-table selection is to use an extra component that lists the tables, creating a tabstrip component associated with the grid. The graphical layout of this new selector control is depicted in Figure 2. In this month's column, I'll build such a selector control, bind it to the DataGrid, and then I'll create a new user control.

The New TabStrip Component

The easiest way to create a component similar to the one shown in Figure 2 is to use a Repeater control to generate a bunch of command buttons, where each button corresponds to a particular table in the DataSet. Each button click will be intercepted and the related DataTable object bound to the grid. However, let's tackle one problem at a time by creating a button list component.

The TabStrip control provides a user interface made of a sequence of buttons, each corresponding to a bound element. The list of buttons is created using a data-bound Repeater control, and the button clicks are handled through the ItemCommand event handler of the Repeater. The TabStrip control also features a number of color properties that let you define the background and foreground color for unselected buttons and even for the button that represents the selected table. The CurrentTabIndex property indicates the zero-based position of the currently selected element. Figure 3 shows the full source of the control.

Figure 3 The New Control (Close Figure 3)
<%@ Control Language="C#" ClassName="TabStrip" %>
<%@ Import Namespace="System.Drawing" %>

<script runat="server">

// Tabs to display
public ArrayList Tabs = new ArrayList();

// Current Tab
public int CurrentTabIndex
{
    get {return (int) ViewState["CurrentTabIndex"];}
}

// Background color
public Color BackColor
{
    get {return (Color) ViewState["BackColor"];}
    set {ViewState["BackColor"] = value;}
}

// Selected background color
public Color SelectedBackColor
{
    get {return (Color) ViewState["SelectedBackColor"];}
    set {ViewState["SelectedBackColor"] = value;}
}

// Foreground color
public Color ForeColor
{
    get {return (Color) ViewState["ForeColor"];}
    set {ViewState["ForeColor"] = value;}
}

// Selected foreground color
public Color SelectedForeColor
{
    get {return (Color) ViewState["SelectedForeColor"];}
    set {ViewState["SelectedForeColor"] = value;}
}

// Select method
public void Select(int index) {
    // Ensure the index is a valid value
    if (index <0 || index >=Tabs.Count)
        index = 0;

    // Updates the current index. Must write to the view state
    // because the CurrentTabIndex property is read-only
    ViewState["CurrentTabIndex"] = index;

    // Refresh the UI
    BindData();

    // Fire the event to the client
    SelectionChangedEventArgs ev = new SelectionChangedEventArgs();
    ev.Position = CurrentTabIndex;
    OnSelectionChanged(ev);
}

// Custom event class
public class SelectionChangedEventArgs : EventArgs
{
    public int Position;
}

public delegate void SelectionChangedEventHandler(object sender,
    SelectionChangedEventArgs e);
public event SelectionChangedEventHandler SelectionChanged;

// Helper function that fires the event by executing user-defined code
void OnSelectionChanged(SelectionChangedEventArgs e)
{
    // SelectionChanged is the event property
    if (SelectionChanged != null)
        SelectionChanged(this, e);
}

//////////////////////////////////////////////////////////////////////

private void Page_Init(object sender, EventArgs e)
{
    if (ViewState["SelectedBackColor"] == null)
        SelectedBackColor = Color.White;
    if (ViewState["SelectedForeColor"] == null)
        SelectedForeColor = Color.Blue;
    if (ViewState["BackColor"] == null)
        BackColor = Color.Gray;
    if (ViewState["ForeColor"] == null)
        ForeColor = Color.White;
    if (ViewState["CurrentTabIndex"] == null)
        ViewState["CurrentTabIndex"] = 0;
}

private void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        BindData();
    }
}

private void BindData()
{
    __theTabStrip.DataSource = Tabs;
    __theTabStrip.DataBind();
}

private Color SetBackColor(object elem)
{
    RepeaterItem item = (RepeaterItem) elem;
    if (item.ItemIndex == CurrentTabIndex)
        return SelectedBackColor;
    return BackColor;
}

private Color SetForeColor(object elem)
{
    RepeaterItem item = (RepeaterItem) elem;
    if (item.ItemIndex == CurrentTabIndex)
        return SelectedForeColor;
    return ForeColor;
}

private Color SetBorderColor(object elem)
{
    RepeaterItem item = (RepeaterItem) elem;
    if (item.ItemIndex == CurrentTabIndex)
        return SelectedBackColor;
    return Color.Black;
}

private void ItemCommand(object sender, RepeaterCommandEventArgs e)
{
    Select(e.Item.ItemIndex);
}
</script>

<asp:Repeater runat="server" id="__theTabStrip"
     OnItemCommand="ItemCommand">
    <headertemplate>
        <table cellpadding="0" cellspacing="0" border="0" ><tr>
    </headertemplate>

    <itemtemplate>
        <td>
            <asp:button runat="server" id="__theTab"
                BorderWidth="1px"
                BorderStyle="solid"
                BorderColor='<%# SetBorderColor(Container) %>'
                text='<%# Container.DataItem %>'
                font-bold='<%# (Container.ItemIndex == CurrentTabIndex) %>'
                backcolor='<%# SetBackColor(Container) %>'
                forecolor='<%# SetForeColor(Container) %>' />
        </td>
    </itemtemplate>

    <footertemplate>
        </tr></table>
    </footertemplate>
</asp:Repeater>

Whenever a button in the list is clicked, the control fires a custom event to the host page. This event, SelectionChanged, requires the following delegate:

public delegate
void SelectionChangedEventHandler(object sender,
    SelectionChangedEventArgs e);

Since the event needs to carry some custom data, a custom delegate is required. The event-specific data is packed into the user-defined SelectionChangedEventArgs class. The class inherits from the base event class, EventArgs, and extends it by adding an integer property named Position. The property denotes the zero-based index of the clicked button:

public class SelectionChangedEventArgs : EventArgs
   {
       public int Position;
   }

The caption text values for the various buttons are stored in a public member of ArrayList named Tabs. The following code snippet shows how to populate the Tabs property with the tables included in a DataSet object:

foreach(DataTable dt in dataSet.Tables)
    tableSelector.Tabs.Add(dt.TableName);

Figure 4 illustrates a typical button list created with the TabStrip user control. Because the constituent elements of the control are buttons, clicks are signaled through the ItemCommand event whose embedded handler updates the CurrentTabIndex property, refreshes the tab colors, and fires the SelectionChanged event to the host page for further processing.

Figure 4 TabStrip Control
Figure 4 TabStrip Control

The TabStrip control is important when setting up a multi-table DataGrid because it provides the selection mechanism for the various tables included in the DataSet. Each button represents a table, and clicking on a button causes the corresponding table within the grid to be selected. Let's take a look at a sample page that hosts a TabStrip component and DataGrid bound to a multi-table DataSet with the following code:

<table><tr><td>
<msdn:tabstrip runat="server" id="__tableSelector"
    SelectedBackColor="cyan"
    OnSelectionChanged="SelectionChanged" />
<asp:datagrid runat="server" id="__theGrid"
    AllowPaging="true"
    OnPageIndexChanged="PageIndexChanged">
    <pagerstyle position="top" />
    <alternatingitemstyle backcolor="ivory" />
    <itemstyle backcolor="#eeeeee" />
</asp:datagrid>
</td></tr></table>

The DataGrid is located just below the TabStrip and, if you properly manipulate the colors, you can create the illusion that they form a single control. In Figure 5, you can see the source code necessary to handle the SelectionChanged event and bind the corresponding table of the DataSet to the grid. Once the binding takes place, the code also adjusts the colors of the pager bar and the header so that there is a sense of continuity between the selected button and the grid underneath. (See Figure 6 for a screenshot of the sample application.) Since the DataGrid control is independent from the TabStrip user control, it can be navigated autonomously using the links on the pager bar.

Figure 5 Handle SelectionChanged and Bind to Grid (Close Figure 5)
<script runat="server">

void SelectionChanged(object sender,
    TabStrip.SelectionChangedEventArgs e)
{
    __theGrid.CurrentPageIndex = 0;
    BindData();
}

void BindData()
{
    __theGrid.DataSource = data.Tables[__tableSelector.CurrentTabIndex];
    __theGrid.DataBind();
    SetupGrid();
}

void PageIndexChanged(object sender, DataGridPageChangedEventArgs e)
{
    __theGrid.CurrentPageIndex = e.NewPageIndex;
    BindData();
}

void SetupGrid()
{
    __theGrid.HeaderStyle.BackColor = __tableSelector.BackColor;
    __theGrid.HeaderStyle.ForeColor = __tableSelector.ForeColor;
    __theGrid.PagerStyle.BackColor = __tableSelector.SelectedBackColor;
}
•••
</script>
Figure 6 Two Grids Acting As One
Figure 6 Two Grids Acting As One

Although it works, this example is still as an aggregation of server controls and code mixed together on a Web page. With this implementation, code reusability is difficult to achieve. To make the solution reusable, you need to combine the TabStrip, the DataGrid, and the code that glues them together into a single control.

The DataSetGrid Component

So far you have a working ASP.NET page that you want to transform into a reusable control that future pages can include. ASP.NET Web user controls provide what you're looking for. With a few simple steps, virtually any Web page can be converted into a user control, saved as an .ascx resource, and become a reusable component.

User controls and ASP.NET pages have so much in common that transforming one into the other is easy. To start out, save the original .aspx file into a .ascx file and open it in your favorite code editor. Next, remove the HTML tags: <html>, <body>, and <form>. This ensures that you avoid conflicts and parsing errors at rendering time, when the layout of the control is merged with the layout of the hosting page, with its own <html> and <body> tags.

Not all <form> tags need to be removed. In ASP.NET you can have as many form elements as needed, but only one can be marked with the runat attribute and set to visible. (For more information on server-side forms, take a look at the May 2003 installment of Cutting Edge.) In light of this, remove from the original page any existing <form runat="server"> elements. If you have HTML forms (<form> tags without the runat attribute), you can leave them in.

You should note that saving the file with an .ascx extension is necessary for proper execution by the ASP.NET HTTP runtime. Finally, make sure that if the page you're converting contains an @Page directive, you change it to an @Control directive. The @Control and @Page directives share several common attributes, a list of which is shown in Figure 7.

Figure 7 Page and Control Common Attributes (Close Figure 7)

Attribute | Description
AutoEventWireup | Indicates whether the control's events are automatically bound to methods with a particular name (for example, Page_Load); the default is true
ClassName | Indicates an alias for the name of the class that will be created to render the user control; this value can be any valid class name, but should not include a namespace
CompilerOptions | A sequence of compiler command-line switches used to compile the control's class
Debug | Indicates whether the control should be compiled with debug symbols; if true, the source code of the class is not deleted from the Temporary ASP.NET Files folder
Description | Provides a text description of the control
EnableViewState | Indicates whether view state for the user control is maintained across page requests; the default is true
Explicit | Determines whether the page is compiled using the Visual Basic Option Explicit mode; ignored by languages other than Visual Basic .NET; false by default
Inherits | Defines a codebehind class for the user control to inherit; can be any class derived from UserControl
Language | Specifies the language used throughout the control
Strict | Determines whether the page is compiled using the Visual Basic Option Strict mode; ignored by languages other than Visual Basic .NET; false by default
Src | Specifies the source file name of the codebehind class to dynamically compile when the user control is requested
WarningLevel | Indicates the compiler warning level at which you want the compiler to abort compilation for the user control

Note that from within a user control, you cannot set any property that affects the overall behavior of the page. For example, you cannot enable or disable tracing, nor can you enable or disable session-state management. You can create the user control using either an inline-code or by specifying codebehind mode. If you prefer inline, you can place all the control-specific code inside the server-side <script> tag; for codebehind, you write the code to the C# or Visual Basic® .NET class file pointed to by the "Src" attribute of the @Control directive. You should note, though, that the Src attribute is not currently recognized and supported by RAD designers such as Visual Studio® .NET. It is, however, fully supported in Web Matrix (http://www.asp.net/webmatrix).

If you develop the user control with Visual Studio .NET, the codebehind class is bound to the source file in a different way. The class code is compiled in the project assembly and made available to the ASP.NET runtime through the Inherits attribute. For editing purposes only, Visual Studio .NET tracks the name of the codebehind file using the custom "CodeBehind" attribute. If you use Web Matrix, the Src attribute can be used to let the ASP.NET runtime know where the code of the component should be read from and dynamically compiled, as can also be achieved with ordinary pages.

The source code of the new DataSetGrid user control is not much different from that of the page displayed in Figure 6. (The core part of the control's source code appears in Figure 5; for the full source, download this month's code archive from the link at the top of this article.) However, the page that makes use of the control is radically different, as you can see in Figure 8.

Figure 8 Using the New Control (Close Figure 8)
<%@ Page Language="C#" %>
<%@ Register TagPrefix="msdn" TagName="DataSetGrid"
    Src="DataSetGrid.ascx" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<script runat="server">
void Page_Load(object sender, EventArgs e)
{
    SqlDataAdapter adapter = new SqlDataAdapter(
        "SELECT lastname, firstname FROM employees;SELECT customerid,
        companyname FROM customers;",
        "SERVER=localhost;DATABASE=northwind;UID=sa;");
    DataSet data = new DataSet();
    adapter.Fill(data);
    data.Tables[0].TableName = "Employees";
    data.Tables[1].TableName = "Customers";

    dataGrid.DataSource = data;
    dataGrid.DataBind();
}

</script>

<html>
<title>Multi-table DataGrid</title>
<body style="font-family:verdana;font-size:8pt;">

<form runat="server">
    <msdn:datasetgrid runat="server" id="dataGrid" />
</form>

</body>
</html>

You register the new control through the @Register directive:

<%@ Register TagPrefix="msdn" TagName="DataSetGrid"
 Src="DataSetGrid.ascx" %>
Next, you use the control within the page using the namespace prefix and the tag name defined in the @Register directive:
<form runat="server">
    <msdn:datasetgrid runat="server" id="dataGrid" />
</form>

DataSetGrid provides a pair of public members—the DataSource property and the DataBind method. Admittedly, these names can recall members of the Microsoft® .NET Framework classes and built-in controls. I opted for these names just for the sake of consistency, but their implementations are wholly local to the .ascx resource file:

public DataSet DataSource;
public void DataBind()
{
    foreach(DataTable dt in DataSource.Tables)
        __tableSelector.Tabs.Add(dt.TableName);

    BindData();
}
The BindData internal method is responsible for binding the selected table to the embedded DataGrid control.

The DataGrid control retains some default settings. In particular, it needs its AutoGenerateColumns property set to true. Since the grid component is not publicly exposed, there's no way for the page author to indicate which columns should be bound or how. The same holds true for column headers, which default to the name of the column returned by the resultset. A simple way to customize the text on the header of the grid's columns is to alias the data columns in the SQL query, as shown here:

SELECT lastname AS 'Family Name'
FROM employees

The names of the tables in the bound DataSet should be customized. The ADO.NET table-mapping engine assigns default names to the various resultsets that a query generates. These names follow a standard naming convention such as Table, Table1, Table2. Although you can customize the prefix (change it from Table to Product, for example), additional resultsets are named with a progressive index. To change the names of tables in the DataSet, you have two options, the simplest of which is to rename the tables before the data binding occurs:

DataSet data = new DataSet();
adapter.Fill(data);
data.Tables[0].TableName = "Employees";
data.Tables[1].TableName = "Customers";

dataGrid.DataSource = data;
dataGrid.DataBind();

In this case, the table renaming takes place after the data has been retrieved. The second option entails configuring the ADO.NET table-mapping mechanism so that the DataSet's tables are automatically generated with the desired name. As you'll see in a moment, customizing the table-mapping engine also results in a slight performance improvement.

The table mapping is the process that controls how ADO.NET data adapters create in-memory tables and columns from a physical data source. A DataAdapter object utilizes the Fill method to populate a DataSet or a DataTable object with data retrieved with a SELECT command. Internally, the Fill method makes use of a DataReader to get to the data and metadata that describe the structure and content of the source tables. The DataReader is then copied into a memory container (the DataSet). The table-mapping mechanism is the set of rules and configurable parameters that let you control how a resultset is mapped onto in-memory objects.

Mapping a resultset to a DataSet is a process that comprises two phases: table mapping and column mapping. During the table-mapping step, the data adapter has to find a name for the DataTable that will contain the rows in the resultset being processed. Each resultset is given a default name that the programmer can change at will; this default name depends on the signature of the Fill method that has been used for the call. For example, let's consider the two overloads here:

adapter.Fill(ds);
adapter.Fill(ds, "MyTable");

In this case the first overload names the first resultset Table, while the second names it MyTable. The DataAdapter object looks in its TableMappings collection for an entry that matches the default name of the resultset. In other words, the TableMappings property of the DataAdapter class should contain name/value pairs in which the name matches one of the default resultset names (Table, Table1, MyTable2, and the like), and the value is the new name the developer wants the table to have:

adapter.TableMappings.Add("Table", "Employees");
adapter.TableMappings.Add("Table1", "Products");
adapter.TableMappings.Add("Table2", "Orders");

// Second arg unspecified, default to TableX names
adapter.Fill(ds);

If the table-mapping collection contains a match, then the data adapter uses the specified name for the corresponding DataTable in the target DataSet (named ds in the previous code snippet). If no mapping is found, then the behavior of data adapter will depend on the value of the MissingMappingAction property. The adapter may skip the unmapped resultset, throw an exception, or just stick to the default name.

Once the name has been determined, the adapter attempts to locate a corresponding DataTable object in the destination DataSet. If such a table already exists in the DataSet, then its current contents are merged with the new resultset, otherwise a missing schema action is required. The MissingSchemaAction property dictates the next action. The adapter can just ignore the table (which won't be loaded in the DataSet), raise an exception, or create an empty table to fill with the resultset (the default action). A similar mapping mechanism exists for the columns in the table. The column-mapping property is named ColumnMappings and is defined on the TableMappings class. The following code snippet shows how to use it:

   DataTableMapping dtm1;
   dtm1 = da.TableMappings.Add("Table",
          "Employees");
   dtm1.ColumnMappings.Add("employeeid", "ID");
   dtm1.ColumnMappings.Add("firstname", "Name");
   dtm1.ColumnMappings.Add("lastname", "Surname");
   da.Fill(ds);

The table and column mapping takes place transparently whenever you call the Fill method on a DataAdapter object. The mapping is an important part of the DataSet population stage performed by adapter objects in ADO.NET. If you configure the table- and column-mappings infrastructure so that the tables in the DataSet have the names they are expected to have, you'll get better adapter performance. When the adapter finds a non-empty mapping collection, it works faster because it doesn't have to figure things out. Since the DataSet already contains properly named tables, you save the extra instructions that would otherwise be needed for late table renaming.

A Real-world Example

Admittedly, the DataSetGrid control is not exactly the type of control you'd use in every application. It turns out to be more useful for those quick applications that developers often need to build on the back end of a Web site to allow for easier customization and maintainance. In such a scenario, you have no scalability concerns that would prevent the caching of large chunks of data into the Session object. You can pack everything into a DataSet object and bind it to the DataSetGrid control. Quick and effective.

The DataSetGrid control can also be used to provide a more attractive representation of data that would normally fit into a single table. The idea is to subdivide the contents of the original query in properly named sub-tables—for example, by a person's initials or by month. Let's suppose that you need to retrieve the list of all customers. Instead of displaying the customers in a unique list that may run to several pages, you could partition the records into various sub-tables in the same DataSet. Displayed through the DataSetGrid control, the tabbed resultset provides users with a more rational search interface. If the user needs to access a customer that starts with M, there's no need to force her to scroll through lots of pages until the customer is found. With just one click, you can display a sub-table that groups all the customers whose name begins with M. The following code runs a query that returns four tables, each containing a subset of customers:

SqlDataAdapter adapter = new SqlDataAdapter(
   "SELECT * FROM customers WHERE companyname >= 'A'
   AND companyname < 'F';" + "SELECT * FROM customers
   WHERE companyname >= 'F' AND companyname < 'M';" +
   "SELECT * FROM customers WHERE companyname >=
   'M' AND companyname
   < 'S';" + "SELECT * FROM customers WHERE companyname >= 'S'",
   "SERVER=localhost;DATABASE=northwind;UID=sa;");
DataSet data = new DataSet();
adapter.Fill(data);

If you name the various tables to reflect the initials of the stored customers, you will create a really cool effect (see Figure 9):

data.Tables[0].TableName = "A-E";
data.Tables[1].TableName = "F-L";
data.Tables[2].TableName = "M-R";
data.Tables[3].TableName = "S-Z";
dataGrid.DataSource = data;
dataGrid.DataBind();

Figure 9 Alphabetical Tabs
Figure 9 Alphabetical Tabs

So far so good. However, can you really say that this relatively simple feature makes the Web Forms DataGrid control similar to the Windows Forms version? Certainly not. One aspect of the Windows Forms DataGrid control that I especially like is the control's ability to represent any parent/child relationship that has been imposed on the tables. If two tables in the bound DataSet are tied by a relationship (through an ADO.NET DataRelation object), the Windows Forms grid adds an extra button to all rows from the parent table. When you click on that button, the DataGrid row expands, revealing all the child records for that row. For example, suppose that you have Customers and Orders tables and a parent/child relationship set between the two. In the Windows Forms grid, clicking the extra button on a customer row would display all the orders issued by that customer. Let's see if it's possible to implement the same feature with the Web Forms DataGrid.

A Master/Detail DataGrid Control

To support data relationships in the DataSet, the DataSetGrid control must support being able to detect when a table that is about to be displayed has children. If it does, you must add an extra button column to activate the selection. The layout of the DataSetGrid user control must be extended too; at a minimum, it has to include a second DataGrid for displaying the detail rows. The following code determines whether a select button column is needed:

DataTable __currentTable = (DataTable)__theGrid.DataSource;
if (__currentTable.ChildRelations.Count <=0)
    return;

// Enable selection on the grid
AddSelectColumn();

The button column is an instance of the ButtonColumn class. It's important that you set the CommandName property of the column to SELECT because of the special meaning it has to the DataGrid infrastructure. When the button is clicked, the whole grid row is repainted using the styles defined through the <selecteditemstyle> tag. At a minimum, this style node will define a different background color:

<selecteditemstyle backcolor="yellow" />

Clicking a button marked with the SELECT command also causes the SelectedIndexChanged event to fire. The DataGrid embedded in the DataSetGrid control must provide an adequate event handler, as shown here:

void SelectedIndexChanged(object sender, EventArgs e)
{
   SelectedItemIndex = __theGrid.SelectedItem.DataSetIndex;
   BindDetails();
}

Using the GetChildRows method on the DataRow object isn't the most efficient way to retrieve the child rows of a parent row within a DataGrid. While it remains the only built-in way to get at the related child rows, using that method in the context of a DataGrid is not sufficient. The method, in fact, returns an array of DataRow objects—a data structure that is not directly bindable to a grid. A much better approach is to create a child view of relations based on the selected record. For this to happen, you need the DataRowView object that corresponds to the selected row. A DataRowView object can be obtained only by position from a DataView object. Subsequently, you must first create a DataView object based on the parent table and then track the absolute index of the clicked row within the table. Fortunately, this information is quite easy to obtain thanks to the DataSetIndex property of the DataGridItem class. DataGrid's programming interface offers the SelectedItem property to access the DataGridItem that represents the clicked row. The following code snippet summarizes the changes needed in the source:

void BindDetails()
{
   DataTable table;
   table = DataSource.Tables[__tableSelector.CurrentTabIndex];
   DataView view = new DataView(table);
      DataRowView drv = view[SelectedItemIndex];
      DataView detailsView =
          drv.CreateChildView("Customer2Orders");

      theDetailsGrid.DataSource = detailsView;
      theDetailsGrid.DataBind();
   }

The code first retrieves the current table and creates a DataView instance on top of it. Next, the corresponding DataRowView object is retrieved. The CreateChildView method takes the name of the relation as an argument and returns a child view. Finally, the child view is bound to the child DataGrid. Figure 10 shows the output.

Figure 10 The Final Product
Figure 10 The Final Product

It is worth noting that a large part of the output you see in the figure is automatically generated by the DataSetGrid user control. The calling page is only responsible for preparing the input DataSet with its tables and relations. If the bound DataSet has no relations, the control works as discussed in the first part of this column, acting as merely a table selector and viewer.

You can close the child view by simply clicking on the Select button once more. When the SELECT button of a grid is clicked, two events are fired sequentially—ItemCommand and SelectedIndexChanged. The first event arrives when the SelectedIndex property of the DataGrid has not been updated yet. Cache the value of SelectedIndex in a global variable at this time, and compare it with the property's value when the SelectedIndexChanged event is fired. If the two values match, then the user clicked twice on the same row; the first click opens the child view while the second closes it.

Conclusion

The DataSetGrid control I presented here is far from perfect. There are a couple of limitations. First, the control has a lot of user interface features locked down. Both the master and the detail grids are embedded and not configurable at the page level. This means that you can't control which columns are displayed and how they are formatted. You can't further customize the appearance of the grids using tooltips, templates, and summary rows.

In addition, the control is architected and implemented as a Web Forms user control, meaning that two .ascx files should be deployed—tabstrip.ascx and datasetgrid.ascx. These user controls can't be inherited and provide poor support for design-time configuration in RAD environments such as Visual Studio .NET. These drawbacks disappear if you re-architect the control as a custom server control. In this discussion I used user controls because I find them ideal for quickly prototyping solutions. Send me your feedback about this and I just might use them in future columns to rebuild the DataSetGrid as a custom ASP.NET control.

Send your questions and comments for Dino to cutting@microsoft.com.

Dino Esposito is an instructor and consultant based in Rome, Italy. He is the author of Building Web Solutions with ASP.NET and ADO.NET and Applied XML Programming for Microsoft .NET, both from Microsoft Press (2002). Reach Dino at dinoe@wintellect.com.


The XML Files: XSLT Keys, Select vs. Match, Conflict Resolution, and More

The XML Files
XSLT Keys, Select vs. Match, Conflict Resolution, and More
Aaron Skonnard

Q How do keys work in XSLT?

A Keys in XSLT are similar to IDs in Document Type Definitions (DTDs). If you define an attribute of type ID in a DTD, the attribute value must be unique in the document. You can also define attributes of type IDREF. In this case, the attribute value must refer to a unique ID value somewhere else in the document. With this type of DTD information in place, you can then use XPath's id function in order to resolve IDs and IDREF relationships in a document at run time. For example, let's consider an XML document with an internal DTD (see Figure 1).

Figure 1 Internal DTD (Close Figure 1)
<!DOCTYPE courses [
  <!ELEMENT courses (course*, employees)>
  <!ELEMENT course EMPTY>
  <!ELEMENT employees (employee*)>
  <!ELEMENT employee EMPTY>
  <!ATTLIST course
            instructors IDREF #REQUIRED
            name CDATA #REQUIRED
  >
  <!ATTLIST employee
            id ID #REQUIRED
            name CDATA #REQUIRED
  >
]>
<courses>
  <course instructors="e101 e102 e103 e104" name="EWS.NET"/>
  <course instructors="e101 e102 e105" name="GWS.NET"/>
  <employees>
    <employee id='e101' name="Aaron Skonnard"/>
    <employee id='e102' name="Simon Horrell"/>
    <employee id='e103' name="Dan Sullivan"/>
    <employee id='e104' name="Scott Bloom"/>
    <employee id='e105' name="Bob Beauchemin"/>
  </employees>
</courses>

The following XPath expression identifies the element with an ID of e104, which in this particular case is an employee element with the name of Scott Bloom:

id('e104')
The following XPath expression identifies the elements with ID values of e101, e102, and e104, which in this case happens to be the employee elements that have the names of Aaron Skonnard, Dan Sullivan, and Scott Bloom:
id('e101 e103 e104')
And finally, the following XPath expression identifies the employees who teach the "GWS.NET" course (specifically Aaron Skonnard, Simon Horrell, and Bob Beauchemin) through the instructors IDREF attribute:
id(/courses/course[2]/@instructors)

As you can see, this technique makes it possible to establish relationships between different elements based on their ID values. The problem with the ID/IDREF technique is that it requires DTD information to be present at run time and it's limited to attributes. To help alleviate these restrictions, XSLT introduced a new concept, called keys, which provides the same basic functionality but with more flexibility. You use the key element to define a key in an XSLT document. It has the following structure:

<xsl:key name="qname" match="pattern" use="expression"/>
You give the key a qualified name so that you can refer to it later from the key function (which behaves like the XPath id function described earlier). You also specify a pattern, which identifies the nodes to which the key applies. Finally, you use the "use" attribute to specify an XPath expression that is evaluated (relative to the nodes identified by the match expression) to produce the actual key value. For example, consider the XML document in Figure 2.

Figure 2 An XML Document (Close Figure 2)
<courses>
  <course>
    <name>EWS.NET</name>
    <instructors>
      <instructor>e101</instructor>
    </instructors>
  </course>
  <course>
    <name>GWS.NET</name>
    <instructors>
      <instructor>e101</instructor>
      <instructor>e102</instructor>
      <instructor>e103</instructor>
    </instructors>
  </course>
  <employees>
    <employee id='e101'>
      <name>Aaron Skonnard</name>
    </employee>
    <employee id='e102'>
      <name>Simon Horrell</name>
    </employee>
    <employee id='e103'>
      <name>Dan Sullivan</name>
    </employee>
  </employees>
</courses>

In this case, you probably want to create a key based on the employee element's id attribute, as illustrated here:

<xsl:key name="employeeId" match="employee" use="@id"/>
This creates a key for all employee elements within the document; the key value is evaluated from the employee element's id attribute. With the key defined, you can then use the key function to retrieve nodes with a given key. For example, the following XSLT fragment illustrates how to retrieve the instructors who teach a given course:
<xsl:template match="course">
  <h2><xsl:value-of select="name"/></h2>
  <h3>Instructors</h3>
  <ul>
   <xsl:for-each select="key('employeeId', ./instructors/*)">
     <li><xsl:value-of select="."/></li>
   </xsl:for-each>
  </ul>
</xsl:template>

The previous fragment will emit the following HTML, assuming the input document shown in Figure 2:

<h2>EWS.NET</h2>
<h3>Instructors</h3>
<ul>
   <li>Aaron Skonnard </li>
</ul>
<h2>GWS.NET</h2>
<h3>Instructors</h3>
<ul>
   <li>Aaron Skonnard</li>
   <li>Simon Horrell</li>
   <li>Dan Sullivan</li>
</ul>
As you can see, keys don't require DTD information to be used and they're not limited to attributes as with ID/IDREF. The fact that keys can be assigned to nodes that match an arbitrary pattern also gives you an extra degree of flexibility.

Q What's the difference between the match and select attributes in XSLT? What do they do, exactly?

A In XSLT, there are various elements that come with a match attribute (such as template and key) and others that come with select attribute (value-of, for-each, apply-templates, and so on). At first glance, both seem to take XPath expressions, but in fact they're quite different.

Select does indeed expect an XPath expression, which is used to select a nodeset for further processing. In the following example, the for-each expression identifies a set of nodes to be iterated over while the nested value-of expression writes the value of each node to the output tree:

<xsl:for-each select="//item">
  <xsl:value-of select="."/>
</xsl:for-each>

The match attribute, on the other hand, takes what's called a pattern. A pattern looks like an XPath expression because it shares the same syntax, but it's treated differently by the XSLT processor. A pattern is used for matching nodes in the tree against the specified criteria. For example, the following template matches any element node that has a grandparent named foo:

<xsl:template match="foo/*/*">
   •••
</xsl:template>
In other words, a pattern describes an "is a" relationship with the nodes in question (that is, is this node an element that has a grandparent named foo?). Here's a more sophisticated example:
<xsl:template match="f:foo[@id > 323]//text()">
   •••
</xsl:template>
In this case, the pattern identifies all text nodes that have an ancestor element named foo from the namespace identified by f, and with an id attribute with a numeric value greater than 323.

Q Why does SelectNodes always return the nodes in document order irrespective of the axis direction (forward/reverse) that was used in the query?

A Consider the following XML document, which I'll call foo.xml:

<foo depth='1'>
  <foo depth='2'>
    <foo depth='3'>
      <bar/>
    </foo>
  </foo>
</foo>

Now consider the following code that evaluates slightly different XPath expressions against the loaded foo.xml document:

XmlDocument doc = new XmlDocument();
doc.Load("foo.xml");

XmlElement b, f1, f2;

b =  (XmlElement)doc.SelectSingleNode("//bar");
f1 = (XmlElement)b.SelectSingleNode("ancestor::foo[1]");
f2 = (XmlElement)b.SelectNodes("ancestor::foo")[0];

Console.WriteLine(f1.GetAttribute("depth"));
Console.WriteLine(f2.GetAttribute("depth"));

You might expect the console output to be the same in both cases, but it's actually different. The first Console.WriteLine outputs "3" while the second outputs "1". The reason it works this way is that axis direction, as defined by the XPath specification, is only significant within XPath predicates. In fact, XPath nodesets are by definition unordered so it's up to the XPath implementation to determine in what order to deliver nodesets.

In the call to SelectSingleNode, however, I asked for the first node in the ancestor axis within a predicate. This guarantees that I'll get the first ancestor node (in reverse document order) starting from the bar element (the foo element with depth='3'). In the call to SelectNodes, on the other hand, I asked for all ancestor foo elements and the implementation returned an XmlNodeList collection with the nodes in document order (completely legal according to the specification). Then, my code asked for the first item in the collection, which happens to be the foo element where depth='1'. Since XPath doesn't specify the order in which nodes are returned, languages that build on XPath often define additional semantics of their own. For example, in the case of XSLT, nodesets are always processed in document order.

Q How do you query a paragraph of text to determine whether it contains a <SCRIPT> element?

A The answer to this question depends on what you mean when you ask if the paragraph contains a <SCRIPT> element. Consider the following document:

<item>
   <name>Something</name>
   <desc>This is a description that contains a <SCRIPT
     language="Javascript" src="script.js"/> element.</desc>
</item>
If the document looks like this, you would use a normal XPath expression that queries against the document's logical data model. For example, the following XPath expression identifies all element nodes in the document tree that contain a child SCRIPT element (in this case, the <desc> element):
//*[SCRIPT]

Now, consider the following document, which uses special entity references for the less-than (<) and greater-than (>) characters within the <desc> element:

<item>
   <name>Something</name>
   <desc>This is a description that contains a &lt;SCRIPT
      language="Javascript" src="script.js"/&gt; element.</desc>
</item>
When an XML processor parses this document, it replaces "&lt;" with the < character and replaces "&gt;" with the > character. Hence, the <desc> element contains the following text when you query it using XPath:
This is a description that contains a <SCRIPT
      language="Javascript" src="script.js"/> element.
So you would use the following XPath expression
//*[contains(.,'<SCRIPT')]
in order to search this document for all elements that contain "<SCRIPT".

Q When multiple XSLT patterns match a given node, how does the processor decide which to use?

A When a node matches more than one pattern found in your XSLT templates (also known as rules), the processor follows the conflict resolution guidelines outlined by the XSLT specification to determine which one to use. These guidelines state that the template with the highest priority will be called when conflicts occur. The algorithm for determining the actual priority of a template, however, requires some additional explanation.

To determine which node has the highest priority, the processor first eliminates all templates that were imported (using the xsl:import element); imported templates automatically have a lower priority than the templates in the importing transformation. The processor then determines the priority values of the remaining templates.

Priorities can be explicitly assigned to a template through the priority attribute. For example, the following template has been assigned a priority of 1:

<xsl:template match="/foo/bar" priority="1">
   <!-- do something interesting -->
</xsl:template>

If a priority has been assigned to a template, then that's the value used by the processor to determine which template has the highest priority. If a priority has not been explicitly assigned, the processor calculates a default for the template. The default priorities assigned by the processor can range from -0.5 to +0.5. Basically, more specific patterns receive higher default priorities. Since the range is -0.5 to +0.5, assigning an explicit priority of 1 to a template will always win over a default priority.

Figure 3 outlines exactly how the default priorities are assigned for the different types of patterns that exist. Patterns that only contain node tests by type (such as *, node, comment, text, and so forth) are the most generic, so they receive a default priority of -0.5. Patterns that only contain namespace wildcards (ns:*) are a bit more specific, so they receive a default priority of -0.25. Patterns that only contain a qualified name test or a literal processing instruction test (such as foo, ns:foo, @bar, processing-instruction('foo'), and so on) receive a default priority of 0. Every other pattern that's more specific than these receives a default priority of 0.5. This means that any pattern with more than one location step, or with a predicate, automatically receives a default priority of 0.5.

Figure 3 Default Priorities (Close Figure 3)

Pattern Type | Default Priority | Examples
Node test by type | -0.50 | *
 |  | @*
 |  | node
 |  | comment
 |  | text
 |  | processing-instruction
Namespace wildcard | -0.25 | ns:*
QName | 0.00 | foo
 |  | ns:foo
 |  | @bar
 |  | @ns:bar
Processing instruction tests by literal | 0.00 | processing-instruction('foo')
Everything else | 0.50 | ns:foo/bar
 |  | ns:foo[@bar]
 |  | foo[contains(.,'Aaron')]
 |  | //foo
Multiple patterns (pattern1 | pattern2) | Treated as distinct templates, whose priorities are calculated independently |

Once the default priorities for all templates have been evaluated, the processor chooses the one with the highest priority. For example, in the following XSLT fragment there are two templates that may match a given bar element (assuming it has a parent foo element). When this occurs, the processor will automatically choose the second template because it has a higher default priority:

<xsl:template match='bar' >
  <!-- default priority = 0 -->
</xsl:template>

<xsl:template match='foo/bar' >
 <!-- default priority = .5 -->
</xsl:template>

There are still situations in which there may be multiple templates with the same priority that match a given node. When this occurs, the processor may either signal an error or choose to use the last template in the document. This is the usual choice. For example, the following XSLT fragment contains two templates that could match the same foo element (one that has both a child bar element and a parent bar element). Since both templates have a default priority of 0.5, the XSLT processor will either signal an error or choose the last template in the document, in this case the one matching bar/foo elements:

<xsl:template match='foo[bar]' >
  <!-- default priority = .5 -->
</xsl:template>

<xsl:template match='bar/foo'>
  <!-- default priority = .5 -->
</xsl:template>

Of course, you're better off avoiding these situations altogether. If you want the processor to always use the first template when these conflicts occur, you simply need to set its priority to 1, as illustrated in the following code:

<xsl:template match='foo[bar]' priority='1'>
  <!-- would have a default priority = .5 -->
</xsl:template>

<xsl:template match='foo/bar' >
  <!-- default priority = .5 -->
</xsl:template>

As you can see, XSLT has an extensive process for determining which templates to call in the case of conflicts. Understanding these rules is crucial when making copious use of XSLT's declarative programming model.

Q What happens when there isn't a template matching a node that is being processed?

A XSLT defines several templates that are built into every transformation by default, whether you provide them explicitly or not. These are referred to as XSLT's built-in templates. There is a built-in template for every type of node that might be processed in an XML document (elements, attributes, processing instructions, comments, and so on). These built-in templates, however, have the lowest priority possible, so they'll never be used before a user-provided template that also matches the same node (see the previous question on conflict resolution). They're only used when no other template matching a specific node is present.

The functionality of the built-in templates can be expressed with the following template definitions:

<!-- built-in templates -->

<xsl:template match="*|/">
   <xsl:apply-templates select="node()"/>
</xsl:template>

<xsl:template match="text()|@*">
   <xsl:value-of select="."/>
</xsl:template>

<xsl:template match="processing-instruction()|comment()"/>

In the case of the root node or element nodes, the processor automatically applies templates to the current node's children (but not its attributes since the call uses child::node()). In the case of text or attribute nodes, the processor automatically outputs the node's value to the result tree. In the case of processing instructions or comments, the processor does nothing—it simply ignores them.

This default functionality turns out to be quite useful with the declarative programming model offered by XSLT. It basically allows you to walk recursively through the tree until a node is matched against one of the provided templates. When the processor encounters a text or attribute node, it prints the node's value to the result tree at the right place.

To illustrate this process, try running the following transformation against any XML input document:

<xsl:transform version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform" />
This transformation doesn't contain any user-provided templates, so the built-in templates will be used instead. For example, try it against the following document:
<foo bar="hi">
  <bar>hello</bar>
  <bar>
     <baz>world</baz>
  </bar>
</foo>

In this case, the XSLT processor will output "helloworld". It recursively walks the tree and outputs the text node values, thanks to the built-in templates. The attribute node is never encountered due to the way xsl:apply-templates is called by the built-in template for elements. If your transformation ever outputs more text than you want it to, it's probably because the processor is recursively walking the tree and hitting text nodes unintentionally. In order to avoid this, you can always override the built-in template for text nodes. For example, the following is a new version of the last transformation with a new template for text nodes:

<xsl:transform version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
   <!-- override built-in template to do nothing -->
   <xsl:template match="text()|@*" />
</xsl:transform>

With this in modification, text nodes will never be output unless you explicitly do so through xsl:value-of. Try running the sample document through this transformation and you'll see that it doesn't output anything. In addition, if you don't want the processor to even walk the tree, you could override the built-in template for elements to do nothing, as shown here:

<xsl:transform version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
   <!-- override built-in template to do nothing -->
   <xsl:template match="*" />
</xsl:transform>

With this in place, the processor won't walk the tree, so it never makes it to the text nodes. If you run the sample document through this transformation, you'll see that it doesn't output anything either. If you override this template, it means that you must be more precise about which nodes you want to process when calling xsl:apply-templates.

Q What's the difference between xsl:include and xsl:import?

A The xsl:include element makes it possible to include templates from one transformation into another. When you use xsl:include, it has to be a top-level element or, in other words, an immediate child of xsl:transform. Then you simply specify the location of the transformation you'd like to include using the href attribute, as illustrated here:

<xsl:transform version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
   <!-- include templates from foo.xsl here -->
   <xsl:include href="http://foo.xsl"/>
   ...

</xsl:transform>

Using xsl:include has no effect on the priority of the included templates. The templates included into the new transformation are treated as if they were defined inline. This is where xsl:import is different.

The xsl:import element also allows you to include templates from one transformation into another. But when doing so, the imported templates automatically receive a lower precedence (that is, a lower default priority) than the templates in the including transformation (see the earlier question on conflict resolution guidelines). This is one of the main differences between xsl:include and xsl:import.

Like xsl:include, xsl:import must also be a top-level element, and you specify the location of the transformation to be imported using the href attribute:

<xsl:transform version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
   <!-- import templates from foo.xsl here -->
   <!-- imported templates have lower priority -->
   <xsl:import href="http://foo.xsl"/>
   ...

</xsl:transform>

XSLT also provides an xsl:apply-imports element (similar to xsl:apply-templates), which allows you to process a set of nodes against the imported templates only. This facilitates overriding templates like you would override a method in object-oriented code. For example, consider the example in Figure 4 which overrides the employee template with additional details.

Figure 4 Overriding a Template (Close Figure 4)
<!-- global.xsl -->
•••
<xsl:template match="employee">
   Name: <xsl:value-of select="name"/>
</xsl:template>
•••
<!-- employee.xsl -->
•••
<xsl:import href="http://global.xsl"/>

<xsl:template match="employee">
   <xsl:apply-imports/>
   Title: <xsl:value-of select="title"/>
</xsl:template>

In this example, employee.xsl imports global.xsl and overrides the employee template. Inside the employee template, it first calls xsl:apply-imports to process the imported employee template (shown in global.xsl), then it continues processing the employee element by outputting its title element. The output from running employee.xsl will look something like this:

Name:  Bob Smith
Title: President

Q Does XSLT provide an if/else statement?

A XSLT provides an if statement (xsl:if), but an else clause doesn't come with it. The xsl:if statement takes a Boolean expression in its test attribute, as shown here:

<xsl:if test="spouse and count(child) > 0">
   Married with children
</xsl:if>

If the test expression evaluates to true, the content within the xsl:if element is instantiated and processed. If you need an else clause, you have to use a different construct called xsl:choose, which is similar to a switch statement. The xsl:choose statement allows you to specify multiple test expressions, and the first one that evaluates to true (starting from the top) is instantiated and processed:

<xsl:choose>
   <xsl:when test="spouse and count(child) = 0">
      Just married
   </xsl:when>
   <xsl:when test="not(spouse) and count(child) > 0">
      Just kids
   </xsl:when>
   <xsl:when test="spouse and count(child) > 0">
      Married with children
   </xsl:when>
</xsl:choose>

The xsl:choose also allows you to have an xsl:otherwise statement, which is instantiated if none of the test expressions evaluate to true. This is what you'd use to implement a traditional if/else statement, as illustrated here:

<xsl:choose>
   <xsl:when test="spouse and count(child) > 0">
      Married with children
   </xsl:when>
   <xsl:otherwise>
      You must have plenty of free time
   </xsl:otherwise>
</xsl:choose>

I can't count the number of times I've started writing an xsl:if statement only to realize that I also needed an else clause, which forces me to start over using xsl:choose. So every time you start using xsl:if, ask yourself if you also need an else.

Q Does XSLT provide a way to control how the output of a transformation is serialized? If so, what options does it provide?

A Yes, XSLT provides the xsl:output element for exactly this purpose. It's a top-level element that should be an immediate child of xsl:transformation. The xsl:output element provides a wide range of options that control how the result tree is serialized. The following shows the syntax for xsl:output:

<xsl:output
  method = "xml" | "html" | "text" | qname
  version = nmtoken
  encoding = string
  omit-xml-declaration = "yes" | "no"
  standalone = "yes" | "no"
  doctype-public = string
  doctype-system = string
  cdata-section-elements = qnames
  indent = "yes" | "no"
  media-type = string
/>

As the XSLT processor runs a transformation, it builds a temporary result tree consisting of what's written to the output, either through static text within a template or through explicit calls to xsl:value-of. Then, the processor serializes the result tree to the output stream. Using xsl:output, you can influence how the XSLT processor performs this final step.

You can use the method attribute to indicate whether you're trying to output XML, HTML, or just plain text. The XML output method always outputs well-formed XML. The HTML output method, on the other hand, allows the processor to make several adjustments to the output in order to produce friendlier HTML documents. For example, empty elements in HTML usually don't have end tags, and script and style tags don't require escape characters. These adjustments make it possible to produce HTML that will be easier to handle for most browsers. The text output method simply outputs all of the text nodes in the result document without any surrounding markup whatsoever.

If you don't specify the output method, the processor will automatically select one based on the temporary result tree. If the root element is named "html" (case insensitive), it chooses the HTML output method; otherwise it defaults to the XML output method. To change the default, you need to specify exactly which output method you want to use via xsl:output, as shown here:

<xsl:transform version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
   <xsl:output method="text"/>
   ...

You can also specify the version for the output method in use. The default version for XML is 1.0 and for HTML it is 4.0 (versioning doesn't apply to the text mode). For example, the following xsl:output element specifies that the output should use HTML 3.0:

<xsl:transform version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
   <xsl:output method="html" version="3.0"/>
   •••

You can also specify which character encoding you'd like the processor to use during serialization. This setting hints to the processor how it should transform the character stream into a byte stream. Although XSLT processors typically follow this hint, they're not required to do so. The encoding indicated in the transformation can be overidden by the processor or certain APIs, which often leads to confusion.

In addition to these settings, you can specify if you'd like to have an XML declaration in the output, along with standalone and DTD identifiers. You can also specify the names of any elements that should be enclosed within CDATA sections in case they contain numerous unsafe XML characters. You can even tell the processor to pretty-print the result using indentations, making the output easier for people to read. Here's a final example:

<xsl:transform
  xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
  version='1.0'>

  <xsl:output method="xml"
    version="1.0"
    omit-xml-declaration="yes"
    indents="yes"
    encoding="iso-8859-1"
    cdata-section-elements="codefrag syntax"/>
  •••
</xsl:transform>

This example indicates that the output should be serialized as XML 1.0 with an XML declaration at the top. It also indicates that the processor should use both the iso-8859-1 character encoding and indentations, and enclose all codefrag and syntax elements within CDATA sections.

Send your questions and comments for Aaron to xmlfiles@microsoft.com.

Aaron Skonnard is an instructor/researcher at DevelopMentor, where he develops the XML and Web Service-related curriculum. Aaron coauthored Essential XML Quick Reference (Addison-Wesley, 2001) and Essential XML (Addison-Wesley, 2000). Reach him at http://staff.develop.com/aarons.


Advanced Basics: Data Binding in Visual Basic .NET

Advanced Basics
Data Binding in Visual Basic .NET
Ken Spencer

Code download available at: AdvancedBasics0308.exe (223KB)

Q How can I get the most out of data binding in the applications I write in Visual Basic® .NET?

A As I mentioned in last month's installment of Advanced Basics, data binding is alive and well in the Microsoft® .NET Framework and it's a great technology that makes dealing with data more useful in many types of applications. Let's pick up where I left off and look at some practical applications of data binding.

First, let's discuss architecture for a moment. Figure 1 shows an overview of a common way of using data binding with a data source. The data is pulled from the source by way of a data library and placed in an untyped DataSet. The untyped DataSet is used in order to enable the use of a generic data access library. This lets you call a single function that returns any DataSet. You can now take this untyped DataSet and merge it with a typed DataSet by using two matching tables. Finally, you can bind controls to the typed DataSet.

Figure 1 Binding
Figure 1 Binding

You might wonder why I didn't bind directly to the untyped DataSet. Well, the typed DataSet provides features such as early binding of fields. This enables you to use .CustomerName as a member of the DataSet instead of referencing the Items collection with the value "CustomerName," making the process of accessing data much more straightforward. This early binding also gives you automatic support for data binding at design time. This enables you to select fields in the editor and even access custom properties in certain controls.

Now let's look at data binding in the real world. Take a look at the following function from my July 2003 column that pulls data from SQL Server™ and returns a DataSet:

Function RetrieveCustomerContacts() As DataSet
  Dim ds As DataSet
    Try
      ds = RunSQLWithDataSet("Select CustomerID,  " & _
      "CompanyName, ContactName, NoOfCustomerVisits " & _
      "from customers", ConnectionString, "Customers")
    Catch ex As Exception
      ds = Nothing
    End Try
  Return ds
End Function

Next, consider a typed DataSet named Customers with the following fields that I've added to the project as dsCustomers.xsd (part of the XSD has been omitted to conserve space):

   <xs:sequence>
       <xs:element name="CustomerID" type="xs:string" />
       <xs:element name="CompanyName" type="xs:string" />
       <xs:element name="ContactName" type="xs:string"
           minOccurs="0" />
       <xs:element name="NoOfCustomerVisits" type="xs:int"
           minOccurs="0" />
   </xs:sequence>

In Visual Studio® .NET, you can wire up this data to a DataSet by dragging a DataSet component from the toolbox and dropping it onto your form, then associating it with the typed DataSet named Customers. Now you can use the properties window of a DataGrid control to select the new DataSet as the DataSource and the Customers table as the DataMember.

You can complete the process by adding the following code in the form's Load event:

   Dim ds As DataSet
   Dim oClass As New SomeComponent.Class1
   Try
       ds = oClass.RetrieveCustomerContacts
       dsCustomers.Merge(ds)
       Catch ex As Exception
       sbarmain.Text = ex.Message
   End Try
This code calls the RetrieveCustomerContacts class to retrieve the DataSet and merge it into the typed DataSet for your form. Of course, you can also perform this type of data binding with other controls such as the listbox and combobox controls. There is another cool feature that comes into play. The same code I just showed can be used to cause the typed DataSet to be loaded into your combobox. Then you can extract the chosen value with just a single line of code in the SelectedIndexChanged event:
txtSelectedID.Text = ComboBox1.SelectedValue

Of course, if you are binding the txtSelectedID control to a class or structure where the property is called CustomerID, you could do this instead of putting the data directly into the control:

CustomerContacts.CustomerID = ComboBox1.SelectedValue
This would update the class's CustomerID field and the data binding would update the value shown in the bound textbox.

Let's take a look at another way data binding makes your data-driven application more user-friendly. Say, for example, you have an application that has to show data in three textboxes from a DataSet or other bindable object. The CurrencyManager can be used to navigate through these controls and provide other features. I looked at several examples using the CurrencyManager and they were fairly obtuse, so I borrowed a concept from a sample in the MSDN® Library and made a more practical example. The interface to this form is shown in Figure 2.

Figure 2 Currency Manager Interface
Figure 2 Currency Manager Interface

This form allows you to scroll through a DataSet and make changes to the data as you go. Each time you change a value and move to another record, the change is persisted to the DataSet. That's pretty much how the data binding worked in Visual Basic 6.0 and before. It's more flexible now with Visual Basic .NET because you have access to the low-level features of data binding.

Let's take a look at the code in Figure 3 so I can share a couple of things I learned. The code is pretty straightforward, but requires a bit of explanation. I will skip the mundane parts such as the class that pulls the data from SQL Server and returns it, but the complete code is in the download for this column.

Figure 3 Key Code Elements For Data Binding (Close Figure 3)
Private WithEvents thisCurrencyManager As CurrencyManager

'Variable defs go here

'Form Load builds the dataset, merges it with CustomerInfo1,
'then binds the controls by calling BindControls

Private Sub BindControls(ByVal thisDataTable As DataTable)
' Bind a TextBox control to a DataTable column
'in a DataSet.
txtCompany.DataBindings.Add("Text", thisDataTable, "CompanyName")
txtName.DataBindings.Add("Text", thisDataTable, "ContactName")
txtCity.DataBindings.Add("Text", thisDataTable, "City")
txtCustomerID.DataBindings.Add("Text", thisDataTable, "CustomerID")

' Specify the CurrencyManager for the DataTable.
thisCurrencyManager = _
    CType(Me.BindingContext(thisDataTable), CurrencyManager)
' Set the initial Position of the control.
thisCurrencyManager.Position = 0
End Sub

Private Sub MoveNext(ByVal thisCurrencyManager As CurrencyManager)

If thisCurrencyManager.Position = thisCurrencyManager.Count - 1 Then
MessageBox.Show("You're at end of the records")
Else
thisCurrencyManager.Position += 1
CheckChanges()
End If
End Sub

Private Sub MoveFirst(ByVal thisCurrencyManager As CurrencyManager)

thisCurrencyManager.Position = 0
CheckChanges()
End Sub

Private Sub MovePrevious(ByVal thisCurrencyManager As CurrencyManager)

If thisCurrencyManager.Position = 0 Then
MessageBox.Show( _
    "You're at the beginning of the records.")
Else
thisCurrencyManager.Position -= 1
CheckChanges()
End If
End Sub

Private Sub MoveLast(ByVal thisCurrencyManager As CurrencyManager)

thisCurrencyManager.Position = thisCurrencyManager.Count - 1
CheckChanges()
End Sub

'Button click events go here

Private Sub txtName_TextChanged(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles txtName.TextChanged
'Exit if starting up
If IsNothing(thisCurrencyManager) Then
Exit Sub
End If

StartEditMode()

End Sub

Private Sub txtCompany_TextChanged(ByVal sender As _
    System.Object,ByVal e As System.EventArgs) _
    Handles txtCompany.TextChanged
StartEditMode()
End Sub

Private Sub txtCity_TextChanged(ByVal sender As _
    System.Object, ByVal e As System.EventArgs) _
    Handles txtCity.TextChanged
StartEditMode()
End Sub

Sub StartEditMode()
cmdSaveChanges.Enabled = True
End Sub

Sub EndEditMode()
Me.BindingContext(CustomerInfo1.Customers).EndCurrentEdit()
End Sub

Private Sub thisCurrencyManager_PositionChanged( _
    ByVal sender As Object, _
    ByVal e As System.EventArgs) _
    Handles thisCurrencyManager.PositionChanged
cmdSaveChanges.Enabled = False
End Sub

Private Sub cmdNew_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles cmdNew.Click
thisCurrencyManager.AddNew()
End Sub

'Other subs go here

The first line of code in Figure 3 defines the CurrencyManager for my application. It's defined with WithEvents to allow the use of its events if I need them:

Private WithEvents thisCurrencyManager As CurrencyManager

The code in the frmBinder2_Load event is pretty standard. It calls a method that returns a DataSet, then merges that DataSet into a typed DataSet. The typed DataSet's Customers table is passed to the BindControls method, which performs the binding.

The BindControls method is where you see the CurrencyManager in action. The first few lines bind four fields from the DataSet to the Text property of the controls:

txtCompany.DataBindings.Add("Text", thisDataTable, "CompanyName")
txtName.DataBindings.Add("Text", thisDataTable, "ContactName")
txtCity.DataBindings.Add("Text", thisDataTable, "City")
txtCustomerID.DataBindings.Add("Text", thisDataTable, "CustomerID")
The last two lines of BindControls actually use the CurrencyManager. The first of these lines sets the CurrencyManager to the BindingContext of the data source (a DataTable in this case):
thisCurrencyManager = CType(Me.BindingContext(thisDataTable), _
    CurrencyManager)
The next line sets the initial position to 0 (the first record):
thisCurrencyManager.Position = 0

Now, I can proceed to the CurrencyManager. The Move methods all change the position within the DataSet by using the CurrencyManager. For instance, the MoveNext method moves the current element pointer forward in the data source as long as the current position is not the last element:

If thisCurrencyManager.Position = _
    thisCurrencyManager.Count - 1 Then
    MessageBox.Show("You're at end of the records")
Else
    thisCurrencyManager.Position += 1
    CheckChanges()
End If
The CheckChanges method is called each time the position changes to determine if the DataSet has any changes.

As I built this sample application, I struggled with one small problem for hours. I could scroll through the records fine and changing the data worked as long as I changed to another row in the data. However, when I used the Save button to save the changes, the GetChanges method was not properly indicating that changes had occurred. I checked the code per the MSDN documentation and the online samples and after a bit of sleuthing, I figured it out.

I was using a DataTable as the data source. The EndCurrentEdit method was being fired by the EndEditMode procedure with the following line of code:

Me.BindingContext(CustomerInfo1, "Customers").EndCurrentEdit()
This did not work; the DataSet showed no changes even though the data did, in fact, change. Finally, I realized I was using the wrong context. The following context worked just fine:
Me.BindingContext(CustomerInfo1.Customers).EndCurrentEdit()
In this case, I am passing only the DataTable which is indeed the data source for the controls. The funny thing is, the original code didn't generate any errors when it executed; it just didn't work. I'm sure the line did actually execute because when I changed the table name (Customers) to CustomersX, the line generated a runtime error. As I said, the code was executing, it was just pointing to the wrong data source.

The other code in this form is pretty straightforward as it implements calls to the move methods and otherwise handles interactions with the CurrencyManager.

The cmdAddNew_Click event is noteworthy because it causes a new row to be added to the data. This clears the form and allows you to enter data into the new row. You can see the changes are automatically saved by moving off the row and back.

The thisCurrencyManager_PositionChanged event is fired when the position is changed. This event is used to reset the state of the SaveChanges button.

For more information on data binding, see Billy Hollis's article "Not Your Father's Data Binding". For further information on data binding in MSDN Magazine, see Data Binding.

Send your questions and comments for Ken to basics@microsoft.com.

Ken Spencer works for 32X Tech (http://www.32X.com), where he provides training, software development, and consulting services on Microsoft technologies.


Security Briefs: Hashing Passwords, The AllowPartiallyTrustedCallers Attribute

Security Briefs
Hashing Passwords, The AllowPartiallyTrustedCallers Attribute
Keith Brown

Code download available at: SecurityBriefs0308.exe (110KB)

Q How can I store passwords in a custom user database?

A There are several options. The simplest might leave you with cleartext passwords. The following example is XML, but you could easily use a database table:

<users>
  <user name='Alice' password='7&y2si(V1dX'/>
  <user name='Bob'   password='mary'/>
  <user name='Fred'  password='mary'/>
</users>
After implementing something like this, you'll likely feel rather uncomfortable that all those passwords are sitting there in one file, in the clear. If you don't feel uncomfortable, you should! This makes it way too easy for an attacker who compromises your system to walk away with user passwords without even breaking a sweat. And, if this happens, it's not just your site that could feel the repercussions—most people use the same password for multiple sites. A stolen password is a privacy violation for the user, and frankly, if you didn't do anything to protect those passwords, you're to blame.

The first approach you might take to protect these passwords is to encrypt them. That's better than nothing, but it's not the best solution either. In order to validate a user's password, you need the encryption key, which means it needs to be available on the machine where the passwords are processed. While this does raise the bar a bit because the attacker must find the key, there's a better solution that doesn't require any key at all: a one-way function.

A cryptographic hash algorithm like SHA-1 or MD5 is a sophisticated one-way function that takes some input and produces a hash value as output, like a checksum, but more resistant to collisions. This means that it's incredibly unlikely that you'd find two messages that hash to the same value. In any case, because a hash is a one-way function, it can't be reversed. There is no key that you need to bury. So let's imagine you hash the password before storing it in the database:

<users>
  <user name='Alice' password='D16E9B18FA038...'/>
  <user name='Bob'   password='5665331B9B819...'/>
  <user name='Fred'  password='5665331B9B819...'/>
</users>

Now when you receive the cleartext password and need to verify it, you don't decrypt the stored password for comparison. Instead, you hash the password provided by the user and compare the result with your stored hash. If an attacker manages to steal your password database, he won't immediately be able to use the passwords, as they can't be reversed back into cleartext. But look closely at Bob and Fred's hashed passwords. If the attacker happened to be Fred, he now knows that Bob uses the same password he does. What luck! Even without this sort of luck, a bad guy can perform a dictionary attack against the hashed passwords to find matches.

The usual way a dictionary attack is performed is to get a list of commonly used passwords, like the lists you'll find at ftp://coast.cs.purdue.edu/pub/dict/wordlists, and calculate the hash for each. Now the attacker can compare the hash values of his dictionary with those in the password database. Once he finds a match, he looks up the corresponding password.

To slow down the attack, use salt. Salt is a way to season the passwords before hashing them, making the attacker's precomputed dictionary useless. Here's how it's done. Whenever you add an entry to the database, you calculate a random string of digits to be used as salt. When you want to calculate the hash of Alice's password, you look up the salt value for Alice's account, prepend it to the password, and hash them together. The resulting database looks like this:

<users>
  <user name='Alice' salt='Tu72*&' password='6DB80AE7...'/>
  <user name='Bob'   salt='N5sb#X' password='096B1085...'/>
  <user name='Fred'  salt='q-V3bi' password='9118812E...'/>
</users>

Note that now there is no way to tell that Bob and Fred are using the same password. Note that the salt itself isn't a secret. The important thing is that it's different for each user account, so generating a random string of digits based on output from RNGCryptoServiceProvider would work fine.

Figure 1 summarizes the levels of security. Once you decide to store hashed passwords, you'll realize there's no way to e-mail the user their password if they forget what it is. This is a good thing! It's pretty silly to e-mail passwords. Take a lesson from paypal.com. They store a set of questions and answers, like "What is your pet's name?" and "What city were you born in?" To change your password, you must provide answers to these types of questions.

Figure 1 Levels of Security
Figure 1 Levels of Security

With a salted password database, the attacker can't use a prehashed dictionary. But he can still perform a dictionary attack using the salt for each account to rehash his dictionary. He can also try to find short passwords with brute-force by calculating the hash of, say, every possible four-character password. You can slow this attack by requiring a certain level of complexity for passwords, including a minimum length. You can also require that users use a combination of uppercase and lowercase letters, digits, and punctuation. Of course, if passwords are too hard to remember, users may write them down. It's a difficult balancing act.

Salt isn't a silver bullet. You should react immediately if your password database has been compromised, even if it uses salted hashes. But salt buys you a wee bit of extra time. It just might give you enough time to discover the attack and disable the affected accounts until the users can change their passwords.

To make it easy for you to get started with this technique, Figure 2 provides a class in C# that will verify passwords using salted hashes. It will also create the salt and calculate the hash for new passwords as new accounts are added to your database. You just need to provide storage for the salt and the password hash, which are both strings. Here's an example of usage:

string password = Console.ReadLine();
SaltedHash sh = SaltedHash.Create(password);

// imagine storing the salt and hash in a database
string salt = sh.Salt;
string hash = sh.Hash;

Console.WriteLine("Salt: {0}", salt);
Console.WriteLine("Hash: {0}", hash);

// after looking up salt and hash, verify a password
SaltedHash ver = SaltedHash.Create(salt, hash);
bool isValid = ver.Verify(password);

Figure 2 SaltedHash Class (Close Figure 2)
namespace DevelopMentor.SecUtils {

using System;
using System.Security.Cryptography;

public sealed class SaltedHash {
    public string Salt { get { return _salt; } }
    public string Hash { get { return _hash; } }

    public static SaltedHash Create(string password) {
        string salt = _createSalt();
        string hash = _calculateHash(salt, password);
        return new SaltedHash(salt, hash);
    }

    public static SaltedHash Create(string salt, string hash) {
        return new SaltedHash(salt, hash);
    }

    public bool Verify(string password) {
        string h = _calculateHash(_salt, password);
        return _hash.Equals(h);
    }

    private SaltedHash(string s, string h) {
        _salt = s;
        _hash = h;
    }

    private static string _createSalt() {
        byte[] r = _createRandomBytes(saltLength);
        return Convert.ToBase64String(r);
    }

    private static byte[] _createRandomBytes(int len) {
        byte[] r = new byte[len];
        new RNGCryptoServiceProvider().GetBytes(r);
        return r;
    }

    private static string _calculateHash(string salt, string password) {
        byte[] data = _toByteArray(salt + password);
        byte[] hash = _calculateHash(data);
        return Convert.ToBase64String(hash);
    }

    private static byte[] _calculateHash(byte[] data) {
        return new SHA1CryptoServiceProvider().ComputeHash(data);
    }

    private static byte[] _toByteArray(string s) {
        return System.Text.Encoding.UTF8.GetBytes(s);
    }

    private readonly string _salt;
    private readonly string _hash;
    private const int saltLength = 6;
}

}

Q When should I use the AllowPartiallyTrustedCallers attribute in my assembly?

A Only after careful review of your code. This attribute was added very late in the development of version 1.0 of the Microsoft® .NET Framework. It wasn't in the beta. In fact, it was so close to the 1.0 release that it didn't make it into the mainstream documentation, but you could read a bit about it in the release notes for the product. Let me first explain the motivation behind its existence.

When you build strongly named assemblies, those assemblies may be placed in the Global Assembly Cache (GAC), which means they can be seen not only by your code, but by any other code on the machine, including mobile code that may have originated from an untrusted source. If you assume your code might have bugs in it, and if you also assume that those bugs could lead to security problems if exploited by malicious code, then you'll be happy to know that your assembly can only be called by fully trusted assemblies by default. This means that random code downloaded from the Internet won't be able to call your code by default because the downloaded code is only partially trusted.

Figure 3 shows an example of a very buggy piece of code that can be exploited by an attacker. Every line of this code has a bug. The Assert statement prevents a stack walk that would keep partially trusted code from accessing the database. The database connection string uses the sa account, which if misused could severely damage or destroy the database. The SQL statement is concatenated so that it incorporates unfiltered external input in the form of a name and a password. This constitutes a SQL injection vulnerability, as an attacker could use the name and password to inject malicious SQL statements! There are so many things wrong with this code, and yet sadly it's typical code that you'd see in a database programming example. If an unsuspecting programmer cuts and pastes code like this into his source file, he's opening security holes. A code audit would catch these bugs, but code is frequently shipped without proper security testing.

Figure 3 Do Not Use (Close Figure 3)
public class HopeThisIsntUsedByEvilCodeBecauseItsNotRobust {
  public string Name;
  public string Password;

  // this entire function consists of horribly bad code
  // you should NEVER EVER use
  public bool IsValidUser() {
    new DbDataPermission(PermissionState.Unrestricted).Assert();
    SqlConnection conn = new SqlConnection(
      "initial catalog=accounts;user id=sa;password=;")
    conn.Open();
    cmd.CommandText = string.Format(
      "select count(*) from users where name='{0}'" +
      "and password='{1}'", Name, Password);
    return ((int)cmd.ExecuteScalar()) > 0;
  }
}

This is where AllowPartiallyTrustedCallers comes in. Microsoft decided at the last minute to add a final knob that had to be turned before partially trusted code would be allowed to call your locally installed, fully trusted code. Here's the deal: without this attribute on your strongly named assembly, there is effectively a link-time demand in your assembly that prevents partially trusted code from linking to it.

Now, hopefully the answer is clearer. The only time you should ever add the AllowPartiallyTrustedCallers attribute to your assembly is after a careful security audit. Be doubly wary if your assembly calls unmanaged code. As soon as you apply this attribute to a GAC-deployed assembly, you're opening that assembly up to attack from external untrusted code. Even the core .NET assemblies don't all have this attribute. For example, partially trusted code isn't allowed to use System.Runtime.Remoting.dll or System.Management.dll. The reason is that Microsoft apparently hasn't convinced itself that partially trusted code can't use these libraries to obtain elevated privileges. Only recently did ASP.NET pass examination and receive this attribute: System.Web.dll was awarded the AllowPartiallyTrustedCallers attribute in version 1.1 of the Microsoft .NET Framework.

Send your questions or comments for Keith to briefs@microsoft.com.

Keith Brown is a member of the technical staff at DevelopMentor, where he spends his time researching, writing, teaching, and generally spreading the word about security to the developer community. He is the author of Programming Windows Security (Addison Wesley, 2000) and is working on a new book on .NET Security. Read chapters of the new book online and reach Keith at http://www.develop.com/kbrown.


Basic Instincts: Naming and Building Assemblies in Visual Basic .NET

Basic Instincts
Naming and Building Assemblies in Visual Basic .NET
Ted Pattison

Contents
What's in a Name?
Building a Strongly Named Assembly
Assembly Signing and Tamper Protection
Wrap-up

This month's installment of Basic Instincts will begin a multi-part series focused on working with assemblies. There is quite a bit you need to know about how assemblies are built, deployed, and versioned. This month I will address several essential details about how to build an assembly with the exact name you want. In subsequent months, I'll discuss your options for deploying assemblies on a production machine and revising assemblies that have already been put into production.

What's in a Name?

Let's start with the basics. Each assembly has a four-part name consisting of the following elements: friendly name, version number, culture setting, and public key (or public key token). When I discuss how the display name works, keep in mind that you should not try to piece together your own.

The first part of an assembly's name is its friendly name, which is simply the file name without the extension. For example, the friendly name of an assembly with a file name of MyLibrary.dll is MyLibrary. The friendly name for a system-supplied assembly file such as System.Data.dll is System.Data.

The second part of an assembly's name is its version number, which consists of a string with four dot-delimited numbers in the form "1.0.24.0." These four numbers represent the major version, minor version, build number, and revision number, respectively.

You can build an assembly with a specific version number by adding this code to the top of one of your project source files:

Imports System.Reflection

<Assembly: AssemblyVersion("1.0.24.0")>
Keep in mind that this behavior is only guaranteed in Visual Basic® .NET. Other compilers may vary.

This example demonstrates how to apply an assembly-level attribute named AssemblyVersion. Note that this example also imports the System.Reflection namespace since that's the place where the AssemblyVersion attribute is defined. Remember that when you apply an assembly-level attribute, it must be defined in a source file after any Imports statements but before any type definitions. Also note that you should apply the AssemblyVersion attribute in one and only one source file per project.

It is important to note that every assembly has a version number. This is true even when you compile an assembly without explicitly assigning it a version number. For example, what happens when you compile an assembly without using the AssemblyVersion attribute? The Visual Basic .NET compiler automatically assigns the assembly a version number of 0.0.0.0.

The third part of an assembly's name is its culture setting, a two-letter code indicating that an assembly has been localized for a particular spoken language. A culture setting can also carry an optional country code. For example, a culture setting of en-US indicates that an assembly has been localized for English in the United States. Most of the assemblies that you will produce and consume will not have a culture setting because cultures are typically only used for resource-only assemblies, or satellite assemblies.

The last part of an assembly's name is defined by a public key, which is a unique value that maps to a particular company or developer. No two companies should ever use the same public key. Therefore, an assembly with a public key should be unique and distinguishable from any other assembly produced by another company. This is true even in the case in which an assembly produced by another company has an identical friendly name, version number, and culture setting.

For example, imagine that two companies produced an assembly named MyLibrary.dll and that both assemblies are culture-neutral and have the same version number. The common language runtime (CLR) is still able to tell each of these assemblies apart as long as each company has compiled their build of MyLibrary.dll with a different public key.

A public key occupies 128 bytes. The value for a public key is often stored in a compact binary format in a key file. It can also be stored or written using a text-based hexadecimal format that looks like Figure 1. Because a public key value is so large, a smaller 8-byte value known as the public key token is usually used in its place. The public key token is usually represented by a 16-character hexadecimal string value that looks like this:

29989D7A39ACF230

Figure 1 Public Key Value in Hex Format (Close Figure 1)
00 24 00 00 04 80 00 00 94 00 00 00 06 02 00 00
00 24 00 00 52 53 41 31 00 04 00 00 01 00 01 00
AD 6A 11 52 AA AD FE 79 3F 6C 54 6F D1 7F F8 CB
C8 D8 34 40 B0 4C E8 03 0A F0 B2 E8 39 52 4D E2
69 1D A6 B8 18 11 33 A9 68 EA A6 7B BB B1 BD 5C
7E 97 47 90 62 F3 9B 15 6E 17 05 79 F5 53 DB 16
E7 7F 4B E6 A9 C0 DB 21 A4 78 28 5D 77 1F 19 3C
7B E1 D7 89 30 12 E3 3A 33 4A 3E A3 1F 07 38 AB
60 5A D7 38 A2 59 5E 6F 96 CF 9E FF D4 AD AF 66
2A 0F 8F FC AE E0 26 D8 C1 EA 0F 0E 6E 99 6F A5

The CLR creates public key tokens from public key values using a special internal function. There is a one-to-one correspondence between a public key value and a public key token. In other words, for a specific public key value, there is exactly one corresponding public key token and vice versa. This is very important because it allows a public key token to identify a public key value which, in turn, allows a public key token to identify the company that has produced an assembly containing a particular public key value.

While humans would be hard pressed to read and write 128-byte public key values, it's obviously much easier to read and write public key tokens. Public key tokens also conserve space. For this reason, the compiler uses the public key token instead of the public key value when adding a reference to track a dependent assembly.

You might find yourself writing a configuration file or writing code in which you must fully qualify an assembly by name. You can specify all four parts of an assembly name using an assembly format string. Here's an example of a format string with a fully qualified assembly name:

MyLibrary,
Version=1.0.24.0,
Culture=neutral,
PublicKeyToken=29989D7A39ACF230

I have used line breaks in this example to make the format string more readable. However, you should realize that there are times when you must write the entire format string in a single line. This is often the case when you are adding a format string to a configuration file or passing it as a parameter to a command-line utility.

As you can see, the first part of a format string holds the assembly's friendly name. The other three parts are stored as name-value pairs delimited by commas. This example of a format string involves an explicit setting for a friendly name, a version number, and a public key token. Also note that the culture setting in this assembly name has been explicitly qualified as neutral.

What should you do when you want to create a format string for an assembly that does not have a public key value? You create one that explicitly states that the public key token has a value of null. Then the fully qualified format string would look like this:

MyLibrary,
Version=1.0.24.0,
Culture=neutral,
PublicKeyToken=null

Building a Strongly Named Assembly

While a public key serves to give your assembly a name that is unique, its purpose goes much further than that. The CLR relies on public-key technology to verify the identity of the company that produced the assembly and to prevent assembly tampering. These two goals are achieved by complementing the public key with a digital signature. An assembly that has a public key and a digital signature is said to have a strong name.

In order to build an assembly with a strong name, you must first acquire a pair of keys. One is a public key whose value will be written into the physical image of assembly files. The other is a private key that is used to generate digital signatures. The process of using the private key in order to write a digital signature to the image of an assembly file is known as signing.

Let's step through the process of generating a public/private key pair and building an assembly with a strong name. This will show you the actual steps that are required in the build process. After you have seen the build mechanics, I'll discuss how the strong name helps to identify the developer and to prevent tampering.

The first thing to note is that you cannot generate a key pair using Visual Studio® .NET. Instead, you must step outside of Visual Studio .NET and use SN.EXE (The .NET Framework Strong Name Utility), a command-line utility supplied by the .NET Framework SDK. To perform various tasks in SN.EXE, you pass command-line switches. Keep in mind that the switches used by SN.EXE are case sensitive. To see a list of the supported switches, simply run SN.EXE and pass it the question mark as a switch:

SN.EXE -?

When you want to create a new key file that contains a new public/private key pair, you should call SN.EXE from the command line, passing the -k switch and the name of the target key file. If you want to generate a key file named AcmeCorp.snk, you would issue the following instruction from the command line:

SN.EXE -k AcmeCorp.snk

While it's possible to generate a new public/private key pair for each assembly project, you can use a single public/private key pair for several different assembly projects. It's common for a company to use the same public/private key pair on a company-wide or department-wide basis. Just make sure that each assembly using the same public/private key pair has a unique friendly name.

Once you've generated (or acquired) a key file with a public/private key pair, you are ready to build an assembly with a strong name. First, make sure you have placed the key file in your project directory. Next, add the following code to the top of one of the source files in your project:

Imports System.Reflection

<Assembly: AssemblyVersion("1.0.24.0")>
<Assembly: AssemblyKeyFile("..\..\AcmeCorp.snk")>

The previous example demonstrates how to apply the assembly-level attribute named AssemblyKeyFile. This attribute is quite similar to the AssemblyVersion attribute in the sense that it is defined in the System.Reflection namespace. You might have noticed that the path to the key file starts with two dots and a backslash followed by two more dots and another backslash:

   ..\..\AcmeCorp.snk
This is the path you should typically use when you have placed a key file such as AcmeCorp.snk in the project directory that holds other projects files such as the .vbproj file and your .vb source files. The dots and backslashes indicate that this path is going to be relative to the place where the Visual Basic .NET compiler is initially building the assembly.

By default, Visual Studio .NET configures a new Visual Basic .NET project to use a subdirectory of the project directory such as \obj\Debug or \obj\Release. Using a path of ..\..\AcmeCorp.snk simply tells the Visual Basic .NET compiler that it should move up two directories from where it is now so it can locate the key file during compilation.

Figure 2 Strongly Named Assembly
Figure 2 Strongly Named Assembly

When you build a project that contains the AssemblyKeyFile attribute, the Visual Basic .NET compiler builds the output assembly with a strong name. In order to do this, the compiler must be able to read the values for the public key and the private key from the key file during compilation. The compiler records the full 128-byte value into the manifest of the assembly file it is outputting. The compiler uses the private key to generate a digital signature that is appended to the end of the output assembly file. The resulting layout of the assembly file is shown in Figure 2.

Assembly Signing and Tamper Protection

Let's take an in-depth look at the .NET Framework scheme to protect against tampering. First I'll discuss digital signatures, which are generated using cryptography. In particular, the .NET Framework relies on two industry-standard forms of cryptography known as one-way hash functions and asymmetric encryption.

A one-way hash function (also known as a hash function) allows you to create a digital fingerprint for a large digital object. You feed a large digital object such as a public key or a file image to a hash function and, in turn, the hash function calculates a much smaller piece of data known as a hash value. The cryptography involved provides two important guarantees. First, the hash function always produces an identical hash value for any given digital object. Second, it is unlikely that the hash function will generate an identical hash value for two different digital objects. This is true even when two really large digital objects differ by only a single bit.

Earlier I mentioned that the CLR uses a special internal function to generate an 8-byte public key token from a 128-byte public key value. It does this by calling a hash function. This is how the CLR and compilers are able to produce identical public key tokens any time they hash the same public key value. This is also how the CLR guarantees that no two public key values will ever hash to the same public key token.

Apart from creating public key tokens, a hash function also plays an important role by generating a digital signature for an assembly with a strong name. However, in such cases the hash function does not generate a hash value from a public key value. Instead, it creates a hash value from the physical image of the assembly file itself.

The hash value of an assembly file serves as its digital fingerprint for a particular build. The idea is that you can send two different assembly files to the same hash function to determine whether their physical layouts are identical. If these two assembly files generate hash values that are equal, you know one assembly file is an exact copy of the other.

The final step in generating a digital signature from the hash value of an assembly file involves asymmetric encryption. Asymmetric encryption is a type of cryptography in which data is encrypted and decrypted using public/private key pairs. The public key and the private key complement each other because one key is used to encrypt data while the other is used to decrypt data. In the case of signing an assembly, the private key is used to generate the digital signature by encrypting the hash value of an assembly file. At some later time, the public key can be used to decrypt the digital signature and retrieve the hash value of that assembly file.

Now that you understand how all the pieces fit together, let's walk through the sequence of events that occur during compilation. The compiler must make two passes to build an assembly with a strong name. In the first pass, the compiler generates the image for the assembly file containing its manifest, its type information, and its executable code in the form of intermediate language (IL). However, the compiler cannot generate the digital signature during this first pass so it just leaves some blank space at the end of the assembly file to act as a placeholder.

In the second pass, the compiler generates a hash value from the physical image of the assembly file that was built during the first pass. Once the compiler has generated this hash value, it encrypts it with the private key to generate the digital signature. Finally, the compiler writes the digital signature into the placeholder at the end of the assembly file, as shown in Figure 2.

Now let's discuss how the CLR verifies the authenticity of an assembly's digital signature at a point in time after it has been distributed by the company that produced it. The CLR uses the public key value within the assembly manifest to decrypt the digital signature and retrieve the hash value of the assembly image that was present during compilation. Next, the CLR uses the same hash function to generate a hash value from the physical image of the current assembly file. The CLR has now generated two different hash values with which it can conduct an important comparison.

The CLR compares the two hash values. If these two hash values are equal, the CLR considers the test a success because it is sure of two things—namely, that the digital signature was generated by someone in possession of the private key and that the physical image of the assembly file present during verification exactly matches the physical image of the assembly file originally signed. If the CLR determines that the two hash values are not equal, it considers the verification test a failure. The CLR knows that either the producer of the assembly did not possess the correct private key or that the assembly's image has been modified after it had been signed unless MyLibrary.dll is in the global assembly cache (GAC). In that case it was verified when it was placed in the GAC, and not reverified at run time.

Let me walk through a typical scenario using MyApp.exe and MyLibrary.dll so you can get a better sense of how the CLR uses a verification check at run time to prevent tampering. Imagine you have built MyLibrary.dll with a strong name and, after that, you have compiled MyApp.exe with a reference to MyLibrary.dll. It is now impossible for someone without your private key to change the behavior of the application on a production machine by replacing the real version of MyLibrary.dll with a tampered version.

Remember that the assembly manifest for MyApp.exe contains a reference with the public key token associated with the same public key compiled into MyLibrary.dll. Therefore, you have the guarantee that the CLR will only load a version of MyLibrary.dll that meets two important criteria. The first is that the assembly file for MyLibrary.dll contains a public key value that matches the public key token in the assembly manifest of MyApp.exe. The second is that MyLibrary.dll has been signed by someone in possession of your private key.

Now consider what happens when a user runs MyApp.exe and it executes code that makes the first call to MyLibrary.dll. At this point, the CLR will attempt to load MyLibrary.dll. However, the CLR knows that MyLibrary.dll has a strong name, so it runs a verification check as the first part of the loading process. This verification check allows the CLR to detect if someone without access to the private key has made changes to the physical image of the assembly file.

Imagine that there is a bad guy who wants to tamper with MyLibrary.dll. Perhaps this bad guy wants to change the behavior of one or more methods by modifying the IL within the assembly file. Ultimately, this bad guy plans to replace your build of MyLibrary.dll on a production machine with a tampered version so that he can change the behavior of MyApp.exe when it runs.

Here's how the verification scheme thwarts the bad guy. For any specific physical layout of the assembly file MyLibrary.dll, there is only one possible digital signature that is valid. Therefore, any changes to an assembly's physical image after it has been signed renders the existing digital signature invalid. If this bad guy so changes the bits of the physical image of MyLibrary.dll after it has been signed, the CLR can detect tampering. A change to the physical image of the assembly will require the assembly file to be signed again. However, the bad guy cannot sign the assembly because he does not have the private key.

Note that this verification scheme doesn't really prevent assembly tampering. It only prevents tampering from going undetected. When the CLR attempts to load a strongly named assembly with a digital signature that doesn't match the physical layout of the assembly file, it fails the load attempt by throwing a System.IO.FileLoadException. In most cases, this means that the hosting application is not going to be able to do its work. However, this scenario is preferable to running the application with evil code that's been altered by the bad guy.

A critical point about tamper detection is that the entire scheme is based on the premise that the private key doesn't fall into the wrong hands. Any individual who acquires your private key can build an assembly with your public key value and a valid digital signature. This makes it possible for the bad guys to defeat the verification checks used by the CLR. If your company plans to distribute strongly named assemblies, you must carefully plan how to manage your private keys so they are not compromised.

Now that you have seen how the CLR protects against tampering, I would like to mention an important restriction when building an assembly project that has a strong name. You can only reference other assemblies that also have strong names. If you attempt to build an assembly project with a strong name while referencing an assembly without a strong name, you will receive a compile-time error.

The reason for this is to ensure the reliability of tamper protection. When you reference a strongly named assembly, the CLR prevents tampering from going undetected by running verification checks against the dependent assembly's digital signature. The scheme would not be as reliable if your application depended on a strongly named assembly which, in turn, depended on another assembly that did not have a strong name. The bad guy could replace the assembly that did not have the strong name and the CLR would not be able to detect this. Thus, the CLR enforces this restriction so you are guaranteed that a strongly named assembly depends on only other strongly named assemblies.

Wrap-up

This month's column has focused on what goes into building and naming an assembly. As you have seen, each assembly has a four-part name that consists of its friendly name, version number, culture setting, and public key token. You have also learned how to construct an assembly format string that will allow you to add a fully-qualified assembly name to a configuration file.

It is important that you know how to build an assembly with a specific version number. You do this by adding the AssemblyVersion attribute to your source code. You also have seen how to build an assembly with a strong name by adding the AssemblyKeyFile attribute to your source code and supplying the compiler with a public/private key pair. I will resume this discussion in the next installment of this column.

Send your questions and comments for Ted to instinct@microsoft.com.

Ted Pattison is an instructor and course writer at DevelopMentor (http://www.develop.com). He has written several books about Visual Basic and COM. He is currently writing a book titled Building Applications and Components with Visual Basic .NET to be published by Addison-Wesley in the fall of 2003.


C++ Q&amp;A: Finding a Win32 Handle, HTML in CHtmlCtrl

C++ Q&A
Finding a Win32 Handle, HTML in CHtmlCtrl
Paul DiLascia

Code download available at: CQA0308.exe (1,261KB)

Q I encountered a problem using your suggested approach to find the Internet Explorer_Server window as described in your September 2001 column. When using GetLastChild with an HTML page containing comboboxes, sometimes it returns the combobox instead of the Microsoft® Internet Explorer window. You can see in Spy++ that comboboxes are child windows of the Internet Explorer window, but sometimes OnNavigateComplete2 was called before these windows were created, which my program didn't account for. I changed the GetLastChild function to check the class name of the child window. If an Internet Explorer_Server window is found, it returns it. Do you know a better solution?

Michael Theis

A Indeed I do, but first let me briefly remind everyone what we're talking about. In the January 2000 issue of Microsoft Systems Journal I showed how to modify the MFC CHtmlView class, which can only live inside a CFrameWnd, to remove the frame dependencies and create a new class called CHtmlCtrl that works in a dialog or any other kind of window.

Then in my September 2001 column, I showed how to disable the (browser) context menu for CHtmlCtrl by subclassing the Internet Explorer_Server window. The window that actually displays the HTML is not the browser (CHtmlView/CHtmlCtrl) window, but a great-grandchild with the class name "Internet Explorer_Server". I presented the function GetLastChild that returns the "last child" of a window. That is, GetLastChild returns the child of the child of the child... until there are no more children. This assumes a window hierarchy where each window has only one child, and the last descendant is the Internet Explorer window. Usually this is correct, but (as Michael found out the hard way) not if the document has children like comboboxes. Oops. I should've tested my code against some more complex HTML, but all I was trying to do was implement a simple About dialog. (Note that in Internet Explorer, edit controls and buttons are not child windows as you might expect.)

Figure 1 shows a class that implements a better way to obtain the Internet Explorer window. CFindWnd finds the first child window of any window with a given class name. In order to use it, all you have to do is write:

CFindWnd ies(m_hWnd, "Internet Explorer_Server");
myHwndIE = ies.m_hWnd;

Figure 1 FindWnd.h (Close Figure 1)
////////////////////////////////////////////////////////////////
// MSDN Magazine -- August 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET on Windows XP. Tab size=3.
// •••
// This class encapsulates the process of finding a window with a given
// class name as a descendant of a given window. To use it, instantiate
// like so:
//
//    CFindWnd fw(hwndParent,classname);
//
// fw.m_hWnd will be the HWND of the desired window, if found.
//
class CFindWnd {
private:
   //////////////////
   // This private function is used with EnumChildWindows to find the
   // child with a given class name. Returns FALSE if found (to stop
   // enumerating).
   //
   static BOOL CALLBACK FindChildClassHwnd(HWND hwndParent,
      LPARAM lParam) {
      CFindWnd *pfw = (CFindWnd*)lParam;
      HWND hwnd = FindWindowEx(hwndParent, NULL, pfw->m_classname, NULL);
      if (hwnd) {
         pfw->m_hWnd = hwnd;  // found: save it
         return FALSE;        // stop enumerating
      }
      // recurse
      EnumChildWindows(hwndParent, FindChildClassHwnd, lParam);
      return TRUE;            // keep looking
   }

public:
   LPCSTR m_classname;        // class name to look for
   HWND m_hWnd;               // HWND if found

   // ctor does the work—just instantiate and go
   CFindWnd(HWND hwndParent, LPCSTR classname)
      : m_hWnd(NULL), m_classname(classname)
   {
      FindChildClassHwnd(hwndParent, (LPARAM)this);
   }
};

The constructor calls a function that uses EnumChildWindows and FindWindowEx to search all descendant windows until it exhausts them or finds one whose class name matches the one requested. You can use FindWindow to find top-level windows, but if you want to search children, you need FindWindowEx, which was first introduced in Win32®. CFindWnd returns the first window that matches, so it's only useful for finding windows that you expect to have only one instance of. In general, when searching for special windows, it's always safest to check the class name.

But—as another reader, Domenico Belgiorno pointed out—you don't even need to subclass the Internet Explorer window to disable the context menu. You can do it completely from within CHtmlCtrl, like so:

BOOL CHtmlCtrl::PreTranslateMessage(MSG* pMsg)
{
  if (pMsg->message == WM_CONTEXTMENU)
    return TRUE; // eat it
  return CHtmlView::PreTranslateMessage(pMsg);
}

This works because MFC implements a very ingenious and powerful feature. In the main message pump inside CWinThread, MFC calls a function CWnd::WalkPreTranslateTree. This function loops through all the parent windows of the window for which the message is destined, calling PreTranslateMessage for each one and stopping if any parent window's PreTranslateMessage returns TRUE. The upshot is that you can override PreTranslateMessage to intercept messages sent to your window's descendants. Very clever!

Experience reveals that to make the previous code snippet work as desired, you must also trap WM_RBUTTONDOWN and WM_RBUTTONDBLCLK, and you should also check to make sure the target window's class name is in fact Internet Explorer_Server so you don't accidentally trap some other child window's context menu (unless that's what you want). Figure 2 shows the final code for CHtmlCtrl::PreTranslateMessage. I enhanced it so it can be configured through a property Get/SetHideContextMenu, and to send a WM_CONTEXTMENU message to the parent window instead of eating it. This lets you implement your own context menu if you like. Note that CHtmlCtrl sends WM_CONTEXTMENU when the right mouse button goes up, not down.

Figure 2 CHtmlCtrl::PreTranslateMessage (Close Figure 2)

HtmlCtrl.h
////////////////////////////////////////////////////////////////
// MSDN Magazine -- August 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET on Windows XP. Tab size=3.
//
#pragma once

//////////////////
// This struct defines one entry in the command map that maps text
// strings to command IDs. If your command map has an entry "about" that
// maps to ID_APP_ABOUT, and your HTML has a link <A HREF="app:about">,
// then clicking the link will invoke the ID_APP_ABOUT command. To set
// the map, call CHtmlCtrl::SetCmdMap.
//
struct HTMLCMDMAP {
   LPCTSTR name;     // command name used in "app:name" HREF in
                     // <A UINT nID;
};

//////////////////
// Class to turn CHtmlView into ordinary control that can live in dialog
// as well as frame.
//
class CHtmlCtrl : public CHtmlView {
protected:
   HTMLCMDMAP* m_cmdmap;   // command map
   BOOL m_bHideMenu;       // hide context menu

public:
   CHtmlCtrl() : m_bHideMenu(FALSE), m_cmdmap(NULL) { }
   ~CHtmlCtrl() { }

   // get/set HideContextMenu property
   BOOL GetHideContextMenu()         { return m_bHideMenu; }
   void SetHideContextMenu(BOOL val) { m_bHideMenu=val; }

   // Set doc contents from string
   HRESULT SetHTML(LPCTSTR strHTML);

   // set command map
   void SetCmdMap(HTMLCMDMAP* val)   { m_cmdmap = val; }

   // create control in same place as static control
   BOOL CreateFromStatic(UINT nID, CWnd* pParent);

   // create control from scratch
   BOOL Create(const RECT& rc, CWnd* pParent, UINT nID,
      DWORD dwStyle = WS_CHILD|WS_VISIBLE,
          CCreateContext* pContext = NULL)
   {
      return CHtmlView::Create(NULL, NULL, dwStyle, rc, pParent,
          nID, pContext);
   }

   // override to intercept child window messages to disable context menu
   virtual BOOL PreTranslateMessage(MSG* pMsg);

   // Normally, CHtmlView destroys itself in PostNcDestroy, but we don't
   // want to do that for a control since a control is usually
   // implemented as a member of another window object.
   //
   virtual void PostNcDestroy() {  }

   // Overrides to bypass MFC doc/view frame dependencies. These are
   // the only places CHtmView depends on living inside a frame.
   afx_msg void OnDestroy();
   afx_msg int  OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest,
      UINT msg);

   // override to trap "app:" pseudo protocol
   virtual void OnBeforeNavigate2( LPCTSTR lpszURL,
      DWORD nFlags,
      LPCTSTR lpszTargetFrameName,
      CByteArray& baPostedData,
      LPCTSTR lpszHeaders,
      BOOL* pbCancel );

   // You can override this to handle "app:" commmands.
   // Only necessary if you don't use a command map.
   virtual void OnAppCmd(LPCTSTR lpszCmd);

   DECLARE_MESSAGE_MAP();
   DECLARE_DYNAMIC(CHtmlCtrl)
};
HtmlCtrl.cpp
////////////////////////////////////////////////////////////////
// MSDN Magazine — August 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET on Windows XP. Tab size=3.
// --
// Implementation for CHtmlCtrl — Web browser in a control. Overrides
// CHtmlView so you don't need a frame—can use in dialog or any other
// kind of window.
//
// Features:
// - SetCmdMap lets you set a command map for "app:command" links.
// - SetHTML lets you set document contents (HTML) from string.

#include "StdAfx.h"
#include "HtmlCtrl.h"

// macro to declare a typedef for ATL smart pointers; eg SPIHTMLDocument2
#define DECLARE_SMARTPTR(ifacename) typedef CComQIPtr<ifacename> SP##ifacename;

// smart pointer to IHTMLDocument2
DECLARE_SMARTPTR(IHTMLDocument2)

// useful macro for checking HRESULTs
#define HRCHECK(x) hr = x; if (!SUCCEEDED(hr)) { \
   TRACE(_T("hr=%p\n"),hr);\
   return hr;\
}

... // same as earlier version

// Return TRUE if hwnd is Internet Explorer window.
inline BOOL IsIEWindow(HWND hwnd)
{
   static LPCSTR IEWNDCLASSNAME = "Internet Explorer_Server";
   char classname[32]; // always char, never TCHAR
   GetClassName(hwnd, classname, sizeof(classname));
   return strcmp(classname, IEWNDCLASSNAME)==0;
}

//////////////////
// Override to trap "Internet Explorer_Server" window context menu
// messages.
//
BOOL CHtmlCtrl::PreTranslateMessage(MSG* pMsg)
{
   if (m_bHideMenu) {
      switch (pMsg->message) {
      case WM_CONTEXTMENU:
      case WM_RBUTTONUP:
      case WM_RBUTTONDOWN:
      case WM_RBUTTONDBLCLK:
         if (IsIEWindow(pMsg->hwnd)) {
            if (pMsg->message==WM_RBUTTONUP)
               // let parent handle context menu
               GetParent()->SendMessage(WM_CONTEXTMENU, pMsg->wParam,
                  pMsg->lParam);
            return TRUE; // eat it
         }
      }
   }
   return CHtmlView::PreTranslateMessage(pMsg);
}

//////////////////
// Override to pass "app:" links to virtual fn instead of browser.
//
void CHtmlCtrl::OnBeforeNavigate2( LPCTSTR lpszURL,
   DWORD nFlags, LPCTSTR lpszTargetFrameName, CByteArray& baPostedData,
   LPCTSTR lpszHeaders, BOOL* pbCancel )
{
   const char APP_PROTOCOL[] = "app:";
   int len = _tcslen(APP_PROTOCOL);
   if (_tcsnicmp(lpszURL, APP_PROTOCOL, len)==0) {
      OnAppCmd(lpszURL + len);          // call virtual handler fn
      *pbCancel = TRUE;                 // cancel navigation
   }
}

//////////////////
// Called when the browser attempts to navigate to "app:foo". Default
// handler searches command map for "foo" command, and sends parent a
// WM_COMMAND message with the ID if found. Call SetCmdMap to set the
// command map. Only override OnAppCmd if you want to do something more
// complex.
//
void CHtmlCtrl::OnAppCmd(LPCTSTR lpszCmd)
{
   if (m_cmdmap) {
      for (int i=0; m_cmdmap[i].name; i++) {
         if (_tcsicmp(lpszCmd, m_cmdmap[i].name) == 0)
            // Use PostMessage to avoid problems with exit command. (Let
            // browser finish navigation before issuing command.)
            GetParent()->PostMessage(WM_COMMAND, m_cmdmap[i].nID);
      }
   }
}

//////////////////
// Set document contents from string
//
HRESULT CHtmlCtrl::SetHTML(LPCTSTR strHTML)
{
   HRESULT hr;

   // Get document object
   SPIHTMLDocument2 doc = GetHtmlDocument();

   // Create string as one-element BSTR safe array for
   // IHTMLDocument2::write.
   CComSafeArray<VARIANT> sar;
   sar.Create(1,0);
   sar[0] = CComBSTR(strHTML);

   // open doc and write
   LPDISPATCH lpdRet;
   HRCHECK(doc->open(CComBSTR("text/html"),
      CComVariant(CComBSTR("_self")),
      CComVariant(CComBSTR("")),
      CComVariant((bool)1),
      &lpdRet));

   HRCHECK(doc->write(sar));  // write contents to doc
   HRCHECK(doc->close());     // close
   lpdRet->Release();         // release IDispatch returned

   return S_OK;
}

Q I'm using your CHtmlCtrl in my dialog-based app. Since I use it to show HTML pages that I create at run time, I would like to know if there's any way to show HTML content without first storing it in a file. Formatting an HTML string and storing it in a file simply for displaying it in the CHtmlCtrl seems tedious.

Joan Murt Llum

A Setting the HTML from a text string is fairly straightforward, even if the mechanics are a bit tedious in C++. If you've ever written any JavaScript, you know you can call document.write any time to write HTML directly into the document as your page is loading. The same is true in C++, but the coding is messy because you have to deal with COM and IHTMLDocument2, BSTRs, and SAFEARRAYs. Fortunately, the ActiveX® Template Library (ATL) has a number of classes that make it a breeze.

Figure 3 HtmlApp Displays HTML On the Fly
Figure 3 HtmlApp Displays HTML On the Fly

Figure 3 shows a new program, HtmlApp, modified from the AboutHtml programs in my January 2000 and September 2001 columns (see previous question) to display an HTML list of top-level windows in its main view. The original AboutHtml program showed how to implement an "about" dialog in HTML, and loaded it from a resource by calling:

m_page.LoadFromResource(_T("about.htm"));

CHtmlView::LoadFromResource opens the URL res://program.exe/about.htm, where program.exe is the actual name of your program. The res: protocol lets you open any HTML file stored as a resource. But what if, instead of loading a resource, you want to generate HTML on the fly—as HtmlApp does when displaying its list of top-level windows? As Joan points out, it's silly to save your HTML to disk, only to immediately read it back. To set the document contents directly, I added a new function, CHtmlCtrl::SetHTML (see Figure 2).

Let me take you through it step by step. First, you have to get the IHTMLDocument2 interface:

SPIHTMLDocument2 doc = GetHtmlDocument();
SPIHTMLDocument2 is equivalent to CComQIPtr<IHTMLDocument2>, an ATL smart pointer to an IHTMLDocument2 interface. (For those few programmers still using COM, if you're not using ATL smart pointers, get with the program!) Next, you have to create a SAFEARRAY that holds your HTML string as a one-element BSTR array. SAFEARRAY is a COM structure for passing arrays safely between platforms. ATL provides CComBSTR and CComSafeArray classes to take the ouch! out of working with BSTRs and safe arrays:
// strHTML is LPCTSTR
CComSafeArray<VARIANT> sar;
sar.Create(1,0);
sar[0] = CComBSTR(strHTML);

Without CComSafeArray and CComBSTR, this would be 10 to 20 lines of grody code with calls to API functions like SafeArrayCreateVector, SafeArrayAccessData, and SafeArrayUnaccessData. You should feel thankful to the ATL folks that you don't have to write that stuff.

Once you have your doc object and contents in a safe array, you're ready to open the doc, write to it, and close it. IHTMLDocument2::write requires VARIANTS and BSTRs, but once again ATL comes to the rescue:

LPDISPATCH lpdRet;
doc->open(CComBSTR("text/html"),  // MIME type
  CComVariant(CComBSTR("_self")), // open in same window
  CComVariant(CComBSTR("")),      // no features
  CComVariant((bool)1),           // replace history entry
  &lpdRet));                      // IDispatch returned
doc->write(sar); // write it
doc->close();    // close
lpdRet->Release();

CHtmlCtrl::SetHTML is pretty handy. There's just one trick to using it: when you first create your CHtmlCtrl, it has no document (GetHtmlDocument returns NULL). So before you call SetHTML, you need to create one. The simplest way is to open a blank document, as shown in the following line of code:

m_wndView.Navigate(_T("about:blank"));

And by the way, if your HTML is simple enough, you can use about: instead of CHtmlCtrl::SetHTML to get your HTML, as shown in the following code snippet:

m_wndView.Navigate(_T
("about:<HTML><B>hello, world</B>
</HTML>"));

This works for simple HTML, but for more complex documents you need SetHTML. HtmlApp builds some HTML on the fly with an IMG, TABLE, and links, to list the visible top-level windows, then calls SetHTML to display it. Figure 3 shows HtmlApp running; Figure 4 shows the code.

Figure 4 HtmlApp (Close Figure 4)

HtmlApp.cpp
////////////////////////////////////////////////////////////////
// MSDN Magazine — August 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET on Windows XP. Tab size=3.
// •••
// HtmlApp shows how to implement an About dialog in HTML using a new
// class, CHtmlCtrl, that lets you use CHtmlView as a control in a
// dialog or any other kind of window. HtmlApp also shows how to set
// HTML from a string and how to disable the browser's context menu.
//

#include "StdAfx.h"
#include "resource.h"
#include "MainFrm.h"
#include "HtmlCtrl.h"
#include "TraceWin.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

class CMyApp : public CWinApp {
public:
   virtual BOOL InitInstance();
protected:
   afx_msg void OnAppAbout();
   DECLARE_MESSAGE_MAP()
} theApp;

BEGIN_MESSAGE_MAP(CMyApp, CWinApp)
   ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
END_MESSAGE_MAP()

BOOL CMyApp::InitInstance()
{
   // Create main frame window (don't use doc/view stuff)
   CMainFrame* pMainFrame = new CMainFrame;
   if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
      return FALSE;
   pMainFrame->ShowWindow(m_nCmdShow);
   pMainFrame->UpdateWindow();
   m_pMainWnd = pMainFrame;

   return TRUE;
}

//////////////////
// About dialog uses HTML control to display contents.
//
class CAboutDialog : public CDialog {
protected:
   CHtmlCtrl m_page;       // HTML control
   virtual BOOL OnInitDialog();
public:
   CAboutDialog() : CDialog(IDD_ABOUTBOX, NULL) { }
   DECLARE_DYNAMIC(CAboutDialog)
};
IMPLEMENT_DYNAMIC(CAboutDialog, CDialog)

//////////////////
// init about dialog
//
BOOL CAboutDialog::OnInitDialog()
{
   // cmd map for CHtmlCtrl handles "app:ok"
   static HTMLCMDMAP AboutCmds[] = {
      { _T("ok"), IDOK },
      { NULL, 0  },
   };
   VERIFY(CDialog::OnInitDialog());
   VERIFY(m_page.CreateFromStatic(IDC_HTMLVIEW, this)); // create HTML
                                                        // ctrl
   m_page.SetHideContextMenu(TRUE);                     // hide context
                                                        // menu
   m_page.SetCmdMap(AboutCmds);                         // set command
                                                        // table
   m_page.LoadFromResource(_T("about.htm"));            // load HTML from
                                                        // resource
   return TRUE;
}

//////////////////
// run about dialog
//
void CMyApp::OnAppAbout()
{
   static CAboutDialog dlg; // static to remember state of hyperlinks
   dlg.DoModal();           // run it
}
MainFrm.h
////////////////////////////////////////////////////////////////
// MSDN Magazine — August 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET on Windows XP. Tab size=3.
//

#include "HtmlCtrl.h"

/////////////////
// Typical main frame...
//
class CMainFrame : public CFrameWnd {
public:
   CMainFrame(){ }
   virtual ~CMainFrame() { }
protected:
   CHtmlCtrl   m_wndView;               // CHtmlCtrl as main view
   CStatusBar  m_wndStatusBar;          // status line
   CToolBar    m_wndToolBar;            // toolbar

   afx_msg int  OnCreate(LPCREATESTRUCT lpCreateStruct);
   afx_msg void OnContextMenu(CWnd* pWnd, CPoint pos);

   // helper to format main window HTML
   CString FormatWindowListHTML();

   DECLARE_DYNCREATE(CMainFrame)
   DECLARE_MESSAGE_MAP()
};
MainFrm.cpp
////////////////////////////////////////////////////////////////
// MSDN Magazine — August 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET on Windows XP. Tab size=3.
//
#include "StdAfx.h"
#include "resource.h"
#include "MainFrm.h"
•••
IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
   ON_WM_CREATE()
   ON_WM_CONTEXTMENU()
END_MESSAGE_MAP()

// Commmand map for app: commands in main window HTML.
HTMLCMDMAP MyHtmlCmds[] = {
   { _T("about"), ID_APP_ABOUT },
   { _T("exit"),  ID_APP_EXIT  },
   { NULL, 0  },
};

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
   VERIFY(CFrameWnd::OnCreate(lpCreateStruct)==0);

•••

   // create/init html control as view
   VERIFY(m_wndView.Create(CRect(), this, AFX_IDW_PANE_FIRST));
   m_wndView.SetHideContextMenu(TRUE);          // hide context menu
   m_wndView.SetCmdMap(MyHtmlCmds);             // set commands
   m_wndView.Navigate(_T("about:blank"));       // create document
   m_wndView.SetHTML(FormatWindowListHTML());   // set contents from
                                                // string
   SetActiveView(&m_wndView);                   // set as active view for
                                                // MFC

   return 0;
}

//////////////////
// Handle context menu command.
// Don't do anything; just display TRACE to prove it was called.
//
void CMainFrame::OnContextMenu(CWnd* pWnd, CPoint pos)
{
   TRACE(_T("CMainFrame::OnContextMenu\n"));
}

////////////////////////////////////////////////////////////////
// Following stuff builds the HTML displayed in the main view

////////////////
// EnumWindows callback proc: if window is visible, add info to HTML
// table.
//
static BOOL CALLBACK MyEnumWindowsProc(HWND hwnd, LPARAM lp)
{
   DWORD style = GetWindowLong(hwnd, GWL_STYLE);
   if (style & WS_VISIBLE) {
      CString& s = *(CString*)lp;
      char cname[256];
      GetClassName(hwnd, cname, sizeof(cname));
      TCHAR text[1024];
      GetWindowText(hwnd, text, sizeof(text)/sizeof(text[0]));
      CString temp;
      temp.Format(_T("<TR><TD>%p&nbsp;</TD><TD>%s</TD><TD>%s</TD></TR>\n"),
         hwnd, cname, text);
      s += temp;
   }
   return TRUE;
}

//////////////////
// This function builds a text string that is the HTML document for the
// main window.
//
CString CMainFrame::FormatWindowListHTML()
{
   // start w/top matter
   CString html = _T("<HTML><BODY STYLE=\"font-family:Verdana;\" \
link=\"#02B7B7\" vlink=\"#02B7B7\">\n\
<TABLE WIDTH=\"100%\">\n\
<TR><TD VALIGN=top><A target=\"new\" HREF=\"http://msdn.microsoft.com/msdnmag\">
<IMG BORDER=0 ALT=\"MSDN Magazine\" SRC=\"res://HtmlApp.exe/msdn.gif\"></A>
</TD>\
<TD COLSPAN=2><B>HtmlApp — List of visible top-level windows</B><BR>\n\
<SMALL>Written by <A target=\"new\" \
HREF=\"http://paul.dilascia.com\">Paul DiLascia</A></SMALL></TD></TR>\n\
<TR><TD><B>hwnd</B></TD><TD><B>class</B></TD><TD WIDTH=75%><B>title</B>
</TD><TR>\n");

   // enumerate top-level windows to append their info
   EnumWindows(MyEnumWindowsProc, (LPARAM)&html);

   // append bottom matter. note commands app:about and app:exit
   html += _T("</TABLE>\n\
<P><B>[<A HREF=\"app:about\">About</A>]&nbsp;&nbsp;\
[<A HREF=\"app:exit\">Exit</A>]</B>\n\
</BODY>\n\
</HTML>");

   return html;
}

Finally, let me point out a totally unrelated but really neat new feature I added for HtmlApp to make CHtmlCtrl even more useful. Back in the January 2000 issue, I showed you how to implement an "app:" pseudo-protocol that lets you create HTML links (anchor elements) that communicate with your program. For example, if you add a link

<A HREF="app:about">About</A>
then CHtmlCtrl::OnBeforeNavigate2 will recognize "app:" and call the special virtual function CHtmlCtrl::OnAppCmd with "about" as parameter. You can make up your own commands and override OnAppCmd in a derived class to process them. After using CHtmlCtrl for a while, I soon found myself frequently deriving from CHtmlCtrl just to override this one function. What a waste of typing! To spare my fingers, I invented a simple notion of command maps that let you convert "app:command" to a normal Windows® WM_COMMAND ID:
HTMLCMDMAP MyHtmlCmds[] = {
  { _T("about"), ID_APP_ABOUT },
  { _T("exit"),  ID_APP_EXIT  },
  { NULL, 0  },
};
To use this map, call CHtmlCtrl::SetCmdMap:
m_wndHtmlCtrl.SetCmdMap(MyHtmlCmds);

Now when the user clicks the link to "app:about", CHtmlCtrl::OnAppCmd searches your command map, finds the "about" entry, and sends its parent window a WM_COMMAND message with ID_APP_ABOUT as the ID—whereupon the command enters the magic MFC command-routing superhighway, where any window along the road can handle the command. Pretty sweet! HtmlApp uses this feature to add About and Exit commands directly to the main window as HTML links (see Figure 3). Figure 2 shows the details. The only trick is that CHtmlCtrl::OnAppCmd sends the command via PostMessage instead of SendMessage because of the fact that you'll find yourself in trouble if you try to close the application from the midst of OnBeforeNavigate2. (I found out the hard way.)

Happy programming!

Send your questions and comments for Paul to cppqa@microsoft.com.

Paul DiLascia is a freelance writer, consultant, and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992). Paul can be reached at http://www.dilascia.com.


Resource File: Creating Privacy-aware Web Sites

Resource File
Creating Privacy-aware Web Sites

On this page we provide information on the most current Microsoft developer resources. In accordance with our ongoing efforts to keep developers informed on issues of security, we're offering some suggestions for creating privacy-aware Web sites.

Privacy issues are of primary concern to those involved in Internet commerce. Some consumers are hesitant to provide information to Web sites without clearly understanding how their data will be used and with whom it will be shared. Web site developers can help allay their users' fears by allowing them to make informed choices whenever data is collected. Here are some simple guidelines:

  • Document the data that you collect, store, and share
  • Create a privacy statement outlining these practices
  • Implement Platform for Privacy Preferences (P3P) so browsers can reflect your practices
  • Provide selections so users can opt out of any secondary use of their data

One of the most important actions that you can take for your company and customers is to document all the information that you collect. You should indicate why it is collected, where it is stored, and with whom it is shared. Describe who within your company has access to the data and any third parties that see the data (a process called Onward Transfer).

Any data that you collect should be protected with encryption during storage and transfer. You can encrypt data using Secure Sockets Layer (SSL), Encrypted File System (EFS), Secure Internet Protocol (IPSec), and Crypto API. Data stores should have access protection to restrict who can see the data.

Only collect data from customers when you need it to perform a service. Make sure you have a retention policy for the data, which defines at what point data must be purged from your system.

The accompanying diagram is an example of how you can document data being collected by a Web server. The diagram not only shows the data that is collected, stored, and shared, but also shows who has access to the data and describes various characteristics about the data.

Whenever you are collecting data on your Web site and plan to use it for a secondary purpose, make sure to tell your visitors your intentions and give them an opportunity to opt-in or out for the usage. For example, if you sell CDs on your site, consumers will have to give you their contact information for billing and shipping purposes. You may want to send them a notice whenever you receive new CDs from the same artist or CDs of a similar musical genre. You should always provide a checkbox to permit the user to select their choice. The checkbox should offer the option to opt out, meaning customers won't be contacted. For best privacy, you can make "opt out" the default.

Once you have performed an analysis, you will need to inform consumers about your data collection and usage practices. You should also include a description of the choices they have for the use of their data. Include information about your retention and protection policies in a privacy statement posted prominently on your Web site. For information on how to create a privacy statement and other tips, read the privacy chapters of the book Microsoft Windows Security Resource Kit (Microsoft Press, 2003).

P3P is a specification developed by the World Wide Web Consortium to permit Web sites to present their privacy statements in a standardized XML format. By implementing P3P, Web sites are permitting P3P-enabled users to programmatically read their privacy statements. The eye symbol on the Internet Explorer 6.0 status bar indicates that the Web site has not implemented P3P or that its policy differs from the preferences set by the user in the privacy dialog. For a step-by-step explanation of the implementation procedure, visit How to Deploy P3P Privacy Policies on Your Web Site and http://www.w3.org.

For more information take a look at the definitive book on P3P—Web Privacy with P3P by Dr. Lorrie Faith Cranor (O'Reilly, 2002). Also see the Security Developer Center.