Friday, 11 December 2009

String Parsing in C#

Regular Expressions are powerful things, but they are difficult to learn when your main objective is to produce business-critical information systems and you must also keep up to date with the latest .net features, the latest browers and quirks, the latest version of CSS, new Javascript libraries.....I think you get the picture. It's something else to learn. This is the reason we google our parsing requirements and re-use existing regular expressions, without actually understanding them.

I had a couple of requirements of my own and couldn't solve the more complex parsing with regular expressions. So, I've knocked up a light-weight string parser to satisfy my needs. I thought I'd share it with you. Here's the class in C#:

public class StringParser {

    private string _s;
    private int _len;
    private int _plen;
    private int _lPos;
    private int _pos;
    private char _c;

    public StringParser(string s) {
        _s = s;
        if (_s != null) {
            _len = s.Length;
            _plen = (_len - 1);
            if (_len > 0) {
                _c = s[0];
                _pos = -1;
            }
        }
    }
  
    /// 
    /// Call to determine if there is still string to parse and to
    /// advance the char position by one
    /// 
    public bool Parse() {
        if (_pos < _plen) {                
            _c = _s[++_pos];
            return true;
        }
        return false;
    }

    /// 
    /// Advances the parser by a given number of chars.
    /// 
    /// number of chars to advance by
    /// A boolean true if successful, otherwise a boolean
    /// false if this advance pushes the position past the max len
    /// of the parsing string.
    public void AdvancePos(int places) {
        int npos = _pos + places;
        if (npos >= _len) {
            throw new IndexOutOfRangeException();
        }
        _pos = npos;
        _c = _s[_pos];
    }

    /// 
    /// Call to return the current char.
    /// 
    public char CurrentChar {
        get { return _c; }
    }

    /// 
    /// Call to check if parser is positioned on given sub string.
    /// 
    /// The sub string to check for at the current
    /// position.
    /// A boolean true if sub string is found at current
    /// position, otherwise false       
    public bool IsParsing(string sub) {
        if ((_pos + sub.Length) < _len) {
            return (_s.Substring(_pos, sub.Length) == sub);
        }
        return false;
    }

    /// 
    /// Call to read a portion/sub string of the string.  This
    /// method advances the position of the curr char.
    /// 
    /// Represents a sub string, or multiple
    /// sub strings(delimiters) to read up to.  If multiple sub
    /// strings are passed then it is the sub string closest to
    /// the current position which is used.
    /// If boolean true is passed then the
    /// sub string will be included in the returned string.
    /// 
    /// The sub string.        
    public string Read(string[] subArr, bool incl) {
        string read = null;
        int spos = _len;
        string wdel = string.Empty;
        foreach (string s in subArr) {
            int si = _s.IndexOf(s, _pos + 1);
            if (si > -1 && si <= spos) {
                spos = si;
                wdel = s;
            }
        }
        if (spos > _pos && spos < _len) {
            // put pos on last char read
            int npos = _pos + (spos - _pos);
            read = _s.Substring(_pos, (npos - _pos)
                + ((incl) ? wdel.Length : 0));
            _pos = npos;
            _c = _s[_pos];
        }
        return read;
    }  

    /// 
    /// Call to advance current position to a given sub string.
    /// This method advances the position of the curr char.
    ///  
    /// The string to find and move to.
    /// If boolean true is passed and the string
    /// is found, then position will advance by one char, otherwise
    /// the position will remain on the found string.
    /// A boolean true if 's' is found, otherwise false.
    ///         
    public bool MoveTo(string sub, bool adv) {
        int npos = _s.IndexOf(sub, _pos + 1);
        if (adv) {
            npos = npos + sub.Length;
        }
        if (npos > _pos && npos < _len) {
            _pos = npos;
            _c = _s[_pos];
            return true;
        }
        return false;
    }

    /// 
    /// Skip forwards to next non-ws char, but only if currently
    /// positioned on a space.  If curr char is not a space then
    /// the position isn't advanced.
    /// 
    public void Squash() {            
        while (_c == ' ' && Parse()) {
        }            
    }
}

Friday, 4 December 2009

Consistent/Equal-Height Columns in CSS

Here is one technique for creating a consistent/equal height column layout in CSS. This takes advantage of relative and absolute positioning.

Copy the HTML and CSS below and paste into a text file. Save the file as default.htm and double click. Add and remove lorem ipsum text to the content div and watch the right-hand columns grow and shrink consistently.

