Refactoring MVP and custom javascript date controls

by MikeHogg 22. September 2014 17:11

I was tasked to fix a custom UI date control in a legacy .Net web application. It was a requirement that each of the date components- day, month, year- had to be in separate dropdownlists. I recommended going with one of the standard date controls that exist in any of the .net or js libraries today (and have been extensively tested - hundred of hours in some cases) but there was a reason for going with a custom control. It was originally written years ago, and I ended up entirely rewriting it because of some design conflicts. 

This was a legacy maintenance project, where only emergency fixes are allowed, and no upgrades or refactoring time is available. Like most of these projects, I wanted to keep as close to the existing tools/libraries/patterns as I could, and write as little as possible custom code; I wanted the fewest changes.  So...

The site was using the old Model View Presenter Pattern (pre MVC), and there were runtime properties of the control, that were not available to the controller during design time.  My controller was going to need these properties, so I followed the existing pattern and set up an interface in the library and exposed my control properties to the controller through this library interface, so we could process these design time variables in our controller and wouldn’t get any surprise runtime exceptions.

The c# code was keeping track of each of the dropdownlists manually, so there were Day Month Year properties to select and clear, and the lists had to be populated depending on currently selected values, and selected values had to be selected or cleared at the right parts of the page lifecycle if we were posting back, and at other parts if we weren't, and gotten from Request values when they weren't available yet in the page lifecycle.  It was all very complicated and manual. I replaced all that with databinding for populating the lists, and just two properties- one SelectedDate property (get only) and one PreselectedDate property(set only) that were databound as well.

It was also using a lot of javascript, and a lot of it was the same as the c# code, populating the lists in the dropdowns again, but this time in the client, based on whether there were 30 or 31 or 29 days in that month and so on when the user changed them... The problem with this was the list counts of the control that was posted back would not match the list counts of the control in viewstate, and .net would barf on

“control tree not matching viewstate(Invalid Postback or callback argument. EventValidation is enabled using enableEventValidation="true") “

and you don't want to turn EventValidation off, or what's the whole point of using .net and viewstate?  Instead, what I do here, is add an AjaxPanel, or a Telerik ajax panel, since Telerik is what they were using in this case, and then all I had to do was call postback onchange of the dropdownlists, and use the same c# databinding code I already wrote.  Removed all the duplicate js code and no more EventValidation exceptions.

Above this, I was supposed to add validation.  (client side only – those were the requirements)  Which is the cool part that I'd like to share.  I could have done this a couple different ways but ended up writing my own short validation function in lieu of some other existing options that would have added much more (still custom) complexity.

Javascript dates are notoriously difficult to work with.  They have carryover parsing, and the zero index months.  Javascript can take a date(2011,12,1) and parse it to be 2012/1/1.  But if I just compare my js date fields after parsing, to my input fields, I could make sure the date is not only valid, but the expected date entered.

 

 

function validateDddp() {
            $('.txtDobValid:first').val('');
 
            var day = $('.dateList')[0].value;  // using asp controls so Id can't be used, using class instead
            var month = $('.dateList')[1].selectedIndex -1;  //js: zero based months
            var year = $('.dateList')[2].value;
            
            var adate = new Date(year, month, day);
            if (isValidDateDddp(adate) 
                && adate.getDate() == day 
                && adate.getMonth() == month
                && adate.getYear() == year - 1900) {
                    $('.txtDobValid:first').val('valid');
            }
 
            $('.txtDobValid:first').change();
        }
 
        function isValidDateDddp(d) {
            if (Object.prototype.toString.call(d) !== "[object Date]")
                return false;
            return !isNaN(d.getTime());
        }
 
        // for page loads where field is already populated 
        $(document).ready(function() {
        if ($.grep($('.dateList'), function(e) { return e.selectedIndex != 0; }).length > 0) {
                validateDddp();
            }
        }
        );

 

For kicks here is the web View custom user control code-behind that handles all the binding/page load scenarios, and now also the ajax posting. Pretty basic.

[System.Web.UI.ValidationProperty("SelectedDate")]
public partial class DropDownDatePicker : Microsoft.Practices.CompositeWeb.Web.UI.UserControl, IDropDownDatePickerWebPartView
{
    private DropDownDatePickerWebPartPresenter _presenter;
    private DateTime dateOfBirth;
     
    public DateTime? SelectedDate
    {
        get
        {
            DateTime result;
            if (DateTime.TryParse(String.Format("{0}/{1}/{2}", this.ddlYear.SelectedValue, this.ddlMonth.SelectedIndex, this.ddlDay.SelectedValue), out result)) return result;
            else return null;
        }
    }
    public DateTime DateOfBirth { set { dateOfBirth = value; } } 
    public DateTime MinDate { get; set; }
    public DateTime MaxDate { get; set; }
    public bool ValidationEnabled { get; set; }
 
 
    [Dependency]
    public DropDownDatePickerWebPartPresenter Presenter
    {
        get
        {
            return this._presenter;
        }
        set
        {
            if (value == null)
                throw new ArgumentNullException("value");
 
            this._presenter = value;
            this._presenter.View = this;
        }
    }
 
 
    protected void Page_Load(object sender, EventArgs e)
    {
        if (MaxDate == DateTime.MinValue)
        {
            MinDate = DateTime.Now.AddYears(-100);
            MaxDate = DateTime.Now;
        }
 
        if (!this.IsPostBack)
        {
            this._presenter.OnViewInitialized();
 
            rfvDateOfBirth.Enabled = ValidationEnabled;
 
            BindLists();
 
            BindSelections();
        }
                   
        this._presenter.OnViewLoaded();
 
    }
 
 
    #region Binds
    private void BindLists()
    {
        ddlDay.DataSource = GetDays(dateOfBirth.Year, dateOfBirth.Month);
        ddlDay.DataBind();
 
        ddlMonth.DataSource = GetMonths();
        ddlMonth.DataBind();
 
        ddlYear.DataSource = GetYears();
        ddlYear.DataBind();
    }
 
