Jumping Into ASP.NET Part 1: Application Planning and Design

 

Mike Amundsen

August 2003

Applies to:
    Microsoft® ASP.NET

Summary: Learn how to plan and design ASP.NET applications. This article refers to a sample knowledge base Web application and discusses the most common aspects of solid application-building practices. (13 printed pages)

Contents

Introduction
Planning a Basic ASP.NET Application
Defining an Application Architecture
Security Planning
Completing the Design Documents
Summary and Call to Action

Introduction

Welcome to "Jumping Into ASP.NET." Over the next several weeks and months, this article series will work through the details of designing, implementing, and deploying a typical Web application using Microsoft® ASP.NET with Microsoft Visual Studio® .NET. The idea behind this series is to touch on most of the common aspects of solid application-building practices. We'll do more than just lay out some Web Forms and do some data-binding to a back-end database. Data-binding and Web Form layout is important, but lots of other things are important, too.

For example, all good coding projects, no matter what the target platform or language, include some basic planning steps such as a statement of purpose, user scenario documentation, and even architecture documents to identify physical and logical boundaries for the solution. In addition, it is good practice to include security planning early in the life cycle of your solution. These things—along with good database modeling, well-designed middleware components, and clean user interface design—all help ensure that the application you finally deploy into production is secure, reliable, and user-friendly.

At this point, some readers might be thinking this is going to be one of those high-toned articles aimed at some kind of enterprise-level, super-geeky scenario that doesn't really apply to the typical small shop, hobbyist, or personal development community. Not so! Even if you are only building a small, Web-based solution for your own personal use, good planning up front will help ensure an easy implementation and deployment at the end of the line. And you don't need to be an expert programmer or Web developer to use these techniques. No matter what your skill level or your target audience, I think you'll find this series useful, informative, and (dare it be said?) even fun.

Throughout the article series, we'll be building a sample knowledge base Web application called DotNetKB. In this first article we'll tackle the design phase of a typical project including the basic planning, the application architecture, and implementation design. By the time we complete this article you'll have all the documentation in place to start building the solution in earnest.

Now that the preliminaries are out of the way, let's jump right in with the first step, The Application Plan.

Planning a Basic ASP.NET Application

The first step in creating an ASP.NET Web-based application with Visual Studio .NET is to put together a basic Application Plan (AP). While doing this is commonplace for large-scale solutions built with multiple developers, a good AP is also very important for even the smallest application. Building an AP can help you think through some common issues before you start coding. This way, you end up identifying challenges and solutions early in the cycle instead of after you've already "coded yourself into a corner" further on down the line. In the book Software Project Survival Guide, author Steve McConnell points out errors in a software project can "cost 50-200 times as much to correct in late in the project" as they would if they are discovered and corrected in the early stages.

What makes up a good project plan? There are many possibilities, but the most basic include a statement of purpose and a set of user scenarios. There are many other materials that can be useful including requirements documents, coding standards, delivery schedules, testing procedures and so on. For the simple example solution we'll be working with, we'll focus on a simple application statement and some user scenarios. We'll work out other items as we go along.

The Application Statement

The project we'll work on for this series (called DotNetKB) is a simple knowledge base Web site that allows users to ask questions and have those questions answered by approved "experts." Visitors will then be able to search and filter the resulting data as they look for solutions for their common ASP.NET problems.

So, here's a basic statement of purpose for our DotNetKB project. DotNetKB is a Web-based application that lists a set of questions asked by visitors and shows the responses to these questions from approved experts. Visitors will be able to add new questions to the system and be able to search and filter the questions and responses based on the question topic, keywords in the questions and/or answers. Visitors will also be able to sort the list of questions by topic or by the date they were added to the system.

Approved experts will be able to log into a secured part of the application to review the questions and add, edit, and delete one or more answers to the question. The application administrator will also be able to establish expert login rights and login profiles as well as add, edit, and delete question topics.

Finally, some basic statistics will be available including the number of questions and answers in the system, the number of responses by each expert and the number of pages viewed to date.

