緣起:
這幾天在看我們程式的信箱功能程式碼,注意到它有上傳附件的功能,上傳完的附件會傳到後台,還會專門開個資料夾放那些附件。蠻特別的,它不是傳到
ws 主機,然後再把檔案路徑記到資料表。
我注意到在上傳檔案的部份,它有用到
Request.Files 來取得使用者上傳的檔案,然後讀取檔案、把檔案給寫到 Server
端的資料夾的部份則是用到 C# 的 Stream。覺得這些功能蠻實用的,而且自己好像也還沒實際寫過從前台上傳檔案到 Server
的程式,所以就試著寫了一次,順便紀錄。
C# FileStream:
公司專案有個 Tools 類別,它有個 static 方法,叫
CopyStream,大概長這樣
功能就是複製一個 Stream 的內容到另一個 Stream,我開個 Console 專案測試,程式碼如下,用 FileStream 來讀取程式檔前路徑下的 txt 檔,然後把內容給 Copy 到 MemoryStream 裡,然後一個 byte 一個 byte 輸出
using System;
using System.Reflection;
using System.IO;
namespace ConsoleApp1
{
class Program
{
public static void Main(String[] args)
{
//程式當前執行路徑下的 txt 檔
string sFilePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
sFilePath += "/test.txt";
MemoryStream memoryStream = new MemoryStream();
using (FileStream fileStream = new FileStream(sFilePath, FileMode.Open, FileAccess.Read))
{
CopyStream(memoryStream, fileStream);
}
foreach (byte b in memoryStream.ToArray())
{
Console.WriteLine(b);
}
memoryStream.Close();
Console.ReadKey();
}
private static void CopyStream(Stream streamDest, Stream streamSource)
{
byte[] buffer = new byte[1024 * 16];
int iRead;
while ((iRead = streamSource.Read(buffer, 0, buffer.Length)) > 0)
{
streamDest.Write(buffer, 0, iRead);
}
streamDest.Position = 0;
}
}
}
那個 test.txt 是放在專案的 bin/Debug 下,內容就
"Hello"
我不確定為啥 CopyStream 的 buffer size
是開 1024*16,有特別去查了一下,就我的理解,回答是說,沒有特別需求的話,開到 4K 或以上似乎就行,開到
16 K 的話就有點浪費記憶體了。
我後來發現,Stream 類別自己就有個
CopyTo
的方法可以使用,把自己的內容給複製到另一個 Stream 裡。我有試著呼叫
fileStream 的 CopyTo 方法,把內容給複製到
memoryStream,結果是一樣的,所以,不太清楚為什麼我們公司的專案還有特別寫了一個方法來做這件事。
Request.Files:
再來是開個 ASP
專案測試擷取檔案上傳的部份,我在新開的專案下新增一個 test asp 頁面。
這是它的 aspx,有三個元件,上傳檔案的
input,顯示一些資訊的 label,還有一個 button。
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="test.aspx.cs" Inherits="WebApplication1.test" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<input type="file" multiple="multiple" runat="server"/>
<asp:Label runat="server" ID="labelTest"></asp:Label>
<asp:Button runat="server" id="btnUpload" Text="上傳" OnClick="btnUpload_Click"/>
</div>
</form>
</body>
</html>
然後這是它的 cs,我第一步是先測試是否真的有從 Request.Files 取到上傳的檔案,它是一個 HttpFileCollection 類別,既然是
Collection,那基本上都會有個 Count
來記錄有多少個成員,所以這邊的程式碼是,點了 button 後,顯示上傳檔案的數量
using System;
namespace WebApplication1
{
public partial class test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void btnUpload_Click(object sender, EventArgs e)
{
labelTest.Text = Request.Files.Count.ToString();
}
}
}
build 完程式,用 iis express 來看我們的 test.asp
頁面會是這樣。
using System;
using System.IO;
using System.Web;
namespace WebApplication1
{
public partial class test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void btnUpload_Click(object sender, EventArgs e)
{
string sFileUploadRootPath = Server.MapPath("/upload");
DirectoryInfo directoryInfo = new DirectoryInfo(sFileUploadRootPath);
if(!directoryInfo.Exists) Directory.CreateDirectory(sFileUploadRootPath);
HttpFileCollection httpFileCollection = Request.Files;
for(int i=0;i<httpFileCollection.Count;i++)
{
HttpPostedFile httpPostedFile = httpFileCollection[i];
if(httpPostedFile.ContentLength > 0)
{
string sFileUploadPath = $"{sFileUploadRootPath}/{httpPostedFile.FileName}";
using(FileStream fileStream = new FileStream(sFileUploadPath, FileMode.OpenOrCreate, FileAccess.Write))
{
httpPostedFile.InputStream.CopyTo(fileStream);
}
}
}
}
}
}
16 行 : 可以用 Server.MapPath 來取得實體路徑,那個 /upload 就是我要放上傳檔的資料夾。
17、18行 : 確認資料夾是否有存在,沒的話建立一個。
22 行 : for 迴圈遊歷 HttpFileCollection,我原本以為可以用 foreach 來做的,但後來發現不行。原來用 foreach 會取到它檔案的名字,是 string,正確做法是用存取陣列的方式來取得每個 HttpPostedFile
25 行 : 檢查,避免空的檔案。
27 行 : 把上傳路徑跟檔案名稱組起來,要給 FileStream constructor 用的路徑。
28 ~ 31 行 : 依路徑開啟 FileStream,然後從 httpPostedFile.InputStream 來取得檔案的 Stream,用 CopyTo 把內容給複製到 fileStream。
這樣就可以了,一樣測試上傳兩張圖片,按完 "上傳" 後,可以發現專案資料夾下面有多一個 upload 的資料夾,點開來看,可以看到兩張上傳的圖片。
沒有留言:
張貼留言