第五天:得到文件單元
今天我們要進行的部分,是比較有趣味性地——得到文件內容。其實,看看我們的要處理的數據,再看看前天文本單元的處理,相信大家也會心中有數。
為了清晰的區分文件和文本單元,這一次,我們用ourRequest.file(index)來對應文本單元的ourRequest.form(index)。當然,因為對于文件,我們需要得到的信息不同于文本,所以這次得到的,也不會是FormElement,而是一個新對象FileElement。
文件單元和文本單元在原始數據上,不同點少得可憐: 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成員。現在,我們就來擴充他: A。UploadRequest(上面設計過,這里是擴充) 這個類和request對象是對應的 屬性: RawData 得到原始數據,方便檢查[只讀] Forms 得到一個有count屬性的計數器, 可以用outRequest.Forms.Count的方式,得到文本表單域的的個數[只讀] Files 得到一個有count屬性的計數器, 可以用outRequest.Files.Count的方式,得到文件表單域的的個數[只讀] Form(index) 可以用數字或文本檢索文本表單域,做用類似request.form。 他返回一個FormElement型的對象 File(index) 可以用數字或文本檢索文件表單域,他返回一個FileElement型的對象 B。FileElement 可以把它看成單個文件域的化身。通過這個類,可以得到詳細的文件信息,比如name,data,path,filename,contentType,size等等。 屬性: Name 文件域的名稱。就是<input type=file name=xxx>里的xxx Data 文件域的內容。二進制串 ContentType 文件域的contentType FilePath 文件域包含的文件在客戶機上的全路徑 FileName 文件域包含的文件的文件名 Size 文件域包含的文件的尺寸
這里是實現。還是存成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 '是數字,合法! 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 '給個一個不是數字的東東?出錯! 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
' 有了這個,就可以檢查原始數據了 Public Property Get RawData() RawData = m_bFormdata End Property
' 這一段丑陋的代碼是為了實現outRequest.Forms.Count這個功能。 Public Property Get Forms() Set Forms = New Counter Forms.setCount(m_dicForms.Count) End Property
' 這一段丑陋的代碼是為了實現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 '是數字?用數字來檢索 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 '給了一個不是數字也不是字符串的東東?出錯! err.raise 2,"IllegalArgument", "非法的表單元素索引" End If End Property
Public Property Get File(index) If isNumeric(index) Then '是數字?用數字來檢索 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 '給了一個不是數字也不是字符串的東東?出錯! err.raise 2,"IllegalArgument", "非法的表單元素索引" End If End Property
Private Sub fill ' 得到數據 m_bFormdata=request.binaryread(request.totalbytes) ' 調用這個函數實現遞歸循環,讀取文本/文件單元 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,自己研究一下?當作業吧) 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
' 截取剩下的部分,遞歸調用這個函數,來得到下一個part1。 Call fillEveryFirstPart(rightb(data, lenb(data)-endpos-1)) End Sub
' 這是一個公用函數,作用是二進制和字符串的轉換 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 '遇到了雙字節,就得兩個字符一起處理 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
' 這是一個輔助類,為了實現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內容,可以: 'response.clear 'response.contenttype = outRequest.File("file1").ContentType 'response.BinaryWrite(outRequest.File("file1").Data) '如果要測試文件2內容,可以: 'response.clear 'response.contenttype = outRequest.File("file2").ContentType 'response.BinaryWrite(outRequest.File("file2").Data) %>
測試表單testform.html還用第三天那個。注意,每一個文本表單域和文件表單域都要填上,原因還是測試碼給得很特殊,讀了各個項目的值,測試了各個屬性。不過,現實情況下,因為事先知道表單域的名稱;即使不知道,也可以用outRequest.Forms.Count/outRequest.Files.Count來循環讀取,所以是沒問題的,不容易出錯。
試試看!怎么樣?成功!注意測試碼最下邊的部分,用來測文件內容的。分了兩段,可以分別打開注釋,進行測試哦。 現在,中英文文本都沒有問題;文件也是各種都行,路徑沒限制。用法也很簡單,很清晰。現在,文本域、文件域的讀取就都解決了!
-------------------------------------------------------- 今天這一段是很有意思的。結合第三天的內容,就可以看到解決upload問題的全貌了。這兩次我都是越寫越興奮,放不下。不過,這可還沒有結束哦!要做一個功能強大的東東出來,還需要限制上傳文件尺寸、限制類型、存盤、入庫等附加功能。所以,打起精神,讓我們一鼓作氣,把他徹底搞定!哦?!現在都一點啦,明天吧,呵呵。。。
============================================================== 第六天:附加功能
現在,核心功能已經實現了。但是,僅僅這樣,還不能大幅度的提高我們的工作效率。一些常用的、重復的操作,象限制上傳文件大小、類型以及文件存盤、入庫等還是應該統一處理。所以,當前的目標,就是封裝常用的功能,盡量讓他好用。
首先,我們看看上傳限制的實現。我們要控制的,有文件大小,和文件類型。大小很容易控制,只要在每次讀取文件放進FileElement類的時候(執行Add方法的時候),看一下它的Size,并且適時的拋出異常就可以了;文件類型控制類似,不過需要判斷一下擴展名(當然也可以利用contentType,不過更依賴機器配置——每一種機器可以識別的contentType各有不同)。
然后,就是結果的永久性保存了。為了靈活起見,入庫就不再封裝,由用戶解決;存盤因為它是原子操作(本身不可分割,而且也不依賴其他操作),可以比較好的封裝。我們可以在UploadRequest類提供SaveTo(serverpath)方法,用來一次性保存所有圖片;另外在FileElement類里提供SaveTo(serverpath)和SaveAs(serverpath, newfilename)方法,分別實現按照原文件名保存圖片以及按照指定文件名保存文件的功能。
考慮到上傳控制的問題,把讀取數據的fill方法放到Class_Initialize已經不合適了。我們另做一個Upload方法,進行文件上傳的具體操作。這樣,就可以在上傳之前,對ourRequest進行設置。新的類設計如下: A。UploadRequest(上面設計過,這里是擴充) 這個類和request對象是對應的 屬性: RawData 得到原始數據,方便檢查[只讀] Forms 得到一個有count屬性的計數器, 可以用outRequest.Forms.Count的方式,得到文本表單域的的個數[只讀] Files 得到一個有count屬性的計數器, 可以用outRequest.Files.Count的方式,得到文件表單域的的個數[只讀] Form(index) 可以用數字或文本檢索文本表單域,做用類似request.form。 他返回一個FormElement型的對象[只讀] File(index) 可以用數字或文本檢索文件表單域,他返回一個FileElement型的對象[只讀] TotalBytes 得到所有文件總大小[只讀] AllowedFilesList 設置允許上傳的擴展名[只寫] DeniedFilesList 設置不允許上傳的擴展名(和AllowedFilesList任取一個就行了)[只寫] MaxFileSize 設置允許上傳的每個文件的大小[只寫] TotalMaxFileSize 設置允許上傳的所有文件的大小[只寫] 方法: Upload 上傳分拆的具體實現方法 SaveTo(path) 保存所有的文件到指定路徑(按原名)
B。FileElement 可以把它看成單個文件域的化身。通過這個類,可以得到詳細的文件信息,比如name,data,path,filename,contentType,size等等。 屬性: Name 文件域的名稱。就是<input type=file name=xxx>里的xxx[只讀] Data 文件域的內容。二進制串[只讀] ContentType 文件域的contentType[只讀] FilePath 文件域包含的文件在客戶機上的全路徑[只讀] FileName 文件域包含的文件的文件名[只讀] Size 文件域包含的文件的尺寸[只讀] 方法: SaveTo(path) 保存當前文件到指定路徑(按原名) SaveAs(path, name) 按給定文件名保存當前文件到指定路徑,如果存在,就覆蓋 SaveWithoutOverwrite(path, name) 按給定文件名保存當前文件到指定路徑,不覆蓋
保存文件的時候,因為它是二進制流,所以,只能用于文本操作的fso是不能用的,這里,我們用到了ado的stream對象,他也是唯一的選擇。但是,一定要注意,因為新版ado才有他,所以,老的系統可能不能正確的進行文件的保存。如果提示了stream對象的問題,請升級MDAC,或者干脆放棄這個功能。因為我們之所以用無組件的方法,就是不想在server上配置。裝MDAC本身已經偏離了目標。
現在,只要實現了這幾個新方法和屬性,我們的無組件上傳就可以說是大功告成了。明天,我們就最終實現這個功能完善的類,并且把前兩天沒有注意到的細節進行一些修補。其實明天的內容不多,新的知識只有stream對象的用法。這些,在論壇里以前就有提及,如果不是很清楚,可以翻看一下論壇的舊貼,或是到http://www.2yup.com/asp/referrence/index.asp下一個ADO參考看看,相信會揭開你的心中疑團。
OK,讓我們一起期待明天吧! ^&^
|