As you can see from the above statement, the solution will be fairly simple. In reading the statement of purpose, you probably started thinking of a number of other features you could add to this application to make it even better. And that demonstrates a primary reason for your project's statement of purpose—to prevent "feature creep." We all know of examples where a simple idea turned into a huge monster because of changing notions of what the final outcome should look like. The old adage, "If you don't know where you're going, you'll probably end up somewhere else" applies just as much to software projects as it does to a summer-time road trip.

Some projects might need more information in the statement of purpose. For our use, this one will fill the bill. Now that we have a clear idea of what the application is supposed to accomplish, we need some detailed information that will describe how users will interact with the system and what tasks they will need to perform to accomplish their goals. We need a set of user scenarios.

Documenting the User Scenarios

Okay, user scenarios are nothing amazing. Often they are just narratives of how users will interact with the application. The key value of user scenarios is that they document everyone's idea of how users will expect the system to work and how the application should respond. By going through this process, you'll end up identifying the data points and functions needed to handle the various user interactions with the system. In other words, well-written user scenarios will help you determine the database, middleware, and user interface elements you'll need to implement to complete your solution.

Note   Visual Studio .NET Enterprise Architect has a nice feature that allows you to use Microsoft Visio® to create the user scenarios using UML (Unified Modeling Language) and then generate basic code from those scenarios. I'll not go into those details here, but you can find a nice article on this topic called Generating .NET Code Using Visio Enterprise Architect's UML by Sreedhar Koganti at the MSDN® Academic Alliance site.

Using the statement of purpose from the previous section, here are a couple example user scenarios for the DotNetKB project.

Searching the knowledge base

Anonymous users will be able to enter one or more keywords and execute a search that will return a list of questions and/or answers that contain those keywords. Users will be able to target the keyword search to just questions or just answers or include both. The return list will show the question along with the count of responses and the number of times this question was accessed by other users. Clicking on a link will return the list of responses (full text) in reverse chronological order*.*

Entering a new question into the knowledge base

Anonymous users will be able to navigate to a screen where they can enter a new question into the database for review and response by the approved experts. Users will enter the title and content of the question and will select one of a series of topics under which to log the question. Users will also be able to enter their name and an associated URL (their email, Web address, and so on.). Inputs will be validated to ensure the minimal data in included and that all input data is safe from attempts at scripting attacks and so on. Once the data is validated and saved to the database, users will see a response screen thanking them for their contribution and directing them to the home page. Users will also have the option of allowing the site to "remember" their name and URL for future visits to the site.

You see how it works, right? Each scenario attempts to detail the important aspects of the user interaction. For example, both scenarios listed above indicate that the user is "anonymous," meaning that they do not need to be logged in or in some other way authorized. The second example also identified several input values, validation steps, and optional behaviors.

Of course, these are just two examples; the complete system will require many more scenarios. Also, it is important to keep in mind that "users" are not just people; "users" could be other applications with which your program needs to communicate, or even other parts of your application. For example, one scenario will describe how the home page will list the most recent additions to the knowledge base for anyone to see. The "user" in this case will be the home page itself. And there will be scenarios to detail how experts can find and respond to new questions and how administrators can update the topic list and manage other parts of the system. I've identified more than 20 scenarios to consider for this simple application. You can find the current list (and any other related materials on this project) at DotNetKB.

Okay, so we have a statement of purpose and some user scenarios. Now it's time to step back for a moment and get a bit technical. We need to define an application architecture that will help use actually implement the scenarios in "living code."

Defining an Application Architecture

Once you have the basic purpose and the list of user scenarios developed for your solution, you need to start looking at the general architecture, too. The main goal here is to identify the logical and physical aspects of the application—how you plan to break it up into useful sections. I've also added an item in this section covering security. Security is something you want to consider at the very beginning of your planning. Security is not something you "add at the end" of your development cycle. We cover this in more detail later in this section.

Logical Architecture

Logically, you want to plan out your solution to identify the "boundaries" between data storage, data access, business rules, user interface and so on. Typically, Web developers will opt for a two-stage model with WebForms holding all the code to access an existing data storage system such as Microsoft SQL Server™. A more robust approach is to create a middle-tier component library that stands between the WebForm user interface and the SQL Server data storage system. This three-tier approach (WebForms, component, database) is usually all that is needed for most applications. In some cases, however, an additional layer might be needed to handle transporting data between servers. This transport layer can be implemented using a platform-independent protocol such as XML-SOAP. However, if you are using Microsoft .NET technologies from end to end, you can use the binary version of the .NET Remoting protocol to do the same work much faster than XML-SOAP.

