HttpPostedFileBase 類別

 

HttpPostedFileBase是專門擔任用來接收前端檔案上傳過來的煤介,

HttpPostedFileBase的屬性有

名稱 描述
ContentLength 在衍生類別中覆寫時,取得上傳的檔案大小 (以位元組為單位)。
ContentType 在衍生類別中覆寫時,會取得所上傳檔案的 MIME 內容類型。
FileName 在衍生類別中被覆寫時,取得用戶端上檔案的完整格式名稱。
InputStream 在衍生類別中覆寫時,取得 Stream 物件,該物件指向所上傳的檔案,以準備讀取該檔案的內容。

個人認為HttpPostedFileBase唯一比較有用的方法為

名稱 描述
SaveAs(String filename) 在衍生類別中覆寫時,儲存所上傳檔案的內容。

 

一、上傳檔案至磁碟

先來看一個基本型的

View為

<form action="@Url.Action("Index")" method="post" enctype="multipart/form-data">
    <input type="file" name="file" value="" style="display:inline;" />
    <input type="submit" value="確定" />
</form>

Controller為

[HttpPost]
public ActionResult Index(HttpPostedFileBase file)
{
    string fileName = System.IO.Path.GetFileName(file.FileName);
    string path = System.IO.Path.Combine(Server.MapPath("~/"), fileName);
    file.SaveAs(path);
    return View();
}

畫面為

但通常上檔案時也會一併上傳其他欄位,

View為

<form action="@Url.Action("Index")" method="post" enctype="multipart/form-data">
    <input type="file" name="file" value="" />
    <leabel>id:</leabel>
    <input type="text" name="id" value="" />
    <br />
    <leabel>name:</leabel>
    <input type="text" name="name" value="" />
    <br />
    <input type="submit" value="確定" />
</form>

Controller為

[HttpPost]
public ActionResult Index(HttpPostedFileBase file, string id, string name)
{
    string fileName = id + "_" + name + "_" + System.IO.Path.GetFileName(file.FileName);
    string path = System.IO.Path.Combine(Server.MapPath("~/"), fileName);
    file.SaveAs(path);
    return View();
}

結果為

 

二、多檔上傳至磁碟

IEnumerable<HttpPostedBase>

View為

<form action="@Url.Action("Index")" method="post" enctype="multipart/form-data">
    <input type="file" name="file" value="" />
    <input type="file" name="file" value="" />
    <input type="file" name="file" value="" />
    <input type="submit" value="確定" />
</form>

Controller為

[HttpPost]
public ActionResult Index(IEnumerable<HttpPostedFileBase> file)
{
    foreach (var item in file)
    {
        string fileName = System.IO.Path.GetFileName(item.FileName);
        string path = System.IO.Path.Combine(Server.MapPath("~/"), fileName);
        item.SaveAs(path);
    }
    return View();
}

結果為

 

三、上傳至資料庫

先準備一資料表

CREATE TABLE TABLE_1 
  ( 
     id  INT IDENTITY(1, 1) NOT NULL PRIMARY KEY, 
     img VARBINARY(max) NULL 
  )

至於Model的新增方法假設大家都很熟了,故稍為帶過,

使用DB first方式新增model

Model部份新增完成

 

Index.cshtml為上傳頁面

