Monday, 2004-04-05
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
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.
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 objStreamSet objStream = Server.CreateObject("ADODB.Stream")
objStream.Type = 1 'adTypeBinary=1
objStream.OpenobjStream.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
Tuesday, 2004-04-13
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.
Thursday, 2004-04-15
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:
- Ensure that you're using the latest version of the Microsoft XML Parser (currently 4.0 SP2) unless there's a very good reason, since there were significant performance improvements between v2 and v3, and no doubt plenty more in v4 (although naturally in all cases you will only reap most of speed gains if you are aware of and use the new objects provided - see the documentation installed along with the parser). Naturally there are probably plenty of bugfixes too.
- You should also be explicitly specifying the version number when creating instances of the objects. This should always be done to ensure that an error is thrown if the correct version isn't installed, instead of dropping down to the highest version installed (e.g. 3), since doing so might cause odd problems which are rather hard to fathom until the penny drops and you realise that an older version is being used instead! For example:
Set XMLDOM = Server.CreateObject("MSXML2.DOMDocument.4.0") - Make use of Application-level DOM caching for huge speed gains (when frequently manipulating/transforming the same XML documents) by using a free-threaded DOM object. Here's a little something I've whipped up to make life easy (if you have any problems/question, simply post a comment):
Function GetCachedXMLDOM(FilePath)
If IsObject(Application(FilePath)) Then
'Load from cache
Set GetCachedXMLDOM = Application(FilePath)
Else
'Load from disk
Set GetCachedXMLDOM = Server.CreateObject("MSXML2.FreeThreadedDOMDocument.4.0")
GetCachedXMLDOM.Load(FilePath)
Application.Lock
Set Application(FilePath) = GetCachedXMLDOM
Application.UnLock
End If
End Function
'example usage follows...Set MenuXMLDOM = GetCachedXMLDOM(Server.MapPath("menu.xml"))
Response.Write Replace(Server.HTMLEncode(MenuXMLDOM.DocumentElement.XML),vbCRLF,"<br />")
- The same technique may be applied to XSLT (stylesheets) and XSD (schemas) by using the Msxml2.XSLTemplate.4.0 and Msxml2.XMLSchemaCache.4.0 objects, since they are both free-threaded (see the docs).
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...
SQL Server output to XML
And here's another helpful function that I wrote yesterday...
Function ExecuteXMLQuery(ByRef Connection, ByRef SQL, ByVal RootNodeName)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.
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 'adExecuteStreamStream.Position = 0
ExecuteXMLQuery = Stream.ReadText(-1)
Set Stream = Nothing
Set Command = Nothing
End Function
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
Saturday, 2004-04-24
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:
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.