What’s wrong with this picture

by MikeHogg 20. November 2006 19:01

 

Here’s one of my first production web apps, a small Survey app that I designed and wrote in VB.Net.  This is funny to me, because I forgot that I ever used VB outside of college.  This was against Oracle… .Net 1.1 or 2.0

	Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
		'Put user code to initialize the page here
		Dim myQuery As New ArrayList
		Dim dctValidation As New Regex("^[0-9][0-9]-[JFMASOND][aepuco][nbrylgptvc]-[0-9][0-9]$")
		Dim txtValidation As New Regex("^[-a-zA-Z0-9 !@?\'"".,]+$")
		If Not IsPostBack Then
			GetInstructors()
			GetCourses()
			txtDCT.Text = DateTime.Now.ToString("dd-MMM-yy")
			GetQuestions("S", scaleRepeater)			' Scale Questions
			GetQuestions("C", commentRepeater)			' Comment Questions
		Else		'posting an Eval
			'Validation
			If Request.Form("txtFN").Length > 50 Or Request.Form("txtLN").Length > 50 _
			 Or Request.Form("ddlInstructor") <= 0 Or Request.Form("ddlCourse") <= 0 _
			 Or Request.Form("ddlInstructor") > 9999999 Or Request.Form("ddlCourse") > 9999999 _
			 Or Not dctValidation.IsMatch(Request.Form("txtDCT")) Or Not txtValidation.IsMatch(Request.Form("txtFN")) _
			 Or Not txtValidation.IsMatch(Request.Form("txtLN")) Then
				lblMisc.Text = "We have a problem here, missing some required information.  <br>" & _
				 "Enable javascript for details and use your browser's back button to return to the Evaluation."
			Else			 ' valid Eval, post transaction to db
				myQuery.Add("insert into surveys (SURVEY_ID, STUDENT_LAST_NAME, STUDENT_FIRST_NAME, COURSE_DATE," & _
				  " INSTRUCTOR_ID, COURSE_ID, UPDATE_USERID, UPDATE_DATE) values(SURVEY_ID_SEQ.NEXTVAL, '" & _
				  Request.Form("txtLN").Replace("'", "''") & "', '" & _
				  Request.Form("txtFN").Replace("'", "''") & "','" & Request.Form("txtDCT") & "','" & _
				  Request.Form("ddlInstructor") & "','" & Request.Form("ddlCourse") & "', " & _
				  "'WebUser', SYSDATE)")
				'POST method relies on Request.forms key names to put query together- 
				'  all number keys have types as values, 
				'  the Qx keys have the answers as values.
				Dim field As String
				Dim myQ As Regex
				For Each field In Request.Form.AllKeys
					If myQ.IsMatch(field, "[1-9][0-9]?") Then					' (one or two digit number)
						If Request.Form(field) = "S" Then
							myQuery.Add("insert into SURVEY_SCALED_ANSWER (SCALE_ANSWER_ID, SURVEY_ID," & _
							   "QUESTION_ID, SCALE_VALUE, UPDATE_USERID, UPDATE_DATE) values (" & _
							   "SCALED_ANSWER_ID_SEQ.NEXTVAL, SURVEY_ID_SEQ.CURRVAL, " & field & _
							   ", '" & Request.Form("Q" & field) & "', 'WebUser', SYSDATE)")
						ElseIf Request.Form(field) = "C" And Not Request.Form("Q" + field) = "" Then
							myQuery.Add("insert into SURVEY_COMMENTS (COMMENT_ID, SURVEY_ID," & _
							 "QUESTION_ID, COMMENT_DESC, UPDATE_USERID, UPDATE_DATE) values (" & _
							 "COMMENTS_ID_SEQ.NEXTVAL, SURVEY_ID_SEQ.CURRVAL, " & field & _
							 ", '" & Request.Form("Q" & field).Replace("'", "''") & "', 'WebUser', SYSDATE)")
						End If
					End If
				Next
				If UpdateTables(myQuery) Then				' transaction processed
					Response.Write("Your evaluation has been recorded.  <p>Your feedback is important, so that OIT can continue to provide <br>Baltimore County Employees with the best possible training. </p> <p>Thank you." & _
				 "</p><p><input type='button' id='btnEnd' onclick='javascript:window.location.href=""/TE""'" & _
				 " value='Fill in another Evaluation' /></p>")
					pnlForm.Visible = False
				End If
			End If
		End If
	End Sub

 

I was really proud of writing this dynamic questions Query array processing routine, instead of hardcoding 20 or 30 inserts, or however many questions were on the survey, but one glaring redball stares at me when I review this code- SQL injection.  Thankfully, this was an intranet web app. 

