MVC&WebForm對照學習:文件下載

說完了WebForm和MVC中的文件上傳,就不得不說用戶從服務器端下載資源了。那麼今天就扯扯在WebForm和MVC中是如何實現文件下載的。提及WebForm中的文件上傳,codeshark在他的博文ASP.NET實現文件下載中講到了ASP.NET中文件下載的4種方式。固然文章主要指的是在WebForm中的實現方式。總結得至關到位,那麼這篇,就先來看看MVC中文件下載的方式。而後再回過頭來看看它們實現方式的關聯。html

Part 1 MVC中的文件下載web

在mvc中微軟封裝好了ActionResult的諸多的實現,這使咱們根據須要靈活地選擇操做結果來響應用戶的操做。這其中一個很重要的實現就是.NET Framework 4.0中的FileResult。表示一個用於將二進制文件內容發送到響應的基類。FileResult類有三個實現:FileContentResult、FileStreamResult、FilePathResult。mvc中的文件下載就是依賴這三個子類。不扯閒,先來看具體實現。瀏覽器

 FileContentResult服務器

public ActionResult FileDownLoad()
{
    FileStream fs = new FileStream(Server.MapPath("~/uploads/Desert.jpg"), FileMode.Open, FileAccess.Read);
    byte[] bytes = new byte[fs.Length];
    fs.Read(bytes, 0, Convert.ToInt32(bytes.Length));
    return File(bytes, "image/jpeg", "Desert.jpg");
}

 FileStreamResultmvc

public ActionResult FileDownLoad()
{
    return File(new FileStream(Server.MapPath("~/uploads/Desert.jpg"), FileMode.Open, FileAccess.Read), "image/jpeg", "Desert.jpg");
}

 FilePathResultapp

