Click here to Skip to main content
15,881,882 members
Articles / Web Development / ASP.NET
Article

SWAT - A simple Web-based Anomalies Tracker - Part 2

Rate me:
Please Sign up or sign in to vote.
4.74/5 (29 votes)
17 Jun 2003CPOL8 min read 245K   3K   66   46
An account of my experience in learning to develop in the .NET environment.

Fig.1 Swat Logon Page

SWAT Part 2

In part 1 I described an application that I devised as a learning project to help me get experience developing for the .NET environment, specifically ASP.NET. The application is a WEB based bug tracking tool. In the previous article I described the requirements for the application and defined the database schema to be used by the application. With that behind us we can dive right into the design of the application. Again, as I mentioned in the first article, this depicts my experience and as such the implementation details presented may not always be the most optimal solution. But it works, so enjoy.

UserAuthentication and the Logon Page

Unlike desktop apps where the operating system provides a nice protected environment, web apps must fend for themselves in the hostile world of the internet. It’s like leaving the front door to your house open for anybody in the world to come in. I don’t think we’d be too comfortable with that. So how do we allow our friends and family access to our house yet prevent all others from trashing the place?

ASP.NET provides several ‘out of the box’ solutions to this problem. We’ll implement the application using Forms based Authentication. Basically we’ll create a logon page for the application that will allow the user to enter some credentials which will be used to authenticate the user. In processing the response to the logon we’ll create a token (cookie) that will be used to identify the user throughout the rest of the session. Any subsequent application pages accessed by the user is first checked against the token before allowing access. If the token is not available or not valid the user is automatically directed back to the logon page.

ASP.NET provides some additional built-in mechanisms to automatically validate users (by embedding the information in the web.config file or storing in an XML file). We won’t be using those facilities. Instead we will be validating the users’ credentials against information stored in the database.

If the client does not allow cookies then we’re hosed (at least with the out of the box version). This is also not the most secure mechanism if we’re concerned about someone getting a hold of the cookie in transit and impersonating the user. Since we’re not protecting Ft. Knox the ‘out of the box’ solution is fine.

Most WEB applications also consist of more than just one page. So part B of the questions is, how do we allow our family access to our bedroom but restrict our friends to only the living area of the house? Unfortunately, ASP.NET does not provide a clean solution to this problem (without putting some restrictions on the client), so we’ll control it in our code. Basically what we’ll do is assign another token to the user that will indicate which part of the house s/he can enter. This is essentially ‘roles’ based access that we’ll implement in the application’s code.

Note: If you want to get a feel for the development environment, follow along with the text. However, if you'd prefer to bypass the typing you can simply download the complete source code and compile.

So let’s get going. Create a new ASP.Net WEB application and call it Swat. Change the name of the default WebForm1 to SwatLogon. Drag two TextBox controls to the page, one for the user's name and the other for a password. In the password's TextBox property pane set the 'TextMode' property to 'Password'. Add labels and a connect button as shown in Fig.1. Set the control ids as follows: 'txtUserName','txtPassword',and 'btnConnect'. I implemented the page using a table simply for ‘neatness’, they say that counts. By the way, the bumble bee is optional.