I was also surprised to read my documentation, now years later, and see how superior it is to my current documentation Smile

TrainEval Application Notes 
** For production implementation:
** 1. zip the entire TE directory
** 2. remove the two (2) web.config files from the zip archive
** 3. remove the TE/Admin/FormsAuth/Users.xml file from the zip archive
** 4. unzip to intranetprod/wwwroot
** 5. enjoy!
audience: developers, troubleshooters
purpose: describe the app in summary, pointing out the main functions and their locations
_______________
TE/default.aspx
---------------
...provides the actual evaluation that users fill out.
custom javascript validation (regex) and server-side validation (regex).
 The client side custom js validation is rather cumbersome, and although it was written with
 multiple browsers in mind and the possibility of different questions or numbers of questions, it 
 should be pretty robust if Evals change at all.
 
a couple other javascript features are included at the bottom of the Eval.aspx html.
uses js.calendar (needs 4 files in directory, see <script> includes in html page)- 
Gets questions from Oracle DB dynamically.  In order to change questions, add or remove, 
 all that needs to be done is to edit the database.  The app will display only questions 
 and text that are active (Active Flag), and display them according to their type- S or C.
 
___________________
TE/Admin/Admin.aspx
-------------------
....provides Edit and Reporting functionality for somebody and one assistant.
uses js.calendar as above.
uses graphics/ directory for button images.
stores Crystal Reports in crystal directory but doesn't use these copies.
uses FormsAuth directory (need to remove read permissions from all other users when implementing)
four sections: 
  Instructors  	(updateable repeater)
  Courses	(updateable repeater)
  Edit Evals	(search form and meat of the program- see below)
  Reports	(search form and two links to Crystal reports)
  
The Instructors and Courses are simple repeaters, allowing changes to Active status and 
 updates to names.  New items can be added at the bottom of each repeater (footer).  I added a filter
 system of alphabet buttons to the Courses page to make it quicker (big improvement) and more user friendly.
 
Reports uses Crystal Enterprise.  This is straightforward.  Couldn't implement the 'Most Recent # Evals' search 
 parameter with Crystal, though.
The Edit Evals section uses httprequest object (AJAX) to display individual results (each eval) without posting back
 each time you page through them.  All Evals are returned and held in Dataset on server when queried, then one at a time is 
 sent to the client as you page through.  This got kind of tricky with stuff like keeping the page count 
 and returning to the same page after an update.  Page count is held in lblMisc... display status (toggle up/down) is 
 held in cookie... and all features in regular Eval are available- validation, Overall Autocalculate,
 and each Eval can be resubmitted (Submit Changes).  One main difference between this page and Evals page is that this 
 page also includes all Instructors and Courses, not just Active ones.  
  
_____________________
Diffs of Dev and Prod
---------------------
 
 To promote to production only the webconfig needs to be changed.

 

I had some neat features in this project.  Not only was it an application for internal members who had taken a course to fill out a survey on an instructor, but it also included the Administrator back end to select subsets of data based on instructor names or courses and then page through the results one by one or view a Crystal Report in the browser.  Here I was constructing controls dynamically, some with fancy mouseovers, another a filter of all the letters of the alphabet, that the Admin could click on to view only surveys on instructors with names ending with that letter.  The Admin Area was written in c#:

        // Setting controls
        private void setButtonsAndPanels(ImageButton btnSelected)
        {
            ImageButton[] ibCollection = { btnInstructors, btnCourses, btnEditEvals, btnReports };
            foreach (ImageButton myButton in ibCollection) {
                if (myButton != btnSelected) {
                    myButton.Attributes.Add("onmouseover","this.src='graphics/btnInv" + myButton.ID.Remove(0,3) + ".gif';");
                    myButton.Attributes.Add("onmouseout","this.src='graphics/btn" + myButton.ID.Remove(0,3) + ".gif';");
                    myButton.ImageUrl="graphics/btn" + myButton.ID.Remove(0,3) + ".gif";
                }
                else {
                    myButton.Attributes.Remove("onmouseover");
                    myButton.Attributes.Remove("onmouseout");
                    myButton.ImageUrl="graphics/btnSel" + myButton.ID.Remove(0,3) + ".gif";
                }
            }
            
            Panel[] pCollection = { pnlCourses, pnlInstructors, pnlEditEvals, pnlReports, pnlSearch, pnlResults};
            foreach (Panel myPanel in pCollection) {
                if (myPanel.ID.Remove(0,3) != btnSelected.ID.Remove(0,3)){
                    myPanel.Visible = false;
                }
                else myPanel.Visible = true;
            }
        }
        private void loadAlphaButtons() {
            LinkButton myButton;
            int counter;
            char letter;
            try {
                pnlAlpha.Controls.AddAt(0,new LiteralControl("Filter: "));
                for (counter = 0;counter <=25;counter++) {
                    letter = (char) (counter+65);
                    myButton = new LinkButton();
                    pnlAlpha.Controls.AddAt( (counter * 2) + 1, myButton);
                    myButton.ID = letter.ToString();
                    myButton.Text = letter.ToString();
                    myButton.ForeColor = Color.Blue;
                    
                    //if (counter < 25) {
                    pnlAlpha.Controls.AddAt(pnlAlpha.Controls.IndexOf(myButton) + 1, new LiteralControl("&nbsp;|&nbsp;"));
                    //}
                    myButton.Click += new System.EventHandler(this.btnAlpha_Click);
                }
                LinkButton btnClear = new LinkButton();
                btnClear.Text = "Clear";
                btnClear.Click += new System.EventHandler(this.btnClear_Click);
                pnlAlpha.Controls.AddAt(53, btnClear);
            }
            catch (Exception ex) {
                throwEx(ex.ToString(), ex.Source.ToString());
            }
        }

