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/");
}
}
}
}
}
參考資料: