搜尋此網誌

2024年3月6日 星期三

C# FileStream 與 ASP HttpPostedFile

緣起:


    這幾天在看我們程式的信箱功能程式碼,注意到它有上傳附件的功能,上傳完的附件會傳到後台,還會專門開個資料夾放那些附件。蠻特別的,它不是傳到 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"


    執行程式後,可以看到 FileStream 讀取的內容確實有複製到我們的 MemoryStream 裡。


    我不確定為啥 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 頁面會是這樣。


    點選 "選擇檔案",我這邊選了兩張我家狗狗的照片,選完後再按上傳


    可以看到,Server 端確實有收到兩個上傳的圖檔,所以下一步就是要把圖檔給存到 Server,程式碼如下

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 的資料夾,點開來看,可以看到兩張上傳的圖片。



沒有留言:

張貼留言