ASP Upload

Se ritieni utile questo articolo, considera la possibilità di effettuare una donazione (il cui importo è a tua completa discrezione) tramite PayPal. Grazie.

Uno dei limiti maggiori di ASP 3 è la mancanza del supporto nativo per l'upload di file e, più in generale, per la gestione di form con enctype="multipart/form-data". In questi casi generalmente si ricorre all'utilizzo di componenti COM, freeware o commerciali, ma non è una strada sempre percorribile (ad esempio qualora non avessimo la possibilità di installare i componenti necessari sul server). In realtà il problema non è così complicato come può sembrare: le difficoltà reali sono solo due: capire come viene costruita la richiesta HTTP e utilizzare i dati binari ricevuti. Il primo problema si risolve con un po' di pazienza nell'analizzare la richiesta, mentre per il secondo utilizzeremo ADO, in particolare invocando il metodo AppendChunk di un recordset creato in memoria (quindi, in realtà, anche la nostra implementazione utilizza un componente COM, che però è generalmente presente su ogni server essendo l'accesso ad un database pressoché d'obbligo!).

Progettazione

Volendo realizzare una libreria da utilizzare in modo semplice in molti progetti, cominciamo definendo l'interfaccia delle classi necessarie mediante un semplice (e puramente indicativo) class diagram:

ASP Upload - Class Diagram

Abbiamo definito la classe principale (UploadManager) che nel costruttore riceverà la Request, la analizzerà e renderà disponibile l'accesso ai campi dei form attraverso i metodi getItem e getItemList che restituiranno un RequestItem (o un array di RequestItem qualora nel form siano presenti più campi con il nome richiesto) per i campi "valore" (input text, radio, checkbox, select, textarea, ecc.) e, in modo analogo, un RequestItemFile / array di RequestItemFile per i campi di tipo input file.

Implementazione

Nella progettazione non ci siamo soffermati sulle differenze imposte dalla tecnologia o dal linguaggio, ma abbiamo esaminato la cosa da un punto di vista puramente teorico. A questo punto però, dovendo passare all'implementazione vera e propria, pur volendo fornire una libreria pressoché equivalente in JScript e in VBScript, risulta necessario scegliere un linguaggio da utilizzare per l'analisi del codice proposto. Pur avendo sviluppato la versione iniziale in JScript e successivamente fatto un semplice porting in VBScript, nel resto dell'articolo utilizzeremo quest'ultimo, in quanto maggiormente diffuso tra gli sviluppatori ASP, quindi presumibilmente più comprensibile. Nel file allegato a questo articolo troverete comunque il codice completo ed un esempio di utilizzo per entrambi i linguaggi.

Iniziamo definendo la nostra classe UploadManager, i fields pubblici e privati e la sub Class_Initialize per l'inizializzazione dei valori:

Class UploadManager
    
    Public TypologyField
    Public TypologyFile
    
    Private m_itemlist    ' array: key1, [val1, val1, val1], key2, [val2], ...
    
    Sub Class_Initialize
        TypologyField = 0
        TypologyFile = 1
    End Sub

End Class

Vediamo ora di implementare la parte principale della nostra applicazione, ovvero l'analisi della richiesta HTTP e la valorizzazione dei nostri oggetti di mappatura dei campi del form.
Aggiungiamo alla nostra classe UploadManager un metodo ParseRequest che dovrà essere chiamato per inizializzare la classe stessa:

