The Resum Application

The Resumé Application

It's time to turn our attention to the Resumé application. Its purpose is to illustrate how to effectively use Web Forms to create applications. I have used this program in my classes for this very purpose, and it never fails that students want to go on and add lots of nice features and extras. If I allowed this to happen for my presentation in this chapter, the Resumé application would suddenly be complicated, and it wouldn't illustrate the use of Web Forms as well because of the added complexities the cool extras would create.

Suffice it to say that you can easily extend the Resumé application for your own purposes. A section at the end of the chapter offers suggestions for making useful modifications. But in our examples, we'll keep things manageable in the name of learning.

Database Schema

Before we get too far, the database schema must be revealed. It's pretty simple there are five tables. A description of the tables follows.

The Login Table

The Login table contains the user's login name, password, and user ID. The schema for the Resumé Login table can be seen in Figure 2.4:

Figure 2.4. The Resumé.Login Table

graphics/02fig04.jpg

The PersonalInfo Table

The PersonalInfo table contains personal information, such as mailing address and phone number. The schema for this table can be seen in Figure 2.5.

Figure 2.5. The Resumé.PersonalInfo Table

graphics/02fig05.jpg

The EducationInfo Table

The EducationInfo table contains information relating to an individual's education. The schema for this table can be seen in Figure 2.6.

Figure 2.6. The Resumé.EducationInfo Table

graphics/02fig06.jpg

The SkillsInfo Table

The SkillsInfo table contains information relating to an individual's skillset. The SkillsInfo table schema can be seen in Figure 2.7.

Figure 2.7. The Resumé.SkillsInfo Table

graphics/02fig07.jpg

The WorkInfo Table

The WorkInfo table contains information relating to an individual's work history. The schema for this table can be seen in Figure 2.8.

Figure 2.8. The Resumé.WorkInfo Table

graphics/02fig08.jpg

The Program in Action

The Resumé program has several distinct screens in which it performs various tasks. In this section, I introduce the major screens and talk about what they do.

The Main Screen

The main screen lets users search for a resumé, or log in and edit their resumés. If they don't have a login name or user ID, they can select the "I am a new user" checkbox, and a new record will be created. You can see the main screen in Figure 2.9. The file name for this screen is default.aspx.

Figure 2.9. The Main Screen Lets You Search, or Log In to Edit a Resumé.

graphics/02fig09.jpg

The Editing Screen

Resumés can be edited from the screen that the Edit.aspx file contains. Users can enter information into any of four sections: Personal, Education, Work, and Skills. A CheckBox exists for each of these sections, and if the box is not selected for any given section, that section's information won't appear in the displayed resumé. You can see the editing screen in Figure 2.10.

Figure 2.10. The Resumé Is Edited from Edit.aspx.

graphics/02fig10.jpg

The Viewing Screen

The screen that actually displays resumés (as opposed to allowing them to be edited) is contained in View.aspx. This screen takes resumé information from the database and renders it to the screen for viewing. A rendered resumé can be seen in Figure 2.11.

Figure 2.11. The Resumé Is Shown from View.aspx.

graphics/02fig11.jpg

The Search Results Screen

A simple screen contained in Search.aspx displays any results for a user's search. The results can be displayed via hyperlinks that are displayed in this page.

Code Listings and Explanations

This section contains the code listings for the Resumé application. Each code listing is explained so that you can fully understand how it works.

The default.aspx.cs Code

Users can do three things from the main application screen: search, create a new user login and then edit a resumé, or enter a valid name and password and then edit a resumé. The code you see in Listing 2.5 is called when users with existing credentials want to edit their resumé.

The DoLogin() method shown in Listing 2.5 first creates a connection to the database by creating a SqlConnection object. The connection string isn't shown because it is contained in the Global.asax file. Keeping the connection string in a central location means that when it's changed, only one place must be edited. Having to search through all your code to change a connection string is a lot of extra work and could cause bugs if any occurrences are missed. The following line shows the connection string that's being used in the Resumé application:

 Application["DBConnectString"] =    "server=localhost;uid=sa;pwd=;database=Resume"; 

A try/catch construct will manage any errors that happen as a result of the database access. All of the database-related classes throw exceptions when something goes wrong.

Inside of the try, the connection is opened, a SQL string is formed, and the SQL is executed with the ExecuteReader() method. This method returns a recordset in the form of a SqlDataReader class.

