Brothers In Code

...a serious misallocation of .net resources

AsyncPostBackTrigger On A Control External To An Update Panel Doesn't Display An Update Progress Control

Consider the following code:


    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>   
    <div>
      <asp:Label ID="Label2" runat="server" Text="Label"></asp:Label>
      <asp:Button ID="Button2" runat="server" Text="Button" />
      <asp:Button ID="Button3" runat="server" Text="Button" />
      <asp:UpdatePanel ID="UpdatePanel1" runat="server">
   <Triggers>
     <asp:AsyncPostBackTrigger ControlID="Button2" />
   </Triggers>
      <ContentTemplate>
        <asp:Button ID="Button1" runat="server" Text="Button" />
        <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
      </ContentTemplate>
      </asp:UpdatePanel>
     <asp:UpdateProgress ID="UpdateProgress1" runat="server" AssociatedUpdatePanelID="UpdatePanel1">
      <ProgressTemplate>
       Please Wait
      </ProgressTemplate>
     </asp:UpdateProgress>
    </div>


//code behind:
  protected void Page_Load(object sender, EventArgs e)
  {
    Label1.Text = DateTime.Now.ToString();
    Label2.Text = DateTime.Now.ToString();

    if (Page.IsPostBack)
    {
      System.Threading.Thread.Sleep(2000);
    }
  }

In the above example, Button1 will do a partial page update for UpdatePanel1.  Button3 will cause a full postback.  Button two will also do a partial page update for UpdatePanel1 since it is listed as a trigger.  However, Button2 will not cause UpdateProgress1 to be displayed.

There is some help for this on the buttom of an article on codeproject.com, but I needed something for multiple controls.  My solution ended up being two functions:


  private void RegisterExternalTriggerFixForUpdateProgress()
  {
    String updateProgressWTriggerFix = @"
      var triggerMappings = new Array();

      function TriggerMapping(controlId, updatePanelId)
        {
          this.TriggerControlId = controlId;
          this.UpdatePanelId = updatePanelId;
        }
        function RegisterTriggerMapping(controlId, updatePanelId)
        {
          triggerMappings.push(new TriggerMapping(controlId, updatePanelId));
        }
        function GetTriggerMapping(control)
        {
          for(var i=0; i<triggerMappings.length; i++)
          {
            if(triggerMappings[i].TriggerControlId == control.id)
            {
              return triggerMappings[i];
            }
          }
          return null;
        }

        var prm = Sys.WebForms.PageRequestManager.getInstance();
        function CancelAsyncPostBack() {
            if (prm.get_isInAsyncPostBack()) {
              prm.abortPostBack();
            }
        }


        prm.add_initializeRequest(InitializeRequest);
        prm.add_endRequest(EndRequest);
        var postBackElement;
        function InitializeRequest(sender, args) {
            if (prm.get_isInAsyncPostBack()) {
                args.set_cancel(true);
            }
            postBackElement = args.get_postBackElement();
       
            var triggerMapping = GetTriggerMapping(postBackElement);
            if (triggerMapping != null) {
                $get(triggerMapping.UpdatePanelId).style.display = 'block';
            }
        }
        function EndRequest(sender, args) {
            var triggerMapping = GetTriggerMapping(postBackElement);
            if (triggerMapping != null) {
                $get(triggerMapping.UpdatePanelId).style.display = 'none';
            }
        }";

    this.Page.ClientScript.RegisterStartupScript(typeof(Page), "UpdateProgressWTriggerFix", updateProgressWTriggerFix, true);
  }
  protected void RegisterExternalAsyncTrigger(Control triggerControl)
  {
    this.Page.ClientScript.RegisterStartupScript(
      this.GetType(),
      triggerControl.ClientID,
      String.Format(@"RegisterTriggerMapping('{0}','{1}');",
        triggerControl.ClientID,
        UpdateProgress1.ClientID),
      true);
  }

Then to fix a particular AsyncPostBackTrigger control, you just need the following call in your Page_Load:


    //fix for async update from a control outside of the update panel
    RegisterExternalTriggerFixForUpdateProgress();
    RegisterExternalAsyncTrigger(Button2);

Regular Expressions In Oracle

More than once I've needed to find all non-ascii characters in a string.  In a .net regular expression this is relatively easy:

[^\x20-\x7F]

I thought that regex would port over reletively easily to Oracle - wrong.

