Quartz Scheduler

by MikeHogg26. October 2013 13:46


Quartz Scheduler seems to be a very robust and mature job scheduler originally created in java.  I’ve only just found out about it and find it useful enough to make a note here about it for future use.  You can find lots of tutorials and samples and users online for it.  It is ported to .Net with Quartz.net.  Adding a quartz scheduler to your project is as easy as a Nuget.  Simple scheduling can happen right out of the box.  It can handle complicated cases, and just about all of the scheduling cases that I've seen.  JobDetails, Jobs, and Triggers are all classes you can mix and match up in different combinations.  It also handles persistence if you want to hook up to an ado store (several Sql and NoSql avail), and logging is built in with another Nuget- Common.Logging.

 

If you want to inject services into your IJobs, though, you will need to create your own IJobFactory and start the scheduler that way

Dim schFactory As ISchedulerFactory = _container.Resolve(Of ISchedulerFactory)()
_scheduler = schFactory.GetScheduler()
_scheduler.JobFactory = New ScheduleJobFactory(_container)

Your factory implements NewJob (and with Quartz 2.2, ReturnJob (do nothing unless you have dispose requirements)) and here is where you craete the job with your Container of choice (Autofac here) so the container can inject what it needs...

public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
        {
            var jobtype = bundle.JobDetail.JobType;
 try
            {
                var schjobtype = typeof(MyJob<>).MakeGenericType(jobtype);
                var schjob = (IJob)Activator.CreateInstance(schjobtype, _container);
 return schjob;
            }
 catch (Exception e)
            {
 using (var l = _container.BeginLifetimeScope())
                {
                    var logger = _container.Resolve<ILogger>();
                    logger.Error(e);
                }
            }
 return new NoOpJob();
        }

... Your JobFactory gets the container from it's constructor

public class ScheduleJobFactory : ISchedulerFactory
    {
        ILifetimeScope _container;
public ScheduleJobFactory(ILifetimeScope container)
        {
            _container = container;
        } 
    }

I couldn't have figured this part out:  Your jobfactory points to a jobtype class of T that does the container resolve of T (!) clever pattern.

