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 
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(" | "));
//}
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("&","&");// 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);
}
}
}