Double-click on the connect button to create an event handler for the button click. Revise the event handler as shown below.
private void btnConnect_Click(object sender, System.EventArgs e)
{
    SqlConnection cnn;
    SqlDataReader dr;
    string strRedirect = "";
    int nUserID = 0;
    int nRoles = 0;
    //Empty database check. If there are no users defined it
    //means it's a new installation.
    //We allow 'Admin' as the user only if the database is empty
    string ConnectionString
                       = "user id=ASPNET;password=;initial catalog=swatbugs;"
                         "data source=localhost;Integrated Security=false;"
                         "connect timeout=30;";
    cnn = new SqlConnection(ConnectionString);
    cnn.Open();
    SqlCommand cmd = cnn.CreateCommand();
    if (txtUserName.Text == "admin")
    {
        //Check to see if the db is empty
        cmd.CommandText = "SELECT * FROM users";
        dr = cmd.ExecuteReader();
        if(dr.Read() == false)
        {
            nUserID = 0;    //It doesn't matter only admin page
                    //will be available
            nRoles = (int)AccessPrivilege.Administrator;
            strRedirect = "SwatMain.aspx";
        }
        dr.Close();
    }
    if (strRedirect.Length == 0)
    {
        cmd.CommandText
                 = "SELECT id, roles FROM users WHERE itemname=@username " +
                   "AND password=@password";
        // Fill our parameters
        cmd.Parameters.Add("@username", 
                           SqlDbType.NVarChar, 64).Value = txtUserName.Text;
        cmd.Parameters.Add("@password", 
                           SqlDbType.NVarChar, 128).Value = txtPassword.Text;
        dr = cmd.ExecuteReader();
        if(dr.Read())
        {
            nUserID = (int)dr["id"];
            if (dr["roles"] != System.DBNull.Value)
            {
                nRoles = System.Convert.ToInt16(dr["roles"]);
                strRedirect = "SwatMain.aspx";
            }
        }
    }            
    cnn.Close();
    if (strRedirect.Length != 0)
    {
        FormsAuthenticationTicket tkt = new FormsAuthenticationTicket(
                    1, //Ticket version
                    txtUserName.Text, //User name associated with ticket
                    DateTime.Now,    //When ticket was issued
                    DateTime.Now.AddMinutes(30),    //When ticket expires
                    true,    //A persistent ticket
                    nRoles.ToString(),    //The user's role
                    FormsAuthentication.FormsCookiePath); 
                                                    //Path cookie valid for
        //Hash the cookie
        string hash = FormsAuthentication.Encrypt(tkt);
        HttpCookie ck = new HttpCookie(FormsAuthentication.FormsCookieName, 
                                       hash);
        //Add cookie to the response
        Response.Cookies.Add(ck);
        Response.Cookies["UserID"].Value = nUserID.ToString();
        Response.Cookies["UserID"].Expires = DateTime.MaxValue;
        Response.Cookies["Roles"].Value = nRoles.ToString();
        Response.Cookies["Roles"].Expires = DateTime.MaxValue;
        Response.Redirect(strRedirect, true);
    }
    else
    {
        lblError.Text = "Invalid logon credentials";
    }
}

The egg is the answer

If we are validating the user based on information stored in the database how can a user logon the first time (to add users to the database)? We could provide a default user/pwd in the database but the user must make sure to at least change the password or it won’t be too secure. I chose a different approach. I hard-coded an ‘admin’ user and allow access only initially when the database is empty (no users). Once a user is added to the database then the ‘admin’ user is no longer allowed access. Unless of course it exists in the database with a valid password.

The first part of the code in the event handler above deals with checking to see if it’s the first time. If the user's name is 'admin' we check to see that the database is empty. If the database is empty then the user is re-directed to the application's main page. If not then the user’s name and password are checked to see if they exist in the database. If the user’s credentials match then his/her role and ID are stored in a cookie and persisted on the client’s machine and we send the user to SWAT’s main page. Otherwise we display a message indicating to the user that the credentials could not be verified.

Since we're using some external SQL and security classes we need to add the following declarations to the top of the SwatLogon code source.

...
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Web.Security;

There are three roles that are supported by the application. The roles determine what parts of the application the user is allowed to access. Add the following enum just before the SwatLogon class definition.

C#
...
public enum AccessPrivilege
{
    Developer = 1,
    Administrator = 2,
    Manager = 4
}
...

To complete the forms authentication mechanism we need to make the following changes to Web.Config in the 'AUTHENTICATION' section.

XML
...
<authentication mode="Forms"> 
    <forms name="SWAT.ASPXFORMSAUTH" loginUrl="SwatLogon.aspx" 
           protection="All">
    </forms>
</authentication>
<authorization>
    <deny users="?"/>
</authorization>
...

You’ll note that the application does not currently encrypt the user’s password. An enhancement to SWAT would be to encrypt the user’s password (note the database already provides space to hold an encrypted password). Along with this, perhaps a page that allows the user to change their password and also their start page. See how easy feature creep is?

SWAT’s Main Page

One of the design goals I have for the application is that the UI needs to be similar to what the user is accustomed to with a traditional desktop app. To me (since I’m the user) that means having a title bar, a toolbar, and a main work area and not seeing the effects of navigation (the window changing every time a link is selected). To accomplish this I implemented the main page of the application using frames. This allowed me to change the contents of the main work area while not affecting what’s displayed in the rest of the window.

Add a new WEB Form to the project and name it SwatMain. Replace the BODY tag in the page source with the following:

HTML
<frameset id="SwatMain" rows="40, 32, *">
    <frame name="TitleBar" src="Titlebar.aspx" frameBorder="0" noResize 
           scrolling="no">
    <frame name="ToolBar" src="Toolbar.aspx" frameBorder="0" noResize 
           scrolling="no">
    <frame name="MainFrame" borderColor="#cccccc" src="<%= MainFramePage %>" 
           frameBorder="yes" noResize>
</frameset>

The source for the title bar and the toolbar is always known so they are hardcoded on the page. You’ll notice however, that the source for the bottom frame is a variable reference since it will be determined dynamically and is dependent on the user’s selection. Revise the SwatMain class as follows.

C#
...        
public string MainFramePage;
private void Page_Load(object sender, System.EventArgs e)
{
   //This is only necessary the first time through when the DB is empty
   //otherwise everybody goes to the bug editing page, for now.
   int nRole = System.Convert.ToInt16(Request.Cookies["Roles"].Value);
   if (nRole == (int)AccessPrivilege.Administrator)
      MainFramePage = "SwatAdmin.aspx";
   else
   {
      if (nRole == (int)AccessPrivilege.Manager)
         MainFramePage = "SwatAnalyze.aspx";
      else
      {
         if (nRole == 0) //Not good, back to logon
            Response.Redirect("SwatLogon.aspx",true);
         else
         {
            //At some point we'll probably allow the user to
            //define his/her start page and go directly there.
            //For now everybody goes to bugs
            MainFramePage = "SwatBugs.aspx";
         }
      }
   }
}

Add a WEB page for the toolbar and the titlebar and name them as indicated above. The titlebar is just a simple two column table that holds a bitmap and the applications title. Here’s the code:

HTML
<body bgColor="#10e7d0" leftMargin="0" topMargin="0">
   <form id="Titlebar" method="post" runat="server">
      <table width="100%">
         <tr>
            <td><IMG src="Images/swat.gif" align="left">
            </td>
            <td width="100%"><span style="FONT-WEIGHT: bold; 
     FONT-SIZE: 20pt; COLOR: #306230; FONT-FAMILY: Trebuchet MS">SWAT</span>
               <span style="FONT-WEIGHT: bold; FONT-SIZE: 12pt; 
     COLOR: #306230; FONT-FAMILY: Trebuchet MS">
                  Simple Web-based Anamolies Tracker</span>
            </td>
         </tr>
      </table>
   </form>
</body>

The toolbar is a little more interesting even though it’s also implemented as a table. The toolbar (you say navigation bar I say toolbar) provides the user with the same functionality and feedback as a toolbar on a desktop application. As the user moves the mouse over an item the button changes appearance. When the user selects a toolbar item the main frame will change accordingly. You'll note that the user's role is coded into the button processing code. If the user does not have access to the functionality associated with a particular button the button’s appearance does not change indicating that option is not available to the user and the user cannot select it. A point of interest is to note that all of the code required to implement the toolbar’s functionality is on the client. Place the following script code in the HEAD tag for page.

JavaScript
<SCRIPT LANGUAGE="JavaScript">
function LoadImage(src) 
{
   if (document.images) 
   {
      imgButton = new Image();
      imgButton.src = src;
      return imgButton;
   }
}
         
function ChangeImages() 
{
   if (document.images && (boolImagesLoaded == true)) 
   {
      for (var i=0; i<ChangeImages.arguments.length; i+=2) 
      {
         document[ChangeImages.arguments[i]].src = ChangeImages.arguments[i+1];
      }
   }
}
         
var boolImagesLoaded = false;
function PreloadImages() 
{
   if (document.images) 
   {
      TbHomeImage = LoadImage("Toolbar_Home_hover.gif");
      TbBugsImage = LoadImage("Toolbar_Bugs_hover.gif");
      TbAnalyzeImage = LoadImage("Toolbar_Analyze_hover.gif");
      TbReportImage = LoadImage("Toolbar_Report_hover.gif");
      TbAdminImage = LoadImage("Toolbar_Admin_hover.gif");
      TbSystemImage = LoadImage("Toolbar_System_hover.gif");
      TbHelpImage = LoadImage("Toolbar_Help_hover.gif");
      TbLogoutImage = LoadImage("Toolbar_Logout_hover.gif");
                    
      boolImagesLoaded = true;
   }
}
         
PreloadImages();
</SCRIPT>

The script simply takes care of caching the images for the toolbar buttons on the client and providing an access function for the images.

