ASP.NET 檔案上傳處理—檔案大小超過限制之處理

ASP.NET 檔案上傳處理檔案大小超過限制之處理

/黃忠成

緣起

我於『極意之道-ASP.NET AJAX/Silverlight聖典』中放入了一個非同步擋案上傳的例子,該例利用了IFRAME技巧,巧妙的規避了AJAX無法處理FileUpload控件上傳的問題,事實上此技巧在網路上已經行之有年,我只能算是應用此技巧的多數人之一。以ASP.NET頁面來處理檔案上傳,大多會遇到兩個問題,一是上傳檔案的大小大於所設下的限制時(ASP.NET預設為4M),網頁會以連線錯誤來回應(如圖1)

1

ASP.NET 檔案上傳處理—檔案大小超過限制之處理

<shapetype id="_x0000_t75" stroked="f" filled="f" path="[email protected]@[email protected]@[email protected]@[email protected]@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 415.5pt; HEIGHT: 272.25pt" o:ole="" type="#_x0000_t75"><imagedata o:title="" src="file:///D:%5CDOCUME~1%5CADMINI~1%5CLOCALS~1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_image001.png"></imagedata></shape>

二是上傳期間,網頁會處於無回應的狀態。第一個問題緣由很簡單,因為當你按下Submit按紐時,瀏覽器便會收集資料上傳,而當上傳檔案過大時,伺服器端會拒絕接收,並切斷連線,導致瀏覽器在未送完要求(Request)時,便因連線中斷而產生錯誤訊息。那有沒有可能在伺服器端預先判斷檔案大小,若超過限制時,便做出正常回應而讓原網頁顯示較友善的錯誤訊息呢?這得分成兩個部份來談,當然!在伺服器端做出判斷後回應錯誤網頁是辦的到的,但問題在於瀏覽器尚未將所有資料上傳完畢,結果還是會因為資料未完全上傳而連線被切斷而顯示出連線錯誤的訊息。那要如何解決這個問題呢?有個手法可以巧妙的解決此問題,就是運用IFRAME搭配AJAX,利用IFRAME來裝載Upload的頁面,然後在使用者按下Submit按紐時,以JavaScriptIFRAME設為不可視,接著於伺服器端判斷上傳檔案的大小,若超過限制則直接跳離,當然!此時IFRAME會出現連線錯誤的網頁,不過由於是處於不可視狀態,所以使用者是看不到的,此時我們可以運用JavaScriptalert來顯示錯誤訊息,或是使用Redirect來導向錯誤訊息網頁,如使用alert,那麼你必須將原來的IFRAME移除,重新建立一個,避免使用者見到那個連線錯誤的網頁。

實作

有了以上的知識,要實現就不困難了,首先要準備一個用來上傳檔案的.aspx,程式碼如下所示。

UploadHandler.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="UploadHandler.aspx.cs" Inherits="UploadHandler" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

<title>Untitled Page</title>

<script language="javascript">

</script>

</head>

<body>

<form id="form1" runat="server">

<div>

<asp:FileUpload ID="FileUpload1" runat="server" />

<!-- 在上傳開始前,IFrame設為不可視,以此規避因上傳檔案過大而產生的連線錯誤頁面 -->

<asp:Button ID="Button1" OnClientClick="window.top.document.getElementById('fileframe').style.visibility = 'hidden';" runat="server"

Text="Button" />

</div>

</form>

</body>

</html>

UploadHandler.aspx.cs

using System;

using System.Collections;

using System.Configuration;

using System.Data;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.HtmlControls;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Web.Configuration;

public partial class UploadHandler : System.Web.UI.Page

{

private int GetMaxRequestSize()

{

//取得預設的maxRequestLength設定值.

Configuration config = WebConfigurationManager.OpenWebConfiguration("~");

HttpRuntimeSection section =

config.GetSection("system.web/httpRuntime") as HttpRuntimeSection;

return section.MaxRequestLength * 1024;

}

protected override System.Collections.Specialized.NameValueCollection

DeterminePostBackMode()

{

//此處是唯一有機會於上傳檔案之Request開始收取前,判斷大小的地方

if (Request.ContentLength > GetMaxRequestSize()) //判斷上傳檔案是否大於4 MB

{

Session["FileIsToLarge"] = true;

//利用Cache來註記檔案太大,稍後的Timer將會查詢此欄位來決定是否Redirect.

return null; //file to large

}

Session["FileIsAccept"] = true;

//接受檔案,稍後的Timer將會查詢此欄位來重新顯示IFrame

//(我們會在上傳前,IFrame隱藏,來避開顯示錯誤.)

return base.DeterminePostBackMode();

}

protected void Page_Load(object sender, EventArgs e)

{

if (IsPostBack)
{
//save file,only working at WebDev.Server
FileUpload1.SaveAs(@"c:\temp1\A" + FileUpload1.FileName);
}

}

}