For our example, we'll define three logical boundaries: the user interface (WebForms), the middle-tier (a .NET component assembly), and the data tier (SQL Server database). Figure 1 shows how this might be represented.

Aa478997.aspnet-jumpinto-part1-01(en-us,MSDN.10).gif

Figure 1. 3-tier diagram

We now have a simple logical model. How is this useful? It helps us think about the boundaries between each logical group. Each logical layer should act as independently as possible from the others. Ideally, changes in layers should have minimal impact on the whole. For example, if you changed the data storage from SQL Server to XML data files, the only other layer affected should be the middle tier layer. The user interface should not have to worry about the change at all. This should give you some ideas on how to implement the actual coding of the solution to enforce this kind of rule.

Also, the logical layers will help us think about security issues. The boundaries between each layer are potential security holes. And each layer will probably have its own specific security implementation (SQL Server rights, .NET runtime rights, ASP.NET security, and so on). Again, we'll address that in a later section here.

Physical Architecture

Now that the logical layers are identified, it's also important to think about the physical layers. For example, you could implement this application all on a single physical machine that has the SQL Server, Internet Information Server, and ASP.NET and .NET runtimes all installed on the same machine. That would be one physical layer. But a more reliable and scalable approach would be to deploy the Web Forms on a cluster of three Web servers, the .NET component assembly hosted on a cluster of two application servers, and have the database hosted on a pair of SQL Servers in fail-over mode. That would result in a physical architecture that includes seven Windows servers in three main groups: Web cluster, component cluster, and database cluster. You might implement the code differently if you knew that the various logical parts of the system would be hosted on different machines.

For our case, we'll assume an efficient, but robust two-tier model: a Web server hosting both the user interface and the components and a database server hosting the SQL Server data storage. This model will give us the flexibility to add more servers in a cluster if the traffic gets high and still keep it simple enough for us to deal with on a small scale. The image below shows how this physical architecture maps against the logical architecture we defined earlier.

Aa478997.aspnet-jumpinto-part1-02(en-us,MSDN.10).gif

Figure 2. Physical architecture mapped against 3-tier architecture

As you can see, the logical and physical architecture need not be the same. There is one more item to cover in the planning stage: security.

Security Planning

Microsoft has a mantra on the subject of security and software: "Secure by design, secure by default, and secure by deployment." In other words, design in security, expect that systems need to be secure by default, and create solutions that can be deployed successfully in a secure environment. Security has always been important. Now that more and more software "lives" on the public Internet, writing secure software is even more critical. Luckily for us, the .NET runtime and the Windows operating system offer a wide range of security options and features that we can easily include in our application. Without getting too tied up in the details of identifying and eliminating security holes in online solutions, we'll point out some of the most common ones and how our application plans to deal with them.

Note   To get a much better understanding of the available options, check out the Microsoft Security Developer Center.

Buffer overflow

This is probably the most common security hole in compiled applications. Since we'll be working with the .NET runtime that was designed to run safely in memory, buffer overruns will be very unlikely. Also, we'll code the solution in Microsoft Visual Basic® .NET, which is not as susceptible to the buffer overflow problem as is, for example C or C++. But even if we were going to build components in C++, we could use a special feature in the compiler—the /GS switch to protect us from most buffer overflow attacks.

Database attacks

Another common security hole might allow malicious users to gain access to the raw data stored in the database. To prevent hackers from gaining control of the data, we'll use only SQL Server stored procedures instead of "inline queries." This greatly reduces the chances of attacks that attempt to insert additional SQL commands into the input stream. We'll also use input validation at several points along the way to make sure all inputs contain only valid characters.

Cross-site scripting attacks

Another common attack on Web applications involves users adding client-side scripting into the input stream that will then execute additional dialogs and trick users into sending personal data to the hacker's own Web site. To solve this, we'll be using a new feature of ASP.NET 1.1 that will screen all inputs for this kind of malicious code and prevent it from posting into the system. We'll also include additional code in the display screens that will automatically disable any scripting or display markup that might make its way into the data store.