    private void RebindDays()
    {
        int year;
        var month = ddlMonth.SelectedIndex;
        var currentday = ddlDay.SelectedValue;
 
        if (ddlYear.SelectedIndex == 0) year = DateTime.Now.Year;
        else int.TryParse(ddlYear.SelectedValue, out year);
 
        if (year <= MaxDate.Year
            && year >= MinDate.Year
            && month > 0
            && month <= 12)
        {
            ddlDay.DataSource = GetDays(year, month);
            ddlDay.DataBind();
            if (ddlDay.Items.FindByValue(currentday) != null) ddlDay.SelectedValue = currentday;
        }
        
    }
    
    private void BindSelections()
    {
        if (dateOfBirth != DateTime.MinValue)
        {
            this.ddlYear.SelectedValue = dateOfBirth.Year.ToString();
            this.ddlMonth.SelectedIndex = dateOfBirth.Month;
            this.ddlDay.SelectedValue = dateOfBirth.Day.ToString();
        }
    }
 
    #endregion
 
 
    #region Get Lists
    private List<string> GetDays(int year, int month)
    {
        var days = new List<string>();
        days.Add("-Day-"); 
        while (days.Count <= DateTime.DaysInMonth(year, month)) days.Add(days.Count.ToString());
        return days;
    }
 
    /// <summary>
    /// We'll can still use Months as List<string> if we use Index for the Int when creating DateTime objects
    /// </summary>
    /// <returns></returns>
    private List<string> GetMonths()
    {
        var months = new List<string>();
        months.Add("-Month-");
        while (months.Count <= 12) months.Add(new DateTime(2001, months.Count, 1).ToString("MMMM"));
        return months;
    }
 
    private List<string> GetYears()
    {
        var years = new List<string>();
        years.Add("-Year-");
        int thisyear = MaxDate.Year;
        int floor = MinDate.Year;
        while (thisyear > floor) years.Add(thisyear--.ToString());
        return years;
    }
 
    #endregion
 
 
    #region Events
    protected void ddl_SelectedIndexChanged(object sender, EventArgs e)
    {
        RebindDays();
        Validate();
 
    }
 
    #endregion
 
    #region Validation
 
    private void Validate()
    {
        if (ValidationEnabled)
        {
            try
            {
                int year = Convert.ToInt16(this.ddlYear.SelectedValue);
                int month = Convert.ToInt16(this.ddlMonth.SelectedIndex);
                int day = Convert.ToInt16(this.ddlDay.SelectedValue);
 
                DateTime d = new DateTime(year, month, day);
                if (d > MaxDate || d < MinDate)
                {
                    throw new Exception(String.Format("Server Side DropDownDatePicker.Validate: Date {0} not in valid range {1} - {2}",
                        d.ToShortDateString(), MinDate.ToShortDateString(), MaxDate.ToShortDateString())); 
                }
            }
            catch (Exception e)
            {
                //log... 
                txtDobValid.Text = String.Empty;
                rfvDateOfBirth.IsValid = false;
                // leave parent to validate page
            }
        }
    }
 
    #endregion
 
 
}
}

Roll Your Own Sorting in 2012

by MikeHogg 17. 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

Logging From Day One (and Exception Handling)

by MikeHogg 9. May 2012 09:50

NLog is so easy to use, it really is like plug and play. Or drag and drop. Add dll to your References. Add this to your web.config, use either file, or db table(what I use). Then, in any class you want to use Logger, just add a line for the static instance:

    public class HomeController : MH.Controllers.AController
    {
        private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); 

 

 

And then to use it:

 

    logger.Info("Some mess");

 

No reason not to have logging available in every web app from the start. I usually use a Log table described like my web.config shows here


<configuration>
  <configSections>
    <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>...  </configSections>
...  <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
    <targets> 
      <target name="db" xsi:type="Database" connectionStringName="CONN"
              commandText="insert into Log(Level, Source, Message, Audit_Date) values(@level, @logger, @message, @time_stamp);">
        <parameter name="@time_stamp" layout="${date}"/>
        <parameter name="@level" layout="${level}"/>
        <parameter name="@logger" layout="${logger}"/>
        <parameter name="@message" layout="${message}"/>
      </target> 
    </targets>
 
    <rules>
      <logger name="*"  writeTo="db"></logger> 
    </rules>
  
  </nlog>

If you can't get it to start working, try using a log file first, or you can add atts like this example:
  <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        internalLogFile="c:\mike.log" internalLogToConsole="true" throwExceptions="true">
    <targets>
      <target xsi:type="File" name="file" fileName="${basedir}/n.log" />

Oh and while we're here, ELMAH is always in my projects even before NLog.  It's just as easy, and actually comes with more features.  I use it with teh DB Table, and automatic emails.  This is all you need to get up and running...

<configuration>
  <configSections>
    <sectionGroup name="elmah">
      <section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah" />
      <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah" />
      <section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah" />
      <section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah" />
    </sectionGroup>
  </configSections>...
 
    <httpModules>
      <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" />
      <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
      <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" />
    </httpModules>
...  <system.webServer>
    <validation validateIntegratedModeConfiguration="false"/>
    <modules runAllManagedModulesForAllRequests="true"> 
        <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" preCondition="managedHandler" />
        <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" preCondition="managedHandler" />
        <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" preCondition="managedHandler" />
    </modules> 
  </system.webServer>... and 
  <elmah>
    <!--
        See http://code.google.com/p/elmah/wiki/SecuringErrorLogPages for 
        more information on remote access and securing ELMAH.   -->
    <security allowRemoteAccess="true" />
    <errorLog type="Elmah.SqlErrorLog, Elmah" connectionStringName="CONN"   >
    </errorLog>
    <errorMail
       to="mike.hogg@havasdiscovery.com"
       subject="[ELMAH] ACMT_Web Exception"  >
    </errorMail> 
    
  </elmah>
  <location path="elmah.axd" inheritInChildApplications="false">
    <system.web>
      <httpHandlers>
        <add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
      </httpHandlers>
      <!-- 
        See http://code.google.com/p/elmah/wiki/SecuringErrorLogPages for 
        more information on using ASP.NET authorization securing ELMAH.      -->
      <authorization>
        <allow roles="Admin" />
        <deny users="*" />
      </authorization>
    </system.web>
    <system.webServer>
      <handlers>
        <add name="ELMAH" verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" preCondition="integratedMode" />
      </handlers>
    </system.webServer>
  </location>