此處有幾點要進一步討論,第一點是GetMaxRequestSize函式,此函式會去抓取web.config中的maxRequestLength設定值,來決定上傳限制。第二點是我覆載了DeterminePostBackMode函式,此函式是唯一可在Page開始收取大量上傳資料前做出判斷的地方,過了此函式,Page就會因為收取過大的資料,而產生例外。第三點,我利用了Session來儲存是否接受上傳的狀態,由主頁面利用ASP.NET AJAXTimer控件來定期查詢此值,若發現FileIsToLarge的值為Ture時,便立刻導向錯誤網頁,若發現FileIsAccept值為True,就代表著上傳檔案已被接受,即刻透過RegisterStartupScript函式以Javascript將隱藏的IFRAME顯示出來(當使用者按下UploadHandler.aspx中的Button按紐時,這裡會用JavascriptIFRAME隱藏起來)此頁面要放在主頁面的IFRAME中,下面是主頁面的程式碼。

Default.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

<title>Untitled Page</title>

</head>

<body>

<form id="form1" runat="server">

<div>

<asp:ScriptManager ID="ScriptManager1" runat="server">

</asp:ScriptManager>

<iframe id="fileframe" src=UploadHandler.aspx frameborder="0"

scrolling="no" style="height:60px"></iframe>

<asp:UpdatePanel ID="UpdatePanel1" UpdateMode="Conditional" runat="server">

<ContentTemplate>

<asp:Timer ID="Timer1" runat="server" Interval="100" ontick="Timer1_Tick">

</asp:Timer>

</ContentTemplate>

</asp:UpdatePanel>

</div>

</form>

</body>

</html>

Default.aspx.cs

using System;

using System.Configuration;

using System.Data;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.HtmlControls;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Reflection;

public partial class _Default : System.Web.UI.Page

{

protected void Timer1_Tick(object sender, EventArgs e)

{

if (Session["FileIsToLarge"] != null) //上傳檔案過大,Redirect.

{

Session.Remove("FileIsToLarge");

Response.Redirect("MaxRequestLength.htm");

}

else if (Session["FileIsAccept"] != null)

//接受上傳,IFrame重新設為可視.

{

Session.Remove("FileIsAccept");

ScriptManager.RegisterStartupScript(this, GetType(),

"DisplayFrame", "$get('fileframe').style.visibility = 'visible';", true);

}

}

}

另外,當上傳超過限制時,此例會導向另一網頁來顯示錯誤,下面是該網頁的HTML程式碼。

MaxRequestLength.htm

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<title>Untitled Page</title>

</head>

<body>

Upload File size to large.

</body>

</html>

結果如我們所預期的,當選了一個較大(大於maxRequestLength設定值)的檔案來上傳時,此例立刻會導向MaxRequestLength.htm來顯示錯誤。下面是web.config的組態設定,其實大多是ASP.NET AJAX預設的設定,我標示出可改變上傳限制的部份,讀者們可自行修改。

web.config

<?xml version="1.0"?>

<configuration>

<configSections>

<sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">

<sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">

<section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="MachineToApplication"/>

<sectionGroup name="webServices" type="System.Web.Configuration.ScriptingWebServicesSectionGroup, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">

<section name="jsonSerialization" type="System.Web.Configuration.ScriptingJsonSerializationSection, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="Everywhere"/>

<section name="profileService" type="System.Web.Configuration.ScriptingProfileServiceSection, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="MachineToApplication"/>

<section name="authenticationService" type="System.Web.Configuration.ScriptingAuthenticationServiceSection, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="MachineToApplication"/>

</sectionGroup>

</sectionGroup>

</sectionGroup>

</configSections>

<system.web>

<pages>

<controls>

<add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

</controls>

</pages>

<!--

Set compilation debug="true" to insert debugging

symbols into the compiled page. Because this

affects performance, set this value to true only

during development.

-->

<compilation debug="true">

<assemblies>

<add assembly="System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>