This was also the beginnings of my personal library functions.  I believe Logging to be a standard feature, and a developer needs of course the boilerplate Data Access library, as well as some standard exception handling techniques.  All of which grew over time, but these were my first techniques…

 {
            DataSet myDS = new DataSet();
            try {
                OracleDataAdapter oraAdp = new OracleDataAdapter(myQuery, oraConn);
                oraAdp.Fill(myDS);
                myRepeater.DataSource=myDS.Tables[0];
                myRepeater.DataBind();
            } 
            catch (Exception ex){
                if (oraConn.State == ConnectionState.Open){ 
                    oraConn.Close();
                }
                throwEx(ex.ToString(), ex.Source.ToString());
            }  
            finally 
            {
                if (oraConn.State == ConnectionState.Open)
                { 
                    oraConn.Close();
                }
            }          
        }
        // Helpers
        private bool updateTables( ArrayList pQuery )
        {
            try {
                OracleCommand oraCmd = new OracleCommand();
                try {
                    oraConn.Open();
                } catch (Exception ex){
                    writeLog(ex.ToString(), ex.Source.ToString());
                    oraConn.Close();
                    oraConn.Open();
                }
                    
                OracleTransaction tran = oraConn.BeginTransaction();
                oraCmd.Connection = oraConn;
                oraCmd.Transaction = tran;   // necessary for MS OracleClient
                try {
                    foreach (string myQ in pQuery){
                        oraCmd.CommandText = myQ;
                        if (myQ != null){
                            oraCmd.ExecuteNonQuery();
                        }
                    }               
                tran.Commit();
                return true;
                }
                catch (Exception ex) {
                    tran.Rollback();
                    throwEx(ex.ToString(), ex.Source.ToString());
                    return false;
                }
            }
            catch (Exception ex){
                throwEx(ex.ToString(), ex.Source.ToString());
                return false;
            }
            finally  {
                if (oraConn.State == ConnectionState.Open) { oraConn.Close();}
            } 
        }
    
        private void throwEx( string errorMess, string sender ) 
        {
            hidePanels(this);
            lblMisc.Text = "<br>We're sorry, the operation you have attempted was not successful.  Use your browser's" +
            " Back Button and try again and if you are still not successful, then call the Help Desk at 8200.";
            lblMisc.Visible = true;
            writeLog(errorMess, sender);
        }
        private void writeLog( string exMsg, string sender ) 
        {
            DateTime time = DateTime.Now;
            FileStream fs = new FileStream(Server.MapPath("logs/errlog.txt"), FileMode.OpenOrCreate, FileAccess.Write);
            StreamWriter s = new StreamWriter(fs);
            s.BaseStream.Seek(0, SeekOrigin.End);
            s.WriteLine(time.ToString() + ":" + sender + ":" + exMsg);
            s.Close();
        }

