by Mark Trescowthick - GUI Computing
ASP is, as someone remarked to me last week, remarkable more for the very fact that it exists (and works most of the time) than for anything else. It really is the shotgun wedding of a number of quite disparate technologies - most of which were originally intended to work client-side and which have been dragooned into server-side service.
Given that history - and given its immaturity - there were always bound to be some 'gotchas' in ASP. One major annoyance is the fact that included files can't be named dynamically, as they are included by the server before any code is executed. This can make for some genuine grief in some circumstances, and this article documents one way around that problem.
But first, some history…
Consider this code snippet :
<%Do While Not rsGroup.EOF If rsGroup("GroupID") = rsPerson("GroupID") then %> <option selected value = "<%=rsGroup("GroupID")%>"><%=rsGroup("GroupName")%> </option> <%else%> <option value = "<%=rsGroup("GroupID")%>"> <%=rsGroup("GroupName")%> </option> <%end if%> <%rsGroup.MoveNext Loop%>
This is vaguely comprehensible, but then it's only doing a very simple thing… dragging some names from a recordset and inserting them in a combo, making sure that the correct one is selected. It's a maze of brackets and percent signs, just waiting to cause annoying bugs. Granted, Visual Interdev's "syntax highlighting" helps a little, but that horrible yellow highlight gives this colour-blind developer a headache, so I avoid it. And even if I didn't, the whole mix doesn't make for the most readable code.
I puzzled over this for a while, and couldn't really see much of a way to do it any better. Then, for no reason I can remember, I decided to see whether a called Sub would insert HTML in-line. And, blow me down, it did - a "discovery" which was greeted with a rousing chorus of "So what?" and "Of course" by all and sundry. I guess I'm just so used to Subs and Functions doing "programmer stuff" that I'd never even considered the issue!
So, the above snippet becomes :
<%Do While Not rsGroup.EOF OptionCreate rsPerson("GroupID"), rsGroup("GroupID"), rsGroup("groupName") rsGroup.MoveNext Loop%>
And I create a small, generic Sub to like this :
<%Sub OptionCreate(TargetOption,ThisOption,ThisText)%> If TargetOption = ThisOption then%> <option selected value ="<%=ThisOption%>"><%=ThisText%> <%else%> <option value ="<%=ThisOption%>"><%=ThisText%> <%end if%> </option> <%End Sub%>
Interestingly, I believe that both the in-line code and the Sub are easier to read and maintain. I use this technique a lot, and the clarity of my code has gone up 1000% (well, I think so, anyway!).
Of course, this led directly to the creation of a range of useful little subs and functions, which happily insert assorted HTML all over the place - reusability nearly always follows from these happy little discoveries, I find.
Which led me directly to …
The page-by-page nature of ASP development (for those of us old enough / unlucky enough to remember CICS, a familiar paradigm), really doesn't make for instinctive code reuse, at least for me. That's made worse by virtue of the fact that the Include facilities available are, as I mentioned above, a wee bit limited. Files can certainly be included, but all the includes are done before any ASP code is run, meaning that they really amount to not much more than a copy and paste exercise. And that is often just not good enough.
Consider a situation I had recently… we were building an on-line quiz application for a client, and part of the design brief was to make the look of the various pages as parameter-driven as possible, while ensuring that the underlying database work always worked. Each Quiz would have three different components (or sub-Quizzes) and each of these would, in turn, be subtly different in look.
This was obviously a case for using Include files… but how? By the time I knew which sub-Quiz I was in and could make some programmatic decisions, it was already too late.
My first thought was to use the Case Statement From Hell within each file to be included, but that soon foundered - each sub-Quiz was just sufficiently different that the HTML was going to be a disaster to maintain if I did it that way. My next thought meandered back to my little in-line HTML example above. What if I could write a Sub to include the appropriate files as and when required? Of course, the fact that Includes weren't processed dynamically seemed to preclude that approach… but perhaps not.
The FileSystem Object was what caught my attention here. If I could write a file from ASP (and I knew I could do that), then surely I could redirect to that file I'd written? And if I could read a file in ASP (and, again, I knew that was easy), then maybe I could sort of combine a series of files in some weird dynamic include? Yes, I know that sounds like the long way around, but if it worked, it would save me a bunch of additional coding and maintenance… and have a high niftyness ratio (I was still wounded from the reception my in-line HTML "discovery" had received!).
And, of course, it was pretty easy once I had the initial thought. I've included the code in the accompanying zip file, but some small sections deserve a look. The code here only writes the appropriate ASP (in this case, "hereiam.asp"), then redirects to it. In practice, of course, this would probably be a function within a larger page. There are two ways to approach this, and the code below illustrates both.
The first step is to set up some all-purpose variables… like the initial line of the ASP. This is where I first came a cropper, too. It turns out that the VBScript parser can get a wee bit confused when it sees something like Initline = "<%@ ", despite the fact that those special characters are enclosed in quotes. Probably fair enough, I guess, and easy enough to get around. Hence :
initline = "<" & "%" & "@" & " LANGUAGE = VBScript " & "%" & ">" startInclude = "<" & "!" & "--" & "#" & "Include file=""" endInclude = """--" & ">" startVB = "<" & "%" endVB = "%" & ">"
The next step is to make the script path-independent, as CreateTextFile requires an absolute path to work. Server.MapPath comes in handy here.
redirectTo = server.mappath("hereiam.asp")
Then we simply create the output file and we're away.
Set fs = CreateObject("Scripting.FileSystemObject") Set outfile = fs.CreateTextFile(redirectTo, True) outfile.writeline initline
The first method of creating the file assumes that all the components are contained within individual include files and don't require anything other than being assembled in the correct order. This is obviously the easiest option, and my 'Style A' approach does just that.
But that's a simple case. Often, the fact that you're even considering this method will probably mean that you want to manipulate the actual contents of the include file before you write it. In that case, as illustrated in the 'Style B' approach, you can simply open the include file and read it line by line. This is obviously going to be less efficient, but if you need the functionality, at least it's available. In the example below, I do nothing but read and write line-by-line, but obviously reading each line into a variable, manipulating it, then writing it is pretty straightforward.
if request.querystring("Style") = "A" then outfile.writeline startVB outfile.writeline "' hereiam.asp : Style A" outfile.writeline endVB outfile.writeline startInclude & "styleA1.inc" & endInclude outfile.writeline startInclude & "styleA2.inc" & endInclude outfile.writeline startInclude & "styleA3.inc" & endInclude outfile.writeline startInclude & "styleA4.inc" & endInclude else outfile.writeline startVB outfile.writeline "' hereiam.asp : Style B" & vbcrlf outfile.writeline endVB incFileName = server.mappath("StyleB1.inc") Set fs = CreateObject("Scripting.FileSystemObject") Set incfile = fs.OpenTextFile(incFileName, 1, False) Do While incfile.AtEndOfStream <> True ' This method is less efficient. ' Used when you need to manipulate contents of the include file. ' Here, we just output what we read outfile.writeline incfile.readline Loop incfile.Close end if
Finally, all we need to do is close the file and redirect to the created .ASP.
outfile.Close response.redirect "hereiam.asp" %>
Obviously (and unfortunately) this method does have some drawbacks… it certainly must have some performance impact (though perhaps not as much as you'd think) and it has a coding overhead that means you'd never want to use it unless you absolutely had to.
But if you need the functionality, and I did, it's about the only way to go.
…oh, and by the way, this little idea did make a favourable impression <g>.