If a record was found as a result of the query, then the reader.Read() method will return the value true. The bFound flag will be set to true so that the code knows that a match for the specified user name was found. The password that was typed in is then compared with what is in the recordset, and if they match, the resumé can be edited.

If the user name wasn't found or the password was incorrect, the user will be notified of the error. This is done by a string assigned to the LoginMessage Label object. Putting text in a Label object couldn't be easier. All you must do is set the Text property to the text that you want contained in the Label.

Listing 2.5 This Code Logs In an Existing User.
 private void DoLogin() {     SqlConnection myConnection =        new SqlConnection(          Convert.ToString( Application["DBConnectString"] ) );     try     {         myConnection.Open();         SqlCommand myCommand;         string strSql = "select * from Login where UserName='" +            UserName.Text.Trim() + "'";         myCommand  = new SqlCommand( strSql, myConnection );         SqlDataReader reader = myCommand.ExecuteReader();         bool bFound = false;         string strPassword = "";         int nID = 0;         if( reader.Read() )         {             bFound = true;             strPassword =               Convert.ToString( reader["Password"]).ToUpper();             nID = reader.GetInt32( 2 );         }         reader.Close();         if( !bFound )         {             LoginMessage.Text = "User name not found.";         }         else if( strPassword != Password.Text.ToUpper() )         {             LoginMessage.Text = "The password is incorrect.";         }         else         {             Session["UserID"] = nID;             Response.Redirect( "Edit.aspx" );         }     }     catch( Exception ex )     {         LoginMessage.Text = ex.Message.ToString();     }     finally     {         if( myConnection.State == ConnectionState.Open )         {             myConnection.Close();         }     } } 

The code in Listing 2.6 (which follows) that creates a new user record has some similarities to the code that finds a match for a user and password, which you just looked at in Listing 2.5. You'll notice the same SqlConnection object that's created, and the same call to the Open() method. The code includes a query to see whether the user name is in the database, to void duplicate user names. All of this is similar to what you've already seen.

The code in Listing 2.6 differs significantly in that once it has been established that the user name in the UserName TextField object is unique, the remainder of the code is intended to create new records in the database tables.

This code is a little more complicated than I had intended. That's because I'm using a SqlTransaction object in case an error occurs along the way. The SqlTransaction object either commits all the database operations in a transaction, or it rolls them all back. The action is all or nothing, and if something goes wrong, none of the operations are committed.

You see, if one of the records isn't created in the one of the tables, such as in the SkillsInfo table, then when a user goes to edit or view the table, a database error will occur because part of the data will not be available. And it's better to catch the problem here than later on.