So, we've got both a logical and physical model for the application and a checklist of security features to be sure to include in the implementation. Armed with this and with the statement of purpose and the user scenarios, we're ready to embark on the last part of this "pre-code" adventure.

Completing the Design Documents

Before jumping directly into the coding portion of the project, it is important to take a bit of time to actually map out the logical components of the application. In the case of our sample solution, we'll have three logical components of the solution to implement: the database, the .NET data access component, and the ASP.NET user interface. Over the next several articles we'll implement each one of these items in complete detail. But for now, we should at least map out the big picture for each component and talk about the most important aspect of the process—documenting the interaction between components.

The Database

For the DotNetKB application, we need to store data in three tables: Topics, Questions, and Answers (see the diagram below).

Aa478997.aspnet-jumpinto-part1-03(en-us,MSDN.10).gif

Figure 3. Topics, Questions, and Answers tables

We'll need stored procedures to allow the middle-tier component to safely access the data, too. The details for the database side will be worked out in the next article. For now, we'll just say that a database document that lists the table names along with all the column details, the default indexes, and a list of stored procedures should be included in a complete database design document. In other words, there should be enough detail in the document to allow someone to successfully implement the data storage portion of the system.

Note If you're paying close attention, you might notice that there was no mention of storing the Expert data in the database. Just to make the project interesting (and give us a chance to work with direct XML data storage), we'll store the Expert information in an XML data file.

The Data Access Component

The data access component design document will outline all the details of the interaction with the data storage system and the interaction with the user interface. In some systems, the data access component will actually be multiple assemblies that deal with various aspects of the process. For example, there might be a set of business rules exposed to the user interface that is completely independent of data storage and retrieval. In that case, it might be wise to implement a business component separate from the data access component.

In our example, we'll actually implement two separate components: the Message component and the DataAccess component. This message-oriented implementation is particularly successful if you plan on supporting XML-based data transfer services such as SOAP Web Services.

The message component

The message component will define a set of classes used to transfer data between the various layers. These messages can exist as binary or XML-text data. The value of a message layer is that it shields the rest of the system from the specific details of a data storage implementation such as SQL Server, XML files, and so on. In addition, by implementing a message layer instead of a more complex library of "smart objects," our solution will more easily support remote calling services such as XML-SOAP, which are not capable of sending class-level logic along with the data.

An example of a message class would be an implementation of a Topic message and the related collection:

Public Class Topic
    Private _ID As Integer
    Private _Title As String
    Private _Description As String

    Public Property ID() As Integer
        Get
            Return _ID
        End Get
        Set(ByVal Value As Integer)
            _ID = Value
        End Set
    End Property

    Public Property Title() As String
        Get
            Return _Title
        End Get
        Set(ByVal Value As String)
            _Title = Value
        End Set
    End Property

    Public Property Description() As String
        Get
            Return _Description
        End Get
        Set(ByVal Value As String)
            _Description = Value
        End Set
    End Property

End Class

Public Class Topics
    Inherits System.Collections.CollectionBase

    Default Public Property Item(ByVal index As Integer) As Topic
        Get
            Return CType(List(index), Topic)
        End Get
        Set(ByVal Value As Topic)
            List(index) = Value
        End Set
    End Property

    Public Function Add(ByVal s As Topic) As Integer
        Return List.Add(s)
    End Function

    Public Sub Remove(ByVal index As Integer)
        List.Remove(index)
    End Sub

End Class

Note   If you've had experience with message-oriented design, you'll know that we will want to make these message classes serializable so that they can be easily sent back and forth between application layers. Luckily for us the .NET runtime knows how to do this without much work on our part. However, when we get to the article on creating the messages, we'll cover the details on how the .NET runtime serializes classes and what we can do to optimize that process in our code.

The details of this approach will be fleshed out in a future article when we implement the message component and the data access component. The design document will contain a list of all the messages and their properties and data types. For now, just start thinking about how you can use this message approach to encapsulate data transfers between layers and create a kind of generic data service that will work with both local and remote scenarios.

The data access component

With the concept of a message class defined, the data access component can concentrate on the details of talking directly to the data storage system and returning the information in the proper message format. In the case of our example, this will involve mapping the SQL Server stored procedures with a request from the user interface and creating a message (or message collection) that can be returned to the user interface for display.

