FileResultを使用してAsp.Net MVCで任意のタイプのファイルをダウンロードしますか?


228

FileResultを使用して、ユーザーがAsp.Net MVCアプリケーションからファイルをダウンロードできるようにすることを提案しました。しかし、私が見つけることができる唯一の例は、常に画像ファイル(コンテンツタイプimage / jpegを指定)に関係しています。

しかし、ファイルの種類がわからない場合はどうなりますか?ユーザーが私のサイトのfileareaからほとんどすべてのファイルをダウンロードできるようにしたい。

私はこれを行う1つの方法(コードについては以前の投稿を参照)を読みましたが、1つを除いて実際に問題なく機能します。[名前を付けて保存]ダイアログに表示されるファイルの名前は、下線付きのファイルパスから連結されます( folder_folder_file.ext)。また、私がBinaryContentResultを見つけたこのカスタムクラスを使用する代わりに、FileResultを返す必要があると人々は思っているようです。

MVCでこのようなダウンロードを行う「正しい」方法を知っている人はいますか?

編集:私は答え(下)を得ましたが、他の誰かが興味を持っているなら私は完全な作業コードを投稿するべきだと思っただけです:

public ActionResult Download(string filePath, string fileName)
{
    string fullName = Path.Combine(GetBaseDir(), filePath, fileName);

    byte[] fileBytes = GetFile(fullName);
    return File(
        fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}

byte[] GetFile(string s)
{
    System.IO.FileStream fs = System.IO.File.OpenRead(s);
    byte[] data = new byte[fs.Length];
    int br = fs.Read(data, 0, data.Length);
    if (br != fs.Length)
        throw new System.IO.IOException(s);
    return data;
}

12
あなたがやっていることはかなり危険です。実行ユーザーがアクセスできるファイルをユーザーがサーバーからダウンロードすることをほぼ許可しています。
ポールフレミング

1
True-ファイルパスを削除し、actionresultの本文でそれを釘付けにすると、多少安全になります。少なくともその方法では、特定のフォルダにのみアクセスできます。
shubniggurath 2013

2
このような潜在的に危険な抜け穴を見つけることができるツールはありますか?
デビッド

stackoverflow.com/a/22231074/4573839Response.ContentType = MimeMapping.GetMimeMapping(filePath);からcontent-typeをとして設定すると便利です
yu yang Jian

クライアント側で何を使用していますか?
FrenkyB

回答:


425

一般的なオクテットストリームMIMEタイプを指定するだけです。

public FileResult Download()
{
    byte[] fileBytes = System.IO.File.ReadAllBytes(@"c:\folder\myfile.ext");
    string fileName = "myfile.ext";
    return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}

4
OK、それを試すことができますが、byte []配列には何が入りますか?
アンデルス

3
気にしないで、私はそれを理解したと思います。ファイル名(完全パス)をFileStreamに読み込み、次にバイト配列に読み込んだので、これは魅力的なように機能しました。ありがとう!
アンデルス

5
これにより、ファイル全体をメモリにロードして、ストリーミングするだけです。大きなファイルの場合、これは独り占めです。より良い解決策は、最初にファイルをメモリにロードする必要がない以下の解決策です。
HBlackorby

13
この答えはほぼ5年前のものなので、ええ。非常に大きなファイルを提供するためにこれを行っている場合は、行わないでください。可能であれば、別の静的ファイルサーバーを使用して、アプリケーションスレッドを拘束しないようにするか、2010年以降にMVCに追加されたファイルを提供するための多くの新しい手法の1つを使用します。これは、MIMEタイプが不明な場合に使用する正しいMIMEタイプを示すだけです。 。ReadAllBytes数年後に編集で追加されました。なぜこれが私の2番目に最も賛成された回答なのですか?しかたがない。
Ian Henry

10
このエラーの発生:non-invocable member "File" cannot be used like a method.
A-Sharabiani '

105

MVCフレームワークはこれをネイティブにサポートします。System.Web.MVC.Controller.Fileのコントローラが、ファイルを返すようにする方法を提供する名前 / ストリーム / アレイ

たとえば、ファイルへの仮想パスを使用すると、次のことができます。

return File(virtualFilePath, System.Net.Mime.MediaTypeNames.Application.Octet,  Path.GetFileName(virtualFilePath));

36

.NET Framework 4.5を使用している場合は、MimeMapping.GetMimeMapping(文字列FileName)を使用してファイルのMIMEタイプを取得します。これは、私がアクションで使用した方法です。

return File(Path.Combine(@"c:\path", fileFromDB.FileNameOnDisk), MimeMapping.GetMimeMapping(fileFromDB.FileName), fileFromDB.FileName);

MIMEマッピングを取得することはすばらしいことですが、実行時にファイルの種類を把握するための大きなプロセスではありませんか?
Mohammed Noureldin 2017

@MohammedNoureldinそれを「構成」するのではなく、ファイル拡張子などに基づく単純なマッピングテーブルがあります。サーバーはすべての静的ファイルに対してそれを行いますが、遅くはありません。
Al Kepp

13

Phil Haackは、カスタムファイルダウンロードアクション結果クラスを作成した素晴らしい記事を持っています。ファイルの仮想パスと名前を指定して保存するだけです。

私はそれを一度使用しました、そしてこれが私のコードです。

        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult Download(int fileID)
        {
            Data.LinqToSql.File file = _fileService.GetByID(fileID);

            return new DownloadResult { VirtualPath = GetVirtualPath(file.Path),
                                        FileDownloadName = file.Name };
        }

私の例では、ファイルの物理パスを保存していたので、このヘルパーメソッドを使用しました-覚えていない場所を見つけて、それを仮想パスに変換しました

        private string GetVirtualPath(string physicalPath)
        {
            string rootpath = Server.MapPath("~/");

            physicalPath = physicalPath.Replace(rootpath, "");
            physicalPath = physicalPath.Replace("\\", "/");

            return "~/" + physicalPath;
        }

これは、Phill Haackの記事から引用したクラス全体です。

public class DownloadResult : ActionResult {

    public DownloadResult() {}

    public DownloadResult(string virtualPath) {
        this.VirtualPath = virtualPath;
    }

    public string VirtualPath {
        get;
        set;
    }

    public string FileDownloadName {
        get;
        set;
    }

    public override void ExecuteResult(ControllerContext context) {
        if (!String.IsNullOrEmpty(FileDownloadName)) {
            context.HttpContext.Response.AddHeader("content-disposition", 
            "attachment; filename=" + this.FileDownloadName)
        }

        string filePath = context.HttpContext.Server.MapPath(this.VirtualPath);
        context.HttpContext.Response.TransmitFile(filePath);
    }
}

1
そうです、その記事も見ましたが、私が使用した記事と同じようなことをしているようです(以前の投稿の参照を参照してください)。 「新しい更新:ASP.NET MVCにボックスに含まれるようになったため、このカスタムActionResultは必要なくなりました。」しかし、残念ながら、彼はこれがどのように使用されるかについて他に何も述べていません。
アンデルス

@ManafAbuRous、コードを注意深く読むと、実際に仮想パスが物理パス(Server.MapPath(this.VirtualPath))に変換されることがわかります。そのため、これを変更せずに直接使用するのは簡単です。PhysicalPath最終的に必要とされるものであり、保存するものである場合は、それを受け入れる代替を作成する必要があります。これは、物理パスと相対パスが同じであること(ルートを除く)を想定しているため、はるかに安全です。多くの場合、保存されるデータファイルはApp_Dataです。これは相対パスとしてはアクセスできません。
ポールフレミング2013

GetVirtualPathはすばらしい...非常に便利です。ありがとうございました!
Zviレドラー

6

イアン・ヘンリーに感謝します!

MS SQL Serverからファイルを取得する必要がある場合は、ここが解決策です。

public FileResult DownloadDocument(string id)
        {
            if (!string.IsNullOrEmpty(id))
            {
                try
                {
                    var fileId = Guid.Parse(id);

                    var myFile = AppModel.MyFiles.SingleOrDefault(x => x.Id == fileId);

                    if (myFile != null)
                    {
                        byte[] fileBytes = myFile.FileData;
                        return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, myFile.FileName);
                    }
                }
                catch
                {
                }
            }

            return null;
        }