I originally didn't have this code in place, but one of my students was getting errors and asked that we add transactions. Because I couldn't give him a good reason to avoid using a transaction object (how would it sound to say I just didn't want to go to the effort?), I added the transactional support. The student works for a Fortune 500 bank, and I guess not having transactional support goes against his very nature. We'll talk more about ADO.NET in Chapter 3, so you'll get this transaction code for now without much explanation.

Most of the database tables have default values. For this reason, the code to add records to the tables is simple it simply inserts the user ID and lets the default values be used for the other fields. For the Login table, though, the user name and password are inserted so that the user can log in later on.

There is one more interesting thing regarding the creation of the Login table record. The ID field in this table is an Identity field. That means the database will automatically create a unique value for this field. This value is then used by the program to uniquely identify a resumé and all its related data. For this reason, getting the ID value immediately after the record is created is important.

It is common to perform a query immediately after the initial record creation, to retrieve the value of an identity column. But this approach requires extra code and one more trip to the database. A mechanism exists by which you can get an identity value back, and that mechanism can be seen in this code (which is extracted from Listing 2.6 below):

 myTransaction = myConnection.BeginTransaction( IsolationLevel.ReadCommitted, "NewUser" ); strSql = "insert into Login (UserName,Password) VALUES ('" + UserName.Text.Trim() + "','" +     Password.Text.Trim() + "') select @ID = @@IDENTITY"; myCommand  = new SqlCommand( strSql, myConnection, myTransaction ); myCommand.Parameters.Add( "@ID", SqlDbType.Int ); myCommand.Parameters["@ID"].Direction = ParameterDirection.Output; myCommand.ExecuteNonQuery(); 

We will go into more detail about command parameters in Chapter 3.

You can also see in Listing 2.6 where the transaction is committed. This event occurs only if no exceptions are thrown. You can also see where a session variable is set to contain the ID value. We cover application and session variables in more detail in Chapter 7.

Listing 2.6 This Code Creates New User Records.
 private void CreateNewUserAndDoLogin() {     SqlConnection myConnection =         new SqlConnection(             Convert.ToString( Application["DBConnectString"] ) );     SqlCommand myCommand;     SqlTransaction myTransaction = null;     try     {         myConnection.Open();         string strSql = "select * from Login where UserName='" +            UserName.Text.Trim() + "'";         myCommand  = new SqlCommand( strSql, myConnection );         SqlDataReader reader = myCommand.ExecuteReader();         bool bFound = false;         if( reader.Read() )         {             bFound = true;         }         reader.Close();         if( bFound )         {             LoginMessage.Text =                "The user name you chose already exists.";             myConnection.Close();             return;         }         myTransaction =            myConnection.BeginTransaction( IsolationLevel.ReadCommitted,               "NewUser" );         strSql = "insert into Login (UserName,Password) VALUES ('" +            UserName.Text.Trim() + "','" +            Password.Text.Trim() + "') select @ID = @@IDENTITY";         myCommand  =            new SqlCommand( strSql, myConnection, myTransaction );         myCommand.Parameters.Add( "@ID", SqlDbType.Int );         myCommand.Parameters["@ID"].Direction =            ParameterDirection.Output;         myCommand.ExecuteNonQuery();         int nID = Convert.ToInt32( myCommand.Parameters["@ID"].Value );         myCommand =           new SqlCommand( "insert into PersonalInfo (UserID) VALUES (" +           Convert.ToString( nID ) +              ")", myConnection, myTransaction );         myCommand.ExecuteNonQuery();         myCommand =          new SqlCommand( "insert into EducationInfo (UserID) VALUES (" +              Convert.ToString( nID ) +              ")", myConnection, myTransaction );         myCommand.ExecuteNonQuery();         myCommand =           new SqlCommand( "insert into WorkInfo (UserID) VALUES (" +              Convert.ToString( nID ) +              ")", myConnection, myTransaction );         myCommand.ExecuteNonQuery();         myCommand =           new SqlCommand( "insert into SkillsInfo (UserID) VALUES (" +              Convert.ToString( nID ) +              ")", myConnection, myTransaction );         myCommand.ExecuteNonQuery();         myTransaction.Commit();         Session["UserID"] = nID;         Response.Redirect( "Edit.aspx" );     }     catch( Exception ex )     {         LoginMessage.Text = ex.Message.ToString();         if( myTransaction != null )         {             myTransaction.Rollback();         }         }     }     finally     {         if( myConnection.State == ConnectionState.Open )         {             myConnection.Close();         }     } } 

When a user clicks the Edit button from the main screen, a button handler is fired. The handler takes a look at the NewUser CheckBox object. If the box is checked, the CreateNewUserAndDoLogin() method is called; otherwise, the DoLogin() method is called, as shown in Listing 2.7.

Listing 2.7 This Code Is for the Button Event-Handler Method Called from default.aspx When the User Clicks the Edit Button.
 private void LoginButton_Click(object sender, System.EventArgs e) {     if( NewUser.Checked )     {         CreateNewUserAndDoLogin();     }     else     {         DoLogin();     } } 
The Edit.aspx.cs Code

The Resumé editing page is fairly straightforward. The process begins by loading the data for a user's resumé and putting that data into TextField objects with which the user can edit the resumé. After the user is done editing, she clicks on the OK button to save to edited information. The data is then saved back to the database.

Listing 2.8 shows the method that loads a user's personal information and places it into the correct TextField objects.

Listing 2.8 This Method Loads Personal Information to Be Edited for a User.
 private void LoadPersonalInfo( SqlConnection myConnection ) {     string strSql = "select * from PersonalInfo where UserUserID"] );     SqlCommand myCommand = new SqlCommand( strSql, myConnection );     SqlDataReader reader = myCommand.ExecuteReader();     if( reader.Read() )     {         Name.Text = reader.GetString("Name" );         Address.Text = reader.GetString("Address" );         City.Text = reader.GetString("City" );         State.Text = reader.GetString("State" );         Zip.Text = reader.GetString( "Zip" );         Phone.Text = reader.GetString("Phone" );         Email.Text = reader.GetString("Email" );         if( reader.GetInt32( 9 ) == 0 )         {             IncludePersonal.Checked = false;         }     }     reader.Close(); } 