For example, below is some sample code from the data access component that retrieves a single Topic record from data storage and returns the proper message format to the user interface.

Public Function GetTopicRecord(ByVal ID As Integer) As Messages.Topic
    Dim t As Messages.Topic = New Messages.Topic
    cn = New SqlConnection(secureConnectionString)
    cd = New SqlCommand("GetTopic", cn)
    cd.CommandType = CommandType.StoredProcedure
    cd.Parameters.Add("@ID", ID)
    cn.Open()
    dr = cd.ExecuteReader()
    dr.Read()
    With t
        .ID = ID
        .Title = dr("Title")
        .Description = dr("Description")
    End With
    Return t
End Function

The design document would include a set of classes and methods to handle each of the requests from the user interface along with details on which stored procedure would be called and which message format would be returned. Again, we'll work out the details of this process in a future article focused on the data access layer.

The Web User Interface

Finally, the user interface design document would include all the user inputs and displays needed to complete the various scenarios. Usually the user interface documentation covers both the mechanical details of the interface as well as the graphical design elements that make the user interface unique. For example, the color scheme, font types, and overall page design is just as important as the names and number of inputs to use to capture the proper data for a search query.

Often, to keep things clean, the mechanical details are outlined in a separate document from the graphical design. That's what we'll do in our case. In a future article, we'll create a comprehensive user interface document and implementation that details the elements of each screen and the associated actions. Then in another article, we'll deal with the graphical aspects of the application emphasizing the use of cascading style sheets as a kind of skinning service.

Below is a typical user interface description covering the Topic editing scenario.

Topic input screen

The Topic screen will show an abbreviated list of all current topics (topic id and topic name) along with an "edit" link beside each topic. Clicking on the "edit" link will call up the associated topic record and display it in a series of input boxes. Title and Description will be editable, but the topic id will be read-only. Users will be able to edit the title and description and then press the "save" button to write the changes to the data store. The inputs will be validated. Both are required inputs and will have a limited length of 30 chars for title and 500 chars for description. Upon completion of the update, a response message will appear confirming the update or, if the update fails, the message will indicate the error condition.

Users will also be able to delete an existing topic record by clicking on the "edit" link in the list and then, after viewing the record details in the display, pressing the "delete" link. Upon completion of the delete, a response message will appear confirming the update or, if the update fails, the message will indicate the error condition. Note that users will not be able to delete topics that have existing questions or answers associated with them.

Finally, users can add entirely new topic records by pressing the "new topic" link on the initial display screen. This will bring up the Title and Description inputs (no ID input will be shown) and provide a "save" button. The inputs will be validated. Both are required inputs and will have a limited length of 30 characters for title and 500 characters for description. Upon completion of the update, a response message will appear confirming the update or, if the update fails, the message will indicate the error condition.

From the above narrative, you could easily implement a completely functional screen. The way to judge a good design document is that it allows the reader to complete the work without additional questions. The final user interface design document would include this kind of narrative for each screen in the application.

Summary and Call to Action

We've outlined the final design documents for the database, the middle-tier, and the user interface implementations. That, along with the architecture and the initial planning documents, wraps up our completed design package. In an actual situation, completing all these documents would take at least several hours for even the smallest system. It could take weeks for larger systems—possibly even months. But while this might be a frustration for some, by going through this work ahead of time, you'll identify most all the major obstacles to completing your solution long before you get to the coding phase of the project. That will reduce the time it takes write the actual code and it will also reduce the number of bugs and roadblocks you'll experience.

In the next article, we'll go through the details of building the data storage system in SQL Server using Visual Studio .NET. We'll define the data tables, create the required stored procedures and set the proper data access to ensure a secure and reliable connection between any components and the data itself.

Now that you have seen one possible example of how to create an application plan, you can start thinking about how to use these elements in your own work to increase overall quality and productivity in your projects. For those interested in learning more about project planning and how it affects software quality, check out Steve McConnell's Software Project Survival Guide.

Mike Amundsen provides training, speaking, and consulting services. To learn more about him or to contact him, visit his site at http://amundsen.com. You can also find updates to this article and related material there.

© Microsoft Corporation. All rights reserved.