@using (Html.BeginForm("UpLoadToDB", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
    <input type="file" name="file" value="" />
    <input type="submit" value="send" />
}

About.cshtml為下載頁面

@model string

@if (Model != null)
{
    @Html.ActionLink("下載", "DownLoadFromDB", new { id = Model })
}

Controller為

using System.Linq;
using System.Web;
using System.Web.Mvc;
using WebApplication2.Models;

namespace WebApplication2.Controllers
{
    public class HomeController : Controller
    {
        public testDBEntities db = new testDBEntities();
        public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public ActionResult UpLoadToDB(HttpPostedFileBase file)
        {
            int length = file.ContentLength;
            byte[] buffer = new byte[length];
            file.InputStream.Read(buffer, 0, length);

            db.TABLE_1.Add(new TABLE_1() { img = buffer });
            db.SaveChanges();

            return View("About", (object)db.TABLE_1.Max(a => a.id).ToString());
        }

        public ActionResult DownLoadFromDB(int id)
        {
            byte[] buffer = db.TABLE_1.First(a => a.id == id).img;
            return File(buffer, "image/png", "image.png");
        }
    }
}

通常資料庫儲存的格式為byte array,所以非常需要InputStream屬性來生成byte array。

來看執行結果,先把網站跑起來

選好檔案後準備上傳

上傳完成出現下載連結

下載成功

 

四、超出最大的要求長度

如果你上傳大容量的檔案時,有可能會遇到「超出最大的要求長度」錯誤

伺服器上傳檔案最大限制是2GB,預設為4MB,

解決此問題可修改Web.confing中的httpRuntime項目的maxRequestLength屬性,

以指定允許的要求大小最大值,以KB為單位。

例如我要指定允許的要求大小最大值為10MB則改成

甚至資安較嚴格的需求還可以限制上傳時間需在N秒以內完成,

如下executionTimeout="300"表示限制300秒內完成上傳

<system.web>
  <compilation debug="true" targetFramework="4.5.2" />
  <httpRuntime targetFramework="4.5.2" maxRequestLength="10240" executionTimeout="300" />
</system.web>

 

五、上傳檔案超過限制大小時,導至特定頁面

由於超過上傳大容量檔案的限制時,會直接秀出「超出最大的要求長度」錯誤,

這對使用者似乎不是很友善,而且會被認為是網站掛掉了,也會增加被駭客破解的風險,

所以希望能夠「上傳檔案超過限制大小時,導至特定頁面」,

本來想去覆寫Controller的OnException事件,但卻遲遲接不到Exception,

深入去找以下八個事件,在上傳檔案超過限制大小時也都不會經過以下八個事件,

protected override void OnAuthentication(AuthenticationContext filterContext)
protected override void OnAuthorization(AuthorizationContext filterContext)
protected override void OnActionExecuting(ActionExecutingContext filterContext)
protected override void OnActionExecuted(ActionExecutedContext filterContext)
protected override void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
protected override void OnResultExecuting(ResultExecutingContext filterContext)
protected override void OnResultExecuted(ResultExecutedContext filterContext)
protected override void OnException(ExceptionContext filterContext)

個人在猜是不是錯誤一發生時就被 IIS Web.config 條件設定給篩掉了,所以才不會繼續往下走事件流程。

 

最後解法為請在專案的Web.config加上如下設定

<configuration>
  <system.webServer>
    <security>
      <requestFiltering>
        <requestLimits maxAllowedContentLength="1024" />
      </requestFiltering>
    </security>
    <httpErrors errorMode="Custom" existingResponse="Replace">
      <remove statusCode="404" subStatusCode="13" />
      <error statusCode="404" subStatusCode="13" prefixLanguageFilePath=""
         path="https://your_error_page" responseMode="Redirect" />
    </httpErrors>
  </system.webServer>
</configuration>

path(黃底部份)路徑的指定,就是要你導向導自訂的錯誤頁面(Error Page),

用來告訴使用者發生了什麼錯誤訊息。

 

還有一解法是使用自訂HttpModule來解也不錯。

 

另外,於Global.asax(HttpApplication)也可以攔截到錯誤;

為了方便理解,所以我先概括一個request跑的流程會經由

IIS Web.config(Svchost.exe) > HttpApplication(Global.asax) > Controller

如果Web.config沒有特別指定錯誤要導向那個特定頁面的話,

也可以使用HttpApplication的事件來截取錯誤

public event EventHandler AcquireRequestState;
public event EventHandler AuthenticateRequest;
public event EventHandler AuthorizeRequest;
public event EventHandler BeginRequest;
public event EventHandler Disposed;
public event EventHandler EndRequest;
public event EventHandler Error;
public event EventHandler LogRequest;
public event EventHandler MapRequestHandler;
public event EventHandler PostAcquireRequestState;
public event EventHandler PostAuthenticateRequest;
public event EventHandler PostAuthorizeRequest;
public event EventHandler PostLogRequest;
public event EventHandler PostMapRequestHandler;
public event EventHandler PostReleaseRequestState;
public event EventHandler PostRequestHandlerExecute;
public event EventHandler PostResolveRequestCache;
public event EventHandler PostUpdateRequestCache;
public event EventHandler PreRequestHandlerExecute;
public event EventHandler PreSendRequestContent;
public event EventHandler PreSendRequestHeaders;
public event EventHandler ReleaseRequestState;
public event EventHandler RequestCompleted;
public event EventHandler ResolveRequestCache;
public event EventHandler UpdateRequestCache;

我們只使用

public event EventHandler Error;

注意,ASP.NET 會使用命名規範 Application_event,自動將應用程式事件繫結至 Global.asax 檔案中的處理常式。

Global.asax.cs

using System;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace WebApplication1
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }

        protected void Application_Error(object sender, EventArgs e)
        {
            // please implement handler for yourself
            System.Exception appException = Server.GetLastError();
            if (appException.Message == "超出最大的要求長度。")
            {
                Server.ClearError();
                Response.Write("<p>超出最大的要求長度。</p>");
                Response.End();
            }
        }
    }
}

 