<html>
    <head>
        <title>Equal Columns in CSS from designcodetest.blogspot.com</title>
        <style type="text/css">
            body{                
                text-align:center;            
            }
            #mainWrap{
                margin:0 auto;
                width:964px;
                text-align:left;
            }
            #head{
                height:100px;
                background:#999;
            }
            #bodWrap {
                position:relative;                
            }
            #content{
                background:#fff;
                padding:10px 370px 10px 10px;
            }
            #midCol{
                background:#f6e5e4;
                position:absolute;
                right:200px;top:0;bottom:0;
                width:160px;
            }
            #rightCol{
                background:#e342e5;
                position:absolute;
                right:0;top:0;bottom:0;
                width:200px;
            }
            #foot{
                background:#ccc;
                width:964px;
                margin:0 auto;
                height:50px;
            }
        </style>
    </head>
    <body>
        <div id="mainWrap">
            <div id="head">
                <h1>Equal-Height Columns Sample</h1>
            </div>
            <div id="bodWrap">
                <div id="content">
                    <p>
                        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec gravida hendrerit odio, non rhoncus magna blandit id. Donec ac metus augue, in pellentesque eros. Phasellus eget mollis erat. Nullam condimentum ornare arcu, et tempus nisl elementum a. Donec molestie placerat felis, non hendrerit tellus sodales sed. Donec mollis sollicitudin purus, ut tempus augue euismod at. Aliquam eget metus ipsum. Pellentesque mollis semper elit, et accumsan eros ullamcorper quis. Maecenas at felis nisl, id viverra felis. Curabitur ullamcorper sem vel orci tempor lobortis. Vivamus viverra lacus in justo convallis tempus. Sed ullamcorper arcu sit amet orci pulvinar tempus. Quisque pretium bibendum nisl eu gravida. Nunc congue metus quis nisl hendrerit sodales. Nunc a nibh scelerisque sapien faucibus sagittis id sit amet mi. 
                    </p>
                </div>
                <div id="midCol">
                    <span>Col 1 with equal/consistent height</span>
                </div>
                <div id="rightCol">
                    <span>Col 2 with equal/consistent height</span>
                </div>
            </div>
            <div id="foot">
                <span>Example by Mark Graham, <a href="http://designcodetest.blogspot.com/" title="Cool articles and tips about design patterns, c#, asp.net, gui design, css, javascript, ajax...">http://designcodetest.blogspot.com/</a></span>
            </div>
        </div>
    </body>
</html>

Wednesday, 2 December 2009

Accurate DATETIME Range Searching in SQL Server

A common aspect of transactional and analytical programming in information systems is searching on a date range. When performing a search it's important that our SQL is accurate. Take a look at the following SQL that will exclude rows that should be included in the results.

where SubmittedTime >= @DateFrom
and SubmittedTime <= @DateTo

This isn't difficult to fix, but it's something that is often overlooked, or missed.

What's wrong with this where clause?

Let's try a scenario: We are working with Win Forms or Web Forms and we need some neat method of allowing a user to pick a date range so they can get results. We drop a couple of pickers on a form and code accordingly. The datetime values received from said controls will have their time portions set to 00:00:00 because these controls, by default, allow us to pick dates. Let's face it, users aren't really concerned with time when they're producing month end reports.

So, when a user picks 1st Nov 2009 to 30th Nov 2009 they want the search to be inclusive, meaning they want data from the 1st and from the 30th to appear in the report. Here's the problem: where SubmittedTime <= @DateTo is searching for all rows whose SubmittedTime is less than or equal to the 30th November 2009 at midnight. Will the data that John Smith entered on the 30th Nov 2009 at 10:05:12 be included in this report? No. Neither will any rows entered on the 30th.

Add One Day and Trunc

To fix this problem we need to add one day to the end date, remove(trunc) the time portion and then use the less than(<) comparison operator in the search. I'm truncating the time portions to be on the safe side.

select * from Inspected
where SubmittedTime >= dateadd(day, datediff(day, 0, @DateFrom), 0)
and SubmittedTime < dateadd(day, datediff(day, 0, @DateTo), 1)
order by SubmittedTime desc

A Re-useable Function

I'm human and I don't write SQL every day. If you're human and occasionally forget things then try this function for size.

CREATE FUNCTION [dbo].[trunc]
(
  @dt datetime
)
RETURNS datetime
AS
BEGIN
  return dateadd(day, datediff(day, 0, @dt), 0);
END

The SQL now looks much cleaner:

select * from Inspected
where SubmittedTime >= dbo.trunc(@DateFrom)
and SubmittedTime < dbo.trunc(@DateTo + 1)
order by SubmittedTime desc

Hats Off

Thursday, 26 November 2009

Deep Copy Data Caching in Asp.Net

Data stored in the cache is shared; it is not thread-safe. If you modify the data then the next request will see your changes. Sometimes developers are under the illusion that because they have assigned the data in the cache to a new object variable or performed a shallow copy of the cached data that they can make change without consequence. Not true. Here are a few classes to help protect you. However, you still need to ensure that your cacheable objects are deep copied (derive from my IDeepCopy and implement).

Combine this code for request-safe data caching.