Listing 2.9 shows three methods, each of which loads a different category of information: education, work, and skills. If the database flag that indicates whether the category is enabled is set to a value of 1, the data is placed into the TextField object with which the user can edit the data.

Listing 2.9 These Methods Load Education, Work, and Skills Information for a User so That Information Can Be Edited.
 private void LoadEducationInfo( SqlConnection myConnection ) {     string strSql = "select * from EducationInfo where UserID='" +        Convert.ToString( Session["UserID"] )  + "'";     SqlCommand myCommand = new SqlCommand( strSql, myConnection );     SqlDataReader reader = myCommand.ExecuteReader();     if( reader.Read() )     {         Education.Text = reader.GetString( 1 );         if( reader.GetInt32( 2 ) == 0 )         {             IncludeEducation.Checked = false;         }     }     reader.Close(); } private void LoadWorkInfo( SqlConnection myConnection ) {     string strSql = "select * from WorkInfo where UserID='" +        Convert.ToString( Session["UserID"] )  + "'";     SqlCommand myCommand = new SqlCommand( strSql, myConnection );     SqlDataReader reader = myCommand.ExecuteReader();     if( reader.Read() )     {         Work.Text = reader.GetString( 1 );         if( reader.GetInt32( 2 ) == 0 )         {             IncludeWork.Checked = false;         }     }     reader.Close(); } private void LoadSkillsInfo( SqlConnection myConnection ) {     string strSql = "select * from SkillsInfo where UserID='" +        Convert.ToString( Session["UserID"] )  + "'";     SqlCommand myCommand = new SqlCommand( strSql, myConnection );     SqlDataReader reader = myCommand.ExecuteReader();     if( reader.Read() )     {         Skills.Text = reader.GetString( 1 );         if( reader.GetInt32( 2 ) == 0 )         {             IncludeSkills.Checked = false;         }     }     reader.Close(); } 

When the Edit.aspx page is first loaded, the Page.Load event is fired and calls each of the methods that load resumé data and populate the Web Forms objects. This code can be seen in Listing 2.10. The code shows creating and opening a connection, calling the four methods to retrieve data, and closing the connection. All exceptions are caught in the Page_Load() method, not in the separate data-retrieval methods.

Listing 2.10 This Method Responds to the Page.Load Event.
 private void Page_Load(object sender, System.EventArgs e) {     if( !IsPostBack )     {         try         {             SqlConnection myConnection =                 new SqlConnection(                   Convert.ToString( Application["DBConnectString"] ) );             myConnection.Open();             LoadPersonalInfo( myConnection );             LoadEducationInfo( myConnection );             LoadSkillsInfo( myConnection );             LoadWorkInfo( myConnection );             myConnection.Close();         }         catch( Exception ex )         {             ErrorLabel.Text = ex.Message.ToString();         }     } } 

Personal information is saved with a single SQL update statement, as shown in Listing 2.11. You create the SQL statement is created by concatenating the data that's in the TextField objects.

Listing 2.11 This Method Saves Personal Information for a User after It Has Been Edited.
 private void SavePersonalInfo( SqlConnection myConnection ) {     int nUseIt = 1;     if( !IncludePersonal.Checked )     {         nUseIt = 0;     }     string strSql = "update PersonalInfo set Name='" + Name.Text +         "',Address='" + Address.Text +         "',City='" + City.Text +         "',State='" + State.Text +         "',Zip='" + Zip.Text +         "',Phone='" + Phone.Text +         "',Email='" + Email.Text +         "',Enabled=" + Convert.ToString( nUseIt ) +         " where UserUserID"] );     SqlCommand myCommand = new SqlCommand( strSql, myConnection );     myCommand.ExecuteNonQuery(); } 

Listing 2.12 shows the three methods that save the education, work, and skills data to the database. None of these methods is catching exceptions; rather, the code that calls the method is doing so.

