asp tutorials, asp.net tutorials, sample code, and Microsoft news from 15Seconds
Data Access  |   Troubleshooting  |   Security  |   Performance  |   ADSI  |   Upload  |   Email  |   Control Building  |   Component Building  |   Forms  |   XML  |   Web Services  |   ASP.NET  |   .NET Features  |   .NET 2.0  |   App Development  |   App Architecture  |   IIS  |   Wireless
 
Pioneering Active Server
 Power Search








Active News
15 Seconds Weekly Newsletter
• Complete Coverage
• Site Updates
• Upcoming Features

More Free Newsletters
Reference
News
Articles
Archive
Writers
Code Samples
Components
Tools
FAQ
Feedback
Books
Links
DL Archives
Community
Messageboard
List Servers
Mailing List
WebHosts
Consultants
Tech Jobs
15 Seconds
Home
Site Map
Press
Legal
Privacy Policy
internet.commerce














internet.com
IT
Developer
Internet News
Small Business
Personal Technology
International

Search internet.com
Advertise
Corporate Info
Newsletters
Tech Jobs
E-mail Offers

HardwareCentral
Compare products, prices, and stores at Hardware Central!

N-Tier Web Applications using ASP.NET 2.0 and SQL Server 2005 - Part 2
By Thiru Thangarathinam
Rating: 4.1 out of 5
Rate this article


  • email this article to a colleague
  • suggest an article

    In the first part of this article, we looked at the application architecture of a N-tier Web application using ASP.NET 2.0 and SQL Server 2005. We have also understood the creation of CLR managed procedures and data access components. In this installment, we will look at the rest of the components of the application such as business logic layer, user interface logic and so on. While looking at these features, we will also understand the new features of ASP.NET 2.0 such as ObjectDataSource control, master pages, and Caching support that greatly aid in simplifying the development of a N-tier Web application.

    Creation of Business Logic Layer

    In this example, since there is not much of business logic, the business layer methods will simply act as stubs that will invoke the data access layer methods. Create a new class named AuthorsBiz and place that under the App_Code directory as well. Modify the code in the class to look as shown below.

    using System;
    using System.Data;
    public class AuthorsBiz
    {
       public AuthorsBiz()
       {		
       }
       public DataTable GetAuthors()
       {
          AuthorsTableAdapters.AuthorsTableAdapter authorDB = new 
            AuthorsTableAdapters.AuthorsTableAdapter();
          return authorDB.GetAuthors();
       }
       public DataTable GetAuthorTitles(string authorID)
       {		
          AuthorsTableAdapters.AuthorTitlesTableAdapter authorDB = new 
            AuthorsTableAdapters.AuthorTitlesTableAdapter();
          return authorDB.GetTitlesByAuthor(authorID);
       }
    }
    

    As you can see from the above, the methods in the AuthorsBiz class simply invoke the data component methods and route the results back to the caller. Now that you have created the business logic and data access layers, let us move onto the user interface layer, which will provide the desired functionality by taking advantage of the classes you have created so far.

    Creation of User Interface Layer

    As mentioned before, the user interface layer will consist of two pages named Authors.aspx and AuthorTitles.aspx that will display the authors and the titles for a specific author respectively. To ensure all the pages in the web site have a consistent look and feel, ASP.NET 2.0 introduces a new feature named master pages. In this approach, a common base master file that contains the common layout for all the pages is created and all the pages in the web site (called content pages) are inherited from the master page. At runtime when the content page is requested through the browser, ASP.NET merges the output of the content page with the master page thereby providing a seamless way to reuse the master page layout and logic. The next section will demonstrate the steps involved in creating a master page and utilizing it from content pages.

    Creating Consistent User Interface using Master Pages

    With ASP.NET 2.0 master pages, you isolate the look and feel and standard behavior for all the pages in your application and move them to a master page. In the master page, you add placeholders (known as ContentPlaceHolder control) for the content pages (or child pages) to add their custom content. When users request the content pages, the output of the content pages are merged with the output of the master page, resulting in an output that combines the layout of the master page with the output of the content page.

    As mentioned before, the master page defines content areas using the ContentPlaceHolder control, and the content pages place their content in the areas identified by the ContentPlaceHolder control in the master page. Pages that use a master page to define the layout can place content only in the areas defined by the ContentPlaceHolder, thus enabling a consistent site design. Master pages are saved with the file extension .master. Apart from containing all the contents that are required for defining the standard look and feel of the application, the master pages also contain all the top-level HTML elements for a page, such as <html>, <head>, and <form>. For this article, add a master page named CommonMaster.Master to the page web site using the Add New Item dialog box. Modify the code in the master page to look as shown below:

    <%@ master language="C#" %>
    <html>
    <head id="Head1" runat="server">
      <title>Master Page</title>
    </head>
    <body>
    <form id="Form1" runat="server">
      <table id="header" style="WIDTH: 100%; HEIGHT: 80px" 
        cellspacing="1" cellpadding="1" border="1">
        <tr>
          <td style="TEXT-ALIGN: center; width: 100%; height: 74px;" 
            bgcolor=teal>
            <asp:label runat="server" id="Header" Font-Size="12pt" 
              Font-Bold="True">Authors Information</asp:label>
          </td>
        </tr>
      </table>
      <b/>
      <table id="leftNav" style="WIDTH: 108px; HEIGHT: 100%" 
        cellspacing="1" cellpadding="1" border="1">
        <tr>
          <td style="WIDTH: 100px"> 
            <table>
              <tr>
                <td>
                  <a href="Home.aspx">Home</a>
                </td>
              </tr>
              <tr>
                <td>
                  <a href="Authors.aspx">Authors List</a>
                </td>
              </tr>
            </table>
          </td>
        </tr>
      </table>                                 
      <table id="mainBody" style="LEFT: 120px; VERTICAL-ALIGN: top; 
        WIDTH: 848px; POSITION: absolute; TOP: 94px; HEIGHT: 100%"
        border="1">
        <tr>
          <td width="100%" style="VERTICAL-ALIGN: top">                
            <asp:contentplaceholder id="middleContent" 
              runat="Server"></asp:contentplaceholder>                
          </td>
        </tr>
      </table>
    </form>
    </body>
    </html>
    

    The above code looks similar to a traditional ASP.NET page and contains simple HTML and ASP.NET controls. The main difference between this page and a standard ASP.NET page is the use of the Master directive and the file suffix .master. Also note the use of the ContentPlaceHolder control, which dictates where content pages can insert content. The id attribute uniquely identifies the placeholder, allowing more than one placeholder to be in a master page. The master page can have code as well as content, allowing the master page to render contents dynamically. The above code defines a header and a left navigation bar as well as the body of the page using HTML table elements. Inside the body table, there is an asp:contentplaceholder control, which will be linked in all the content pages so that the content pages can insert their own content. Any content page that uses the above master page will automatically inherit the header, left navigation and the body from the master page.

    Now that you have created the master page, let us create a content page that can leverage the master page. To this end, create a Web Form using Visual Studio 2005, name it as Authors.aspx as shown below.

    Note that in the above screenshot, Select master page option is selected and this will result in the master page picker dialog box being displayed. Select CommonMaster.master file from the list. This is shown below.

    Click OK in the above dialog box. After the page is created, modify the code of the Authors.aspx Web page to look as follows:

    <%@ Page Language="C#" MasterPageFile="~/CommonMaster.master" %>
    <asp:content id="Content1" contentplaceholderid="middleContent" 
      runat="server">
      <asp:objectdatasource runat="server" id="authorsSource" 
        typename="AuthorsBiz" selectmethod="GetAuthors">
      </asp:objectdatasource>
      <asp:gridview runat="server" AutoGenerateColumns="false" 
        id="authorsView" datasourceid="authorsSource">
        <alternatingrowstyle backcolor="Silver"></alternatingrowstyle>
        <Columns>
          <asp:HyperLinkField DataTextField="au_id" 
            HeaderText="Author ID" DataNavigateUrlFields="au_id" 
            DataNavigateUrlFormatString="AuthorTitles.aspx?AuthorID={0}">
          </asp:HyperLinkField>
          <asp:BoundField HeaderText="Last Name" 
            DataField="au_lname"></asp:BoundField>
          <asp:BoundField HeaderText="First Name" 
            DataField="au_fname"></asp:BoundField>
          <asp:BoundField HeaderText="Phone" 
            DataField="phone"></asp:BoundField>
          <asp:BoundField HeaderText="Address" 
            DataField="address"></asp:BoundField>
          <asp:BoundField HeaderText="City" 
            DataField="city"></asp:BoundField>
          <asp:BoundField HeaderText="State" 
            DataField="state"></asp:BoundField>
          <asp:BoundField HeaderText="Zip" 
            DataField="zip"></asp:BoundField>
          <asp:BoundField HeaderText="Timestamp" 
            DataField="timestamp"></asp:BoundField>
        </Columns>
      </asp:gridview>
    </asp:content>
    

    As part of the page directive, there is a new attribute named MasterPageFile that allows you to specify the master page you want to use. Then you define an asp:content control and link that up to the master page's asp:contentplaceholder using the contentplaceholderid attribute. Inside the asp:content element, two controls are defined: an ObjectDataSource and a GridView control. The ObjectDataSource control acts as a data source for the GridView control, which simply displays all the authors in a list. An ObjectDataSource control is a new control that is introduced with ASP.NET 2.0 that greatly simplifies the way in which the middle tier business objects are consumed from an ASP.NET page. Let us discuss this control in detail.

    ObjectDataSource Control and N-Tier Development

    ASP.NET 2.0 provides excellent native support for N-Tier application design by introducing the ObjectDataSource control, which is one of the most compelling new entries in the ASP.NET 2.0 arsenal of controls and components. It promotes the use of a strong object model in the middle tier of distributed applications and makes it easier than ever to set up a direct link between the business logic classes and the user interface layer.

    In ASP.NET v1.x, the middle tier would expose some sort of custom business objects to the user interface and then a developer would bind the data to the controls programmatically. Now with ASP.NET 2.0, this binding code is no longer necessary thanks to the ObjectDataSource control. With this control, you can simply bind to an output of an object's method directly to data-bound controls such as GridView, dropdown list and so on.

    In the previous example, the ObjectDataSource control is used to directly bind the results of the GetAuthors method in the AuthorsBiz class and display them in a GridView control. This is shown below:

      <asp:objectdatasource runat="server" id="authorsSource" 
        typename="AuthorsBiz" selectmethod="GetAuthors">
      </asp:objectdatasource>
    

    The ObjectDataSource control is used to instantiate the AuthorsBiz object that will then be responsible for retrieving the appropriate data from the database through the data access layer. The first property you need to set for this control is the TypeName property that tells the control the name of the class to instantiate. This can be the name of any class included in or referenced by the user interface assembly as long as it has a default public constructor (no parameters). Next, you specify the methods of the object, which will handle Select, Update, Insert, or Delete requests. To specify a method used to retrieve data, set the SelectMethod property. The select method must return an object that can be bound to a control such as a Dataset, XmlDocument, or Collection. If the method being invoked requires parameters, you can specify the parameters using the SelectParameters collection which is defined using the <SelectParameters> sub element. You can see an example of this in the implementation of the AuthorTitles.aspx page.

    Now that you have had a look at the Authors.aspx page, let us move onto discuss the AuthorTitles.aspx page. Code for the AuthorTitles.aspx page is as follows:

    <%@ Page Language="C#" MasterPageFile="~/CommonMaster.master" %>
    <asp:content id="Content1" contentplaceholderid="middleContent" 
      runat="server">
      <asp:objectdatasource runat="server" id="authorTitlesSource" 
        typename="AuthorsBiz" selectmethod="GetTitlesByAuthor">
        <SelectParameters>
          <asp:QueryStringParameter Type="String" Direction="Input" 
            Name="authorID" QueryStringField="AuthorID" />
        </SelectParameters>
      </asp:objectdatasource>
      <asp:gridview runat="server" id="authorTitlesView" 
        datasourceid="authorTitlesSource">
        <alternatingrowstyle backcolor="Silver"></alternatingrowstyle>
      </asp:gridview>
    </asp:content>
    

    As you can see, the above code is very similar to the Authors.aspx except for the difference that the ObjectDataSource control uses asp:QueryStringParameter element to pass the author id (which is retrieved from the page query string) as an argument to the GetAuthorTitles method of the AuthorsBiz object.

    Caching

    Caching is a technique that allows you to keep frequently accessed pages or resources (that are expensive to construct) in the memory instead of recreating them from scratch every time. If you have data on the web page that does not change frequently and it is expensive construct then they can be considered as good candidates for caching. Caching is one of the powerful features that can be immensely useful in increasing the performance of a web application. ASP.NET 1.x Cache API was a revolutionary feature that provided capabilities such as declarative output caching, programmatic output caching, and invalidation of cached items when the contents of an XML file or another cached item changes and so on. Even though all these features were of excellent use in increasing the performance of the web application, ASP.NET 1.x did not provide a mechanism for invalidating the data in the cache object when the data in a database changes. This is a much sought after feature that will finally ship with the ASP.NET 2.0 version. Since the authors list does not change frequently, I will show you how to cache the authors list in the object data source control. As part of this, I will also demonstrate how to take advantage of the cache invalidation mechanism features so that the cached authors list can be automatically removed when the authors table changes in the database.

    As mentioned before, one of the most interesting features of ASP.NET 2.0 is the introduction of database-triggered cache invalidation. This is a very common capability most of the applications will require. ASP.NET 2.0 addresses this by providing the database triggered cache invalidation capability that will allow you to ensure that the items in the cache are kept up-to-date with the changes in the database.

    SQL Server Based Cache Invalidation Mechanism

    The SQL Server based cache invalidation mechanism works with SQL Server 7.0 and above. However with SQL Server 7.0 and 2000, only table level cache invalidation mechanism is supported. This means that the cached items will be automatically invalidated any time the data in the table changes. The next release of SQL Server (code-named Yukon) called SQL Server 2005 will also feature row-level cache invalidation mechanism providing a finer level of accuracy over the cached data.

    In Sql Server 7 and Sql Server 2000, table level cache invalidation is supported using a polling system. Through this system, the ASP.NET process will poll the database (pull model) every so many seconds to check and see which tables have changed since it last checked. Even though the pull model works for most cases, it is not an efficient approach. However this will be enhanced in SQL Server 2005 to have SQL Server 2005 actually notify (Push model) ASP.NET, whenever a particular row of data has been modified. SQL Server 2005 accomplishes this by using a feature named Notification Delivery Services (that uses ports 80), which directly interacts with HTTP.SYS of IIS 6.0 to notify the web server of updates to the specific rows.

    Before you can establish cache dependency with SQL Server, you need to perform the following steps.

    • You must have <cache> element in the configuration file (web.config)
    • You also need to perform one time setup of the tables or databases you want to monitor using either the aspnet_regsql utility or the EnableTableForNotifications method.

    After you have completed the above steps, ASP.NET can start invalidating the data in the cache when the SQL Server data changes. To begin, add the appropriate cache related settings in the web.config file as shown below.

    <configuration>      
      <connectionStrings>
        <add name="pubsConnectionString"       
    connectionString="Server=localhost;Database=Pubs;integrated   
      security=true" />
      </connectionStrings>        
      <system.web>
        <caching>
           <sqlCacheDependency enabled="true">
       <databases>
         <add name="Pubs"
            connectionStringName="pubsConnectionString"
            pollTime="60000" />
       </databases>
    </sqlCacheDependency>
        </caching>
      </system.web>
    </configuration>
    

    In the above configuration entries, you specify the name of the database in which you want to enable the cache notification mechanism using the <caching> element. As you can see, there is a new section in web.config called <connectionString> in which you add the connection strings to the database. Once you add the connectionString to the connectionStrings section, you can then reference it from the sqlCacheDependency/databases section.

    Next step is to enable the specific tables in the pubs database for notification. Note that this step is not required for SQL Server 2005. This example will make use of the aspnet_regsql utility to accomplish this. The aspnet_regsql utility creates an extra table named AspNet_SqlCacheTablesForChangeNotification that is used to keep track of the changes to all the monitored tables in the database. It also creates a number of triggers and stored procedures to enable this capability. To run the aspnet_regsql utility, open up the .NET Framework 2.0 SDK Command Prompt and enter the following command.

    aspnet_regsql -S localhost -E -d pubs -ed

    This will enable the database to support cache invalidation mechanism. Once you have enabled this for the pubs database, you can now enable it for the individual tables contained in the pubs database. To enable cache invalidation for the authors table, execute the following command.

    aspnet_regsql -S localhost -E -d pubs -t authors -et

    Now that you have created the plumbing required for the database cache invalidation, let us look at the steps involved in utilizing it from the Authors.aspx page.

    Enabling Caching in the ObjectDataSource Control

    In this section, you will see how to enable caching at the ObjectDataSource control. There are three important properties that need to be set in the ObjectDataSource control to enable caching. They are:

    • EnableCaching - By setting this attribute to true, you enable caching in an ObjectDataSource control.
    • CacheDuration - This property allows you to set or get the duration of the cached data in the ObjectDataSource control. This attribute is specified in terms of seconds.
    • SqlCacheDependency - Specifies the connection string element value to use from the web.config file.

    After adding the caching related attributes, the Authors.aspx looks as follows:

    <%@ Page Language="C#" MasterPageFile="~/CommonMaster.master" %>
    <asp:content id="Content1" contentplaceholderid="middleContent" 
      runat="server">
      <asp:objectdatasource EnableCaching="true" 
        SqlCacheDependency="Pubs:authors" 
        CacheDuration="10000" runat="server" id="authorsSource" 
        typename="AuthorsBiz" selectmethod="GetAuthors">
      </asp:objectdatasource>
      <asp:gridview runat="server" AutoGenerateColumns="false" 
        id="authorsView" datasourceid="authorsSource">
        <alternatingrowstyle backcolor="Silver">
        </alternatingrowstyle>
        <Columns>
          <asp:HyperLinkField DataTextField="au_id" 
            DataNavigateUrlFields="au_id" 
            DataNavigateUrlFormatString="AuthorTitles.aspx?AuthorID={0}">
          </asp:HyperLinkField>
          <asp:BoundField DataField="au_lname"></asp:BoundField>
          <asp:BoundField DataField="au_fname"></asp:BoundField>
          <asp:BoundField DataField="phone"></asp:BoundField>
          <asp:BoundField DataField="address"></asp:BoundField>
          <asp:BoundField DataField="city"></asp:BoundField>
          <asp:BoundField DataField="state"></asp:BoundField>
          <asp:BoundField DataField="zip"></asp:BoundField>
        </Columns>
      </asp:gridview>
    </asp:content>
    

    The preceding code listing demonstrates sql cache invalidation with the ObjectDataSource control. As you can see, the ObjectDataSource control contains both EnableCaching and SqlCacheDependency attributes. The SqlCacheDependency property uses the following syntax.

    SqlCacheDependency="Pubs:authors"

    The attribute declaration lists the name of the connection string identifier, followed by the name of the database table. Because of this attribute, any time data in the authors table of the Pubs database changes, the cached data will be automatically invalidated. The connection string identifier name that you are specifying here should already be defined in the caching\sqlCacheDependancy\databases section of the web.config file.

    Putting It All Together

    Now that you have understood the implementation of the application, let us test its functionality by navigating to the Authors.aspx page using the browser. You should see an output that is somewhat similar to the following.

    If you refresh the page again, you will see the same timestamp in the displayed output, which is due to the fact that you have enabled caching on the ObjectDataSource control. Now to test the SQL Server based trigger invalidation, change the data in the authors table in the pubs database and refresh the page. You should now see a change in the timestamp displayed in the page. This clearly shows that the SQL Server based trigger invalidation mechanism automatically invalidates the output of the ObjectDataSource control as soon as the data in the authors table changes. Clicking on the author id results in the following page that displays all the titles for a specific author.

    Conclusion

    In this series of articles, you have understood a wide range of ASP.NET and SQL Server 2005 features utilized to create an N-Tier ASP.NET 2.0 web based application. The sample application discussed in this article showcased the new features of .NET Framework 2.0 such as TableAdapter Configuration Wizard, and App_Code directory. This article also discussed how the ObjectDataSource control supports layered application design by allowing you to directly bind the output of an object's method directly to the controls in an ASP.NET page. The architecture used by the example application provides a number of advantages. They are as follows:

    • Changes to the user interface or to the application logic are largely independent from one another, allowing the application to evolve easily to meet new requirements.
    • Creating stored procedures using managed code provide performance improvements because of the compiled logic that gets executed close to the database server.
    • If changes to the middle layer classes are required, they can be modified and deployed immediately onto the App_Code directory and the ASP.NET will pick up those changes immediately and compile them on-demand. This enables easy deployment of upgrades to the existing classes that contain the business logic.
    • Using master pages in the presentation layer provide a huge advantage in terms of providing a consistent look and feel across all the pages in the web site.
    • Caching of database tables results in increased performance. Also the new database cache invalidation provides the ability to keep the changes in the database up-to-date with the data in ASP.NET cache.
    • The ability to generate data access logic classes (using Data Component Wizard) without writing a single line of code is a very powerful feature can go a long way in increasing the developers' productivity.
    • ASP.NET 2.0 facilitates N-Tier application design by providing the ObjectDataSource control that is specifically suited for consuming middle tier objects. This new control enables "code-less" data binding by providing the ability to seamlessly integrate the data returned from the middle layer objects with the ASP.NET presentation layer.

  • Rate This Article
    Not HelpfulMost Helpful
    1 2 3 4 5
    Other Articles
    Jul 14, 2005 - An Innovative Technique for Creating Reusuable Page Templates in ASP.NET 1.x
    Code reusuability is one of the major goals of any good object-oriented programmer. While the ASP.NET framework has made code reusuability easier and more elegant than it was in classic ASP, one area where reusuability could be improved is at the UI level. This article outlines a technique that you can use in ASP.NET 1.x that allows every page in your web application to inherit not only the functionality of a base page, but its UI as well.
    [Read This Article]  [Top]
    Jun 23, 2005 - Monitoring SharePoint Usage through an ASP.NET Web Application
    In this article, Gayan Peiris looks at creating an ASP.NET web application that will display the usage details of a selected SharePoint site. Building such an application enables SharePoint administrators to gather all SharePoint usage data from a central location.
    [Read This Article]  [Top]
    May 12, 2005 - Retrieving SharePoint Site Information in an ASP.NET Web Application
    In this article, Gayan Peiris examines using the SharePoint Object Model to access SharePoint site information from an ASP.NET web application. It should be of particular interest to SharePoint administrators who can use the included code as a starting point for development of their own web-based SharePoint administration application.
    [Read This Article]  [Top]
    Dec 23, 2004 - Automated Deployment for Side By Side .NET Web Apps for Visual Studio .NET 2003
    In this article, David Every outlines a step-by-step account of how he solved the problems he encountered while implementing an auto-deployment process. He also describes how to create a stable process for automated remote .NET deployment featuring "side-by-side" capability.
    [Read This Article]  [Top]
    Sep 29, 2004 - Developing Web Parts with ICellConsumer Interface
    Most default SharePoint Server Web Parts can be connected across organizations. The third article in this series shows how to develop connectable Web Parts that consume information provided by other Web Parts.
    [Read This Article]  [Top]
    Aug 10, 2004 - Implementing and Promoting Daily Builds
    Automatic daily builds is a well known software engineering best practice. This article introduces a strategy for implementing and promoting daily builds and offers tips and tricks for preventing and fixing breaks.
    [Read This Article]  [Top]
    Jun 21, 2004 - Using Open Source .NET Tools for Sophisticated Builds
    Building an application can be more than pressing F5. With an increasing number of quality packages being released, developers for the .NET platform now have options to create a very sophisticated build process. Aaron Junod describes a sample build environment and shows how a number of tools can work together to make reliable, predictable, and value-added builds.
    [Read This Article]  [Top]
    Jun 24, 2003 - Programming for the Palm Part 1 - Creating the Palm Application
    The first part of this three part series walks through the process of creating a mobile blog application using a BASIC development environment for Palm OS devices called NS Basic. Subsequent articles will focus on synchronizing the data to the desktop using C# and creating an installer.
    [Read This Article]  [Top]
    Jun 18, 2003 - Online Database Functions Testing Tool
    This short article provides source code for a classic ASP online database functions testing application and shows how to configure and use the tool for either SQL Server or Oracle.
    [Read This Article]  [Top]
    May 22, 2003 - Rewrite.NET -- A URL Rewriting Engine for .NET
    In this article Robert Chartier shows you how to use functionality in the .NET Framework to rewrite requested URLs on the fly.
    [Read This Article]  [Top]
    Mailing List
    Want to receive email when the next article is published? Just Click Here to sign up.

    Support the Active Server Industry



    JupiterOnlineMedia

    internet.comearthweb.comDevx.commediabistro.comGraphics.com

    Search:

    Jupitermedia Corporation has two divisions: Jupiterimages and JupiterOnlineMedia

    Jupitermedia Corporate Info


    Legal Notices, Licensing, Reprints, & Permissions, Privacy Policy.

    Advertise | Newsletters | Tech Jobs | Shopping | E-mail Offers