I also didn’t learn about JSON until later, but still got by using XML to pass data and messages back and forth from server to a web page to change DOM elements on the fly.  Here a server method…

        //      xmlhttprequest interaction 
        private void getResultRow(int pageNum){
            try {
                DataTable dt = new DataTable();
                dt = (DataTable)Cache["myX" + Session.SessionID];
                DataRow dr = dt.Rows[pageNum];
                
                StringBuilder xResult = new StringBuilder("<Survey>");
                foreach ( DataColumn c in dt.Columns ) {
                    string colName = c.ColumnName;
                    string colVal = dr[colName].ToString().Replace("&","&amp;");   // xml issue with &s
                    xResult.Append("<" + colName + ">" + colVal + "</" + colName + ">");
                }
                xResult.Append("</Survey>");
                
                Response.ContentType = "text/xml";
                Response.Write(xResult.ToString());
                Response.End();
            }
            catch (Exception ex){
                if ( ex.GetBaseException().GetType().Name == "ThreadAbortException" ) {
                    return;
                }
                throwEx(ex.ToString(), ex.Source.ToString());
            }
        }

The javascript was fun to write.  I did a lot of DOM manipulation:

var myPage, pageTotal;
function getPage(pageNum) {
    //alert('getPage');  //debug
    // NOTE: pageNum/myPage comes from pagebuttons already inc/decremented.  
    // this function is also called by server in btnUpdate_click where pageNum is lost so...
    myPage = pageNum;  
    pageTotal = document.getElementById('lblPageTotal').innerHTML;
    
    if (pageNum >= 0) {
        if (pageNum < pageTotal) {
            document.getElementById('lblPageCount').innerHTML = (pageNum + 1);
            LoadXMLDoc("Admin.aspx?Page=" + pageNum);   
        } else {alert('Page ' + pageNum + ': No next page');myPage--;} //set increment back
    } else {alert('Page 1:Cannot go back'); myPage++;}  // set decrement back
}
var reqXML;
function LoadXMLDoc(url){ 
  //alert(url);       //debug
  if (window.XMLHttpRequest){ //Mozilla, Firefox, Opera 8.01, Safari, and now IE7?
    reqXML = new XMLHttpRequest(); 
    reqXML.onreadystatechange = BuildXMLResults; 
    reqXML.open("POST", url, true); 
    reqXML.send(null); 
  }
  else if(window.ActiveXObject){ //IE
    reqXML = new ActiveXObject("Microsoft.XMLHTTP"); 
    if (reqXML) { 
      reqXML.onreadystatechange = BuildXMLResults; 
      reqXML.open("POST", url, true); 
      reqXML.send(); 
    } 
  }
  else{ //Older Browsers
    alert("Your Browser does not support Ajax!");
  }
  blinkProgress();
} 
var tid
function blinkProgress() {
    document.getElementById('inProgress').style.left = (document.body.clientWidth - 200) / 2;
    //alert('blink');  //debug
    if (document.getElementById('inProgress').style.display=="none") {
        document.getElementById('inProgress').style.display="";
    } else document.getElementById('inProgress').style.display="none";
    tid = setTimeout('blinkProgress()', 500);
}
function BuildXMLResults(){
  if(reqXML.readyState == 4){ //completed state
    clearTimeout(tid);
    document.getElementById('inProgress').style.display="none";
    if(reqXML.status == 200){ //We got a sucess page back
      if(reqXML.responseText.indexOf("ID") >= 0){   //dummy test
        //window.status = reqXML.responseXML; //display the message in the status bar
        //alert('Success: \n' + reqXML.responseText);   //debug
        setData(reqXML.responseXML.documentElement);
      }
      else{
        //Something's not right
        //alert('XML:\n' + reqXML.responseXML + 'Text:\n' + reqXML.responseText);   //debug 
        alert("There was a problem retrieving the XML data:\n" + reqXML.statusText);
      }
    } 
    else{
      //display server code not be accessed
      //alert('readyState: ' + reqXML.readyState + '\nstatus: ' + reqXML.status + 'responseText: ' + reqXML.responseText);   //debug
      alert("There was a problem retrieving the XML data:\n" + reqXML.statusText);
    }		
  }
}
//----- fills in Eval with answers from XML response, uses the three functions following this one
function setData(rX){
    clearData();
    
    if(rX == null) {
        alert('An error has occured, setData did not receive any data');
        return;
    }
    for(var c=0;c<rX.childNodes.length;c++){
        var myElement = rX.childNodes[c].nodeName;
        if (rX.childNodes[c].textContent){
            var myText = rX.childNodes[c].textContent;  // for diff DOMS
        }else var myText = rX.childNodes[c].text;       // for diff DOMS
        switch(myElement) {
            case "SURVEY_ID" : document.getElementById('txtIDresults').value = myText;break;
            case "FIRST_NAME" : document.getElementById('txtFNresults').value=myText;break;
            case "LAST_NAME" : document.getElementById('txtLNresults').value=myText;break;
            case "DATE_COURSE_TAKEN" : document.getElementById('txtDCTresults').value=myText;break;
            case "INSTRUCTOR" : setDDL("ddlIresults",myText);break; 
            case "COURSE" : setDDL("ddlCresults",myText);break;
            default: if (myElement.indexOf("Q")==0) {
                setQ(myElement, myText);
                }
        }
    }
    // set any reds from a previous validation to black again
    var aTD = document.getElementsByTagName('TD');    
    var cellNum = 0;
    while ( aTD[cellNum] ) {
        if ( aTD[cellNum].style && aTD[cellNum].style.color && aTD[cellNum].style.color == 'red' ) {
            aTD[cellNum].style.color = 'black';
        }
        var cellChild = 0;
        while ( aTD[cellNum].childNodes[cellChild] ){
            var obj = aTD[cellNum].childNodes[cellChild];
            if ( obj.style && obj.style.color && obj.style.color == 'red' ) {
                obj.style.color = 'black';
            }
            cellChild++;
        }
        cellNum++;
    }
}
//----- clears any values left from previous Eval
function clearData() {
  for (c=0; c < document.forms[0].length; c++) {
    ele = document.forms[0].elements[c];
    if ( ele.getAttribute('type') == 'text' && ele.id.indexOf('search') < 0 ) {
        ele.value = '';
    }
    else if ( ele.getAttribute('type') == 'radio' ) {
        ele.checked = false;
    }
    else if ( ele.tagName == 'SELECT' && ele.id.indexOf('search') < 0 ) {
        ele.selectedIndex = 0;
    }
  } 
}
//----- makes Course and Instructor Drop Down List selections
function setDDL(aDDLName, sText){
    //alert(sText);   //debug
    var aDDL = document.getElementById(aDDLName);
    //alert('len = ' + aDDL.length + '\nsText = ' + aDDL.options[2].text);   //debug
    for(var c = 0;c < aDDL.length;c++){
        if (sText == aDDL.options[c].value){
            aDDL.selectedIndex = c;
        }
    }
}
//----- fills in scale and comment answers
function setQ(qNum, answer){
    var qEle = document.getElementsByName(qNum);
    if (qEle[4]){    // radio question
        if (answer < 6 && answer > 0) {  // test valid answer
            for(var c = 0;c < 5;c++){
                if (qEle[c].value == answer){
                    qEle[c].checked = true;
                }
            }
        }
    } else {qEle[0].value = answer;}  //Comment Question
}

 

