65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 ASP.NET 进行文件上传

2002年1月23日

CPOL

7分钟阅读

viewsIcon

3963141

downloadIcon

83398

本文向您展示如何使用 ASP.NET 和 C# 进行文件上传。

Sample Image

回到 ASP 时代,通过网页上传文件是一个棘手的问题。问题在于,由于用于从客户端浏览器提交表单的加密类型,在服务器端接收此类文件是一项复杂的任务。数据必须以安全的字节数组形式检索并在解密后才能使用。大多数人求助于第三方 DLL。少数人编写了自己的 DLL,甚至有些人使用 VBScript 为此问题编写了纯 ASP 解决方案。幸运的是,.NET Framework SDK 附带了一些类,这些类从 Web 开发人员的角度使文件上传变得简单。本文将演示以下概念:

设置用于文件上传的 HTML 表单

HTML 表单能够上传文件的要求非常简单:您必须使用 multipart/form-data 加密,并且必须使用 post 方法。

<form id="Form1" method="post" runat="server" enctype="multipart/form-data">

用于上传文件的 HTML 输入控件的类型为 file

<input id="filMyFile" type="file" runat="server">

这是 HTML 表单需要完成的所有操作,以便客户端能够将文件提交到您的 ASP.NET 应用程序。

接收上传的文件

<INPUT TYPE="file" 对应于服务器端的 System.Web.UI.HtmlControls.HtmlInputFile 类型对象。因此,如果您像我一样,为页面使用 CodeBehind 架构,那么您的类中将有一个类似的成员字段定义:

protected HtmlInputFile filMyFile;

HtmlInputFile 类有几个不同的属性,但这里唯一真正关心的是 PostedFile。此属性将告诉我们所有关于已上传到服务器的文件的信息。如果客户端未提交文件,则 PostedFile 属性为 null。因此,我们可以通过执行以下操作来简单地检查是否发送了文件:

if( filMyFile.PostedFile != null )
{
    // File was sent

}
else
{
    // No file

}

如果文件确实已上传,则 PostedFile 属性将包含有效的 System.Web.HttpPostedFile 对象。HttpPostedFile 为我们提供了 4 个属性:

  • ContentLength:上传文件的大小(字节)
  • ContentType:上传文件的 MIME 类型,即“image/gif”
  • FileName:客户端系统上上传文件的完整路径,即 c:\Some folder\MyPicture.gif
  • InputStream:流对象,使我们能够访问上传的数据

让我们找出文件的大小

// Get a reference to PostedFile object

HttpPostedFile myFile = filMyFile.PostedFile;

// Get size of uploaded file

int nFileLen = myFile.ContentLength;

既然我们知道了大小,就可以继续检索数据了。首先,我们需要分配一个字节数组来存储数据:

// Allocate a buffer for reading of the file

byte[] myData = new byte[nFileLen];

接下来,我们可以使用 InputStream 对象将上传的文件读入缓冲区

// Read uploaded file from the Stream

myFile.InputStream.Read(myData, 0, nFileLen);

此时,我们已成功将上传的文件检索到一个名为 myData 的字节数组中。接下来如何处理它在很大程度上取决于您的应用程序的要求。我将在接下来的部分中展示将文件保存到硬盘和数据库。

将上传的文件保存到磁盘

我为我的演示项目编写了一个简单的函数,用于将文件存储到磁盘:

// Writes file to current folder

private void WriteToFile(string strPath, ref byte[] Buffer)
{
    // Create a file

    FileStream newFile = new FileStream(strPath, FileMode.Create);

     // Write data to the file

    newFile.Write(Buffer, 0, Buffer.Length);

    // Close file

    newFile.Close();
}

我将文件要存储的完整路径和包含文件数据的缓冲区的引用传递给此函数。它使用 System.IO.FileStream 对象将缓冲区写入磁盘。这种非常简单的三行代码方法在大多数情况下都适用。这里有几个注意事项:获取上传文件的文件名和安全性。由于 PostedFileFileName 属性是客户端计算机上上传文件的完整路径,我们可能只希望使用该路径的文件名部分。我们可以使用一个非常方便的小工具类:System.IO.Path,而不是使用一些解析技术来查找反斜杠等。

string strFilename = Path.GetFileName(myFile.FileName);

安全是另一个问题。在我的演示项目中,我将文件存储在项目执行的同一文件夹中。为了使其正常工作,ASPNET 帐户(用于执行 ASP.NET 进程的帐户)必须对该文件夹具有写入权限。默认情况下没有,因此您需要右键单击该文件夹,转到“安全”选项卡,并将 ASP.NET 帐户添加到列表中。然后通过选中“写入”复选框并单击“确定”来授予该帐户写入权限。

将上传的文件保存到数据库

以下函数在我的演示项目中用于将上传的文件存储到数据库中:

// Writes file to the database