Listing 2.12 These Methods Save Education, Work, and Skills Information After It Has Been Edited.
 private void SaveEducationInfo( SqlConnection myConnection ) {     int nUseIt = 1;     if( !IncludeEducation.Checked )     {         nUseIt = 0;     }     string strSql = "update EducationInfo set Information='" +         Education.Text +         "',Enabled=" + Convert.ToString( nUseIt ) +         " where UserUserID"] );     SqlCommand myCommand = new SqlCommand( strSql, myConnection );     myCommand.ExecuteNonQuery(); } private void SaveWorkInfo( SqlConnection myConnection ) {     int nUseIt = 1;     if( !IncludeWork.Checked )     {         nUseIt = 0;     }     string strSql = "update WorkInfo set Information='" + Work.Text +         "',Enabled=" + Convert.ToString( nUseIt ) +         " where UserUserID"] );     SqlCommand myCommand = new SqlCommand( strSql, myConnection );     myCommand.ExecuteNonQuery(); } private void SaveSkillsInfo( SqlConnection myConnection ) {     int nUseIt = 1;     if( !IncludeSkills.Checked )     {         nUseIt = 0;     }     string strSql = "update SkillsInfo set Information='" +         Skills.Text +         "',Enabled=" + Convert.ToString( nUseIt ) +         " where UserUserID"] );     SqlCommand myCommand = new SqlCommand( strSql, myConnection );     myCommand.ExecuteNonQuery(); } 

When a user clicks the OK button, an event handler is fired, as shown in Listing 2.13. This event method creates and opens a database connection, and then calls the four methods that save the resumé data.

Exceptions are caught in this method rather than in the individual data-saving methods. The Cancel button simply redirects the user back to the main page.

Listing 2.13 This Code Is for the OK Button Event Handler.
 private void OKButton_Click(object sender, System.EventArgs e) {     try     {         SqlConnection myConnection =            new SqlConnection(               Convert.ToString( Application["DBConnectString"] ) );         myConnection.Open();         SavePersonalInfo( myConnection );         SaveEducationInfo( myConnection );         SaveSkillsInfo( myConnection );         SaveWorkInfo( myConnection );         myConnection.Close();         Response.Redirect( "default.aspx" );     }     catch( Exception ex )     {         ErrorLabel.Text = ex.Message.ToString();     } } private void CancelButton_Click(object sender, System.EventArgs e) {     Response.Redirect( "default.aspx" ); } 
The Search.aspx.cs Code

The search page calls into a method named SearchResumes(), as shown in Listing 2.14. This method takes two parameters that contain the search criteria (these criteria were passed from default.aspx), forms a SQL query, executes the query, and then renders any results as selectable hyperlinks.

The parameters are retrieved with the QueryString() method. For instance, a parameter named Info can be retrieved as follows:

 string strInfo = Convert.ToString( Request.QueryString["Info"] ); 

The SQL query can contain either one or two pieces of information Name or Zip. Because this is true, some relatively simple code constructs the query, based on whether one or two parameters have been specified.

Once the query string has been constructed, the ExecuteReader() method is called. If there are any results, then the reader.Read() method will return true for each record found. Each of the records is then output to the HTML stream with a call to the Response.Write() method.

Listing 2.14 This Method Searches for Resumés Based on the Search Criteria.
 public void SearchResumes() {     try     {         SqlConnection myConnection =            new SqlConnection(              Convert.ToString( Application["DBConnectString"] ) );         string strName = Convert.ToString( Request.QueryString["Name"]);         string strZip = Convert.ToString( Request.QueryString["Zip"] );         string strSql = "select Name,UserId from PersonalInfo where ";         bool bNeedAnd = false;         if( strName.Length > 0 )         {             strSql += "Name like '%" +  strName + "%'";             bNeedAnd = true;         }         if( strZip.Length > 0 )         {             if( bNeedAnd )             {                 strSql += " and ";                 strSql += "Zip like '%" +  strZip + "%'";             }         }         myConnection.Open();         SqlCommand myCommand = new SqlCommand( strSql, myConnection );         SqlDataReader reader = myCommand.ExecuteReader();         while( reader.Read() )         {             Response.Write(                 "<p align=\"center\"><font color=\"yellow\">" +                 reader.GetString( 0 ) +                 "</font> <a href=\"View.aspx?User\">View</a></p>" );         }         reader.Close();     }     catch( Exception ex )     {         Response.Write( ex.Message.ToString() );     }     finally     {         if( myConnection.State == ConnectionState.Open )         {             myConnection.Close();         }     } } 
The View.aspx.cs Code

The code to view resumé information is fairly simple. A database connection is created and opened in the Page_Load() method, as you see in Listing 2.15. The LoadPersonalInfo() method (as shown in Listing 2.16) is then called, and the personal information is placed into Label objects (named Name, Address, City, State, Zip, Phone, and Email). If, however, the Enabled column in the database is set to a value of 0, this information will not be rendered.