I also did a fair amount of animation…

// the search Results page slides up or down
function toggleDisplay()  {
    //alert('display');  //debug
    // match gets an array, the second value ([1]) is what we are looking for- the value of display
    var cV = document.cookie.match ( 'display=(.*?)(;|$)' );
    if ( (cV) && cV[1] == "down" )  {
        document.cookie = "display=up";
        move("up");
        document.getElementById('ddlCsearch').style.display = ''; 
        document.getElementById('lbIsearch').style.display = ''; 
        document.getElementById('tblSearch').style.width = '800px';
    } else {
        document.cookie = "display=down";
        move("down");
        document.getElementById('ddlCsearch').style.display = 'none';  //ie6
        document.getElementById('lbIsearch').style.display = 'none';   //ie6 
        document.getElementById('tblSearch').style.width = '300px';
    }
}
function move(direction){
    //alert('#2:  move' +  document.getElementById('pnlResults').style.top);  //debug
    var currTop = document.getElementById('pnlResults').style.top.substr(0, document.getElementById('pnlResults').style.top.length - 2);
    if (direction == 'down') {
        if (currTop <= 30) {
            return;
        }else {
            document.getElementById('pnlResults').style.top = currTop - 30;
            setTimeout('move("down")',20);
        }
    }else {
        if (currTop >= 340) {
            return;
        }else {
            document.getElementById('pnlResults').style.top = eval(currTop) + 30;
            setTimeout('move("up")',20);
        }
    }
}

Tags:

VB.Net | Javascript | C# | ASP.Net

About Mike Hogg

Mike Hogg is a c# developer in Brooklyn.

More Here

Favorite Books

This book had the most influence on my coding style. It drastically changed the way I write code and turned me on to test driven development even if I don't always use it. It made me write clearer, functional-style code using more principles such as DRY, encapsulation, single responsibility, and more. amazon.com

This book opened my eyes to a methodical and systematic approach to upgrading legacy codebases step by step. Incrementally transforming code blocks into testable code before making improvements. amazon.com

More Here