public ActionResult FileDownLoad()
{
    return File(Server.MapPath("~/uploads/Desert.jpg"), "image/jpeg", "Desert.jpg");

看了上面的代碼是否是感受這彷佛比codeshark文章中講到的WebForm中的文件下載代碼更簡潔了?確實!那麼咱們同時也不由要問:不是說mvc中的文件下載依賴於FileContentResult、FilePathResult、FileStreamResult嗎,爲何這裏邊無一例外都是return File(......)呢?這個對於接觸過mvc的你來講,相信難不倒你,F12一下就全明白了:框架

(圖1-1)asp.net

原來File(......)方法的背後返回的是FileContentResult、FileStreamResult、FilePathResult的實例。這就不足爲怪了。那麼也就是說其實上面的3鍾實現方式你徹底能夠改爲以下形式:ide

//FilePathResult
return new FilePathResult(Server.MapPath("~/uploads/Desert.jpg"), "image/jpeg") { FileDownloadName = "Desert.jpg" };
//FileStreamResult
return new FileStreamResult(new FileStream(Server.MapPath("~/uploads/Desert.jpg"), FileMode.Open, FileAccess.Read), "image/jpeg") { FileDownloadName = "Desert.jpg" };
//FileContentResult
return new FileContentResult(System.IO.File.ReadAllBytes(Server.MapPath("~/uploads/Desert.jpg")), "image/jpeg") { FileDownloadName = "Desert.jpg" };

若是你經過ILSpay查看源碼的話,你會發現其實對應的return File(......)的內部實現就是如此。ui

問題1:不給fileDownloadNane賦值效果會如何

回過頭來再看下圖1-1,咱們還會發現FileContentResult、FileStreamResult、FilePathResult的下載方法,還各自對應的存在一個沒有第三個參數fileDownloadNane的方法重載。那麼這個方法又是用來幹嗎呢?小段代碼看下便知:

public ActionResult FileDownLoad()
{
    return File(Server.MapPath("~/uploads/Desert.jpg")
}

--------------------------------------------------------------------------------運行結果-------------------------------------------------------------------------------------------------------------------

經過運行結果截圖一眼就能看出,此時將圖片直接輸出在頁面上,這實現圖片的顯示功能。那麼同時咱們也可以知道,一樣的這樣也是能夠的:

return new FilePathResult(Server.MapPath("~/uploads/Desert.jpg"), "image/jpeg") ;

問題2:加不加fileDownloadNane背後到底作了什麼

固然其它兩種方式一樣如此。那麼咱們不由要問:爲何給不給fileDownloadName賦值,實現的效果徹底不同呢?那麼你確定想到了在內部確定作了什麼來加以區分。那麼就以FilePathResult爲例,用ILSpy來看看究竟:

(圖1-2)

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

(圖1-3)

先看圖1-2在FileResult類中有一個FileDownloadName公開屬性,咱們知道ActionResult類最終要執行子類的ExecuteResult方法,而在FileResultExecuteResult方法中,對FileDownloadName進行了非空判斷。若是不爲空則經過Response.AddHeader()方法向客戶端瀏覽器發送文件。若是爲空則調用子類FilePathResult重寫方法WriteFile() (圖1-3所示)直接向頁面輸出響應流。固然FileContentResult、FileStreamResult道理相同。

問題3:FileContentResult、FileStreamResult、FilePathResult文件下載的方式到底有什麼不一樣

嗯,要回答這個問題,不用說,仍是得看看它的內部實現。那麼咱們就一個個地來看看究竟。

圖1-2和圖1-3種已經看得很清楚,最終文件下載時指定了FileResultExecuteResult方法,而在ExecuteResult方法中調用了FileResult的子類的WriteFile方法。那麼我只須要查看每一個子類的WriteFile方法便知道了。

FileContentResult

FileStreamResult

FilePathResult

再去看看codeshark的文章ASP.NET實現文件下載,在這篇文章中他總結了文件下載的4種方式:

方式一:TransmitFile實現下載。將指定的文件直接寫入 HTTP 響應輸出流,而不在內存中緩衝該文件。

方式二:WriteFile實現下載,將指定的文件直接寫入 HTTP 響應輸出流。注意:對大型文件使用此方法時,調用此方法可能致使異常。可使用此方法的文件大小取決於 Web 服務器的硬件配置。

方式三:WriteFile分塊下載

方式四:Response.BinaryWrite()流方式下載

那麼與mvc中文件下載對照一下,不難發現,FilePathResult其實是採用的Response.TransmitFile的方式實現下載的;而FileStreamResult是採用的WriteFile分塊下載;FileContentResult在文章中沒有直接的對照方式,而它採用的是Response.OutputStream.Write的方式。另外Response.BinaryWrite()流的方式在mvc中沒有實現。固然在msdn上給出了實現我直接貼出實現代碼:

實現代碼:

public class BinaryContentResult : ActionResult
{
    public BinaryContentResult()
    {
    }

    // Properties for encapsulating http headers.
    public string ContentType { get; set; }
    public string FileName { get; set; }
    public byte[] Content { get; set; }

    // The code sets the http headers and outputs the file content.
    public override void ExecuteResult(ControllerContext context)
    {
        context.HttpContext.Response.ClearContent();
        context.HttpContext.Response.ContentType = ContentType;

        context.HttpContext.Response.AddHeader("content-disposition", 
            "attachment; filename=" + FileName);

        context.HttpContext.Response.BinaryWrite(Content);
        context.HttpContext.Response.End();
    }
}

調用代碼:

public ActionResult Download(string fn)
{
    // Check whether the requested file is valid.
    string pfn = Server.MapPath("~/App_Data/download/" + fn);
    if (!System.IO.File.Exists(pfn))
    {
        throw new ArgumentException("Invalid file name or file not exists!");
    }

    // Use BinaryContentResult to encapsulate the file content and return it.
    return new BinaryContentResult()
    {
        FileName = fn,
        ContentType = "application/octet-stream",
        Content = System.IO.File.ReadAllBytes(pfn)
    };
}

問題4:那麼FileContentResult的Response.OutputStream.Write()和Response.WriteFile()又有什麼區別暱

 關於這個問題,我查了好久,沒有獲得比較滿翼的答案,在這裏但願大神指點一二!

Part 2 WebForm中的文件下載

看看這篇文章ASP.NET實現文件下載一目瞭然。四種方式很少說了。到這裏,不得不說MVC中的文件下載is so easy!這還得歸功於微軟的封裝。那麼問題來了,在WebForm中咱們是否是因該也封裝一個這樣的實現,這樣在之後使用的時候不用寫(固然通常是copy)重複寫這麼寫這些容易忘記的代碼了暱? Of course, just do it! 網上我沒有找到一個針對這個實現的封裝(有大神代碼能夠貢獻一下)。我索性本身寫一個,好很差,就不說了。

1.定義抽象類

先定義一個抽象列FileDownloader,定義公共的下載行爲WriteFile方法和執執行下載的Execute方法以及文件下載名_fileDownloadName。代碼以下:

public abstract class FileDownloader
{
    private string _fileDownloadName;
    public FileDownloader(string contentType)
    {
        if (string.IsNullOrEmpty(contentType))
        {
            throw new ArgumentException("contentType");
        }
        this.ContentType = contentType;
    }
    public void Execute(HttpContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        HttpResponse response = context.Response;
        response.ClearContent();
        response.ContentType = this.ContentType;
        if (!string.IsNullOrEmpty(this.FileDownloadName))
        {
            context.Response.AddHeader("content-disposition",
            "attachment; filename=" + this.FileDownloadName);
        }
        this.WriteFile(context.Response);
        response.Flush();
        response.End();
    }
    protected abstract void WriteFile(HttpResponse response);
    public string ContentType { get; private set; }
    public string FileDownloadName
    {
        get
        {
            return (this._fileDownloadName ?? string.Empty);
        }
        set
        {
            this._fileDownloadName = value;
        }
    }
}

2.建立實現類

此處模擬MVC中的實現方式建立對應的WebForm中的實現方式,爲了易於區分說明,我採起和MVC中一樣的類名。

MVC中FileContentResult的下載方式(區別於FileStreamResult分區下載),這裏實現一下:

public class FileContentResult : FileDownloader
{
    public FileContentResult(byte[] fileContents, string contentType)
        : base(contentType)
    {
        if (fileContents == null)
        {
            throw new ArgumentNullException("fileContents");
        }
        this.FileContents = fileContents;
    }
    public byte[] FileContents { get; private set; }
    protected override void WriteFile(HttpResponse response)
    {
        response.OutputStream.Write(this.FileContents, 0, this.FileContents.Length);
    }
}

 FileStreamResult(分區下載):

public class FileStreamResult : FileDownloader
{
    public FileStreamResult(Stream fileStream, string contentType)
        : base(contentType)
    {
        if (fileStream == null)
        {
            throw new ArgumentNullException("fileStream");
        }
        this.FileStream = fileStream;
    }
    public Stream FileStream { get; private set; }
    protected override void WriteFile(HttpResponse response)
    {
        Stream outputStream = response.OutputStream;
        using (this.FileStream)
        {
            byte[] buffer = new byte[4096];
            while (true)
            {
                int num = this.FileStream.Read(buffer, 0, 4096);
                if (num == 0)
                {
                    break;
                }
                outputStream.Write(buffer, 0, num);
            }
        }
    }
}

FIlePathResult:

public class FIlePathResult : FileDownloader
{
    public FIlePathResult(string fileName, string contentType)
        : base(contentType)
    {
        if (string.IsNullOrEmpty(fileName))
        {
            throw new ArgumentNullException("fileName");
        }
        this.FileName = fileName;
    }
    public string FileName { get; private set; }

    protected override void WriteFile(HttpResponse response)
    {
        response.TransmitFile(this.FileName);
    }
}

另外上面咱們也採用了微軟上BinaryContentResult的實現,這裏一樣也實現一下BinaryContentResult:

public class BinaryContentResult : FileDownloader
{
    public BinaryContentResult(byte[] fileContents, string contentType)
        : base(contentType)
    {
        if (fileContents == null)
        {
            throw new ArgumentNullException("fileContents");
        }
        this.FileContents = fileContents;
    }

    protected override void WriteFile(HttpResponse response)
    {
        response.BinaryWrite(FileContents);
    }

    public byte[] FileContents { get; private set; }
}

另外還有HttpResponse.WriteFile(net 2.0中的提出的,對大型文件使用此方法時,調用此方法可能會引起異常這裏姑且稱之爲舊的實現方式

public class FilePathOldResult : FileDownloader
{
    public FilePathOldResult(string fileName, string contentType)
        : base(contentType)
    {
        if (string.IsNullOrEmpty(fileName))
        {
            throw new ArgumentNullException("fileName");
        }
        this.FileName = fileName;
    }
    public string FileName { get; private set; }
    protected override void WriteFile(HttpResponse response)
    {
        response.WriteFile(this.FileName);
    }

Part 3 問題開發

在文件下載中,咱們可能須要自動獲取文件的MIME類型,這在指定文件的下載類型時。那麼如何獲取文件的MIME類型呢?在Mitchell Chu的博客.NET獲取文件的MIME類型(Content Type)中給出了比較好的答案。這裏就不作贅述。

Part 4 The end

回過頭來看仍是那句話,由於MVC和WebForm都是基於ASP.NET框架,所以文件下載功能的背後仍是採用了相同的組件實現。

注:因爲我的技術有限,對某些概念的理解可能會存在誤差,若是你發現本文存在什麼bug,請指正。謝謝!

完。

相關文章
相關標籤/搜索