First, I found more than one site reference the \x sequence.  However I just couldn't get it to work.

I then tried a brute force version:

[^A-Za-z 0-9 \.,\?''""!@#\$%\^&\*\(\)\-_=\;:<>\/\\\|\}\{\[\]`~]

To my surprise that didn't work either.  After struggling with it for a while I found the regex reference page from Oracle.  It states:

The following regular expression operators are allowed within the character list, any other metacharacters included in a character list lose their special meaning (are treated as literals):

  • Range operator '-'
  • POSIX character class [::]
  • POSIX collating sequence [. .]
  • POSIX character equivalence class [= =]

So in other words, character sets are pretty limited in what they can have in it.  If a backslash is treated as a literal and not as an escape sequence, then that explained why my brute force version didn't work - sort of.  I did eventually get it to work but for some reason I had to remove the ']' within the character class.  So that wasn't perfect either.

Finally I gave it one last shot.  Rather than the hex range, I tried the character range for the same values - from space to tilde (look at an ascii table of values and this will make sense).

[^ -~]

Finally this seemed to work as expected (keep in mind thought that character ranges are affected by the NLS_SORT variable - anything other than binaray, might yield unexpected results).  I ran the following query as a test:


select regexp_replace('This is a test '||chr(191), '[^ -~]', '$') from dual;

The only thing left was to handle non-printable chars like crlf and tabs.  I new the hex code was out so I generated the first ascii character with the chr function to replace the space:


select regexp_replace('This is a test crlf'||chr(10)|| 'blah', '[^' || chr(1) ||'-~]', '$') from dual;

Update: After I wrote this article I found a section on regular-expressions.info that explains some of the oracle differences.

 

Simple Stopwatch/Timer in Oracle

I needed to record the execution time in a stored proc.  I thought this would be straight forward until i realized the resolution on an oracle date datatype is only to the second.  Timestamp has the additional resolution (as well as timezone if  you want it).  Even cooler is that when you subtract two timestamps you get an interval datatype with the difference.  From there, however, I wanted to pass out the value in milliseconds, but that required each component of the interval to be extracted, multiplied, and added together.  Here's some sample code of the whole process.


declare
  v_start timestamp := systimestamp;
  v_end timestamp;
  v_elapsed interval day to second(3);
  v_elapsed_ms number;
  i number;
begin
  for i in 1..100000 loop
    v_elapsed_ms := 0;
  end loop;

  v_end := systimestamp;
 
  v_elapsed := v_end - v_start;
  v_elapsed_ms :=
    ( extract(hour from v_elapsed)* 3600 +
      extract(minute from v_elapsed)* 60 +
      extract(second from v_elapsed) ) * 1000;

 
  dbms_output.put_line('elapsed time:');
  dbms_output.put_line(to_char(v_elapsed));
  dbms_output.put_line(to_char(v_elapsed_ms));
end;

Keyboard Input Differences Between Winforms and WPF

I'm currently converting a winforms app into a WPF app and discovered the key events are significantly different in WPF.  Here's a few tips and things to look out for:

  • The old KeyPressEventArgs.KeyChar is just a char and included the modifier key stroke (CTRL-S = 'S'-64 = (Char)19, SHIFT-S = 's'-32 = 'S').
  • The new KeyEventArgs.Key is an enum that includes every key.  Modifier keys are their own key stroke.
  • (Char)KeyInterop.VirtualKeyFromKey(e.Key) will get you the char value from the new enum, although it is always upper case. 
  • Since modifier keys raise an event in WPF, I find I'm ignoring them alot:

    //ignore modifier keys
    if (e.Key == Key.LeftCtrl ||
            e.Key == Key.RightCtrl ||
            e.Key == Key.LeftAlt ||
            e.Key == Key.RightAlt ||
            e.Key == Key.LeftShift ||
            e.Key == Key.RightShift)
    {
      e.Handled = false;
      return;
    }

Generating A WCAT Script With Fiddler

The Web Capacity Analysis Tool (WCAT) is simple tool for stressing a web server.  It's fairly straight forward, but because it's UI is very minimal, it can be a little tedious to set up a test.  Microsofts older Web Application Stress Tool (WAS) had a nice feature to record a browser session by acting as a proxy and so I did a quick google search to see if I could find something similar for WCAT.  As further testimate to Fiddler's awesomeness, "thomad" has written a great WCAT Fiddler plugin that does exactly what I want.