</configuration> 

There's a db script to create the necessaries. I think that's it.  Comes with an Admin Area automatically and a dashboard app, if you set up authorization in your web then you should be able to see it with the Admin role and no further configuration.  ELMAH is good for catching all uncaught exceptions.  It has replaced my standard libraries and error handling methods in global.asax.

 

I also set up my own ErrorController, and some views, for my handled (known) errors.

public class ErrorController : AController
    {
        public ActionResult Index()
        { 
            Models.Error e = GetError();
            e.Title = "Error!";
            e.Message = "We are sorry.  An error has occurred.  Please try again or contact support";
 
            return View(e);
        }
 
        public ActionResult NotFound()
        {
            Models.Error e = GetError();
            e.Title = "Page Could Not Be Found";
            e.Message = "Sorry, that page could not be found";
 
            return View(e);
        }
 
        private Models.Error GetError()
        {
            Models.Error result = new Models.Error();
            Exception ex = null;
 
            try
            {
                ex = (Exception)HttpContext.Application[Request.UserHostAddress.ToString()];
            }
            catch { }
 
            if (ex != null) result.Exception = ex;
            
            return result;
        }

If you want to manually log errors in your app using ELMAH, just do this (wrapped in my lib/logger library):

 

 

public static void LogWebException(Exception ex)
        {
            try
            {
                Elmah.ErrorSignal.FromCurrentContext().Raise(ex, System.Web.HttpContext.Current);

 

Or... add a filter to Exception handling and in that hook tell ELMAH to log handled. Now all of your handled exceptions will be logged also.

namespace MH.Web.Mvc3.Controllers
{
    public class ElmahHandledErrorLoggerFilter : IExceptionFilter
    {
        public void OnException(ExceptionContext context)
        {
            // Log only handled exceptions, because all other will be caught by ELMAH anyway.
            if (context.ExceptionHandled)
                Elmah.ErrorSignal.FromCurrentContext().Raise(context.Exception);
        }
 
        // ADD THIS TO GLOBAL ASAX
        ///public static void RegisterGlobalFilters (GlobalFilterCollection filters)
        //{
        //    filters.Add(new ElmahHandledErrorLoggerFilter());
        //    filters.Add(new HandleErrorAttribute());
        //}
    }
}

 

 

 

ELMAH has a habit of becoming bothersome with all the 404s for robot.txt.   Put this in  your web.config to stop them..

 

 

    <errorFilter>
      <test>
        <or>
          <and>
            <equal binding="HttpStatusCode" value="404" type="Int32" />
            <equal binding="Context.Request.Path" value="/favicon.ico" type="string" />
          </and>
          <and>
            <equal binding="HttpStatusCode" value="404" type="Int32" />
            <equal binding="Context.Request.Path" value="/robots.txt" type="string" />
          </and>
        </or>
      </test>
    </errorFilter>
    
  </elmah>

Add a background tag on all of your web pages showing the current Environment

by MikeHogg 23. March 2009 20:41

This was a neat trick.  When working with UAT and STAGE and DEV and however many other environments, it can sometimes be confusing which database your particular web server is actually hooked up to.  Here I set up an HttpHandler to write out a string as an image memory stream, and then with some CSS trickery it shows up repeating with low opacity all over each page, faint enough that it doesn’t bother you, but enough so that you won’t ever mistake yourself for being in a different environment.

First in the BasePage PreRender I check for conditional, in case, for instance, you don’t want to use this on Production:

        protected override void OnPreRender(EventArgs e)
        {
            //todo: we could make this a webresource instead of static img
            Image img = new Image(); 
            try
            {
                string prod = System.Configuration.ConfigurationSettings.AppSettings["dontShowHeaderForThisDatabase"];
                if (!LIB.Gen_Util.getDBName().ToUpper().Contains(prod.ToUpper()))
                {
                    img.ImageUrl = "DBImage.ashx";
                    img.Style.Add("width", "100%");
                    img.Style.Add("height", "100%");
                    img.Style.Add("z-index", "-1");
                    img.Style.Add("position", "absolute");
                    img.Style.Add("top", "20px"); 
                    // this is a pain- if we have <% %> tags in page then this will break
                    //this.Form.Controls.Add(img);
                    this.Page.Controls.Add(img);
                }
                base.OnPreRender(e);
            }

 

 

DBImage.ashx is created once then cached in the HttpHandler:

    public class HttpHandler :IHttpHandler
    {
        #region IHttpHandler Members
        public bool IsReusable
        {
            get { return false; }
        }
        public void ProcessRequest(HttpContext context)
        {
            try
            {
                byte[] ba;
                if (HttpContext.Current.Cache["dbimage"] == null)
                {
                    ba = Gen_Util.CreateHeaderImage(Gen_Util.getDBName());
                    if (ba != null)
                    {
                        HttpContext.Current.Cache["dbimage"] = ba;
                    }
                }
                else
                {
                    ba = (byte[])HttpContext.Current.Cache["dbimage"];
                }
                if (ba != null)
                {
                    context.Response.BinaryWrite(ba);
                }
                context.Response.End();
            } 
        }
        #endregion
    }

 

It will get called for each Request, with this line in the web.config:

    <httpHandlers>
      ...
      <add verb="GET" path="DBImage.ashx" type="CEG.CPS.Settlements.LIB.HttpHandler" />

 

And the CreateHeaderImage is the tricky CSS part:

        public static byte[] CreateHeaderImage(string text)
        { 
            try
            {
                Bitmap bm = new Bitmap(320, 240, PixelFormat.Format32bppArgb);
                Graphics g = Graphics.FromImage(bm);
                g.SmoothingMode = SmoothingMode.HighQuality;        // ?
                g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;   // ?
                g.Clear(Color.White);
                GraphicsPath p = new GraphicsPath();
                Font f = new Font("Impact", 20F);
                Rectangle r = new Rectangle(0, 0, 320, 240);
                StringFormat sf = new StringFormat();
                String repeatedText = string.Empty;
                for (int x = 0; x < 48; x++)  // 8 rows of 6
                { 
                    if (x % 6 == 0 && x != 0)
                    {
                        repeatedText += "\n";
                    } repeatedText += text + "  "; 
                }  
                p.AddString(repeatedText, f.FontFamily, (int)f.Style, f.Size, r, sf);
                
                // transparency shade 75
                SolidBrush b = new SolidBrush(Color.FromArgb(75,Color.Gray));
                
                g.FillPath(b, p);
                
                f.Dispose();
                b.Dispose();
                g.Dispose();
                MemoryStream ms = new MemoryStream();
                bm.Save(ms, ImageFormat.Bmp);
                bm.Dispose();
                return ms.GetBuffer();
            }
    }

 

And that’s it.

Tags:

C# | ASP.Net

Using an abstract Server Control as an updateable module

by MikeHogg 19. March 2009 14:15

The main part of this feature was to show a grid of some data, different database uptimes and other performance metrics, on a web page.  The interesting part was that the databases that were tracked changed often.  Sometimes there were 12, and then one would get killed, and two more were created, and the next week another new one would be built. Not only would it be better to put the datasources in a config of some sort, but it would be even better if a manager could edit that config through an easy-to-use UI on a web page. 

Rather than build two separate pages, I saw that there were some re-useable components in this use case, and so I created a Server control, with an XML config.  Most of the XML I/O I put in an Abstract class, and then from that I inherited and Admin Control and a Display Control.

First the Common Elements:

    interface IGrid
    { 
        DataTable getDataSource();
        void createColumns(DataTable aTable);
        void styleGrid();
    }
    public abstract class AGrid : GridView, IGrid
    {
        public string xFile{get; set;}
        public string headerBackColor{get; set;}
        public string gridBackColor {get;set;} 
        public string title { get; set; }        
        protected DataTable myTable; 
        protected override void OnInit(EventArgs e)
        {
            try
            {
                base.OnInit(e);
                this.AutoGenerateColumns = false;
                myTable = getDataSource();
                if (Page.IsPostBack)
                {
                    this.DataSource = myTable;
                    if (this.EditIndex > -1 )
                    {
                        this.DataBind();
                    }
                }
            } 
        }
        protected override void OnLoad(EventArgs e)
        { 
                base.OnLoad(e);
                if (!Page.IsPostBack)
                {
                    if (myTable != null)
                    {
                        createColumns(myTable);
                        this.DataSource = myTable;
                        this.DataBind();
                        styleGrid();
                    }
                }  
        } 
        public DataTable getDataSource()
        { 
            <snip>                      
        }
        public virtual void createColumns(DataTable myTable){
            try
            { <snip>
        }
        public void styleGrid()
        {
            try
            {
                if (this.gridBackColor != String.Empty)
                {
                    this.BackColor = System.Drawing.Color.FromName(this.gridBackColor);
                }
                else this.BackColor = System.Drawing.Color.Silver;
                this.BorderStyle = BorderStyle.Ridge;
                this.Font.Size = 10;
                this.Font.Name = "Verdana";
                this.Font.Bold = true;
                
                this.GridLines = GridLines.Horizontal;
                this.Style.Add("font-variant", "small-caps");
                this.Style.Add("text-align", "right");
                this.CellPadding = 2;
                if (this.headerBackColor != String.Empty)
                {
                    this.HeaderStyle.BackColor = System.Drawing.Color.FromName(this.headerBackColor);
                }
                else this.HeaderStyle.BackColor = System.Drawing.Color.MidnightBlue;
                this.HeaderStyle.ForeColor = System.Drawing.Color.White;
                this.HeaderStyle.Font.Size = 12;
                if (this.title != String.Empty)
                {
                    this.Caption = this.title;
                }
                else this.Caption = "Grid Monitor 1.0";
                
                this.CaptionAlign = TableCaptionAlign.Top;
                this.BorderWidth = 1;
            }
            catch (NullReferenceException e)
            {
                Console.WriteLine("Probably xml issue: " + e.ToString());
            }
        }

 

The Admin Grid was just a simple DataGrid and used the built-in OnDataBound to add editting controls and commands, and the OnRowEditing events to Add/Edit/Remove nodes in the XML file.

 

    [DefaultProperty("Text")]
    [ToolboxData("<{0}:AdminGrid runat=server></{0}:AdminGrid>")]
    public class AdminGrid : AGrid
    {
        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        [Localizable(true)]
          
        protected override void OnInit(EventArgs e)
        {
            this.AutoGenerateEditButton = true;
            this.ShowFooter = true;
            
            base.OnInit(e); 
        } 
        protected override void OnPagePreLoad(object sender, EventArgs e)
        {
            base.OnPagePreLoad(sender, e);
            if (Page.IsPostBack && this.EditIndex == -1)
            {
                this.DataBind();
            }
        } 
        protected override void OnDataBound(EventArgs e)
        {
            base.OnDataBound(e);
            foreach (DataControlFieldCell cell in this.FooterRow.Cells)
            {
                if (cell.ContainingField.GetType().Equals(typeof(System.Web.UI.WebControls.BoundField)))
                {
                    TextBox myC = new TextBox();
                    cell.Controls.Add(myC);
                }
                else if (cell.ContainingField.GetType().Equals(typeof(System.Web.UI.WebControls.CheckBoxField)))
                {
                    CheckBox myC = new CheckBox();
                    cell.Controls.Add(myC);
                }
                else if (cell.ContainingField.GetType().Equals(typeof(System.Web.UI.WebControls.CommandField)))
                {
                    LinkButton myC = new LinkButton();
                    myC.Text = "Add New";
                    myC.ID = "btnAddNew";
                    myC.CommandName = "New";
                    myC.CommandArgument = "New";
                    cell.Controls.Add(myC);
                }
            }
         
        protected override void OnRowCommand(GridViewCommandEventArgs e)
        {
            try
            {
                base.OnRowCommand(e);
                if (e.CommandName == "New")
                {
                    DataRow newRow = myTable.NewRow();
                    //insert
                    for (int x = 0; x < myTable.Columns.Count; x++)
                    {
                        Control myControl = this.FooterRow.Cells[x + 1].Controls[0];
                        if (myControl.GetType().Equals(typeof(CheckBox)))
                        {
                            newRow[x] = ((CheckBox)myControl).Checked;
                        }
                        else if (myControl.GetType().Equals(typeof(TextBox)))
                        {
                            newRow[x] = ((TextBox)myControl).Text;
                        }
                    }
                    myTable.Rows.Add(newRow);
                    WriteXml(myTable, HttpContext.Current.Server.MapPath(xFile));
                    this.DataSource = myTable;
                    this.DataBind();
                }
            }
        protected override void OnRowUpdating(GridViewUpdateEventArgs e)
        { 
            //DataTable myTable = (DataTable)HttpContext.Current.Session["myTable"];
            DataTable oldTable = (DataTable)this.DataSource;
            GridViewRow myRow = this.Rows[e.RowIndex];
            for (int x = 0; x < myRow.Cells.Count; x++)
            {
                Control myControl = myRow.Cells[x].Controls[0];
                
                if (myControl.GetType().Equals(typeof(CheckBox)))
                {
                    oldTable.Rows[e.RowIndex][x - 1] = ((CheckBox)myControl).Checked;
                    //myTable.Rows[e.RowIndex][]
                }
                else if (myControl.GetType().Equals(typeof(TextBox)))
                {
                    oldTable.Rows[e.RowIndex][x - 1] = ((TextBox)myControl).Text;
                }
                    WriteXml(myTable, HttpContext.Current.Server.MapPath(xFile));
                    this.DataSource = myTable;
                    this.DataBind();
                }
            }
        }

 

The DisplayGrid has some neat UI components

[assembly: WebResource("DBMonitor.Resources.button_red.png","image/png")]
[assembly: WebResource("DBMonitor.Resources.button_green.png", "image/png")]
[assembly: WebResource("DBMonitor.Resources.button_yellow.png", "image/png")]
namespace DBMonitor
{
    [DefaultProperty("Text")]
    [ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")]
    public class DisplayGrid : AGrid
    {
        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        [Localizable(true)]
        public string Text <snip>
        protected override void OnInit(EventArgs e)
        { 
                this.title = "Database Environments";
                this.headerBackColor = "MidnightBlue";
                this.gridBackColor = "Silver";
                this.xFile = "Config/dbmon.xml";
                base.OnInit(e); 
        }
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            DataTable myTable = this.DataSource as DataTable;
            if (myTable.Columns.IndexOf("Active") >= 0)
            {
                myTable.DefaultView.RowFilter = "Active = true";
                this.DataBind();
            }
        }
        protected override void OnRowDataBound(GridViewRowEventArgs e)
        {
            base.OnRowDataBound(e);
            try
            {
                //find BE Last Refreshed value
                int x = ((DataTable)this.DataSource).Columns.IndexOf("BE_Last_Run");
                if (x >= 0 && e.Row.RowType != DataControlRowType.Header)
                {
                    System.Web.UI.WebControls.Image myLight = new System.Web.UI.WebControls.Image();
                      
                    DateTime lastBE = new DateTime();
                    if (e.Row.Cells[x].Text.Contains(':'))
                    {
                        try
                        {
                            lastBE = DateTime.Parse(e.Row.Cells[x].Text);
                        }
                        catch (FormatException ex)
                        {
                            lastBE = DateTime.MinValue;
                            
                        }
                        e.Row.Cells[x].Text = lastBE.ToShortTimeString();
                        TimeSpan myAge = DateTime.Now - lastBE;
                        if (ConfigurationSettings.AppSettings["debug"] == "true")
                        {
                            logThis("myAge: " + myAge.ToString() + " and now is " + DateTime.Now.ToString() +
                                " and lastBE is " + lastBE.ToString() +
                                " and timespan.fromhours(1) is " +
                            TimeSpan.FromHours(1).ToString() +
                            " and now minus lastBE is " + myAge, EventLogEntryType.Information);
                        }
                        if (myAge > TimeSpan.FromHours(1))
                        {
                            myLight.ImageUrl = this.Page.ClientScript.GetWebResourceUrl(this.GetType(),
                                    "DBMonitor.Resources.button_red.png");
                        }
                        else if (myAge > TimeSpan.FromMinutes(10))
                        {
                            myLight.ImageUrl = this.Page.ClientScript.GetWebResourceUrl(this.GetType(),
                                "DBMonitor.Resources.button_yellow.png");
                        }
                        else
                        {
                            myLight.ImageUrl = this.Page.ClientScript.GetWebResourceUrl(this.GetType(),
                                "DBMonitor.Resources.button_green.png");
                        }
                    }
                    else
                    {
                        myLight.ImageUrl = this.Page.ClientScript.GetWebResourceUrl(this.GetType(),
                                    "DBMonitor.Resources.button_red.png");
                    }
                    e.Row.Cells[0].Controls.Add(myLight);
                    e.Row.Cells[0].BackColor = Color.White;
                }
            }

Adding it to a page is just a couple lines then- here the first line registers the namespace, and the last selected line places it in a div.  In this case I populated my attributes in the DisplayGrid class, but if I was to use this in several other places, I could remove those and populate my attributes here in the html element.

<%@ Register Assembly="DBMonitor" Namespace="DBMonitor" TagPrefix="dbm"  %> 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Status Page</title>
    <link href="StyleSheet.css" rel="stylesheet" type="text/css" />
    <link href="mobile.css" rel="stylesheet" type="text/css" media="handheld"/>
    
    <script src="stock-ticker.js" type="text/javascript"></script>
     <meta http-equiv=Refresh content="120" />
     
    <script type="text/javascript">
        
        var ticker;
        function loadTicker(){
            ticker = new Ticker('ticker','myTicker',4,15);
            ticker.start();
            
        }
 
    </script>
</head>
<body onload="loadTicker();">
    <form id="form1" runat="server"> 
    
   <table style="table-layout:fixed"><tr><td style="width:200px;overflow:hidden">
       <div id="myTicker" class="ticker" style="width:5000px" nowrap="nowrap">
            <asp:Xml DocumentSource="~/Config/ticker.xml" runat="server" ID="ticker" TransformSource="~/ticker.xslt"></asp:Xml>
   </div></td></tr></table>
   
    <div id="newEnv">
        <dbm:DisplayGrid ID="something" runat="server"></dbm:DisplayGrid>
    </div>

 

And that’s it.

Using .Net to let users create Scheduled Tasks to send themselves issue tracking reports from MySql

by MikeHogg 11. March 2009 19:39

I had written a reporting page in asp.Net that queried the MySql database of our issue tracking system.  Users in different teams could use the page to create reports on the issues created, resolved, and worked on by their team as well as filtering by any number of other attributes. 

The page in itself was neat, and necessary, as the issue tracking system had grown at a pace of 50 new issues weekly for a couple of years.  But it was neater to build a way for different users to save their more complicated report filters, and even neater to allow them to send them an automatic email containing their report on any daily or weekly schedule.  Although it sounded like a lot of work at first, it turned out to be easy to set up.

The first part was to only show each user their own saved reports, and allow them to edit and save again, without affecting or viewing anybody else’s.  Because we were in an intranet environment, this was easy enough, using Windows as our authentication scheme meant I always already had the current user, and I could then save the reports in a table with their Id, and only show links to reports that they owned. 

The report page had over twenty filters and group by’s and sorts so saving all of the settings individually was not the way to go.  Instead I would take the output of that page, the actual query, generated dynamically, and save that.  Then all I had to do was get the query, saved in the database, and run it.  The code that created the query can be a great exercise for refactoring, and it is incredibly long, but you can imagine it was quick to write initially, and I only ever had to add one or two features to it …

        using (MySqlDataAdapter myAdp = new MySqlDataAdapter())
        {
            myCmdQuery.Append("select distinct iss_id  Issue_ID ");
            if (project) { myCmdQuery.Append(",prj_title Project "); };
            if (category) { myCmdQuery.Append(",prc_title Category "); };
            if (summary) { myCmdQuery.Append(",iss_summary  Summary "); };
            if (assignee)
            {
                myCmdQuery.Append(", (select group_concat(usr_full_name order by usr_full_name separator ', ') " +
                    " from eventum_user join eventum_issue_user " +
                    " on isu_usr_id = usr_id where isu_iss_id = iss_id) Assignees ");
            };
            if (status) { myCmdQuery.Append(",sta_title Status "); };
            if (priority) { myCmdQuery.Append(",pri_title Priority "); };
            if (createDate) { myCmdQuery.Append(",iss_created_date Created_Date "); };
            if (createAge) { myCmdQuery.Append(",datediff(curdate(), iss_created_date) Days_Old "); };
            if (updateDate) { myCmdQuery.Append(",iss_updated_date Updated_Date "); };
            if (updateAge) { myCmdQuery.Append(", datediff(curdate(),iss_updated_date) Days_Since_Update "); };
            if (updateUser)
            {
                myCmdQuery.Append(",(select usr_full_name from eventum_user join eventum_issue_history " +
                      "on usr_id = his_usr_id where his_id = (select max(his_id) from " +
                      "eventum_issue_history where his_iss_id = iss_id)) Update_User ");};
            if (updateSummary) { myCmdQuery.Append(",(select his_summary from eventum_issue_history where " +
                "his_id = (select max(his_id) from eventum_issue_history where his_iss_id = iss_id)) Update_Summary "); }; 
            if (abbrev)
            {
                if (notes) { myCmdQuery.Append(", left(n.not_note,200) Last_Internal_Note "); };
                if (closeComments) { myCmdQuery.Append(", left(nc.not_note,200) Closed_Comment "); };
            }
            else
            {
                if (notes) { myCmdQuery.Append(", n.not_note Last_Internal_Note "); };
                if (closeComments) { myCmdQuery.Append(", nc.not_note Closed_Comment "); };
            }
            if (custom) { myCmdQuery.Append(", cfo_value Custom "); };
            if (expResDate) { myCmdQuery.Append(", iss_expected_resolution_date Exp_Res_Date "); };
            if (dupe) {myCmdQuery.Append(", iss_duplicated_iss_id dupe "); };
            myCmdQuery.Append("FROM eventum_issue e ");
            myCmdQuery.Append("join eventum_status s on e.iss_sta_id = s.sta_id ");
            myCmdQuery.Append("join eventum_project p on e.iss_prj_id = p.prj_id ");
            myCmdQuery.Append("join eventum_project_category pc on e.iss_prc_id = pc.prc_id ");
            if (priority) { myCmdQuery.Append("join eventum_project_priority pp on e.iss_pri_id = pp.pri_id "); };
            if (notes)
            {
                myCmdQuery.Append("left outer join eventum_note n on iss_id = not_iss_id and not_id = " +
                    "  (select max(not_id) from eventum_note nn " +
                    "   where n.not_iss_id = nn.not_iss_id and nn.not_title <> 'Issue closed comments') ");
            }
            if (closeComments)
            {
                myCmdQuery.Append("left outer join eventum_note nc on iss_id = nc.not_iss_id and nc.not_id = " +
                    "  (select max(ncc.not_id) from eventum_note ncc " +
                    "   where nc.not_iss_id = ncc.not_iss_id and ncc.not_title = 'Issue closed comments') ");
            }
            if (custom)
            {
                myCmdQuery.Append("left outer join eventum_issue_custom_field on icf_iss_id = iss_id " +
                    "left outer join eventum_custom_field_option on cfo_id = icf_value ");
            } 
            if (this.ddlUserAssignment.SelectedIndex > 0)
            {
                myCmdQuery.Append("join eventum_issue_user on iss_id = isu_iss_id ");
            }
            myCmdQuery.Append("WHERE prj_status = 'active' ");
            if (this.ddlProject.SelectedIndex > 0)
            {
                myCmdQuery.Append("and prj_id = " + this.ddlProject.SelectedValue.ToString() + " ");
            }
            if (this.ddlUserAssignment.SelectedIndex > 0)
            {
                myCmdQuery.Append(" and isu_usr_id = " + this.ddlUserAssignment.SelectedValue.ToString() + " ");
            }
            if (this.rblDate.SelectedIndex == 3)  //NOT touched
            {
                myCmdQuery.Append("and not exists(select 3 from eventum_issue_history where his_iss_id = iss_id " );
                
                if (this.ddlGroupAction.SelectedIndex > 0)
                {
                    myCmdQuery.Append(" and his_usr_id in (select usr_id from eventum_user where usr_grp_id = " +
                        ddlGroupAction.SelectedValue + ") ");
                }
                if (this.ddlUserAction.SelectedIndex > 0)
                {
                    myCmdQuery.Append(" and his_usr_id = " + ddlUserAction.SelectedValue + " ");
                }
                if (numberOfDays != null)
                {
                    myCmdQuery.Append(" and datediff(curdate(), his_created_date) <= " + numberOfDays + " ");
                }
                else
                {
                    if (start != null)
                    {
                        myCmdQuery.Append(" and his_created_date >= '" + start + "' ");
                    }
                    if (end != null)
                    {
                        myCmdQuery.Append(" and his_created_date <= '" + end + "' ");
                    }
                }
                myCmdQuery.Append(") ");
            }
            if (this.rblDate.SelectedIndex == 2)  //touched
            {
                myCmdQuery.Append("and exists(select 2 from eventum_issue_history where his_iss_id = iss_id ");
                
                if (this.ddlGroupAction.SelectedIndex > 0)
                {
                    myCmdQuery.Append(" and his_usr_id in (select usr_id from eventum_user where usr_grp_id = " +
                        ddlGroupAction.SelectedValue + ") ");
                }
                if (this.ddlUserAction.SelectedIndex > 0)
                {
                    myCmdQuery.Append(" and his_usr_id = " + ddlUserAction.SelectedValue + " ");
                }
                 
                if (numberOfDays != null)
                {
                    myCmdQuery.Append(" and datediff(curdate(), his_created_date) <= " + numberOfDays + " ");
                }
                else
                {
                    if (start != null) { myCmdQuery.Append(" and his_created_date >= '" + start + "' "); };
                    if (end != null) { myCmdQuery.Append(" and his_created_date <= '" + end + "' "); }
                }
                myCmdQuery.Append(") ");
            }
            else if (this.rblDate.SelectedIndex == 1)  //closed
            {
                myCmdQuery.Append("and exists(select 1 from eventum_issue_history where his_iss_id = iss_id " + 
                    "and his_htt_id = 23 ");
                
                if (this.ddlGroupAction.SelectedIndex > 0)
                {
                    myCmdQuery.Append(" and his_usr_id in (select usr_id from eventum_user where usr_grp_id = " +
                        ddlGroupAction.SelectedValue + ") ");
                }
                if (this.ddlUserAction.SelectedIndex > 0)
                {
                    myCmdQuery.Append(" and his_usr_id = " + ddlUserAction.SelectedValue + " ");
                }
                if (numberOfDays != null)
                {
                    myCmdQuery.Append(" and datediff(curdate(), iss_closed_date) <= " + numberOfDays + " ");
                }
                else
                {
                    if (start != null) { myCmdQuery.Append(" and iss_closed_date >= '" + start + "' "); };
                    if (end != null) { myCmdQuery.Append(" and iss_closed_date <= '" + end + "' "); };
                }
                myCmdQuery.Append(") ");
            }
            else if (this.rblDate.SelectedIndex == 0)    //created
            {
                if (this.ddlGroupAction.SelectedIndex > 0)
                {
                    myCmdQuery.Append(" and iss_usr_id in (select usr_id from eventum_user where usr_grp_id = " + 
                        ddlGroupAction.SelectedValue + ") ");
                }
                if (this.ddlUserAction.SelectedIndex > 0)
                {
                    myCmdQuery.Append(" and iss_usr_id = " + ddlUserAction.SelectedValue + " ");
                }
                
                if (numberOfDays != null)
                {
                    myCmdQuery.Append(" and datediff(curdate(), iss_created_date) <= " + numberOfDays + " ");
                }
                else
                {
                    if (start != null) { myCmdQuery.Append(" and iss_created_date >= '" + start + "' "); };
                    if (end != null) { myCmdQuery.Append(" and iss_created_date <= '" + end + "' "); };
                }
                
            }
            if (closed && !open) { myCmdQuery.Append(" and iss_closed_date is not null "); };
            if (open && !closed) { myCmdQuery.Append(" and iss_closed_date is null "); };
            if (!open && !closed) { Response.Write("umm.  Not Closed and Not Open?  Kindly try again."); return; };
            if (this.ddlGroupAssignment.SelectedIndex > 0)
            {
                myCmdQuery.Append(" and iss_id in (select isu_iss_id from eventum_issue_user " +
                    "join eventum_user on isu_usr_id = usr_id " +
                    "where usr_grp_id = " + this.ddlGroupAssignment.SelectedValue + ") ");
            }
            if (this.txtKeyword.Text.Length > 0)
            { 
                myCmdQuery.Append(" and iss_id in ( select * from ((SELECT DISTINCT(iss_id)  FROM evdb.eventum_issue " +
                    "WHERE  MATCH(iss_summary, iss_description) AGAINST ('" + this.txtKeyword.Text + "' IN BOOLEAN MODE) " +
                    ") UNION (  SELECT DISTINCT(not_iss_id)  FROM  evdb.eventum_note " +
                    "WHERE  MATCH(not_note) AGAINST ('" + this.txtKeyword.Text + "' IN BOOLEAN MODE) " +
                    ") UNION ( SELECT DISTINCT(sup_iss_id) FROM evdb.eventum_support_email, " +
                    "evdb.eventum_support_email_body WHERE sup_id = seb_sup_id AND " +
                    "MATCH(seb_body) AGAINST ('" + this.txtKeyword.Text + "' IN BOOLEAN MODE) ) ) a )   ");
            }
            if (this.ddlCategory.SelectedIndex > 0) { myCmdQuery.Append(" and iss_prc_id = " + this.ddlCategory.SelectedValue.ToString()); };
            if (this.ddlPriority.SelectedIndex > 0) { myCmdQuery.Append(" and iss_pri_id = " + this.ddlPriority.SelectedValue.ToString()); };
            myAdp.SelectCommand = new MySqlCommand(myCmdQuery.ToString());
            myAdp.SelectCommand.Connection = new MySqlConnection(ConfigurationManager.ConnectionStrings["evdbConn"].ToString());
            Session["_query"] = myCmdQuery.ToString();
            DataTable myTable = new DataTable();
            try
            {
                myAdp.Fill(myTable);
                DataView myDV = new DataView(myTable);
                // insert sorts in reverse order here  FIRST USER SORTS, THEN GROUPINGS
                if (this.ddlSort3.SelectedIndex > 0)
                {
                    addSort(ddlSort3.SelectedItem.Text.Replace(" ", "_"), ref myDV);
                }
                if (this.ddlSort2.SelectedIndex > 0)
                {
                    addSort(ddlSort2.SelectedItem.Text.Replace(" ", "_"), ref myDV);
                }
                if (this.ddlSort1.SelectedIndex > 0)
                {
                    addSort(ddlSort1.SelectedItem.Text.Replace(" ", "_"), ref myDV);
                }
                
                if (this.chkGroupAssignees.Checked)
                {
                    addSort("Assignees", ref myDV);
                }
                if (this.chkGroupCategory.Checked)
                {
                    addSort("Category", ref myDV);
                }
                if (this.chkGroupProject.Checked)
                {
                    addSort("Project", ref myDV);
                }

The result grid also had grouping on any of three separate attributes and was also written in the long declarative style, which I am not proud of, but it resulted in an organized and detailed report grid.

 

The second part required the use of the webserver’s Scheduled tasks to run daily and check the database for any reports that were scheduled to go at that time.  The task ran a simple console script.  I saved the task using impersonation and  .Net interfaced with Scheduled Tasks easily.

        ScheduledTasks st = new ScheduledTasks();
        Trigger newTrigger;
        Task newTask;
        string username = User.Identity.Name.Replace("SOMECORP\\","").ToLower();
        string reportName = "QBR" + username + this.txtScheduledReportName.Text;
        if (this.txtScheduledReportName.Text.Length > 0 && username.Length > 0)
        {
            try
            {
                int adjustedHour = (this.ddlAM.SelectedIndex == 0) ? Convert.ToInt16(this.ddlHour.SelectedValue) :
                    (Convert.ToInt16(this.ddlHour.SelectedValue)) + 12;
                st.DeleteTask(reportName); // in case we are saving new version
                DeleteReport(reportName);
                newTask = st.CreateTask(reportName);
                if (newTask != null)
                {
                    newTask.ApplicationName = "c:\\bin\\QbertEmailer.exe";
                    newTask.Parameters = "\"" + reportName + "\"";
                    newTask.SetAccountInformation("someserviceaccount", "password");
                    if (this.ddlFrequency.SelectedIndex == 0)
                    {
                        newTrigger = new DailyTrigger(
                            (short)adjustedHour,
                            (short)Convert.ToInt16(this.ddlMinute.SelectedValue.Replace(":", "")));
                    }
                    else
                    {
                        newTrigger = new WeeklyTrigger(
                            (short)adjustedHour,
                            (short)Convert.ToInt16(this.ddlMinute.SelectedValue.Replace(":", "")),
                            (DaysOfTheWeek)Convert.ToInt16(this.ddlWeekday.SelectedValue));
                    }
                    newTask.Triggers.Add(newTrigger);
                    // Call LogonUser to get a token for the user
                    Utils.ImpersonateUser iu = new Utils.ImpersonateUser();
                    iu.Impersonate("corp", "someserviceaccount", "password");
                    newTask.Save();
                    newTask.Close();
                    iu.Undo();
                    this.bindGrid();
                    this.SaveReport(reportName, true);
                    resetScheduleForm();
                }
                else
                {
                    Response.Write("Problem creating report name.  Please try again");
                }
            }
            catch (Exception ex)
            {
                logThis(ex.ToString(), EventLogEntryType.Error);
                Response.Write("Could not save report.  Click back and try again or contact your favorite QBERT support representative");
                //continue
            }
            finally
            {
                st.Dispose();
            }
        }
        else
        {
            Response.Write("Bad User or Report Name, cannot save.");
        }
    }

 

AD lookups provided email addresses.  I didn’t put the email addresses in our database, because that would just add another place users had to manage their email addresses, and employees already managed their emails through the company’s AD system. 

                using (DirectorySearcher ds = new DirectorySearcher("(samaccountname=" + user + ")"))
                {
                    
                    ds.PropertiesToLoad.Add("mail");
                    SearchResult sr = ds.FindOne();
                    if (sr == null)
                    {
                        return;
                    }
                    if (sr.Path.Length == 0)
                    {
                        return;
                    }
                    user = sr.Properties["mail"][0].ToString();
                }

The email was simple html, just formatting the results of the saved report query, run at that moment.

htmlReport.Append("<html><table cellspacing='0' border='1'style='border-collapse:collapse;font-size:9pt'>" + 
                        "<tr style='background-color:Silver;font-size:X-Small;'>");
                        foreach (DataColumn dc in dt.Columns)
                        {
                            htmlReport.Append("<td>" + dc.ColumnName + "</td>");
                        }
                        htmlReport.Append("</tr><tr>");
                        int x = 1;
                        foreach (DataRow dr in dt.Rows)
                        {
                            if (x == 1)
                            {
                                htmlReport.Append("<tr>");
                                x = x * -1;
                            }
                            else
                            {
                                htmlReport.Append("<tr style='background-color:AliceBlue;'>");
                                x = x * -1;
                            }
                            foreach (object fieldVal in dr.ItemArray)
                            {
                                if (dr.ItemArray[0].ToString().Equals(fieldVal.ToString()))
                                {
                                    htmlReport.Append("<td><a href='http://boitqueue/view.php?id=" +
                                        fieldVal.ToString() + "'>" + fieldVal.ToString() + "</td>");
                                }
                                else
                                {
                                    htmlReport.Append("<td>" + fieldVal.ToString() + "</td>");
                                }
                            }
                            htmlReport.Append("</tr>");
                        }
                        htmlReport.Append("</table></html>");

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