Public Function ParseRequest(r)
    Dim contenttypelist, rs, requeststring, boundry, data, i, infoend, info, value, item, ii, keyexists, iv
    ReDim m_itemlist(-1)
    contenttypelist = Split(LCase(r.ServerVariables("HTTP_CONTENT_TYPE")), ";")
    If InStr(contenttypelist(0), "multipart/form-data") = 0 Then Err.Raise 513, "UploadManager", "Form non di tipo ""multipart/form-data"""
    Set rs = Server.CreateObject("ADODB.Recordset")
    rs.Fields.Append "mBin", 201, r.TotalBytes
    rs.Open()
    rs.AddNew()
    rs("mBin").AppendChunk r.BinaryRead(r.TotalBytes)
    rs.Update()
    requeststring = CStr(rs("mBin"))
    boundry = Split(contenttypelist(1), "=")(1)
    data = Split(requeststring, boundry)
    For i = 0 To UBound(data)
        infoend = InStr(data(i), vbCrLf & vbCrLf)    'two sets of crlf mark the end of the information about this field; everything after that is the value
        If infoend > 1 Then
            info = Mid(data(i), 2, infoend - 2)        'pull the info for this field, minus the stuff at the ends...
            value = Mid(data(i), infoend + 4, Len(data(i)) - infoend - 7)        'skip the crlf pairs at the start and the crlf-- at the end: length = Len(data(i)) - 4 - (infoend + 4) + 1 = Len(data(i)) - 4 - infoend - 4 + 1 = Len(data(i)) - infoend - 7
            If InStr(LCase(info), "filename=") > 0 Then
                Set item = New RequestItemFile
                item.Info = info
                item.Value = value
            Else
                Set item = New RequestItem
                item.Info = info
                item.Value = value
            End If
            ' field exists in m_itemlist?
            keyexists = False
            For ii = 0 To UBound(m_itemlist) Step 2
                If m_itemlist(ii) = LCase(item.Name) Then
                    iv = m_itemlist(ii + 1)
                    ReDim Preserve iv(UBound(iv) + 1)
                    Set iv(UBound(iv)) = item
                    m_itemlist(ii + 1) = iv
                    keyexists = True
                    Exit For
                End If
            Next
            If Not keyexists Then
                ReDim Preserve m_itemlist(UBound(m_itemlist) + 2)
                m_itemlist(UBound(m_itemlist) - 1) = LCase(item.Name)
                m_itemlist(UBound(m_itemlist)) = Array(item)
            End If
        End If
    Next
End Function

Le righe iniziali dichiarano le variabili utilizzate e verificano che la richiesta sia di tipo corretto, in caso contrario viene generato un errore. A questo punto, appoggiandosi ad un Recordset ADO creato in memoria, convertiamo la richiesta binaria in una stringa, quindi scomponiamo la stringa stessa così da ricavare i singoli campi del form ed i rispettivi valori e con questi valorizzare le classi di mappatura del tipo corrispondente (RequestItem o RequestItemFile). Aggiungiamo ad UploadManager i due metodi pubblici per l'accesso ai dati:

Public Function GetItem(name)    ' return first field
    Dim i
    Set GetItem = Nothing
    For i = 0 To UBound(m_itemlist) Step 2
        If m_itemlist(i) = LCase(name) Then
            Set GetItem = m_itemlist(i + 1)(0)
            Exit Function
        End If
    Next
End Function

Public Function GetItemList(name)
    Dim i
    GetItemList = Array()
    For i = 0 To UBound(m_itemlist) Step 2
        If m_itemlist(i) = LCase(name) Then
            GetItemList = m_itemlist(i + 1)
            Exit Function
        End If
    Next
End Function

Per completare la libreria creiamo le classi di mappatura sui campi del form.
Per l'accesso ai campi di tipo "valore" generiamo la più semplice, RequestItem:

Class RequestItem

    Public Info
    Public Value
    
    Public Property Get Name
        Dim s, e
        s = InStr(Info, "name=""") + Len("name=""")
        If InStrRev(Info, """;") > 0 Then
            e = InStrRev(Info, """;") - 1
        Else
            e = InStrRev(Info, """") - 1
        End If
        Name = Mid(Info, s, e - s + 1)
    End Property
    
    Public Property Get Typology
        Dim um
        Set um = New UploadManager
        Typology = um.TypologyField
        Set um = Nothing
    End Property
    
End Class

Quindi, per i campi di tipo "file", creiamo la classe RequestItemFile, che ci consentirà di accedere alle proprietà del file caricato (utilizzabili per l'implementazione di controlli custom di verifica del file inserito, ad esempio in funzione della dimensione, del content-type o dell'estensione) e di procedere al suo salvataggio sul server:

Class RequestItemFile

    Public Info
    Public Value
    
    Public Property Get Name
        Dim s, e
        s = InStr(Info, "name=""") + Len("name=""")
        If InStrRev(Info, """;") > 0 Then
            e = InStrRev(Info, """;") - 1
        Else
            e = InStrRev(Info, """") - 1
        End If
        Name = Mid(Info, s, e - s + 1)
    End Property
    
    Public Property Get Typology
        Dim um
        Set um = New UploadManager
        Typology = um.TypologyFile
        Set um = Nothing
    End Property
    
    Public Property Get FullPath
        Dim s, e
        s = InStr(Info, "filename=""") + Len("filename=""")
        e = InStrRev(Info, """" & vbCrLf) - 1
        FullPath = Replace(Mid(Info, s, e - s + 1), "/", "\")
    End Property
    
    Public Property Get FileName
        Dim s
        s = InStrRev(FullPath, "\") + 1
        FileName = Mid(FullPath, s, Len(FullPath) - s + 1)
    End Property
    
    Public Property Get FileExtension
        Dim s
        s = InStrRev(FileName, ".") + 1
        FileExtension = Mid(FileName, s, Len(FileName) - s + 1)
    End Property
        
    Public Property Get FileSize
        FileSize = Len(Value)    ' byte
    End Property
    
    Public Property Get ContentType
        Dim s
        s = InStr(Info, "Content-Type: ") + Len("Content-Type: ")
        ContentType = Mid(Info, s, Len(Info) - s + 1)
    End Property
    
    Public Function Save(remotepath, remotefilename)
        Dim fn, fso, rpl, cp, i, f
        remotepath = Replace(remotepath, "/", "\")
        If Right(remotepath, 1) <> "\" Then remotepath = remotepath & "\"
        fn = remotepath & remotefilename
        Set fso = Server.CreateObject("Scripting.FileSystemObject")
        ' build remote path
        If Not fso.FolderExists(remotepath) Then
            rpl = Split(remotepath, "\")
            cp = rpl(0)
            For i = 1 To UBound(rml)
                cp = cp & "\" & rpl(i)
                If Not fso.FolderExists(cp) Then fso.CreateFolder(cp)
            Next
        End If
        ' save file
        Set f = fso.OpenTextFile(fn, 2, True)
        f.Write Value
        f.Close
        Set f = Nothing
        Set fso = Nothing
    End Function

End Class

Utilizzare la libreria di Upload

Per utilizzare il codice nella nostra pagina ASP non dobbiamo far altro che includere la libreria e scrivere poche righe di codice per richiamarne i metodi, come nell'esempio seguente:

Dim um, f
Set um = New UploadManager
um.ParseRequest(Request)

' Text
Set f = um.GetItem("myText")
Response.Write("<h3>TEXT field</h3>")
Response.Write("NAME=" & f.Name & "<br>")
Response.Write("VALUE=" & f.Value & "<br>")
Response.Write("TYPE=" & f.Typology & "<br>")

' File
Set f = um.GetItem("myFile")
Response.Write("<h3>FILE field</h3>")
Response.Write("NAME=" & f.Name & "<br>")
'Response.Write("VALUE=" & f.Value & "<br>")
Response.Write("TYPE=" & f.Typology & "<br>")
Response.Write("FULLPATH=" & f.FullPath & "<br>")
Response.Write("FILENAME=" & f.FileName & "<br>")
Response.Write("FILEEXTENSION=" & f.FileExtension & "<br>")
Response.Write("SIZE=" & f.FileSize & "<br>")
Response.Write("CONTENTTYPE=" & f.ContentType & "<br>")
f.Save Server.MapPath("."), "MY_" & f.FileName

Set um = Nothing

Si noti che non esistono limitazioni sul numero di file che è possibile uploadare.