The table code is also pretty much straightforward but a little 'crammed-in'. All of the functionality is defined within the table code, changing the toolbar image when the mouse is over the button, what to do on a mouse click, etc. For some of the buttons the UserRoles variable is used to determine if the user has access to that functionality. The code simply cancels the event if the user cannot perform the function. An enhancement might be to pop-up a dialog indicating to the user that functionality is not available for the specified role. Add the following to the FORM tag for the toolbar.

HTML
<table cellpadding="0" cellspacing="0">
   <tr>
      <td>   </td>
      <td>
         <a ONMOUSEOVER="changeImages('TbHomeImage', 'Toolbar_Home_hover.gif');
           return true; " 
           ONMOUSEOUT="changeImages('TbHomeImage', 'Toolbar_Home.gif'); 
           return true; ">
         <img src="Toolbar_Home.gif" alt="Home" border="0" name="TbHomeImage" 
              height="32" width="32"></a>
      </td>
      <td>    </td>
      <td>
         <a href="SwatBugs.aspx" target="MainFrame"  
            onclick="if (1 & <%=UserRole%>) return true; else return false;" 
            ONMOUSEOVER="if (1 & <%=UserRole%>) changeImages('TbBugsImage', 
           'Toolbar_Bugs_hover.gif'); else window.event.cancelBubble = true; 
            return true; " 
           ONMOUSEOUT="changeImages('TbBugsImage', 'Toolbar_Bugs.gif'); 
           return true; ">
            <img src="Toolbar_Bugs.gif" alt="Bugs" border="0" 
                 name="TbBugsImage" height="32"  width="32"></a>
      </td>
      <td>  </td>
      <td>
         <a href="SwatAnalyze.aspx" target="MainFrame" 
            onclick="if (4 & <%=UserRole%>) return true; else return false;" 
            ONMOUSEOVER="if (4 & <%=UserRole%>) changeImages('TbAnalyzeImage', 
                        'Toolbar_Analyze_hover.gif');
            else window.event.cancelBubble = true; return true; " 
            ONMOUSEOUT="changeImages('TbAnalyzeImage', 'Toolbar_Analyze.gif'); 
                        return true; ">
            <img src="Toolbar_Analyze.gif" alt="Analyze" border="0" 
                 name="TbAnalyzeImage" height="32" width="32"></a>
      </td>
      <td>  </td>
      <td>
         <a href="SwatReport.aspx" target="MainFrame" 
            ONMOUSEOVER="changeImages('TbReportImage', 
                        'Toolbar_Report_hover.gif'); return true; " 
            ONMOUSEOUT="changeImages('TbReportImage', 'Toolbar_Report.gif'); 
                        return true; ">
            <img src="Toolbar_Report.gif" alt="Reports" border="0" 
                 name="TbReportImage" height="32" width="32"></a>
      </td>
      <td>  </td>
      <td>
         <a href="SwatAdmin.aspx" target="MainFrame"
            onclick="if (2 & <%=UserRole%>) return true; else return false;" 
           ONMOUSEOVER="if (2 & <%=UserRole%>) changeImages('TbAdminImage', 
           'Toolbar_Admin_hover.gif');
           else window.event.cancelBubble = true; return true; " 
            ONMOUSEOUT="changeImages('TbAdminImage', 'Toolbar_Admin.gif'); 
            return true; ">
            <img src="Toolbar_Admin.gif" alt="Admin" border="0" 
             name="TbAdminImage" height="32" width="32"></a>
      </td>
      <td style="WIDTH: 3px">    </td>
      <td style="WIDTH: 10px">
         <a onclick="if (2 & <%=UserRole%>)
            {refWindow=window.open('SwatOptions.aspx','referralWindow',
            'width=350,height=400,scrollbars=no,menubar=no,resizable=no');
             refWindow.focus();} return false;" 
            ONMOUSEOVER="if (2 & <%=UserRole%>) changeImages('TbOptionsImage', 
            'Toolbar_System_hover.gif'); return true; " 
            ONMOUSEOUT="changeImages('TbOptionsImage', 'Toolbar_System.gif'); 
            return true; " 
            target="_blank" href="SwatOptions.aspx">
            <img src="Toolbar_System.gif" alt="Swat Options" border="0" 
                  name="TbOptionsImage" height="32" 
           width="32"></a>
      </td>
      <td>    </td>
      <td>
         <a onclick="refWindow=window.open('SwatHelp.htm',
           'referralWindow',
            'width=350,height=520,scrollbars=yes,menubar=no,resizable=yes'); 
            refWindow.focus(); return false;" 
            ONMOUSEOVER="changeImages('TbHelpImage', 'Toolbar_Help_hover.gif'); 
            return true; " ONMOUSEOUT="changeImages('TbHelpImage', 
            'Toolbar_Help.gif'); return true; " target="_blank" 
            href="SwatHelp.aspx">
            <img src="Toolbar_Help.gif" alt="Help" border="0" 
            name="TbHelpImage" height="32" width="32"></a>
      </td>
      <td>    </td>
      <td>
         <a ONMOUSEOVER="changeImages('TbLogoutImage', 
             'Toolbar_Logout_hover.gif'); return true; " 
            ONMOUSEOUT="changeImages('TbLogoutImage', 'Toolbar_Logout.gif');
              return true; ">
            <img src="Toolbar_Logout.gif" alt="Logout" border="0" 
                  name="TbLogoutImage" height="32"
            width="32"></a>
      </td>
   </tr>