我也有看到有人會使用BeginRequest事件來做,

不過他是以ASP.NET架構下的想來寫的,如果應用在ASP.NET MVC適用嗎?

嗯?!不曉得,先參考參考。

Global.asax.cs

using System;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using System.Web;
using System.Web.Configuration;

namespace WebApplication1
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }

        protected void Application_BeginRequest(Object sender, EventArgs e)
        {
            HttpRuntimeSection runTime = (HttpRuntimeSection)WebConfigurationManager.GetSection("system.web/httpRuntime");
            //Approx 100 Kb(for page content) size has been deducted because the maxRequestLength proprty is the page size, not only the file upload size
            int maxRequestLength = (runTime.MaxRequestLength - 100) * 1024;

            //This code is used to check the request length of the page and if the request length is greater than
            //MaxRequestLength then retrun to the same page with extra query string value action=exception

            HttpContext context = ((HttpApplication)sender).Context;
            if (context.Request.ContentLength > maxRequestLength)
            {
                IServiceProvider provider = (IServiceProvider)context;
                HttpWorkerRequest workerRequest = (HttpWorkerRequest)provider.GetService(typeof(HttpWorkerRequest));

                // Check if body contains data
                if (workerRequest.HasEntityBody())
                {
                    // get the total body length
                    int requestLength = workerRequest.GetTotalEntityBodyLength();
                    // Get the initial bytes loaded
                    int initialBytes = 0;

                    if (workerRequest.GetPreloadedEntityBody() != null)
                        initialBytes = workerRequest.GetPreloadedEntityBody().Length;

                    if (!workerRequest.IsEntireEntityBodyIsPreloaded())
                    {
                        byte[] buffer = new byte[512000];
                        // Set the received bytes to initial bytes before start reading
                        int receivedBytes = initialBytes;
                        while (requestLength - receivedBytes >= initialBytes)
                        {
                            // Read another set of bytes
                            initialBytes = workerRequest.ReadEntityBody(buffer, buffer.Length);

                            // Update the received bytes
                            receivedBytes += initialBytes;
                        }

                        initialBytes = workerRequest.ReadEntityBody(buffer, requestLength - receivedBytes);
                    }
                }
                // Redirect the user to the same page with querystring action=exception.
                try
                {
                    context.Response.Redirect("http://www.your.error_page/");
                }
                catch (Exception)
                {
                    context.Response.Redirect("http://www.your.error_page/");
                }
            }
        }

    }
}

 

參考資料:

ASP.NET 無法上載大型檔案(大於 4MB)