public class MyJob<T> : IJob where T : IJob
    {
        ILifetimeScope _container;
public MyJob(ILifetimeScope container)
        {
            _container = container;
        }
public void Execute(IJobExecutionContext context)
        {
 using (var lscope = _container.BeginLifetimeScope())
            {
                var someJob = lscope.Resolve<T>();
                someJob.Execute(context);
            }
        }

 

Your Jobs still inherit from IJob and don't reference the generic job... just register them like normal, and specify these specific classes, in your quartz.jobs.xml

 

...

 

Diagnostics- if you have trouble getting your job xml files to work (or are trying to use deprecated 1.0 version xml with upgraded 2.0 quartz) and want to see the logging, you can add Common.Logging config to your app.config.  The section def:

 

<sectionGroup name="common">
<section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" />
</sectionGroup>

The config-

<common>
<logging>
<factoryAdapter type="Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter, Common.Logging">
<arg key="level" value="DEBUG" />
<arg key="showLogName" value="true" />
<arg key="showDataTime" value="true" />
<arg key="dateTimeFormat" value="yyyy/MM/dd HH:mm:ss:fff" />
</factoryAdapter>
</logging>
</common>

 

 

I've seen lots of online users hook up their common.logging to log4net to monitor the scheduler process, and there is a FactoryAdapter for log4net if you want to go that route.

 

While we are in config, note that the quartz looks for config in 3 or 4 places.  First in a java style file quartz.config, then in app.config, then in some other places.  If you want to remove the quartz.config and use just .net style, this is a sample app.config section def-

<section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" />

 

and config-

<quartz>
<add key="quartz.scheduler.instanceName" value="_scheduler" /> <!-- whatfor-->
<!--     Configure Thread Pool -->
<add key="quartz.threadPool.type" value="Quartz.Simpl.SimpleThreadPool, Quartz" />
<add key="quartz.threadPool.threadCount" value="10" />
<add key="quartz.threadPool.threadPriority" value="Normal" />
<!--     Configure Job Store --><!--
    <add key="quartz.jobStore.type" value="Quartz.Simpl.RAMJobStore, Quartz" />
      <add key="quartz.plugin.xml.type" value="Quartz.Plugin.Xml.JobInitializationPlugin, Quartz" />-->
<add key="quartz.plugin.xml.fileNames" value="~/config/ScheduledJobs.config" />
<!--<add key="quartz.plugin.xml.scanInterval" value="10" />-->
<add key="quartz.plugin.xml.type" value="Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz" />
</quartz>

That uses the defaults.  The XmlSchedulingDataProcessorPlugin I think is the included jobs.xml reader and required if you use xml, as the default it something else.

Roll Your Own Sorting in 2012

by MikeHogg17. November 2012 09:51

Had a client request that turned into rolling my own filter/paging/sorting.  I've done this so many times before, but not much in the last five years, as it has become a more or less 'built in' feature in most framework user controls.  I have never kept my own library of code for this, because in each case the specific use was customized and the result was quite spaghetti like and nothing I was proud of.

But this time, I think I generalized it enough, and plus, I was able to use a predicate pattern I've used before and so I wanted to write this up for future reference.

 

Oh, the client codebase is in VB.  My first time using VB since college.  I'd prefer not to use it again either, but at least the .net libraries are the same.

 

Remember from previous efforts the state issues, so careful attention to what steps we do at each specific step throughout page lifecycle...

 

We're using aspnet ListView control, with templates and the aspnet PagerControl, which has an eccentricity where you have to databind after Page_Load, usually in Page_PreRender, so we GetData in PageLoad and save it in a page property without binding yet.  Also in Page_Load we bind all of our filter list controls.

 

Here's the Page_Load's GetData. Notice we actually databind a ListView here but that one is the hidden one.  We use a hidden one that is unpaged for the google map pushpins.

 

 

Public Sub GetData()
            MyCarFeed = New CarFeed(Me.objDP.GetListItems(Me.ModuleID, Me.objCore.Language.CurrentLanguage, "sort").Tables("cars"))
 Dim filtertext As String = Me.txtFilter.Text.ToLower()
            Filtered = MyCarFeed.Cars.Where(Function(c) _
                (String.IsNullOrEmpty(filtertext) OrElse String.Concat( _
                 c.Description, _
                 c.Subtitle, _
                 c.Requirements, _
                 c.CompanyInformation, _
                 c.ContactInfo, _
                 c.Name).IndexOf(filtertext) > -1)). _
            Where(ColorFilter()). _
            Where(SpecialFilter()). _
            Where(AgeFilter()). _
            Where(SizeFilter()). _
            OrderByDescending(Function(c) c.DateFrom)
 ' while we are here
 Me.LvHiddenForMap.DataSource = Filtered
 Me.LvHiddenForMap.DataBind()
End Sub

 

 

in the Page_PreRender we call BindPagedList... As long as I can bind again in prerender the PageControl takes care of paging automatically.

 

 

 
Private Sub BindPagedList()
 If _sorted = False Then
                Sort(ViewState("SortExpression"))
 End If
 Me.lv.DataSource = If(Filtered Is Nothing, Filtered, Filtered.ToArray())
 Me.lv.DataBind()
End Sub
 

 

 

Oh another detail for the finicky DataPager Control, in our case, because we are using it in a template, so we can't add an event to it in markup, we call this method on init:

Protected Sub SetPageSize(ByVal sender As Object, ByVal e As System.EventArgs) ' handles DataPager Init... but it's a templated control so we have to hook it up in html event 
 If (Not Me.CurrentCarsPageSize Is Nothing) Then
 CType(sender, DataPager).PageSize = Me.CurrentCarsPageSize
 End If
End Sub

 

 

So if we are posting back on a sort then we call Sort here.  It's the same Sub we call on the Sorting event:

 

 

 
Protected Sub Sorting(ByVal sender As Object, ByVal e As ListViewSortEventArgs) 
            Sort(e.SortExpression)
End Sub
 

 

 

And the sort keeps a couple vars in viewstate

 

 

Private Sub Sort(ByVal sortexpression As String) 
 If _pageclicked And Not sortexpression Is Nothing Then
 ' just rebind 
 If ViewState("SortOrder") Is Nothing Then
                    Filtered = Filtered.OrderBy(Function(c As Car) GetCarSortProperty(sortexpression, c))
 Else
                    Filtered = Filtered.OrderByDescending(Function(c As Car) GetCarSortProperty(sortexpression, c))
 End If
 Else
 If Not sortexpression Is Nothing Then
 If ViewState("SortExpression") Is Nothing OrElse Not ViewState("SortExpression").Equals(sortexpression) Then
 ' first click  (on this header e.g. new sort)
                        Filtered = Filtered.OrderBy(Function(c As Car) GetCarSortProperty(sortexpression, c))
                        SetArrowImages(sortexpression)
                        ViewState("SortOrder") = Nothing
                        SetFirstPage()
 Else
 If ViewState("SortOrder") Is Nothing Then
 ' second click
                            Filtered = Filtered.OrderByDescending(Function(c As Car) GetCarSortProperty(sortexpression, c))
                            SetArrowImages(sortexpression, False)
                            ViewState("SortOrder") = "Desc"
 Else
 ' third click- reset 
                            sortexpression = Nothing
                            SetArrowImages(sortexpression)
                            ViewState("SortOrder") = Nothing
 End If
 End If
 End If
                ViewState("SortExpression") = sortexpression
                _sorted = True
 End If
End Sub

 

 

The GetCarSortProperty is nothing more than checking which sort (we are lucky here we only have two columns)

 

 

Private Function GetCarSortProperty(ByVal sort As String, ByVal c As Car) As Object
 Return IIf(sort.Equals("Name"), c.GetType().GetProperty("Name").GetValue(c, Nothing), IIf(sort.Equals("Location"), c.GetType().GetProperty("Location").GetValue(c, Nothing), Nothing))
End Function

 

 

And of course the obligatory SetArrowImages

 

 

Private Sub SetArrowImages(ByVal sortexpression As String, Optional ByVal ascending As Boolean = True)
Dim Asc As String = "~/_css/cars/icn_arrow_up.png"
Dim Desc As String = "~/_css/cars/icn_arrow_down.png"
 Dim imgSortName As Image = New Image()
 Dim imgSortLocation As Image = New Image()
 If Not lv.FindControl("imgSortName") Is Nothing Then
                imgSortName = CType(lv.FindControl("imgSortName"), Image)
                imgSortLocation = CType(lv.FindControl("imgSortLocation"), Image)
 End If
 If Not sortexpression Is Nothing Then
                imgSortName.ImageUrl = IIf(sortexpression.Equals("Name"), IIf(ascending, Asc, Desc), Nothing)
                imgSortLocation.ImageUrl = IIf(sortexpression.Equals("Location"), IIf(ascending, Asc, Desc), Nothing)
                imgSortName.Visible = sortexpression.Equals("Name")
                imgSortLocation.Visible = sortexpression.Equals("Location")
 Else '   for third click
                imgSortName.Visible = False
                imgSortLocation.Visible = False
 End If
End Sub

 

 

 

the cool parts are A) that we are generating the filter checkboxes dynamically, and B) the predicates...