Listing 2.17 shows the three other information retrieval methods, which retrieve the education, work, and skills data from the database for the resumé.

Listing 2.15 This Code Is Fired When the Page First Loads. The Code Then Calls the Methods That Load the Information with Which the Resumé Will Be Rendered.
 private void Page_Load(object sender, System.EventArgs e) {     if( !IsPostBack )     {         try         {             SqlConnection myConnection =                 new SqlConnection(                    Convert.ToString( Application["DBConnectString"] ) );             myConnection.Open();             LoadPersonalInfo( myConnection );             LoadEducationInfo( myConnection );             LoadSkillsInfo( myConnection );             LoadWorkInfo( myConnection );             myConnection.Close();         }         catch( Exception ex )         {             ErrorLabel.Text = ex.Message.ToString();         }     } } 
Listing 2.16 This Method Loads Personal Information for a User so That It Can Be Viewed.
 private void LoadPersonalInfo( SqlConnection myConnection ) {     string strSql = "select * from PersonalInfo where UserUserID"] );     SqlCommand myCommand = new SqlCommand( strSql, myConnection );     SqlDataReader reader = myCommand.ExecuteReader();     if( reader.Read() )     {         if( reader.GetInt32( 9 ) != 0 )         {             Name.Text = reader.GetString( 2 ) + "<br>\r\n";             Address.Text = reader.GetString( 3 ) + "<br>\r\n";             City.Text = reader.GetString( 4 ) + ", ";             State.Text = reader.GetString( 5 ) + " ";             Zip.Text = reader.GetString( 6 ) + "<br>\r\n";             Phone.Text = reader.GetString( 7 ) + "<br>\r\n";             Email.Text = reader.GetString( 8 );             EmailLabel.Text = "Send Email: <a href=\"mailto:" + _               Email.Text +                 "\">" + Email.Text +                 "</a>";         }     }     reader.Close(); } 
Listing 2.17 These Methods Load Education, Work, and Skills Information for a User so That Information Can Be Viewed.
 private void LoadEducationInfo( SqlConnection myConnection ) {     string strSql = "select * from EducationInfo where UserID='" +          Convert.ToString( Request.QueryString["UserID"] )  + "'";     SqlCommand myCommand = new SqlCommand( strSql, myConnection );     SqlDataReader reader = myCommand.ExecuteReader();     if( reader.Read() )     {         if( reader.GetInt32( 2 ) != 0 &&             reader.GetString( 1 ).Length > 0 )         {             Education.Text =                 "<font color=\"cyan\" size=\"5\">Education</font><br>" +                 reader.GetString( 1 ).Replace( "\r\n", "<br>\r\n" );         }     }     reader.Close(); } private void LoadWorkInfo( SqlConnection myConnection ) {     string strSql = "select * from WorkInfo where UserID='" +        Convert.ToString( Request.QueryString["UserID"] )  + "'";     SqlCommand myCommand = new SqlCommand( strSql, myConnection );     SqlDataReader reader = myCommand.ExecuteReader();     if( reader.Read() )     {         if( reader.GetInt32( 2 ) != 0 &&             reader.GetString( 1 ).Length > 0 )         {             Work.Text =                 "<font color=\"cyan\" size=\"5\">Work</font><br>" +                 reader.GetString( 1 ).Replace( "\r\n", "<br>\r\n" );         }     }     reader.Close(); } private void LoadSkillsInfo( SqlConnection myConnection ) {     string strSql = "select * from SkillsInfo where UserID='" +         Convert.ToString( Request.QueryString["UserID"] )  + "'";     SqlCommand myCommand = new SqlCommand( strSql, myConnection );     SqlDataReader reader = myCommand.ExecuteReader();     if( reader.Read() )     {         if( reader.GetInt32( 2 ) != 0 &&             reader.GetString( 1 ).Length > 0 )         {             Skills.Text =                 "<font color=\"cyan\" size=\"5\">Skills</font><br>" +                 reader.GetString( 1 ).Replace( "\r\n", "<br>\r\n" );         }     }     reader.Close(); } 


ASP. NET Solutions - 24 Case Studies. Best Practices for Developers
ASP. NET Solutions - 24 Case Studies. Best Practices for Developers
ISBN: 321159659
EAN: N/A
Year: 2003
Pages: 175

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net