PureText - Pasting Without Formatting

It's always driven me nuts that the default paste method in most programs includes the formatting of the original source.  Later MS Office programs finally have some options to paste "using the destination format", but that didn't help me when pasting into things like this BlogEngine.net editor.  The quick fix was to paste first into notepad, but I finally had enough and googled a better solution.  Sure enough, a guy named Steve Miller created a nice little tray app called pure text.  You copy as you always would but substitute windows-v for ctrl-v.  Exactly what I was looking for!

Keep a Winforms Application in the Foreground

I needed a way to force an application to stay on top.  I thought it would be as simple as "this.Activate" in the form's deactivate event.  It looked like it was trying to work - when clicking out of the app, it's start bar button would blink, but it wouldn't bring the app back on top.  I then proceeded to try just about everything under the sun:

  • Form.Activate
  • Form.BringToFront
  • Form.Focus
  • Form.Show
  • [DllImport("User32.dll")]
    public static extern Int32 SetForegroundWindow(int hWnd);

Everything seemed to do the same thing.  Finally I thought that maybe I was trying to refocus the form too soon.  I decided to create a timer and delay the Activate call:

//declare the timer
private System.Timers.Timer restoreFocusTimer = new System.Timers.Timer();

//setup the timer in the constructor
restoreFocusTimer.AutoReset = false;
restoreFocusTimer.SynchronizingObject = this;
restoreFocusTimer.Interval = 1000;
restoreFocusTimer.Elapsed += new System.Timers.ElapsedEventHandler(restoreFocusTimer_Elapsed);

//define the handler
void restoreFocusTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
  this.Activate();
}


//enable the timer in the deactivate event (I also added a preprocessor directive so i could disable it when debugging.
private void MainForm_Deactivate(object sender, EventArgs e)
{
  if (!this.formClosing)
  {
    #if !NOALWAYSONTOP
    restoreFocusTimer.Enabled = true;
    #endif
   }
}

 

Oracle Indexes - Nulls and Function Based Indexes

I had a hard time creating a couple of indexes for some oracle tables.  Things I thought for sure would work in SQL Server just would not work in oracle.  Consider the following query:


select
  slh_label_batch_id,
  trunc(min(expected_ship_date)) as Expected_Ship_Date,
  trunc(min(actual_ship_date)) as First_Ship_Date,
  nullif(max(nvl(trunc(actual_ship_date), to_date('1-dec-9999'))),to_date('1-dec-9999')) as Last_Ship_Date,
  count(actual_ship_date) as Completed_Ship_Count,
  count(slh_label_batch_id) as Expected_Ship_Count
from ship_label
group by slh_label_batch_id
having min(trunc(expected_ship_date)) between trunc( to_date('1-mar-2009','dd-mon-yyyy')) and trunc(to_date('31-mar-2009','dd-mon-yyyy'))
order by min(expected_ship_date) desc

I thought i'd speed up the above with the following index:


create index ix_ship_label_batch on ship_label(slh_label_batch_id, expected_ship_date, actual_ship_date);

To my surprise, explain plan still said it was doing a full table scan.  This didn't make any sense to me - the index a "covering" index for this query, which means it shouldn't need the table at all.

After some time i descovered that oracle will exclude index rows that are all nulls.  Even still, I'm not looking for nulls, right?  Wrong, no nulls would make the group by return one less row.  The fix was to add one not null column to the index.  Unfortunately, there wasn't a single not null column on the table (ugghh).  The simple fix was to use Oracle's function-based indexing feature and index the result of a non-null function.  My new index was:


create index ix_ship_label_batch on epi_ship_label, label_batch_id, expected_ship_date, actual_ship_date, nvl(confirm_flag,'N'));

 

Cool Way to Generate a Date Dimension Table in Oracle

Say you want a weekly sales report grouped by each day and you want to show days with zero sales. Usually this is accomplished by creating and joining to a date table that has one row for each day. If you're going to be doing this report all the time or need additional groupings like quarter or fiscal year, than it makes sense to create this table. But sometimes I need to do this for one report in a database that doesn't already have this table. For oracle, Uli Bethke describes what I think is the best way to date. In short, it's clever use of oracle's 'connect by' clause to recursively join dual to itself. For example, the following would generate all days in march:

SELECT
trunc(to_date('1-mar-2009','dd-mon-yyyy')) + NUMTODSINTERVAL(n,'day') AS Full_Date
FROM (
select (level-1) n
from dual
connect by level-1 <= trunc(to_date('31-mar-2009','dd-mon-yyyy')) - trunc(to_date('1-mar-2009','dd-mon-yyyy'))
);

Is Your Next Programmer a Moron?

We have been interviewing for a .net developer position for quite a while - way too long in fact.  What I thought would be a quick interview process has turned into a very rude awaking.

When I was looking for a job I was contacted by a contractor with a position that interested me.  I was warned however, that the interview process included a 4 hour programming test.  Many bodies had been thrown at this test and very few succeeded.  I try not to be arrogant but I do like to think that I'm good at my job.  Plus I have no certifications and no big name companies on my resume so I usually welcome the chance to prove myself.  The test wasn't rocket science but it wasn't simple either.  It was a simple recursion/stack problem, some database queries, a basic dynamic web page problem, and creating a functional calendar.  It was a lot of time to give a prospective employer but when I got the job I thought the test was well worth it after so many other companies didn't even give me a phone call or an email.

OK, a year later I'm looking to clone myself.  As much as I thought the test was warranted, I also thought that 4 hours is a really long time.  I created a trimmed down version that was nothing more than sql queries and a simple databinding exercise.  After a few candidates we decided it was too data-centric.  My boss forwarded me the fizz-buzz question.  I just couldn't believe what it's author was saying; senior level programmers can't write this trivial block of code?  As more of an experiment than anything, we added in our own variation of fizz-buzz.

And this is where the horror starts.  The very next candidate came in, sat down for 20 minutes, and asked to leave citing "personal reasons" without writing a single line of code.  I couldn't believe it.  I examined all the possibilities: Maybe she didn't understand the mod operator?  I suppose you could do years of CRUD style programming and never need it?  Maybe she couldn't read English?  Maybe she was a VB.net programmer (the test is in C#)?  But then there was always the last option - this 10 page resume couldn't actually write code.

We had another come in and at least attempt it but also failed miserably.  Another came in and passed fizz-buzz, but couldn't write a database query to save his life.  Another came in and did do well on both fizz-buzz and the queries.  He didn't even know ASP.net, but because he at least seemed like he had his head screwed on straight we were willing to train him.  But problems with his contracting house kept him from coming aboard.

I couldn't get over that fizz-buzz relies very heavily on a single operator.  I think it was denial more than anything.  I couldn't help but thinking something is wrong with the test instead of the candidates.   Consequently, I added another simple question.  It only required a boolean be returned if a second phrase could be made from the letters of the first phrase.  I figured if they couldn't do fizz-buzz I'd give them a second attempt.

Originally we had advertised only a 3 month contract and we theorized that the short contract was one of the reasons behind the substandard pool we were pulling from.  We upgraded the position to a 6 month contract to hire and called back a promising applicant that originally turned the short contract down.  I started with him just like I did with everybody else; I explained each question, showed him where the answers were (yes we display what the exact answer should look like), went over any hang-ups that I thought he might have, and reiterated that he can ask any question.  I didn't even get thru explaining my new anagram question when he stopped me and said something like "if this is the what you're looking for I don't think I want to work here."  I reiterated that it's OK if there were parts that he couldn't do.  He didn't care.  I brought him back to my boss and he asked what he didn't like about the test, he referred to it as a bunch of puzzle questions that he needed google for and that we should look at his resume to the systems he's "architected."  I walked him out and he described his (superior) interview process of asking questions about polymorphism and factory patterns.  I just wanted to slap him and say "Don't you understand?  It's people like you why we do this!  Yes you can use some big words and put fancy projects on your resume.  You can call yourself a senior developer and an architect.  But you can't write code!!"

On my way back to my desk I realized my blood was boiling.  I understood why good developers either can't find a job or can't get the pay they deserve.  I understood why so many projects fail.  I understood why I spend so much time cleaning up crap.  I understood why we have been trying to fill a position for 2 months.  It's because it would appear that the ratio of fake to genuine developer resumes out there is about 5:1 or worse!  And the really bad news is that these simple tests don't even guarantee that you have a good programmer.  They only tell you if you have a programmer at all. 

The moral of the story?  If you want to build a house, hire a guy that can hammer a nail.  If you want to build a car, hire a guy that can loosen a bolt.  If you want to build a piece of software, than hire a developer that can write programming 101 code.  Test your programmers!