in

ASP.NET Weblogs

This Blog

Syndication

ShowUsYour<Blog>

Irregular expressions regularly

March 2004 - Posts

  • A tale about a victory involving ASP.NET and encapsulation

    There are challenges in building cool, dynamic Web UI's

    A short while back I took on the task of migrating a VB6 windows client application into an ASP.NET app. It's quite an exciting task because of the fact that Windows is such a rich UI environment whereas the web has always been seen quite limited in comparison.

    Throughout the course of the conversion I had to solve many challenging problems such as: UI state management, User preference state management, emulating some of the complex UI Widgets which had been used in the Windows client and messaging event notifications between each component both on the server and in the client. To do this I borrowed heavily from Metabuilders and developed some of my own. I've since made the source code to some of these public:

    There are also unique and interesting problems to solve

    One of the most challenging problems that I had to solve was that, the app. which I was converting consisted of thousands of input forms. Each form was dynamically generated and had both it's schema and data stored in the database. To render a form I had to: A) get the UI schema information from one set of tables; B) dynamically construct UI elements and add them to the WebForm; C) fetch any previously input data and read it into the controls. There were also instances where more than one result could be returned so I had to build a custom pager to "page" through the data. The information about each UI element included:

    LabelInformation:     lblWidth, lblHeight, lblCaption, lblLeft, lblTop, LabelOnly
    ControlInformation:   ctlType, ctlDataField, ctlWidth, ctlHeight, ctlLeft, ctlTop
    MiscData:             StatusBarText, DataFormat
    

    In the Windows application, rendering controls was very easy because they could create a container panel and offset the position of the UI elements based on 0,0 representing the top, left corner of the panel. In a web application things weren't quite that simple because the UI is much more fluid. To get around this I decided to render each dynamic form in an embedded IFRAME so that I could always count on 0,0 representing a reliable document position rather than having to calculate varying page offsets based on the structure of the page which contained the form. I also decided to use Dennis Bauer's DynamicControlsPlaceholder:

        http://www.denisbauer.com/ASPNETControls.aspx

    This control helps to "cushion" you from some of the nasties associated with adding controls to pages at run-time.

    The IFRAME approach was neat because it enabled me to easily pass arguments in from the containing document via the querystring and have a "Sub-Form" which hosted the DynamicControlsPlaceholder be responsible for doing the lookup in the database for the form information and determining how to layout the controls based on whether I was editing or viewing and also to handle any paging which needed to be done.

    In many instances the actual data records being displayed in the dynamic forms was query'able from an interface which was hosted in the container document so, for that reason I decided to de-couple the form-schema and form result-data via the Session object into 2 separate state objects: Session("DynamicFormSchema") and Session("DynamicResultData"). This way, my dynamic form needed only to check for the existance of them to kick off the rendering process and this also ensured that the Dynamic Form was only responsible to rendering/layout logic. The logic therefore became:

    ' PSUEDO-CODE FOR PAGE_LOAD
    If Not IsPostBack Then
        ' interrogate the querystring for out "mode"
        SetMode( Request.QueryString("Mode") )
        RenderForm( Session("DynamicFormSchema") )
        DisplayFormData( Session("DynamicResultData") )
        
    End If
    
    ' PSUEDO-CODE FOR RenderForm
    For Each dr As DataRow In Session("DynamicFormSchema")
        
        Dim ctl As WebControl = DynamicControlFactory.Make( dr )
        ctl.ID = dr.Item("DataField").ToString
        ctl.Attributes.Add("ColumnIndex", dr.Item("ColumnIndex").ToString)
        DynamicCanvass.Controls.Add(ctl)
    Next
    
    ' PSUEDO-CODE FOR DisplayFormData
    Dim data As DataRow = dtDynamicResultData.Rows(RecordPager.SelectedIndex - 1)
    For Each ctl As Control In DynamicCanvass.Controls
        Select Case True
            Case TypeOf ctl Is TextBox:
                Dim colIdx As Integer = CInt(CType(ctl, TextBox).Attributes("ColumnIndex"))
                If Not row.Item(colIdx) Is Nothing Then
                    CType(ctl, TextBox).Text = data.Item(colIdx).ToString
                End If
                '....
                
            Case TypeOf ctl Is CheckBox:
                '....
        End Select
        CType(ctl, WebControl).Enabled = Me.IsEditMode
    Next
    

     

    Handling paging events was simple too...

    Private Sub RecordPager_SelectedIndexChanged() Handles RecordPager.SelectedIndexChanged
        RenderForm( Session("DynamicResultData") )
    End Sub
    

     

    I thought that I'd blog this so that anybody who had an interest in building this type of dynamic UI's in WebForms could get a "from 40,000ft" overview of the approach which I took. All-in-all it was an incredibly painless experience. I think that the key to something such as this is encapsulation, something that ASP.NET has provided us with the ability to do quite easily!. As an example of this, it was very reassuring to be building atop such a useful control as the DynamicControlsPlaceholder and I'm sure that this took away many potential sources of angst.

  • A great checklist for migrating to ASP V2

    Posted Mar 31 2004, 01:48 PM by digory with no comments
    Filed under:
  • Maintaining monolithic regex expressions

    [Cross posted on my regex blog - http://blogs.regexadvice.com/dneimke/archive/2004/03/31/877.aspx - please leave any comments there]

    I think that it speaks volumes about the readability and maintainability of a regex which is several hundred characters long when it receives 3 comments within 8 hours picking up on errors/improvements. Picking up where I left off yesterday, I'd like to show how to further improve the maintainability of the expression. Yesterday I showed the BNF for a DateLiteral in VB and I then expanded that out into a monolithic pattern ( The pattern on RegexLib.com ):

    DateLiteral ::= # [ Whitespace+ ] DateOrTime [ Whitespace+ ] #
    DateOrTime ::=
       DateValue Whitespace+ TimeValue |
       DateValue |
       TimeValue
    DateValue ::=
       MonthValue / DayValue / YearValue |
       MonthValue – DayValue - YearValue
    TimeValue ::=
       HourValue : MinuteValue [ : SecondValue ] [ WhiteSpace+ ] [ AMPM ]
    MonthValue ::= IntLiteral
    DayValue ::= IntLiteral
    YearValue ::= IntLiteral
    HourValue ::= IntLiteral
    MinuteValue ::= IntLiteral
    SecondValue ::= IntLiteral
    AMPM ::= AM | PM
    

     

    A better approach would be to build sub-expressions and to roll them up into a complete production in exactly the same manner as the grammar does it. Start with the Leaf nodes:

    Private re_Whitespace As String = "\s"
    ' Patterns for the components of TimeValue
    Private re_HourValue As String = "(?'HourValue'(0?[1-9])|1[0-9]|2[0-4])    (?# Hour 01 - 24 )"
    Private re_MinuteValue As String = "(?'MinuteValue'[:]0?[1-9]|[1-5]\d|60)     (?# Minute 01 - 60 )"
    Private re_SecondValue As String = "(?'SecondValue'[:]0?[1-9]|[1-5]\d|60)    (?# Second :01 - :60 )"
    Private re_AMPM As String = "(?'AMPM'[AP]M)"
    ' Patterns for the components of DateValue
    Private re_MonthValue As String = "(?'Month'(0?[1-9])|1[0-2])      (?# Month 01 - 12 )"
    Private re_DayValue As String = "(?'Day'0?[1-9]|[12]\d|3[01])    (?# Month 01 - 31 )"
    Private re_YearValue As String = "(?'Year'\d{4})"
    

     

    Now that I've emulated the BNF for the leaf nodes I can start rolling up into the next 2 productions ( TimeValue and DateValue )

    ' TimeValue ::= HourValue : MinuteValue [ : SecondValue ] [ WhiteSpace+ ] [ AMPM ] 
    Private re_TimeValue As String = _
        "(?'TimeValue'" & re_HourValue & _
            re_MinuteValue & _
            re_Whitespace & "*" & _
            re_SecondValue & "?" & _
            re_AMPM & "?" & _
        ")"
    ' (?# DateValue ::= MonthValue / DayValue / YearValue | MonthValue - DayValue - YearValue )
    Private re_DateValue As String = _
        "(?'DateValue'" & _
            re_MonthValue & _
            "    (?'Sep'[-/])    (?# Date separator '-' or '/' )" & _
            re_DayValue & _
            "    \k'Sep'         (?# whatever date separator was previously matched )" & _
            re_YearValue & _
        ")"
    

     

    That was very painless and didn't really add that much literal text to the overall production. Maintaining either TimeValue and DateValue is still relatively painless because you can quickly tweak stuff in the leaf node expressions. I'll now roll-up again into the DateOrTime production:

    ' (?'DateOrTime'  (?# DateOrTime ::= DateValue Whitespace+ TimeValue | DateValue | TimeValue )
    Private re_DateOrTime As String = _
        "(?'DateOrTime'" & re_DateValue & re_Whitespace & "+" & re_TimeValue & "|" & _
        re_DateValue & "|" & re_TimeValue & ")"
    

     

    Again, this has added almost zero extra pattern to maintain; just the quantifiers and the alternation operator to join the sub-expressions (nonterminals). One last roll-up and we're done:

    ' DateLiteral ::= # [ Whitespace+ ] DateOrTime [ Whitespace+ ] #
    Private re_DateLiteral As String = "#" & re_Whitespace & "?" & re_DateOrTime & re_Whitespace & "?#"
    

     

    The pattern is now ready for use:

    Dim test As String = "#1:45:39 PM#"
    Dim re As New Regex(re_DateLiteral, RegexOptions.IgnorePatternWhitespace)
    MessageBox.Show(re.Match(test).Success)
    

     

    So, although the original pattern looked quite lage, in reality you can use the techniques which I've shown here to make large patterns very simple to maintain.

    Posted Mar 31 2004, 09:09 AM by digory with no comments
    Filed under:
  • Dear Vashili,

    In reply to your question (which has been posted via my blog nearly 15 times today).  Yes, I do know how to Shutdown a computer through code.  Learn how to use your resources better my friend!


    Sender: Vashilli
    Url: (removed)

    IP Address: 212.158.233.234
    =====================================

    Hi

    Sorry if this is a little off topic, but would anybody know of any good examples on the web of how to perform an automatic reboot using C#?

    Posted Mar 30 2004, 07:51 PM by digory with 5 comment(s)
    Filed under:
  • Using BNF to create regex's

    While reading the VB Language Spec today I decided to attempt to follow the grammar syntax for matching DateTime Literals:

         http://msdn.microsoft.com/library/en-us/vbls7/html/vblrfvbspec2_4_6.asp?frame=true

    DateLiteral ::= # [ Whitespace+ ] DateOrTime [ Whitespace+ ] #
    DateOrTime ::=
       DateValue Whitespace+ TimeValue |
       DateValue |
       TimeValue
    DateValue ::=
       MonthValue / DayValue / YearValue |
       MonthValue – DayValue - YearValue
    TimeValue ::=
       HourValue : MinuteValue [ : SecondValue ] [ WhiteSpace+ ] [ AMPM ]
    MonthValue ::= IntLiteral
    DayValue ::= IntLiteral
    YearValue ::= IntLiteral
    HourValue ::= IntLiteral
    MinuteValue ::= IntLiteral
    SecondValue ::= IntLiteral
    AMPM ::= AM | PM
    

     

    The resulting pattern can be seen (fully commented) here:

         http://www.regexlib.com/REDetails.aspx?regexp_id=638

    Posted Mar 30 2004, 02:50 PM by digory with 2 comment(s)
    Filed under: ,
  • Quotes about Trees

    He who plants a tree, plants a hope.
    -- Lucy Larcom, Plant a Tree


    Either make the tree food, and his fruit good; or else make the tree corrupt, and his fruit corrupt: for the tree is known by his fruit.
    source: Matthew (ch. XII, v. 33)


    I think that I shall never see A poem as lovely as a tree. . . . . Poems are made by fools like me, But only God can make a tree
    source: Pindaric Ode on the Death of Sir H. Morison

    Two roads diverged in a yellow wood,
    And sorry I could not travel both
    And be one traveler, long I stood
    And looked down one as far as I could
    To where it bent in the undergrowth.
    -- Robert Frost, Two Roads


    It was the noise Of ancient trees falling while all was still Before the storm, in the long interval Between the gathering clouds and that light breeze Which Germans call the Wind's bride.
    source: The Fall of the Trees


    Why are there trees I never walk under but large and melodious thoughts descend upon me?
    -- Walt Whitman, Song of the Open Road, 1856

    Posted Mar 30 2004, 09:19 AM by digory with no comments
    Filed under:
  • Expressing the grammar for my lexer

    The other day I started blog'ging a series of entries regarding parsing text:

    1. Building a basic Parser : http://weblogs.asp.net/dneimke/archive/2004/03/24/94832.aspx
    2. Parsing into Nodes : http://weblogs.asp.net/dneimke/archive/2004/03/24/95046.aspx

    I received some feedback from those entries from Scott and Frans correcting me on some erroneous terminology and pointing me to some useful reading material:

    I started reading the book yesterday and I've read as far as chapter 3 so far; it's a fascinating book and has already explained so many principles which I never even knew about.  For example it lists out the following phases of compilers:

    • Lexical analyzer
    • Syntax analyzer
    • Semantic analyzer
    • Intermediate code generator
    • Code optimizer
    • Code generator

    In the next entry relating to "Building a basic Parser" I'm going to focus on a context free grammer called BNF (Backus-Naur Form) which is a notation used to describe the syntax of text and is especially useful when building "lexers". As an aside, when learning about BNF I came across this cool page which outlines the BNF notation for the Visual Basic .NET language:

        Visual Basic .NET Grammar Summary

  • Terrarium : Some data about saving energy

    As I mentioned in my last post, it's important to conserve energy in the Terrarium game. So, if you are moving - maybe looking for your next meal - and are not under any threat of attack, it is wise to meander along rather than sprint. Likewise, it is much better writing code which allows you to avoid being attacked as opposed to running away from a fight.

    In my code I manage my moving and speed through some helper routines, the most useful of these allows me to move away from another organism (either animal or plant species).

    Private Sub MoveAwayFrom(ByVal org As OrganismState)
        Dim comingFrom As Direction = DirectionRelativeToMe(org)
        If comingFrom = Direction.DownLeft Then
            BeginMoving(New MovementVector(New Point(Position.X + 10, Position.Y + 10), mCurrentMovingSpeed))
        ElseIf comingFrom = Direction.UpRight Then
            BeginMoving(New MovementVector(New Point(Position.X - 10, Position.Y - 10), mCurrentMovingSpeed))
        ElseIf comingFrom = Direction.UpLeft Then
            BeginMoving(New MovementVector(New Point(Position.X + 10, Position.Y - 10), mCurrentMovingSpeed))
        Else
            BeginMoving(New MovementVector(New Point(Position.X - 10, Position.Y + 10), mCurrentMovingSpeed))
        End If
    End Sub
    

     

    As you can see, the speed with which I move is determined before calling this routine and can be based upon an "Urgency" calculation to determine whether I should be moving at my slowest speed, my fastest speed, or somewhere in between. Here is a snippet of code which sits in my Attacked event handler and adjusts the speed to my fastest speed if I really need it:

    mCurrentMovingSpeed = Me.c_MaxSpeed
    MoveAwayFrom(attacker)
    

     

    All-in-all, controlling your energy is very important. I ran two tests on a new build of Herbert and used a global variable to track how far he moved from birth to death and how old he was when he died. In the first test, he is born and moves at full speed from that moment on, in the second test he moved at a speed of 4; here's a brief rundown of the results:


    In both cases Herbert joined the Terrarium with approx 40% energy

    /

    When speed was set to 4, Herbert was able to complete approximately 293 turns with each turn taking up just over .3% of energy. In contrast, when the speed was set at 32, Herbert was only able to complete between 3 and 6 turns.

    When speed was set to 4, Herbert was able to travel a distance of 982 compared to only 140 when speed was set at 32.

  • Getting ready for life in the Terrarium

    I've been working on Herbert (my Terrarium Herbivore) this week and I've made some pretty good progress. When I started I just grabbed a Herbivore Skeleton and ran it - I actually introduced it into and environment chock-full of plant and no Carnivores just to observe how it acted of it's own accord. I noticed that, although they had no competition, my Herbert species would eventually become extinct after several hours due to poor ability to reproduce. In fact, I ran the game for 10 hours and 80 Herberts reduced to extinction after about 12 hours.

    Chapter 10 of the Advanced Developer Documentation talks about the requirements for reproduction; they are:

    1. You must be Ready to grow. That is, your current State must be IsMature and the current GrowthWait must be zero.
    2. You must a have normal or greater energy level
    3. You must have room to grow - this could be up to 8px distance from any plant or creature.

    So, from that, I was able to determine that the cause of my reproduction issues was that I never quite had enough room to reproduce because I was always parked up against a Plant feeding. So, I changed my original algorithm:

    Original algorithm
    If Me.CanEat Then
        BeginEating(targetPlant)
    End If
    
    New algorithm
    If ShouldEat() Then
        ....
    Else
        ' this allows me to back off which increases my liklehood of reproduction
        If NearToPlant() Then
            MoveAwayFrom(targetPlant) 
        End If
        If CanReproduce Then
            BeginReproduction(Nothing)
        End If
    End If
    

    I recompiled Herbert1 and ran it. After 30 minutes in an identical environment to the original Herbert my population had grown from 10 to 240! It stayed at ~240 for the remainder of the game (due to sickness etc) so, this improvement solved my reproduction issues.

    My next tasks are to build in some smarts regarding the conservation of energy, early detection of predators and escaping algorithms.

    Posted Mar 27 2004, 08:38 AM by digory with 1 comment(s)
    Filed under:
  • Me and the gang

    This morning I asked one of my workmates to take my photo so that I could use it online and stuff - which, he did.  Next thing this was circulating the office:

         http://www.flws.com.au/MeAndTheGang.jpg

    I sent that link to a friend who works in "the big house" and his comment was: "Yeah, it's easy to tell you were the greenest programmer in the group.  <snicker />".... very nice guys! :-)

    Posted Mar 26 2004, 02:38 PM by digory with 3 comment(s)
    Filed under:
More Posts Next page »