Here's one of the categories of filter, called Colors, used in the GetData() at the top of this post

 

 

Where(ColorFilter()). _

 

 

And here's the function

 

 

 
Private Function ColorFilter() As Func(Of Car, Boolean)
 ' replicating PredicateBuilder...
 Dim t As Predicate(Of Car) = Function() True
 Dim f As Predicate(Of Car) = Function() False
 Dim cbl As List(Of CheckBox) = GetCheckBoxList(Me.lvColors)
 For Each cb As CheckBox In cbl.Where(Function(c As CheckBox) c.Checked)
 Dim cbstring As String = cb.Text.ToLower()
                f = PredicateExtensions.Or(f, Function(c As Car) c.ColorList.ToLower().Contains(cbstring))
 Next
            t = If(cbl.Any(Function(c) c.Checked), PredicateExtensions.And(t, f), t)
 Return t.ToFunction()
End Function
Private Function GetCheckBoxList(ByVal lv As ListView) As List(Of CheckBox)
 Dim result As List(Of CheckBox) = New List(Of CheckBox)
 For Each di As ListViewDataItem In lv.Items
 For Each c As Control In di.Controls
 If (Not TryCast(c, CheckBox) Is Nothing) Then
                        result.Add(DirectCast(c, CheckBox))
 End If
 Next
 Next
 Return result
End Function
 

 

 

A couple of small details necessary for this page:

 

 

Private Sub SetFirstPage()
 Dim dp As DataPager = lv.FindControl("DataPager")
 If Not dp Is Nothing Then
                dp.SetPageProperties(0, dp.MaximumRows, False)
 End If
End Sub
Protected Sub btnSubmit_Click(sender As Object, e As System.EventArgs) Handles btnSubmit.Click
            SetFirstPage()
            ViewState("SortExpression") = Nothing
            SetArrowImages(Nothing)
End Sub
Protected Sub PagePropertiesChanging(sender As Object, ByVal e As PagePropertiesChangingEventArgs)
            _pageclicked = True
End Sub

 

 

 

I'm using a predicate extension above in ColorFilters() and the other checkbox functions, which is just a helper that saved me from importing a full blown predicatebuilder, but I had to translate it from c#.

 

 

 
Public Module PredicateExtensions
        <System.Runtime.CompilerServices.Extension()> _
Public Function [And](Of T)(original As Predicate(Of T), newPredicate As Predicate(Of T)) As Predicate(Of T)
 Return Function(item) original(item) AndAlso newPredicate(item)
End Function
        <System.Runtime.CompilerServices.Extension()> _
Public Function [Or](Of T)(original As Predicate(Of T), newPredicate As Predicate(Of T)) As Predicate(Of T)
 Return Function(item) original(item) OrElse newPredicate(item)
End Function
''' <summary>
''' Converts a Predicate into a Func(Of T, Boolean)
''' </summary>
''' <typeparam name="T"></typeparam>
''' <param name="predicate"></param>
''' <returns></returns>
''' <remarks></remarks>
        <System.Runtime.CompilerServices.Extension()> _
Public Function ToFunction(Of T)(ByVal predicate As Predicate(Of T)) As Func(Of T, Boolean)
 Dim result As Func(Of T, Boolean) = Function(item) predicate.Invoke(item)
 Return result
End Function
End Module
 

 

 

And that's it.

Tags:

Linq | VB.Net | ASP.Net

What’s wrong with this picture

by MikeHogg20. 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);
        }
    }
}

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