The contents of CacheArgs.cs
using System;
using System.Web.Caching;

namespace ProtectedCache {

    // This is a purely a data object without behavior that plays
    // a bigger part in a framework that I have written.  I'll
    // continue to use it for this example even though it doesn't
    // have an obvious purpose.

    public sealed class CacheArgs {

        private DateTime _absoluteExpiration = Cache.NoAbsoluteExpiration;
        private TimeSpan _slidingExpiration = Cache.NoSlidingExpiration;

        public CacheDependency GetCacheDependency() {
            return null; // Implement when required
        }

        public DateTime GetAbsoluteExpiration() {
            return _absoluteExpiration;
        }

        public void SetAbsoluteExpiration(DateTime dt) {
            _absoluteExpiration = dt;
        }

        public TimeSpan GetSlidingExpiration() {
            return _slidingExpiration;
        }

        public void SetSlidingExpiration(TimeSpan ts) {
            _slidingExpiration = ts;
        }
    }
}
The contents of IDeepCopy.cs
namespace ProtectedCache {

    // Simple interface to indicate intent.  I was thinking
    // about using the existing ICloneable interface and
    // documenting it for developers to ensure deep copies
    // are performed.  I decided not to and opted for a new
    // self-documenting interface.

    public interface IDeepCloneable {

        object DeepClone();
    }
}
The contents of Cacher.cs
using System;
using System.Web;
using System.Collections.Generic;
using System.Threading;

namespace ProtectedCache {

    public static class Cacher {

        public static T Load<T>(string key)
            where T : IDeepCloneable {            

            // Perform a thread-safe read of the cache

            object item = HttpRuntime.Cache[key];
            if (item != null) {

                // Treat an item that is not of expected type
                // as exceptional cases. What you're requesting
                // should be in the cache.

                if (!(item is T)) {
                    throw new InvalidCastException(
                        "Object is not of type " + typeof(T));
                }                
                return (T)((IDeepCloneable)item).DeepClone();
            }
            return default(T);            
        }
        
        public static void Save<T>(T item, string key,
            CacheArgs args) where T : IDeepCloneable {
            
            // Perform thread-safe write to cache, deep cloning
            // the item

            HttpRuntime.Cache.Insert(key,
                item.DeepClone(),
                args.GetCacheDependency(),
                args.GetAbsoluteExpiration(),
                args.GetSlidingExpiration());
        }
    }
}

Extract Substring Using SQL

I've been doing some reading on Microsoft forums and I came across quite a lot of posts with questions about string manipulation using various Transact-Sql string manip. functions. It prompted me to write this little snippy-snappy-snoo

One of the questions was about extracting a portion of a string. Here's an example of how to extract a sub string from a string that has consistent seperation of parts. The following Sql will extract and print the third part (number 6717) from the string assigned to the @data variable.

declare @data varchar(100);
set @data = 'Asl/UKE/6717/08';
declare @firstpass varchar(100);
set @firstpass = left(@data, len(@data) - charindex('/', reverse(@data)));
print right( @firstpass, charindex('/', reverse(@firstpass))-1);

The above was tested on Sql Server 2005

Tuesday, 24 November 2009

A .Net Framework Configuration, Distribution & Release Resources Page

How to Check the Latest Version of .Net Framework Installed

Type javascript:alert(navigator.userAgent) into your browser's address bar and you will receive an alert informing you of the versions of .Net installed on the local machine. Hats off to Walker News for this little jogger.

Asp.Net Error Logging in Global.asax

Here is a snippet of code you can use if you require some global, catch-all error logging. Create a Global.asax file in your web project and replace the generated Application_Error handler with the following code. Also, create a Logs directory in the root of your web app. This is where the log files are written.

void Application_Error(object sender, EventArgs e) {
    Exception ex = Server.GetLastError().GetBaseException();
    string dt = DateTime.Now.ToString("yyyyMMdd_hhmmsstt");        
    using (System.IO.StreamWriter sw = new System.IO.StreamWriter(
        Server.MapPath(VirtualPathUtility.ToAbsolute("~/Logs/")) +
        dt, true)) {
        sw.WriteLine("Logged: " + dt);
        sw.WriteLine("Request: " + Request.Url.OriginalString);
        sw.WriteLine("Form: " + Request.Form.ToString());
        sw.WriteLine("Exception Message: " + ex.Message);
        sw.WriteLine("Target Site: " + ex.TargetSite);
        sw.Write("Stack Trace: " + ex.StackTrace.Trim());
        sw.Flush();
    }
}

There are better logging solutions out there but this is still a useful snippet if you need something snappy that doesn't require much of a learning curve.

My Posts Go Here

Asp.Net Tips

Avoiding Redirects

C#

CSS Tips

Design Patterns

Registry Pattern

Google Ads

SQL & Database Tips

.Net Config