Asp Classic Include Once

I know this is an old topic, but I thought I'd add my two cents in case anyone's interested.

I wrote a function that does precisely what you want: includes the given file exactly one no matter how many times it was called.

class ImportFunction
    private libraries_
    private fso_

    private sub CLASS_INITIALIZE
        set libraries_ = Server.createObject("Scripting.Dictionary")
        set fso_ = Server.createObject("Scripting.FileSystemObject")
    end sub

    public default property get func (path)
        if not libraries_.exists(path) then

            on error resume next
            with fso_.openTextFile(path)
                executeGlobal .readAll
                if Err.number <> 0 then 
                    Response.write "Error importing library "
                    Response.write path & "<br>"
                    Response.write Err.source & ": " & Err.description
                end if
            end with
            on error goto 0

            libraries_.add path, null
        end if
    end property
end class
dim import: set import = new ImportFunction

Notes:

  • This uses a faux function simulated with a default property. If this bothers you, it's easily refactored.
  • The dictionary must be persistent through all includes, whereas persisting the fso merely avoids reconstructing it. If you don't like the idea of keeping them around after the imports are done, you could modify the class to get syntax like this:

    with new Importer
        .import "foo"
        .import "bar/baz"
    end with
    

No. Sorry. You have to manage includes yourself.


Thanks Thom Smith for your piece of code.

Below a version based on yours to handle UTF8 source files (and who manages opening & closing ASP tags).

Dim IncludeOnce
Class IncludeOnceClass

    Private libraries_
    Private adostream_

    Private Sub CLASS_INITIALIZE
        Set libraries_ = Server.createObject("Scripting.Dictionary")
        Set adostream_ = CreateObject("ADODB.Stream")
        adostream_.CharSet = "utf-8"
        adostream_.Open
    End Sub

    Public Default Property Get includeFile (path)
        Dim lpath : lpath=LCase(path)
        if Not libraries_.exists(lpath) then
            Dim code
            libraries_.add LCase(lpath), "stuff"
            On Error Resume Next
            adostream_.LoadFromFile(lpath)
            code = Replace(Replace(adostream_.ReadText(),"<"&"%",""),"%"&">","")
            executeGlobal code
            If Err.number <> 0 Then 
                Response.write "Error importing library "
                Response.write lpath & "<br>"
                Response.write Err.source & ": " & Err.description
                Response.write "<pre><tt>" & replace(code,"<","&lt;") & "</tt></pre>"
                Response.End
            End if
            On Error Goto 0
        End If
    End Property

End Class

Set IncludeOnce = new IncludeOnceClass

As you see, I don't close the stream to let further includes be processed.

Update: Since paths are case insensitive, this version converts them to lower case before adding to the Dictionary as keys. Also they are added before code evaluation, to prevents duplicate evaluations in case of inclusion loop (recursion) :

Given A.asp and B.asp :

' A.asp
IncludeOnce Server.MapPath("/some/path/B.asp")

' B.asp
IncludeOnce Server.MapPath("/some/path/A.asp")

Since A is not yet evaluated at all at B code inclusion, the second IncludeOnce will occur a duplicate evaluation if it's not yet registered in the dictionary, which we won't want. This update fixes the issue.