どこのAppModelがあるEntityFrameworkモデルとMyFilesに提示するテーブルデータベースに。 FILEDATAはあるvarbinary(MAX)MyFilesフォルダのテーブル。


2

その単純なことは、ファイル名を含む物理パスをdirectoryPathに与える

public FilePathResult GetFileFromDisk(string fileName)
{
    return File(directoryPath, "multipart/form-data", fileName);
}

このメソッドを呼び出すクライアント側はどうですか?ダイアログとして保存を表示したい場合はどうでしょうか?
FrenkyB

0
   public ActionResult Download()
        {
            var document = //Obtain document from database context
    var cd = new System.Net.Mime.ContentDisposition
    {
        FileName = document.FileName,
        Inline = false,
    };
            Response.AppendHeader("Content-Disposition", cd.ToString());
            return File(document.Data, document.ContentType);
        }

-1

if(string.IsNullOrWhiteSpace(fileName))return Content( "filename not present");

        var path = Path.Combine(your path, your filename);

        var stream = new FileStream(path, FileMode.Open);

        return File(stream, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);

-4

GetFileはファイルを閉じる(または、using内で開く)必要があります。次に、バイトへの変換後にファイルを削除できます。ダウンロードはそのバイトバッファで行われます。

    byte[] GetFile(string s)
    {
        byte[] data;
        using (System.IO.FileStream fs = System.IO.File.OpenRead(s))
        {
            data = new byte[fs.Length];
            int br = fs.Read(data, 0, data.Length);
            if (br != fs.Length)
                throw new System.IO.IOException(s);
        }
        return data;
    }

だからあなたのダウンロード方法で...

        byte[] fileBytes = GetFile(file);
        // delete the file after conversion to bytes
        System.IO.File.Delete(file);
        // have the file download dialog only display the base name of the file            return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, Path.GetFileName(file));

2
今まで、これまでのように生産に全体をメモリにファイルをロードしないでください
makhdumi
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.