private int WriteToDB(string strName, string strType, ref byte[] Buffer)
{
    int nFileID = 0;

    // Create connection

    OleDbConnection dbConn = new OleDbConnection(GetConnectionString());

    // Create Adapter

    OleDbDataAdapter dbAdapt = new OleDbDataAdapter("SELECT * FROM tblFile", dbConn);
            
    // We need this to get an ID back from the database

    dbAdapt.MissingSchemaAction = MissingSchemaAction.AddWithKey;
            
    // Create and initialize CommandBuilder

    OleDbCommandBuilder dbCB = new OleDbCommandBuilder(dbAdapt);

    // Open Connection

    dbConn.Open();
                    
    // New DataSet

    DataSet dbSet = new DataSet();
                    
    // Populate DataSet with data

    dbAdapt.Fill(dbSet, "tblFile");

    // Get reference to our table

    DataTable dbTable = dbSet.Tables["tblFile"];

    // Create new row

    DataRow dbRow = dbTable.NewRow();

    // Store data in the row

    dbRow["FileName"] = strName;
    dbRow["FileSize"] = Buffer.Length;
    dbRow["ContentType"] = strType;
    dbRow["FileData"] = Buffer;

    // Add row back to table

    dbTable.Rows.Add(dbRow);

    // Update data source

    dbAdapt.Update(dbSet, "tblFile");

    // Get newFileID

    if( !dbRow.IsNull("FileID") )
        nFileID = (int)dbRow["FileID"];
            
    // Close connection

    dbConn.Close();

    // Return FileID

    return nFileID;
}

我在演示项目中使用了一个 Access 数据库,它只有一个表 tblFile,其中包含 5 个字段,定义如下:

FileID – 自动编号 Filename – 文本 255 FileSize - 长整型 ContentType – 文本 100 FileData – OLE 对象

上述函数中的代码非常直观。我创建了 OleDbConnectionOleDbDataAdapterDataSet 对象,然后将一行添加到 DataSet 中唯一的表中。这里需要注意的重要一点是,需要创建 OleDbCommandBuilder 对象并使用对 OleDbDataAdapter 对象的引用进行初始化,以便自动为我们构建插入查询。此外,如果您想检索刚刚存储在数据库中的文件的新分配的 FileID,您需要确保将 Adapter 的 MissingSchemaAction 属性设置为 MissingSchemaAction.AddWithKey。这确保了当您调用 DataAdapterUpdate 方法时,主键/自动编号 FileID 字段将使用新的 ID 填充。

本地 ASP.NET 帐户必须对数据库文件具有写入权限,您的代码才能成功。如果您的文件放置在 wwwroot 下的任何位置,您需要在运行我的演示项目之前手动为数据库文件授予所有必要的权限。

从数据库中检索文件

我在我的演示项目中使用了相同的 ASPX 页面来读取和返回数据库中的文件数据。我检查了传递给页面的 FileID 参数。如果它已传递,我知道我需要返回一个文件而不是处理用户的输入:

private void Page_Load(object sender, System.EventArgs e)
{
    // Check if FileID was passed to this page as a parameter

    if( Request.QueryString["FileID"] != null )
    {
        // Get the file out of database and return it to requesting client

        ShowTheFile(Convert.ToInt32(Request.QueryString["FileID"]));
    }
            
}

ShowTheFile 函数在这里完成了所有工作:

// Read file out of the database and returns it to client

private void ShowTheFile(int FileID)
{
    // Define SQL select statement

    string SQL = "SELECT FileSize, FileData, ContentType FROM tblFile WHERE FileID = " 
                + FileID.ToString();

    // Create Connection object

    OleDbConnection dbConn = new OleDbConnection(GetConnectionString());

    // Create Command Object

    OleDbCommand dbComm = new OleDbCommand(SQL, dbConn);

    // Open Connection

    dbConn.Open();

    // Execute command and receive DataReader

    OleDbDataReader dbRead = dbComm.ExecuteReader();

    // Read row

    dbRead.Read();

    // Clear Response buffer

    Response.Clear();

    // Set ContentType to the ContentType of our file

    Response.ContentType = (string)dbRead["ContentType"];

    // Write data out of database into Output Stream

    Response.OutputStream.Write((byte[])dbRead["FileData"], 0, (int)dbRead["FileSize"]);

    // Close database connection

    dbConn.Close();

    // End the page

    Response.End();
}

我使用 OleDbConnectionOleDbCommandOleDbDataReader 对象来检索数据。下一步是清除 Response 输出缓冲区,以确保除了文件数据之外没有其他信息发送给客户端。这仅在您的页面缓冲已打开(ASP.NET 中的默认设置)的情况下才有效。我将 Response 对象的 ContentType 属性设置为文件的内容类型。然后我将文件数据写入 Response 对象的 Output 流中。这里重要的是在最后调用 Response.End() 以防止该页面的进一步处理。我们在这里完成了,我们不希望其他代码执行。

安全注意事项

重要

由于我的演示项目将文件写入磁盘或数据写入 Access 数据库,因此您需要确保在您要写入的文件夹和文件上正确设置了安全权限。.NET 的 Beta 版本使用 System 帐户执行 ASP.NET 进程,并且它可以访问计算机上的所有内容。然而,.NET 的最终版本在本地 ASP.NET 帐户下运行所有 ASP.NET 进程。该帐户需要对用于写入数据的所有文件夹和文件具有写入权限。

为了使我的演示项目在 .NET 发布版本下工作,您需要将其放置在一个文件夹中(wwwroot 下的任何位置),并按如下方式设置该文件夹的权限:右键单击该文件夹,转到“属性”、“安全”选项卡,确保本地 ASPNET 帐户对该文件夹具有读取和写入权限。如果没有,请将本地 ASPNET 帐户添加到列表中,并授予其读取和写入权限。单击“确定”。

© . All rights reserved.