</table>

On the server side the only thing that’s required is to initialize the UserRole variable when the page gets loaded. Add the following to the Toolbar class.

C#
public int UserRole;

private void Page_Load(object sender, System.EventArgs e)
{
   UserRole=System.Convert.ToInt16(Request.Cookies["Roles"].Value);
}

After making sure you’ve got the images installed compile and run the application. You’ll be able to login using ‘admin’ as the user. Can’t do much yet but you’ll be able to see the toolbar responding to mouse moves and check for any syntax errors. The error that's displayed is because we don't have anything to put in the main frame. That's what we're going to do next. The next article starts coding Swat's admin page and it's a long one so get ready.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionwhat did i missed? Pin
mordin9-Nov-03 5:28
mordin9-Nov-03 5:28 
AnswerRe: what did i missed? Pin
Al Alberto9-Nov-03 14:41
Al Alberto9-Nov-03 14:41 
AnswerRe: what did i missed? Pin
Serge Desmedt1-Feb-07 0:59
professionalSerge Desmedt1-Feb-07 0:59 
GeneralTrusted Connection vs. Connection using a defined user Pin
Member 58083212-Sep-03 2:50
Member 58083212-Sep-03 2:50 
GeneralRe: Trusted Connection vs. Connection using a defined user Pin
Al Alberto13-Sep-03 7:11
Al Alberto13-Sep-03 7:11 
GeneralRe: Trusted Connection vs. Connection using a defined user Pin
Member 58083215-Sep-03 8:00
Member 58083215-Sep-03 8:00 
GeneralRe: Trusted Connection vs. Connection using a defined user Pin
angus_grant28-Jul-04 19:12
angus_grant28-Jul-04 19:12 
GeneralRe: Trusted Connection vs. Connection using a defined user Pin
Al Alberto29-Jul-04 15:35
Al Alberto29-Jul-04 15:35 
Thanks.
GeneralRe: Trusted Connection vs. Connection using a defined user Pin
Misty_Blue10-Nov-04 9:17
Misty_Blue10-Nov-04 9:17 
GeneralWeb service with FormAuthentication Pin
Smartchoice3-Sep-03 13:59
sussSmartchoice3-Sep-03 13:59 
GeneralDatabase Connection String Pin
sides_dale23-Jun-03 19:03
sides_dale23-Jun-03 19:03 
GeneralRe: Database Connection String Pin
Al Alberto24-Jun-03 5:18
Al Alberto24-Jun-03 5:18 
GeneralRe: Database Connection String Pin
drcasper31-Jul-03 8:05
drcasper31-Jul-03 8:05 
GeneralStored Procedures Pin
sides_dale23-Jun-03 18:47
sides_dale23-Jun-03 18:47 
GeneralRe: Stored Procedures Pin
Al Alberto24-Jun-03 5:12
Al Alberto24-Jun-03 5:12 
GeneralChecking for records in the user-table Pin
AlexKeizer21-Jun-03 3:29
AlexKeizer21-Jun-03 3:29 
GeneralRe: Checking for records in the user-table Pin
Al Alberto21-Jun-03 14:14
Al Alberto21-Jun-03 14:14 
GeneralRe: Checking for records in the user-table Pin
drcasper31-Jul-03 8:44
drcasper31-Jul-03 8:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.