Marcus's Musings

Changing over...

I'm switching to UApplication's blogging app UBlog Reload this weekend (probably later tomorrow), so I thought I should give a heads-up that the URL of this blog (and the RSS feed) will be changing - they will be:

  • http://blog.marcustucker.com
  • http://blog.marcustucker.com/rss2.asp
  • Once the changeover occurs, the old RSS URL will stop working, so please add the new one to your RSS aggregator now (although it won't work for now), and delete the old one when it dies. Incidentally, the new RSS feed will support carious parameters for recent posts, categories, etc. so that you can tailor the feed to suit you. It'll be self-explanatory once the blogs have been switched.

    Finally, please note that because of the way my host implements subdomains, you will find that the current old URL will still work, but beware that some links and/or images could get mangled, so it's best that you access the new blog using the new URL. I'll be changing my sigs on various forums appropriately.

    Thanks for bearing with me during this changeover.

    SQL Server output to XML

    And here's another helpful function that I wrote yesterday...

    Function ExecuteXMLQuery(ByRef Connection, ByRef SQL, ByVal RootNodeName)
    Dim Command, Stream
    Set Command = Server.CreateObject("ADODB.Command")

    Set Stream = Server.CreateObject("ADODB.Stream")
    Stream.Type = 2 'adTypeText
    Stream.Open

    Command.ActiveConnection = Connection
    Command.Dialect = "{5D531CB2-E6Ed-11D2-B252-00C04F681B71}"

    Command.CommandText = "<" & RootNodeName & " xmlns:sql='urn:schemas-microsoft-com:xml-sql'>" & _
    "<sql:query>" & _
    SQL & _
    </sql:query>" & _
    "</" & RootNodeName & ">"

    Command.Properties("Output Stream") = Stream
    Command.Execute , , 1024 'adExecuteStream

    Stream.Position = 0
    ExecuteXMLQuery = Stream.ReadText(-1)

    Set Stream = Nothing
    Set Command = Nothing
    End Function

    Simply pass an active SQL Server 2000 connection, an SQL statement like "SELECT Customer.ID, Customer.Name, Order.ID, Order.TotalQuantity, Order.TotalValue FROM tblCustomers AS Customer LEFT JOIN tblOrders AS Order ON Order.CustomerID = Customer.ID FOR XML AUTO, ELEMENTS", and a suitable root element name (e.g. "CustomerOrders") to get an XML document (as a text string) in response, ready for transforming, storing, sending, etc.

    You might prefer to receive an XML DOM instead, in which case you can easily modify the above function and make the Command object populate a DOM object directly without using the Stream object.

    In fact, now I've written that, I'm tempted to add an extra parameter to the function to produce either output as required! But I'm not going to post it... you can do it yourself! :p

    Kludge alert!

    I've got fed up with the font-size problems in these posts (largely due to poorly-formed HTML being generated by the blog app), so I've applied fixed-pixel font sizes as a workaround, and it seems to be fine. I'm almost definitely going to move to another blog app - UBlog from www.uapplication.com looks promising... is anyone running it? Otherwise I might go to TextPattern or MoveableType...

    MSXML hints & tips

    I'm about to sit down and write a dynamic XML-based navigation system for a new web app at work, and I thought I'd share a few handy tips about MSXML:

    That's all for now! On with the coding!

    And another thing (or three)

    I should have mentioned that you can (and *should*) read up on the ADODB.Stream object in your ADO documentation, which you can download as part of the ADO SDK, or view online in the MSDN library. I'm sure I saw a good article about Stream vs FSO a while ago, but can't track it down at present.

    Similarly, instead of using manual procedures for CSV (or otherwise delimited) files, you should be using the JET OLEDB driver to manipulate it directly - for more on this topic, check out the recent MSDN article "Much ADO About Text Files".

    And since I've just been looking at some of my old code which uses it, I thought I'd quickly draw attention to one of the Scripting.Dictionary's lesser-known properties - in a nutshell, it has a .CompareMode property, which lets you alter its case sensitivity in a couple of subtly different ways.

    See the online MSDN documentation for the full lowdown. Because of a help authoring mess-up, this property doesn't *appear* to be in the downloadable windows scripting 5.6 documentation that I'm forever referring people to, but it actually is there - it's merely in the wrong place! Find it Script Runtime -- FileSystem Object -- Reference -- Properties instead (or search for it).

    Finally, here's a good page about ASP/VBScript coding standards and best practices that I found by accident the other day. It's (pretty much) spot on in my book and a highly recommended read to those wishing to improve their technique.

    Binary file streaming (aka "dynamic downloads")

    Here's a binary file streaming function (and supporting code) that I wrote quite some time ago to make implementing dynamic download functionality almost effortless. I originally posted the first version of this code way back in 2002, in this thread @ SPF, and then it came up again in this one, by which time I'd refined it a little. I recommend that you read both threads to see the context that it is intended to be used in, as well as a few helpful comments from myself and others.

    Incidentally, I use the ADO.Stream object instead of the FSO (FileSystem Object) because the latter is horribly inefficient and totally unsuitable for manipulation of binary data (although it can be done). ADO.Stream is highly optimized for fast IO, and will happily whizz through even *huge* files (i.e. tens or perhaps even hundreds of megabytes in size) with the lowest possible server load.

    So anyway, here it is. I hope you find it useful. Let me know if you have any problems.

    'Load a file from disk
    Function LoadStream(FilePath)
    Dim objStream

    Set objStream = Server.CreateObject("ADODB.Stream")

    objStream.Type = 1 'adTypeBinary=1
    objStream.Open

    objStream.LoadFromFile FilePath
    LoadStream = objStream.Read

    objStream.Close
    Set objStream = Nothing
    End Function


    'returns the MIME header type for a given extension
    Function GetMIMEType(Extension)
    dim Ext
    Ext = UCase(Extension)

    select case Ext

    'Common documents
    case "TXT", "TEXT", "JS", "VBS", "ASP", "CGI", "PL", "NFO", "ME", "DTD"
    sMIME = "text/plain"
    case "HTM", "HTML", "HTA", "HTX", "MHT"
    sMIME = "text/html"
    case "CSV"
    sMIME = "text/comma-separated-values"
    case "JS"
    sMIME = "text/javascript"
    case "CSS"
    sMIME = "text/css"
    case "PDF"
    sMIME = "application/pdf"
    case "RTF"
    sMIME = "application/rtf"
    case "XML", "XSL", "XSLT"
    sMIME = "text/xml"
    case "WPD"
    sMIME = "application/wordperfect"
    case "WRI"
    sMIME = "application/mswrite"
    case "XLS", "XLS3", "XLS4", "XLS5", "XLW"
    sMIME = "application/msexcel"
    case "DOC"
    sMIME = "application/msword"
    case "PPT","PPS"
    sMIME = "application/mspowerpoint"

    'WAP/WML files
    case "WML"
    sMIME = "text/vnd.wap.wml"
    case "WMLS"
    sMIME = "text/vnd.wap.wmlscript"
    case "WBMP"
    sMIME = "image/vnd.wap.wbmp"
    case "WMLC"
    sMIME = "application/vnd.wap.wmlc"
    case "WMLSC"
    sMIME = "application/vnd.wap.wmlscriptc"

    'Images
    case "GIF"
    sMIME = "image/gif"
    case "JPG", "JPE", "JPEG"
    sMIME = "image/jpeg"
    case "PNG"
    sMIME = "image/png"
    case "BMP"
    sMIME = "image/bmp"
    case "TIF","TIFF"
    sMIME = "image/tiff"
    case "AI","EPS","PS"
    sMIME = "application/postscript"

    'Sound files
    case "AU","SND"
    sMIME = "audio/basic"
    case "WAV"
    sMIME = "audio/wav"
    case "RA","RM","RAM"
    sMIME = "audio/x-pn-realaudio"
    case "MID","MIDI"
    sMIME = "audio/x-midi"
    case "MP3"
    sMIME = "audio/mp3"
    case "M3U"
    sMIME = "audio/m3u"

    'Video/Multimedia files
    case "ASF"
    sMIME = "video/x-ms-asf"
    case "AVI"
    sMIME = "video/avi"
    case "MPG","MPEG"
    sMIME = "video/mpeg"
    case "QT","MOV","QTVR"
    sMIME = "video/quicktime"
    case "SWA"
    sMIME = "application/x-director"
    case "SWF"
    sMIME = "application/x-shockwave-flash"

    'Compressed/archives
    case "ZIP"
    sMIME = "application/x-zip-compressed"
    case "GZ"
    sMIME = "application/x-gzip"
    case "RAR"
    sMIME = "application/x-rar-compressed"

    'Miscellaneous
    case "COM","EXE","DLL","OCX"
    sMIME = "application/octet-stream"

    'Unknown (send as binary stream)
    case else
    sMIME = "application/octet-stream"
    end select

    GetMimeType = sMIME
    End Function


    'Sends the specified file to the browser
    sub SendStreamToBrowser(FileStream, FileName, ContentType, IsInline)
    Dim FileExt, FileSize

    'Disable error checking
    on error resume next

    'Clear buffer
    Response.Clear

    FileExt = mid(FileExt, instrrev(FileName,".") + 1)
    FileSize = Ubound(FileStream) + 1

    'Add filename to header
    Response.AddHeader "Connection", "keep-alive"
    Response.AddHeader "Content-Length", FileSize

    'Check if data should be delivered inline or not
    If IsInline = True then
    'Allow the browser to render the file inside a browser window (if it can)
    Response.AddHeader "Content-Disposition","inline; filename=" & FileName
    Else
    'Force browser to save file
    Response.AddHeader "Content-Disposition","attachment; filename=""" & FileName & """"
    End If

    'Get ContentType for download
    select case ContentType
    case false
    'Generic binary ContentType and Charset
    Response.ContentType = "application/octet-stream"
    Response.Charset = "UTF-8"

    case ""
    'Find out what it should be
    Response.ContentType = GetMIMEType(FileExt)

    case else
    'Use the ContentType that was passed
    Response.ContentType = ContentType
    end select

    'Send data to client
    Response.BinaryWrite(FileStream)
    Response.Flush
    End Sub

    Blogbits

    American ATM machines... they inspire confidence! (Apparently was /.'d last month, but I only just noticed it)

    And following on from an interesting thread about teaching yourself to program that was going on at Sitepoint forums last month, I found a great page which talks about what's wrong with SO MANY programming books out there.

    When will I ever learn?!

    I seem to keep making promises to post stuff but completely failing to because I never get round to it... and posting completely different stuff, like this very interesting blog post about the technology that powers Google. Found that at Simon Willison's excellent blog... who found it somewhere else... and so on... and so forth. Make sure you also read this related blog post mentioned in the comments.

    I'm busy packing for a business trip to Germany at the moment, but I will try my damndest to catch up on my broken blog promises before I leave tomorrow morning. But that's not a promise! :p

    Back in town

    I arrived back home on Monday and started back at work today. I'll post some snaps and work through my to-do list (see previous post) as soon as I have a moment, so if you're waiting for more code, watch this space!

    Notes to self

    Upon return to England:
    1) Shower
    2) Sleep off jetlag
    3) Publish ASP HTTP class
    4) Publish ASP debugging class
    5) Republish ASP synamic download functions (originally posted here)

    ;-)

    A quick update

    I've been enjoying a nice relaxing holiday here in Florida, with the highlights being a day at Universal Islands Of Adventure and two day stay at the historic town of St Augustine, where the what we now know as the United States of America was actually founded (*not* Jacksonville, as is often claimed).

    The plan had been to visit Universal Studios today, but unfortunately car problems have put paid to that. Nevermind, I'm going to soak up some rays in the garden instead...

    I'm off!

    Just thought I'd mention that I'm off to Tampa Bay, Florida for a 10 day holiday (well-deserved, I think, because although I've had some fun on my business trips, I haven't had a proper holiday for about 2 years), so I won't be around on the forums much (if at all), and I probably won't be posting anything here...

    Pleased to see that the weather's looking pretty good... :D

    Florida, here I come!

    Radical new forum architecture & functionality?

    Last week I started a thread at Sitepoint Forums about some ideas I had for a new forum architecture, making extensive use of metadata to avoid some problems which are common to large forums (like which forum a thread "belongs" in), to allow users to create dynamic views of the forum tailored to their requirements, and to greatly enhance the sophistication of forum searches when trying to locate a solution to a problem (or find an old post).

    There has been a little discussion, but not as much as I'd hoped, so perhaps you might have a few comments to throw into the melting pot!? Has it been done already?

    Code snippet - dispose of an object / array

    It's always wise to manually dispose of objects and arrays that you use, since although ASP's garbage collection is much improved in ASP 3.0 (IIS 5), it is known to overlook some things, and hence lead to memory leaks.

    Rather than typing the same old "Set objSomething = Nothing" every time, it's much easier to use a function/sub to do the job for you, and unsuprisingly I have one up my sleeve. It will automatically call .Close() on ADO objects, call .RemoveAll() on Dictionaries, and Erase arrays as appropriate - how many of you remember to do those every time?! Finally, it sets the variable to Empty, as if it was never used - not essential, but I like it!

    Sub Kill(ByRef Obj)
    Select Case True
    Case IsObject(Obj)
    Select Case LCase(TypeName(Obj))
    Case "recordset", "command", "stream", "connection"
    'closeable ADO objects
    If Obj.State <> 0 then
    Obj.Close
    End If

    case "dictionary"
    'remove all the pairs
    Obj.RemoveAll

    Case else
    'something else so don't
    'do anything special

    End Select

    Set Obj = Nothing

    Case IsArray(Obj)
    'clear the array
    Erase Obj

    Case Else
    'do nothing at all

    End Select

    'Now revert it to an unitialized state
    Obj = Empty
    End Sub
    NOTE: It's also good to standardize your Sub calling syntax... I use the Call SubName(Param1, Param2, ...) syntax so that whenever I pass parameters to something I use brackets.

    Code snippet - output CSV from Recordset... a better way

    Amusingly, I've just found that I've coded a CSV generating function before, and I did a better job the first time round!! lol

    Here's how I *should* have done it the other day...!

    Sub RecordsetToCSV(ByRef RS, ByVal CSVFilePath, ByVal IncludeFieldNames)
    Set objCSVFile = CreateObject("ADODB.Stream")
    Call objCSVFile.Open

    If IncludeFieldNames Then
    'string concatenation issues aren't a problem for a small string of field names
    For Each Field In RS.Fields
    If FieldNames = "" Then
    FieldNames = Field.Name
    Else
    FieldNames = FieldNames & "," & Field.Name
    End If
    Next

    FieldNames = FieldNames & vbCRLF
    Call objCSVFile.WriteText(FieldNames, 1)
    End If

    Call objCSVFile.WriteText(FieldNames & RS.GetString(adClipString, , ",", vbCRLF, ""))
    Call objCSVFile.SaveToFile(CSVFilePath, 2)
    Set objCSVFile = Nothing
    End Sub

    Much better! :-)

    More BlogWorks XML needed features

    Blog post categories with view filtering and corresponding separate RSS feeds (in addition to the existing one). I rather like YoungPup's approach but would probably use small (16x16 pixel) icons (with the category description on a tooltip via the TITLE attribute) instead of the *nix-style list.

    Code snippet - create a disconnected Recordset

    Disconnected Recordsets are very handy for manipulating dynamically-populated tables of information (e.g. views of folders, with names, sizes, dates, times, etc) and so I have a handy little function for summoning one when needed:

    Function CreateDisconnectedRecordset()
    Dim RS
    Set RS = Server.CreateObject("ADODB.Recordset")
    RS.CursorLocation = adUseClient
    Set RS.ActiveConnection = Nothing
    RS.CursorType = adOpenStatic
    RS.LockType = adLockBatchOptimistic
    Set CreateDisconnectedRecordset = RS
    End Function

    Code snippet - output CSV from Recordset

    Recently I desperately needed to examine some tables from an MSSQL database on a remote server (in Italy) that I couldn't access directly, and which didn't have Enterprise Manager (or any other useful tools) installed (so I couldn't get someone to extract them for me).

    I came up with a VBScript Windows shell script to do the job, and thought that others might find my CSV file dumping function handy (you can use it as-is in an ASP script or shell script). Apologies for the lack of documentation (done in a hurry and I don't have time to comment it now), but I think it's fairly self explanatory anyway...

    Function RenderValue(ByVal Value)
    If IsNull(Value) Then
    RenderValue = ""
    Else
    RenderValue = Value
    End If
    End Function


    'Outputs a recordset to a CSV file
    Sub SaveRecordSetAsCSV(ByRef objRS, ByVal CSVFilePath)
    Set objCSVFile = CreateObject("ADODB.Stream")
    Call objCSVFile.Open

    If Not objRS.EOF Then
    DataArray = objRS.GetRows

    XMax = objRS.Fields.Count - 1
    YMax = UBound(DataArray, 2)
    Else
    XMax = objRS.Fields.Count - 1
    YMax = 0
    End If

    For X = 0 To XMax - 1
    Call objCSVFile.WriteText(objRS.Fields(X).Name & ",")
    Next
    Call objCSVFile.WriteText(objRS.Fields(XMax).Name, 1)

    If IsArray(DataArray) Then
    For Y = 0 To YMax
    For X = 0 To XMax - 1
    Call objCSVFile.WriteText(RenderValue(DataArray(X, Y)) & ",")
    Next

    Call objCSVFile.WriteText(HandleNull(RenderValue(XMax, Y)), 1)
    Next
    End If

    Call objCSVFile.SaveToFile(CSVFilePath, 2)

    Set objCSVFile = Nothing
    End Sub

    You'll probably want to add your own error handling code, and you probably should modify the RenderValue function to handle strings containing commas (I didn't need to at the time)!

    UPDATE: See post above for a better way.

    Blog bug

    There seems to be a problem (at least in IE 6, haven't had a chance to test in anything else) with the paragraph immediately following a <pre> element (which I've been using to post the code below)... I'll sort it out when I get a chance...

    EDIT 1: it looks like there's problems *inside* <pre> elements too... hmm...
    EDIT 2: In fact, I'm really not happy with the design of the site at all... it's virtually untouched from the default BlogWorks XML install, and for some unfathomable reason, all the pages currently have an embedded CSS stylesheet rather than linking to a shared external one.

    <MentalNote>Really must give the site a redesign soon...</MentalNote>

    A few words on enumerated constants

    A particularly common error which comes up again and again (although happily on the decline) is people using enumerated constants (e.g. adOpenStatic) - something which is definitely to be encouraged (because of increased readability/maintainability, futureproofing if the value of the enumerated constant changes, etc.) - but failing to actually import the constants themselves into their scripts, which leads to all sorts of errors. This is entirely understandable, since this essential step is shamefully overlooked in the scripting documentation and most other relevant Microsoft reference material. When it *is* mentioned, usually only the antiquated method of including the ADOVBS.INC file is offered. However, there is a better way, which is to import the binary type library (a file containing information about the contents of a DLL, including enumerated constants) directly.

    In your scripts (preferably in an include file that every script in your site will share, or in the GLOBAL.ASA) simply add the following lines *outside* of the script tags:

    <!-- METADATA TYPE="TypeLib" NAME="Microsoft ActiveX Data Objects 2.5 Library"
    UUID="{00000205-0000-0010-8000-00AA006D2EA4}" -->
    <!-- METADATA TYPE="TypeLib" NAME="Microsoft ADO Ext. 2.5 for DDL and Security"
    UUID="{00000600-0000-0010-8000-00AA006D2EA4}" -->
    The above will import all the enumerated constants you need for using the ADO and ADOX objects (from ADO version 2.5), which include ADODB.Connection, ADODB.Recordset, ADODB.Stream, ADOX.Catalog, etc. The import of these values is much quicker (i.e. more efficient) than the traditional ADOVBS.INC method because the values are retrieved directly from the compiled binary typelib (rather than evaluating an ASP script) and also helps to avoid versioning problems. You will also find that only ADO has a readily available .INC file (which is out of date), so you would have to create your own for other libraries if you wanted to use that old approach.

    These typelib imports work by looking up the UUID number in the section of the Windows registry where all the typelibs are stored (HKCR\TypeLib) to find where the typelib file is located on disk. For more on finding these yourself (which you may well need to do), read this excellent tutorial.

    Incidentally, if you use the FSO, you may find it helpful to import is the Microsoft Scripting Runtime ({420B2830-E718-11CF-893D-00A0C9054228}), so that you don't have to do this.

    Code snippets - random integers and URL decoding

    It's about time I posted some code (I keep promising people on forums, but somehow I never get round to it) so here's a few bits & bobs to start off with. I've got a huge code library to sift through, so I'll try to pick some things which will be helpful to a lot of people, as well as a few more interesting things here & there...

    Firstly, generating random integer numbers within a specified range seems to crop up from time to time, so here's what I wrote a number of years ago. Notice the way I use the Select Case statement, which I think it far more elegant than the equivalent If...ElseIf...Else statements would be...

    'Returns a random integer in the range specified (inclusive)
    Function RndInt(LowerBound, UpperBound)
    Randomize()

    Select Case True
    'rather pointless, but since it's possible...
    Case UpperBound = LowerBound
    RndInt = UpperBound

    'swap bounds if they're the wrong way round
    Case UpperBound < LowerBound
    RndInt = Int((LowerBound - UpperBound + 1) * Rnd + UpperBound)

    'just generate it as normal
    Case Else
    RndInt = Int((UpperBound - LowerBound + 1) * Rnd + LowerBound)
    End Select
    End Function

    Quite some time ago I noticed on MSDN that VBScript does actually have Escape() and UnEscape() functions, which rather oddly are not documented in the downloadable Windows Scripting 5.6 reference help (if you don't have this downloaded, installed, and in regular use when coding then shame on you!!). I then used the UnEscape() function to whip up a simple to decode URLs encoded with Server.URLEncode (Microsoft didn't see fit to provide one themselves):
    'Decodes a string encoded with Server.URLEncode()
    Function URLDecode(ByVal str)
    str = UnEscape(str)
    str = Replace(str,"+"," ")
    str = Replace(str,"%2A","*")
    str = Replace(str,"%40","@")
    str = Replace(str,"%2D","-")
    str = Replace(str,"%5F","_")
    str = Replace(str,"%2B","+")
    str = Replace(str,"%2E",".")
    str = Replace(str,"%2F","/")
    URLDecode = str
    End Function
    More to follow later... must do some work!

    Ok, so I lied!

    Been back a week, no posts. I've been busy, ok?

    I've just stumbled across "disc golf"! Apparently it's big in the States, but I'm totally flabbergasted - I honestly have never heard of it!! It sounds good fun though, and I'll try to give it a shot when I visit Florida at the end of the month. There's even a course here in London, so if I like it I might take it up! :-)

    Fundamental flaw...

    I'm now back in the UK and will get busy on a few posts over the next couple of days, but for the moment I'm just going to comment on a fairly major (IMHO) bug that I've found in BlogWorks XML...

    Compare http://marcustucker.com/blog/ and http://www.marcustucker.com/blog/... what do you notice? Well, I'll give you a handy hint... all the comments disappear on the main pages (but are present on the comments pages accessed via the respective links)!

    I haven't had a chance to look at the source, but it would seem that the code responsible for this is using absolute (rather than relative) URLs in the comments-related storage and lookup.. which doesn't make much sense to me, and is something I'll definitely have a look at pronto...

    BlogWorks XML missing features

    There are a few missing features in this software, of which the most serious is the total lack of an image upload facility, something I hadn't checked out before I left for my trip!

    So far I have been going through the relatively laborious process of uploading to my homegrown CMS (that powers my main site), then grabbing the URLs (of the uploaded image and the thumbnail that my CMS automatically generates), building an HTML table manually with the necessary hyperlink and image code, and then finally being able to publish the post. This is plain silly, and I'll develop something to sort this out when I return!

    Similarly, I think it would be handy (for me at least) to be able to see all the comments (and trackbacks) in one place, ordered reverse-chronologically, rather than having to click the "comments" link at the bottom of each post. Although I've seen all the comments that have been posted so far because I've noticed them, once I've got quite a few posts up, it will cease to be practical to check them all manually. There are also a few bugs in the post preview feature which I'll fix while I'm at it.

    Do any of you use BlogWorks? If you have any requests for new features/fixes, I'd be happy to have a go at them too.

    Naturally, I will get in touch with Rick (who's currently looking after BlogWorks XML) and offer my modifications for integration/public use (if he wants to do so).

    Apologies

    Two to make (not that anyone complained, but I feel a little guilty).

    Firstly, for those expecting to tune in to a technical blog, I have committed the cardinal blogging sin of hijacking it for personal, non-technical stuff - I promise that once I return to England (in 10 days) I'll make up for it with some quality posts (perhaps even before then)... in the meantime, perhaps my input (and that of others) in these posts/threads might be of interest to some of you (for some reason, closing/deallocating/reusing objects seems to be a hot topic at the moment, and things aren't always as you might expect)...

    Deallocating objects in VBScript (see here too)
    Calling ByVal or ByRef?

    Secondly, for those of you who ARE actually interested in finding out how my travels are going, I'm sorry haven't been posting very regularly, however this is largely because it's been damn near impossible to get an internet connection in hotels. However, now I've finally sorted out an Italian ISP I should be fine for the rest of my stay, although since it's going on the company tab I can't use it as much as I would like to!! lol

    Some more eye candy from Como last week:

    Since my last post I've had a great time in Venice thanks to Orsola (last photo, with me & two of her friends), who has been kind enough to show me around (helping me avoid the hordes of tourists and bad restaurants along the way). We've got quite a lot planned for next weekend (when I return to Venice), including my attending a concert on Friday evening, where she is performing as part of a Venetian gospel choir. Other anticipated highlights should include the glassmaking island of Murano, the Basilica (best avoided until next weekend because of the 2hr queues), and perhaps another few churches.

    Venice - damn cold, but very beautiful:

    I left Venice on Sunday evening, I've been staying in a beautiful hotel in Mira (just outside Venice) since then, and will be moving to a hotel in Padova tomorrow. Expect another update in a few days!

    Incidentally, I'm about halfway through my business trip (that is after all why I'm here), and I must say that I'm delighted with the way the trip has gone from a that perspective! For those of you that don't know, I've been migrating from a web developer role to a business analyst, and this is my first real taste of how things will (hopefully) be.

    It's been *very* satisfying to be the person doing the actual systems analysis from scratch for a change, rather than being the poor sod that's got to work with a half-planned half-specified system that someone else has come up with and deal with all the things have been overlooked and inevitably turn up at the 11th hour during development!!

    Naturally I have no illusions that I've managed some sort of "perfect" analysis or achieved an ideal technical specification, and I'm realistic about there being changes between now and project completion, but I reckon I've got 99.9% of it nailed down based on the information I've been able to extract from each consultation session, and I've really enjoyed the challenge of working with new people and new systems to get the job done. It hasn't just been about the creature comforts of being on a business trip abroad!!

    This bodes very well for the future, and I'm looking forward to carrying on with everything tomorrow (for the third of four consultations). Perhaps I've spoken too soon - I hope not, but I guess I'll just have to wait and see...

    And finally, I've booked my 9-day holiday to Florida for the end of March, and I can't wait! Hurray!!

    Venezia

    I've dropped into an Internet café here in Venice, but alas it's not possible to post any more snaps at the moment (nor can I print out the Word document in my email which contains my hotel reservation information)... perhaps tomorrow.

    The carnival is in full swing AND it's Valentine's day too, so Piazza San Marco is overrun with tourists! I must admit that I was slightly depressed at the thought of being in the most romantic city in the world on this particular day (travelling and eating alone is depressing enough as it is), but fortunately I met a lovely Venetian girl last night who has been showing me around a few less touristy places this morning, and she's promised to show me more this evening & tomorrow... and next weekend when I return! So perhaps today might not turn out to be so bad after all - and I hope you enjoy your evening too!! ;)

    Arrivederchi!

    EDIT: corrected typo in title

    Ciao amici!

    Some of you may know that I'm out in Italy on a business trip at the moment...

    My journey out (yesterday) was rather nice, with magnificent views of the Alps from the plane as I neared Milan, and a magical night-time scene at Lake Como as I arrived at my hotel. I went for an evening walk, and enjoyed a wonderful 5-course meal at a local trattoria (a small family run restaurant), before a few DVDs in bed (on the laptop) before dozing off.

    The lake was beautiful this morning, and the hills around it are quite something too. There's a funicular railway, and since I'm done for the day, I'm going to take a trip up to the top - hopefully I'll be able to catch the sunset from the summit...

    Here's a few choice snaps:

    AlpsLake by nightLake by nightMountain

    More tomorrow! Arrivederchi!

    Top ten web application vulnerabilities

    OWASP last month published their list of the top ten vulnerabilities in web applications. Now how many of us can honestly put our hand on our heart and say "Yes! I have given serious consideration to each of these issues and how they might affect my new web application. No worries there." ??

    Not me, that's for sure. Don't get me wrong. I would *like* to! There's nothing I hate more than having to cut corners on a project... but if you've barely been given enough time to implement the basic functionality that the project requires before it goes live, it's nigh on impossible to find the time to add anything beyond the most basic secruity features, let alone devote the time and effort throughout the entire project development process to cover the full spectrum.

    Of course, by using a modular library of code that I've built up over the years, I have plenty of data validation (server-side and client-side), password hashing, and other security-related snippets of code that I can pull out of a hat and drop straight into an app, but that should only be a starting point, not the end of the security measures!

    I think the biggest problem is perception - the powers that be seem to think that web applications are a quick 'n' easy alternative to developing a "real" application! They think it's all RAD (man)!!

    Well I've got news... web applications ARE "real" applications - all the same rules apply! But there is a very important difference... you're not just exposing the application to your intended user base... if it's published on the WWW then anyone can have a go at exploiting it... and what's the business cost going to be if someone causes mayhem? A damn sight more than it would take to get it right in the first place, I'll bet!!

    Ok, rant over.

    I'd like to see more discussion on security throughout the web development cycle, so I've proposed that a new forum is created at Sitepoint forums to this end. Security should not be an afterthought. It should be there right from the start.

    Managing expectations

    Any of you who know me from the forums will know that I religiously code everything myself from scratch, since 3rd party scripts are usually poorly written from a number of standpoints (good practice, efficiency, code neatness) and rarely live up to expectation.

    However, I needed a blog in a hurry (I'm going on a 3-week business trip to Italy tomorrow) and so had to grab something to do the job off-the-peg. BlogWorks XML, a GPL'd classic ASP blog script abandoned by its original author - and now kept alive (but unsupported) by Rick Hurst - seemed to fit the bill the best, so I downloaded it and had it up and running within 5 minutes. In the brief time that I've spent playing with it, I've been pretty impressed with the user interface, perceived speed, and overall sophistication that it offers, although I'm yet to delve into the code itself to pass judgement on how it's put together! lol

    Anyway, what spurned me to start this particular post was that I just noticed that there's no commenting facility, which is a bit of a shame. As soon as I return from my travels I'll add this feature, but until then, I invite you to submit your comments (if any) to me via email.

    EDIT: I take it all back! It turns out that BlogWorks XML *does* support commenting! I've edited the text of the links at the bottom of each of these posts to make it more obvious... so comment away!

    And so it began...

    We've all got to start somewhere, so this is the first post of my new blog, which over time will hopefully come to be an interesting blend of my thoughts on web development (ASP / XHTML / JavaScript / MSSQL in particular), comments about various threads from the forums at Sitepoint and Codingforums, code snippets and other random bits & bobs.

    Your own thoughts and comments are welcomed, so don't be shy to get in touch!!