ASP無組件上傳·從原理剖析到實戰(zhàn)(中)
發(fā)表時間:2024-05-31 來源:明輝站整理相關軟件相關文章人氣:
[摘要]第五天:得到文件單元 今天我們要進行的部分,是比較有趣味性地——得到文件內(nèi)容。其實,看看我們的要處理的數(shù)據(jù),再看看前天文本單元的處理,相信大家也會心中有數(shù)。 為了清晰的區(qū)分文件和文本單元,這一次,我們用ourRequest.file(index)來對應文本單元的ourRequest.form(in...
第五天:得到文件單元
今天我們要進行的部分,是比較有趣味性地——得到文件內(nèi)容。其實,看看我們的要處理的數(shù)據(jù),再看看前天文本單元的處理,相信大家也會心中有數(shù)。
為了清晰的區(qū)分文件和文本單元,這一次,我們用ourRequest.file(index)來對應文本單元的ourRequest.form(index)。當然,因為對于文件,我們需要得到的信息不同于文本,所以這次得到的,也不會是FormElement,而是一個新對象FileElement。
文件單元和文本單元在原始數(shù)據(jù)上,不同點少得可憐:
1。第一行多了一個filename="xxx"模塊;
2。多了一個用于指示contentType的第二行。
感興趣的目標信息不同,所以,得到的對象FileElement也和FormElement有一些不同點:
1。不需要count屬性(不存在checkbox情況);
2。不需要Item(index)(同上,不存在checkbox情況);
3。需要一個ContentType屬性;
4。需要一個FilePath屬性;
5。需要一個FileName屬性;
6。需要一個Size屬性;
7。因為需要的是二進制,所以,沒有必要進行二進制=>字符串的轉換;
8。因為需要的是二進制,所以,屬性Value改成Data更合適
此外,UploadRequest也應該相應的添加Files屬性、Form(index)方法、以及m_dicFiles成員。現(xiàn)在,我們就來擴充他:
A。UploadRequest(上面設計過,這里是擴充)
這個類和request對象是對應的
屬性:
RawData 得到原始數(shù)據(jù),方便檢查[只讀]
Forms 得到一個有count屬性的計數(shù)器,
可以用outRequest.Forms.Count的方式,得到文本表單域的的個數(shù)[只讀]
Files 得到一個有count屬性的計數(shù)器,
可以用outRequest.Files.Count的方式,得到文件表單域的的個數(shù)[只讀]
Form(index) 可以用數(shù)字或文本檢索文本表單域,做用類似request.form。
他返回一個FormElement型的對象
File(index) 可以用數(shù)字或文本檢索文件表單域,他返回一個FileElement型的對象
B。FileElement
可以把它看成單個文件域的化身。通過這個類,可以得到詳細的文件信息,比如name,data,path,filename,contentType,size等等。
屬性:
Name 文件域的名稱。就是<input type=file name=xxx>里的xxx
Data 文件域的內(nèi)容。二進制串
ContentType 文件域的contentType
FilePath 文件域包含的文件在客戶機上的全路徑
FileName 文件域包含的文件的文件名
Size 文件域包含的文件的尺寸
這里是實現(xiàn)。還是存成doupload.asp:
<%
'=========================================================================
'' 這個,是存儲文本域信息的的類。每一個name的文本域,對應一個這樣的類。
'=========================================================================
Class FormElement
' m_開頭,表示類成員變量。
Private m_dicItems
Private Sub Class_Initialize()
Set m_dicItems = Server.CreateObject("Scripting.Dictionary")
End Sub
' count是咱們這個類的一個只讀屬性
Public Property Get Count()
Count = m_dicItems.Count
End Property
' Value是一個默認屬性。目的是得到值
Public Default Property Get Value()
Value = Item("")
End Property
' Name是得到文本域名稱。就是<input name=xxx>里的xxx
Public Property Get Name()
Keys = m_dicItems.Keys
Name = Keys(0)
Name = left(Name,instrrev(Name,"_")-1)
End Property
' Item屬性用來得到重名表單域(比如checkbox)的某一個值
Public Property Get Item(index)
If isNumeric(index) Then '是數(shù)字,合法!
If index > m_dicItems.Count-1 Then
err.raise 1,"IndexOutOfBound", "表單元素子集索引越界"
End If
Itms = m_dicItems.Items
Item = Itms(index)
ElseIf index = "" Then '沒給值?那就返回所有的!逗號分隔
Itms = m_dicItems.Items
For i = 0 to m_dicItems.Count-1
If i = 0 Then
Item = Itms(0)
Else
Item = Item & "," & Itms(i)
End If
Next
Else '給個一個不是數(shù)字的東東?出錯!
err.raise 2,"IllegalArgument", "非法的表單元素子集索引"
End If
End Property
Public Sub Add(key, item)
m_dicItems.Add key, item
End Sub
End Class
'=========================================================================
'' 這個,是存儲文件域信息的的類。每一個name的文件,對應一個這樣的類。
'=========================================================================
Class FileElement
' m_開頭,表示類成員變量。
Private m_strName
Private m_bData
Private m_strContentType
Private m_strFilePath
Private m_strFileName
Private m_lSize
' Data是一個默認屬性。目的是得到值
Public Default Property Get Data()
Data = m_bData
End Property
' Name是得到文件域名稱,就是<input type=file name=xxx>里的xxx
Public Property Get Name()
Name = m_strName
End Property
' ContentType是得到文件contentType
Public Property Get ContentType()
ContentType = m_strContentType
End Property
' FilePath是得到文件在客戶端的路徑
Public Property Get FilePath()
FilePath = m_strFilePath
End Property
' FilePath是得到文件在客戶端的路徑
Public Property Get FileName()
FileName = m_strFileName
End Property
' Size是得到文件大小
Public Property Get Size()
Size = m_lSize
End Property
Public Sub Add(name, data, contenttype, path)
m_strName = name
m_bData = data
m_strContentType = contenttype
m_strFilePath = path
m_strFileName = right(path, len(path)-instrrev(path, "\"))
m_lSize = lenb(data)
End Sub
End Class
'=========================================================================
'' 這個,是我們模擬的request類。我們用它完成asp的request完成不了的任務 :)
'=========================================================================
Class UploadRequest
Private m_dicForms
Private m_dicFiles
Private m_bFormdata
Private Sub Class_Initialize()
Set m_dicForms = Server.CreateObject("Scripting.Dictionary")
Set m_dicFiles = Server.CreateObject("Scripting.Dictionary")
Call fill()
End Sub
' 有了這個,就可以檢查原始數(shù)據(jù)了
Public Property Get RawData()
RawData = m_bFormdata
End Property
' 這一段丑陋的代碼是為了實現(xiàn)outRequest.Forms.Count這個功能。
Public Property Get Forms()
Set Forms = New Counter
Forms.setCount(m_dicForms.Count)
End Property
' 這一段丑陋的代碼是為了實現(xiàn)outRequest.Files.Count這個功能。
Public Property Get Files()
Set Files = New Counter
Files.setCount(m_dicFiles.Count)
End Property
Public Property Get Form(index)
If isNumeric(index) Then '是數(shù)字?用數(shù)字來檢索
If index > m_dicForms.Count-1 Then
err.raise 1,"IndexOutOfBound", "表單元素索引越界"
End If
Items = m_dicForms.Items
Set Form = Items(index)
ElseIf VarType(index) = 8 Then '字符串?也行!
If m_dicForms.Exists(index) Then '存在,就返回值
Set Form = m_dicForms.Item(index)
Else '不存在,就給個空值——request對象就是這么做的。
Exit Property
End If
Else '給了一個不是數(shù)字也不是字符串的東東?出錯!
err.raise 2,"IllegalArgument", "非法的表單元素索引"
End If
End Property
Public Property Get File(index)
If isNumeric(index) Then '是數(shù)字?用數(shù)字來檢索
If index > m_dicFiles.Count-1 Then
err.raise 1,"IndexOutOfBound", "文件元素索引越界"
End If
Items = m_dicFiles.Items
Set File = Items(index)
ElseIf VarType(index) = 8 Then '字符串?也行!
If m_dicFiles.Exists(index) Then '存在,就返回值
Set File = m_dicFiles.Item(index)
Else '不存在,出錯!
err.raise 2,"NullRef", "文件元素索引不存在"
End If
Else '給了一個不是數(shù)字也不是字符串的東東?出錯!
err.raise 2,"IllegalArgument", "非法的表單元素索引"
End If
End Property
Private Sub fill
' 得到數(shù)據(jù)
m_bFormdata=request.binaryread(request.totalbytes)
' 調(diào)用這個函數(shù)實現(xiàn)遞歸循環(huán),讀取文本/文件單元
Call fillEveryFirstPart(m_bFormdata)
End Sub
Private Sub fillEveryFirstPart(data)
' 這就是name="
const_nameis=chrb(110)&chrb(97)&chrb(109)&chrb(101)&chrb(61)&chrb(34)
' 這就是filename="
const_filenameis=chrb(102)&chrb(105)&chrb(108)&chrb(101)&_
chrb(110)&chrb(97)&chrb(109)&chrb(101)&chrb(61)&chrb(34)
' 這是回車<return>
bncrlf=chrb(13) & chrb(10)
' 得到divider,分隔符
divider=leftb(data,instrb(data,bncrlf)-1)
' 起始位置
startpos = instrb(data,divider)+lenb(divider)+lenb(bncrlf)
' 終止位置,從起始位置開始到下一個divider
endpos = instrb(startpos, data, divider)-lenb(bncrlf)
If endpos < 1 Then '沒有下一個了!結束!
Exit Sub
End If
part1 = midb(data, startpos, endpos-startpos)
' 得到part1的第一行
firstline = midb(part1, 1, instrb(part1, bncrlf)-1)
'沒有filename=",有name=",說明是一個文本單元(這里有一個BUG,自己研究一下?當作業(yè)吧)
If Not instrb(firstline, const_filenameis) > 0_
And instrb(firstline, const_nameis) > 0 Then
' 得到表單域名稱,就是<input type=sometype name=somename>里的somename。
fldname = B2S(midb(part1,_
instrb(part1, const_nameis)+lenb(const_nameis),_
instrb(part1, bncrlf)_
-instrb(part1, const_nameis)-lenb(const_nameis)-1))
' 得到表單域的值
fldvalue = B2S(midb(part1,_
instrb(part1, bncrlf&bncrlf)+lenb(bncrlf&bncrlf),_
lenb(part1)-instrb(part1, bncrlf&bncrlf)+lenb(bncrlf&bncrlf)))
If m_dicForms.Exists(fldname) Then
Set fElement = m_dicForms.Item(fldname)
m_dicForms.Remove fldname
Else
Set fElement = new FormElement
End If
fElement.Add fldname&"_"&fElement.Count, fldvalue
m_dicForms.Add fldname, fElement
'有filename=",有name=",說明是一個文件單元(這里還是有一個BUG,研究出來沒?)
ElseIf instrb(firstline, const_filenameis) > 0_
And instrb(firstline, const_nameis) > 0 Then
' 得到表單域名稱,就是<input type=file name=somename>里的somename。
fldname = B2S(midb(part1,_
instrb(part1, const_nameis)+lenb(const_nameis),_
instrb(part1, const_filenameis)_
-instrb(part1, const_nameis)-lenb(const_nameis)-3))
' 得到表單域的值
fldvalue = midb(part1,_
instrb(part1, bncrlf&bncrlf)+lenb(bncrlf&bncrlf),_
lenb(part1)-instrb(part1, bncrlf&bncrlf)+lenb(bncrlf&bncrlf))
' 得到路徑
filepath = B2S(midb(part1,_
instrb(part1, const_filenameis)+lenb(const_filenameis),_
instrb(part1, bncrlf)_
-instrb(part1, const_filenameis)-lenb(const_filenameis)-1))
' 得到contenttype
contenttype = B2S(midb(part1,_
instrb(part1, bncrlf)+lenb(bncrlf)+14,_
instrb(part1,_
bncrlf&bncrlf)-instrb(part1, bncrlf)-lenb(bncrlf)-14))
If lenb(fldvalue) > 0 Then 'size>0說明有文件傳來了。
If m_dicFiles.Exists(fldname) Then
Set fElement = m_dicFiles.Item(fldname)
m_dicFiles.Remove fldname
Else
Set fElement = new FileElement
End If
fElement.Add fldname, fldvalue, contenttype, filepath
m_dicFiles.Add fldname, fElement
End If
End If
' 截取剩下的部分,遞歸調(diào)用這個函數(shù),來得到下一個part1。
Call fillEveryFirstPart(rightb(data, lenb(data)-endpos-1))
End Sub
' 這是一個公用函數(shù),作用是二進制和字符串的轉換
Private Function B2S(bstr)
If not IsNull(bstr) Then
for i = 0 to lenb(bstr) - 1
bchr = midb(bstr,i+1,1)
If ascb(bchr) > 127 Then '遇到了雙字節(jié),就得兩個字符一起處理
temp = temp & chr(ascw(midb(bstr, i+2, 1) & bchr))
i = i+1
Else
temp = temp & chr(ascb(bchr))
End If
next
End If
B2S = temp
End Function
End Class
' 這是一個輔助類,為了實現(xiàn)outRequest.Forms.Count功能。
Class Counter
Private m_iCnt
' count是咱們這個類的一個只讀屬性
Public Property Get Count()
Count = m_iCnt
End Property
Public Function setCount(cnt)
m_iCnt = cnt
End Function
End Class
%>
<%
'下面是測試碼
set outRequest = new UploadRequest
%>
<%=outRequest.Form(0).Name%>:<%=outRequest.Form("file1_desc")%><br>
<%=outRequest.Form(1).Name%>:<%=outRequest.Form("file2_desc")%><br>
<%=outRequest.Form(2).Name%>:<%=outRequest.Form(2).Count%><br>
<%=outRequest.Form(3).Name%>:<%=outRequest.Form(3)%>
一共有<%=outRequest.Forms.Count%>個文本單元<hr>
<%=outRequest.File(0).Name%>:
<%=outRequest.File("file1").ContentType%>:
<%=outRequest.File("file1").Size%>byte:
<%=outRequest.File("file1").FileName%>:
<%=outRequest.File("file1").FilePath%><br>
<%=outRequest.File(1).Name%>:
<%=outRequest.File("file2").ContentType%>:
<%=outRequest.File("file2").Size%>byte:
<%=outRequest.File("file2").FileName%>:
<%=outRequest.File("file2").FilePath%><br>
一共有<%=outRequest.Files.Count%>個文件單元<hr>
<%
'如果要測試文件1內(nèi)容,可以:
'response.clear
'response.contenttype = outRequest.File("file1").ContentType
'response.BinaryWrite(outRequest.File("file1").Data)
'如果要測試文件2內(nèi)容,可以:
'response.clear
'response.contenttype = outRequest.File("file2").ContentType
'response.BinaryWrite(outRequest.File("file2").Data)
%>
測試表單testform.html還用第三天那個。注意,每一個文本表單域和文件表單域都要填上,原因還是測試碼給得很特殊,讀了各個項目的值,測試了各個屬性。不過,現(xiàn)實情況下,因為事先知道表單域的名稱;即使不知道,也可以用outRequest.Forms.Count/outRequest.Files.Count來循環(huán)讀取,所以是沒問題的,不容易出錯。
試試看!怎么樣?成功!注意測試碼最下邊的部分,用來測文件內(nèi)容的。分了兩段,可以分別打開注釋,進行測試哦。
現(xiàn)在,中英文文本都沒有問題;文件也是各種都行,路徑?jīng)]限制。用法也很簡單,很清晰,F(xiàn)在,文本域、文件域的讀取就都解決了!
--------------------------------------------------------
今天這一段是很有意思的。結合第三天的內(nèi)容,就可以看到解決upload問題的全貌了。這兩次我都是越寫越興奮,放不下。不過,這可還沒有結束哦!要做一個功能強大的東東出來,還需要限制上傳文件尺寸、限制類型、存盤、入庫等附加功能。所以,打起精神,讓我們一鼓作氣,把他徹底搞定!哦?!現(xiàn)在都一點啦,明天吧,呵呵。。。
==============================================================
第六天:附加功能
現(xiàn)在,核心功能已經(jīng)實現(xiàn)了。但是,僅僅這樣,還不能大幅度的提高我們的工作效率。一些常用的、重復的操作,象限制上傳文件大小、類型以及文件存盤、入庫等還是應該統(tǒng)一處理。所以,當前的目標,就是封裝常用的功能,盡量讓他好用。
首先,我們看看上傳限制的實現(xiàn)。我們要控制的,有文件大小,和文件類型。大小很容易控制,只要在每次讀取文件放進FileElement類的時候(執(zhí)行Add方法的時候),看一下它的Size,并且適時的拋出異常就可以了;文件類型控制類似,不過需要判斷一下擴展名(當然也可以利用contentType,不過更依賴機器配置——每一種機器可以識別的contentType各有不同)。
然后,就是結果的永久性保存了。為了靈活起見,入庫就不再封裝,由用戶解決;存盤因為它是原子操作(本身不可分割,而且也不依賴其他操作),可以比較好的封裝。我們可以在UploadRequest類提供SaveTo(serverpath)方法,用來一次性保存所有圖片;另外在FileElement類里提供SaveTo(serverpath)和SaveAs(serverpath, newfilename)方法,分別實現(xiàn)按照原文件名保存圖片以及按照指定文件名保存文件的功能。
考慮到上傳控制的問題,把讀取數(shù)據(jù)的fill方法放到Class_Initialize已經(jīng)不合適了。我們另做一個Upload方法,進行文件上傳的具體操作。這樣,就可以在上傳之前,對ourRequest進行設置。新的類設計如下:
A。UploadRequest(上面設計過,這里是擴充)
這個類和request對象是對應的
屬性:
RawData 得到原始數(shù)據(jù),方便檢查[只讀]
Forms 得到一個有count屬性的計數(shù)器,
可以用outRequest.Forms.Count的方式,得到文本表單域的的個數(shù)[只讀]
Files 得到一個有count屬性的計數(shù)器,
可以用outRequest.Files.Count的方式,得到文件表單域的的個數(shù)[只讀]
Form(index) 可以用數(shù)字或文本檢索文本表單域,做用類似request.form。
他返回一個FormElement型的對象[只讀]
File(index) 可以用數(shù)字或文本檢索文件表單域,他返回一個FileElement型的對象[只讀]
TotalBytes 得到所有文件總大小[只讀]
AllowedFilesList 設置允許上傳的擴展名[只寫]
DeniedFilesList 設置不允許上傳的擴展名(和AllowedFilesList任取一個就行了)[只寫]
MaxFileSize 設置允許上傳的每個文件的大小[只寫]
TotalMaxFileSize 設置允許上傳的所有文件的大小[只寫]
方法:
Upload 上傳分拆的具體實現(xiàn)方法
SaveTo(path) 保存所有的文件到指定路徑(按原名)
B。FileElement
可以把它看成單個文件域的化身。通過這個類,可以得到詳細的文件信息,比如name,data,path,filename,contentType,size等等。
屬性:
Name 文件域的名稱。就是<input type=file name=xxx>里的xxx[只讀]
Data 文件域的內(nèi)容。二進制串[只讀]
ContentType 文件域的contentType[只讀]
FilePath 文件域包含的文件在客戶機上的全路徑[只讀]
FileName 文件域包含的文件的文件名[只讀]
Size 文件域包含的文件的尺寸[只讀]
方法:
SaveTo(path) 保存當前文件到指定路徑(按原名)
SaveAs(path, name) 按給定文件名保存當前文件到指定路徑,如果存在,就覆蓋
SaveWithoutOverwrite(path, name) 按給定文件名保存當前文件到指定路徑,不覆蓋
保存文件的時候,因為它是二進制流,所以,只能用于文本操作的fso是不能用的,這里,我們用到了ado的stream對象,他也是唯一的選擇。但是,一定要注意,因為新版ado才有他,所以,老的系統(tǒng)可能不能正確的進行文件的保存。如果提示了stream對象的問題,請升級MDAC,或者干脆放棄這個功能。因為我們之所以用無組件的方法,就是不想在server上配置。裝MDAC本身已經(jīng)偏離了目標。
現(xiàn)在,只要實現(xiàn)了這幾個新方法和屬性,我們的無組件上傳就可以說是大功告成了。明天,我們就最終實現(xiàn)這個功能完善的類,并且把前兩天沒有注意到的細節(jié)進行一些修補。其實明天的內(nèi)容不多,新的知識只有stream對象的用法。這些,在論壇里以前就有提及,如果不是很清楚,可以翻看一下論壇的舊貼,或是到http://www.2yup.com/asp/referrence/index.asp下一個ADO參考看看,相信會揭開你的心中疑團。
OK,讓我們一起期待明天吧! ^&^