ASP.NET MVCでファイルを表示/ダウンロードに戻す


304

データベースに格納されているファイルをASP.NET MVCのユーザーに送り返すときに問題が発生しました。私が欲しいのは、2つのリンクをリストするビューです。1つはファイルを表示し、ブラウザに送信されたMIMEタイプに処理方法を決定させ、もう1つはダウンロードを強制します。

呼び出されたファイルを表示することを選択しSomeRandomFile.bak、ブラウザにこのタイプのファイルを開くための関連付けられたプログラムがない場合は、デフォルトのダウンロード動作に問題はありません。ただし、というファイルを表示することを選択した場合、SomeRandomFile.pdfまたはSomeRandomFile.jpgファイルを単に開いてほしい場合。ただし、ファイルの種類に関係なくダウンロードプロンプトを強制できるように、ダウンロードリンクを脇に置いておきます。これは理にかなっていますか?

私が試したところFileStreamResult、ほとんどのファイルで機能しますが、そのコンストラクタはデフォルトでファイル名を受け入れないため、不明なファイルにはURLに基​​づいてファイル名が割り当てられます(コンテンツタイプに基づいて付与する拡張子がわかりません)。ファイル名を指定して強制すると、ブラウザがファイルを直接開くことができなくなり、ダウンロードプロンプトが表示されます。他の誰かがこれに遭遇しましたか?

これらは、これまでに試した例です。

//Gives me a download prompt.
return File(document.Data, document.ContentType, document.Name);

//Opens if it is a known extension type, downloads otherwise (download has bogus name and missing extension)
return new FileStreamResult(new MemoryStream(document.Data), document.ContentType);

//Gives me a download prompt (lose the ability to open by default if known type)
return new FileStreamResult(new MemoryStream(document.Data), document.ContentType) {FileDownloadName = document.Name};

助言がありますか?


更新: この質問は多くの人の意見を和らげるように思われるので、更新を投稿すると思いました。Oskarによって国際文字に関して追加された以下の承認された回答に関する警告は完全に有効であり、ContentDispositionクラスを使用したために何度かヒットしました。その後、これを修正するために実装を更新しました。以下のコードは、ASP.NET Core(フルフレームワーク)アプリでのこの問題の最新の具体化によるものですが、System.Net.Http.Headers.ContentDispositionHeaderValueクラスを使用しているため、古いMVCアプリケーションでも最小限の変更で機能するはずです。

using System.Net.Http.Headers;

public IActionResult Download()
{
    Document document = ... //Obtain document from database context

    //"attachment" means always prompt the user to download
    //"inline" means let the browser try and handle it
    var cd = new ContentDispositionHeaderValue("attachment")
    {
        FileNameStar = document.FileName
    };
    Response.Headers.Add(HeaderNames.ContentDisposition, cd.ToString());

    return File(document.Data, document.ContentType);
}

// an entity class for the document in my database 
public class Document
{
    public string FileName { get; set; }
    public string ContentType { get; set; }
    public byte[] Data { get; set; }
    //Other properties left out for brevity
}

回答:


430
public ActionResult Download()
{
    var document = ...
    var cd = new System.Net.Mime.ContentDisposition
    {
        // for example foo.bak
        FileName = document.FileName, 

        // always prompt the user for downloading, set to true if you want 
        // the browser to try to show the file inline
        Inline = false, 
    };
    Response.AppendHeader("Content-Disposition", cd.ToString());
    return File(document.Data, document.ContentType);
}

注:上記のこのコード例では、ファイル名に国際的な文字が適切に含まれていません。関連する標準化については、RFC6266を参照してください。ASP.Net MVCのFile()メソッドの最新バージョンとContentDispositionHeaderValueクラスがこれを適切に説明していると思います。-オスカー2016-02-25


7
正しく思い出せば、ファイル名にスペースがない限り、引用符で囲むことができます(これを実現するためにHttpUtility.UrlEncode()を実行しました)。
キースウィリアムズ

22
注:これを使用して設定Inline = trueする場合は、その3 パラメーターのオーバーロードを使用しないようにしてください。3 File()番目のパラメーターとしてファイル名が使用されます。それはされます IEで動作しますが、Chromeは、重複するヘッダーを報告し、画像を提示することを拒否します。
ファウスト

74
var document = ...はどのタイプですか?
TTT 2013

7
@ user1103990、それはあなたのドメインモデルです。
Darin Dimitrov 2013

3
MVC 5を使用すると、すでに応答ヘッダーの一部であるため、content-dispositionヘッダーは必要ありません。しかし、ダウンロードダイアログはFFでのみ表示され、ChromeとIEではダイアログが表示されません
Legends

124

「document」変数の型ヒントがないため、受け入れられた回答で問題が発生しました。var document = ...それで、他の誰かが問題を抱えている場合に備えて、代わりに機能したものを投稿します。

public ActionResult DownloadFile()
{
    string filename = "File.pdf";
    string filepath = AppDomain.CurrentDomain.BaseDirectory + "/Path/To/File/" + filename;
    byte[] filedata = System.IO.File.ReadAllBytes(filepath);
    string contentType = MimeMapping.GetMimeMapping(filepath);

    var cd = new System.Net.Mime.ContentDisposition
    {
        FileName = filename,
        Inline = true,
    };

    Response.AppendHeader("Content-Disposition", cd.ToString());

    return File(filedata, contentType);
}

4
document変数は、返したいドキュメントに関する情報を表す単なるクラス(POCO)でした。それは受け入れられた答えでも尋ねられました。ORM、手動で構築されたSQLクエリ、ファイルシステム(情報の取得元)、またはその他のデータストアから取得できます。ドキュメントのバイト/ファイル名/ MIMEタイプがどこから来たかという元の質問には関係がなかったため、コードを汚さないように省略しました。ただし、ファイルシステムのみを使用して例を提供していただきありがとうございます。
Nick Albrecht

1
ファイル名にUS-ASCII以外の国際文字が含まれている場合、このソリューションは正しく機能しません。
Oskar Berggren 2016

1
ありがとう、私の日を保存しました:)
Dipesh 2016年

1
代替がAppDomain.CurrentDomain.BaseDirectoryあるSystem.Web.HttpContext.Current.Server.MapPath("~")、これはローカルマシンに比べて実サーバ上で良い仕事があり、。
クリストンプソン

15

ダリン・ディミトロフの答えは正しいです。単なる追加:

Response.AppendHeader("Content-Disposition", cd.ToString());応答に「Content-Disposition」ヘッダーがすでに含まれている場合、ブラウザがファイルのレンダリングに失敗する可能性があります。その場合は、次のように使用できます。

Response.Headers.Add("Content-Disposition", cd.ToString());

content-typeにも影響があることがわかりました。pdfファイルの場合、content-typeをSystem.Net.Mime.MediaTypeNames.Application.Octetに設定するとInline = true、を設定してもダウンロードが強制されますが、に設定するとResponse.ContentType = MimeMapping.GetMimeMapping(filePath)application/pdfダウンロードではなく正しく開くことができます
yu楊カン

Response.Headers.AddIIS統合パイプラインモードが必要です。さらに、アプリプールが統合として設定されている場合でも、例外がスローされます。解決。を使用しResponse.AddHeaderます。SOスレッドを参照してください:stackoverflow.com/questions/22313167/...を
ローランド

12

ファイルを表示するには(例:txt):

return File("~/TextFileInRootDir.txt", MediaTypeNames.Text.Plain);

ファイルをダウンロードするには(例:txt):

return File("~/TextFileInRootDir.txt", MediaTypeNames.Text.Plain, "TextFile.txt");

注:ファイルをダウンロードするには、fileDownloadName引数を渡す必要があります


このアプローチの唯一の問題は、ダウンロードを強制する方法を使用する場合にのみファイル名が提供されることです。ブラウザにファイルの開き方を判断させたいときに、正しいファイル名を送信できません。したがって、外部アプリは私のURLをファイル名として使用しようとします。これは通常、データベース内のドキュメントのIDの一種であり、通常はファイル拡張子なしです。これにより、名前がファイルへのアクセスに使用されるURLと一致しなくなります。提供しない場合のシナリオです。
Nick Albrecht

InlineContent-Disposition のプロパティを使用すると、ファイル名を設定する機能と、ダウンロードを強制する動作を分離することができます。
Nick Albrecht

4

私はこの答えはよりきれいであると信じています(https://stackoverflow.com/a/3007668/550975に基づく )

    public ActionResult GetAttachment(long id)
    {
        FileAttachment attachment;
        using (var db = new TheContext())
        {
            attachment = db.FileAttachments.FirstOrDefault(x => x.Id == id);
        }

        return File(attachment.FileData, "application/force-download", Path.GetFileName(attachment.FileName));
    }

9
推奨されませんが、Content-Dispositionは、明確さと互換性のために推奨される方法です。stackoverflow.com/questions/10615797/...
ニック・アルブレヒト

それをありがとう。MIMEタイプをに変更しましたapplication/octet-streamが、それでもファイルが表示されずにダウンロードされ、互換性があるようです。
Serj Sagan 2013

1
コンテンツタイプについて嘘をつくことは、本当に悪い考えのように聞こえます。一部のブラウザは、正しいコンテンツタイプに依存して、「保存または開く」ダイアログでユーザーにアプリケーションを提案します。
Oskar Berggren

ブラウザーにアプリを提案させることを意図している場合は問題ありませんが、この質問は特にダウンロードを強制することについてです...
Serj Sagan '26 / 02/26

@SerjSagan保存/開くの選択を提供するのではなく、特定の種類のファイルを直接表示するか、プラグインを使用するようにデフォルト設定するブラウザーの動作を回避することのほうが重要だと思います。現時点ではJPEGなどを試していないので、正確な動作はわかりません。
Oskar Berggren 2016年

3

FileVirtualPath-> Research \ Global Office Review.pdf

public virtual ActionResult GetFile()
{
    return File(FileVirtualPath, "application/force-download", Path.GetFileName(FileVirtualPath));
}

5
これは前の回答ですでに言及されており、推奨されません。理由の詳細については、次の質問を参照してください。stackoverflow.com/questions/10615797/…–
Nick Albrecht

1
このように、サーバーのメモリにファイルをロードすることでサーバーのリソースを使用しませんが、正しいですか?
Ian Jowett、2015

1
私はあなたの右の@CrashOverrideをRについての必要はありませんよう、そう信じて
Bishoyハンナ

1

以下のコードは、APIサービスからpdfファイルを取得してブラウザーに応答するために機能しました。

public async Task<FileResult> PrintPdfStatements(string fileName)
    {
         var fileContent = await GetFileStreamAsync(fileName);
         var fileContentBytes = ((MemoryStream)fileContent).ToArray();
         return File(fileContentBytes, System.Net.Mime.MediaTypeNames.Application.Pdf);
    }

2
答えてくれてありがとう。ファイルの名前を指定する機能を含むソリューションを探していた部分を逃しました。バイトを返すだけなので、名前はURLから推測されます。私は例としてPDFを使用していましたが、私の場合は他の複数のファイルタイプでも機能させるためにPDFが必要でした。.NET Framework 4.xおよびMVC <= 5を使用している限り、ソリューションが機能することを述べておきます。.NETCoreに対して実行している場合、最善の策は、使用することですMicrosoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider
Nick Albrecht

@NickAlbrecht私は.Net Coreを使用しませんでした-上記の例はブラウザーでのPDF応答用です。ただし、ダウンロードしたい場合は、File(fileContentBytes、System.Net.Mime.MediaTypeNames.Application.Pdf、 "your file name")を試してください。あなたが答えを得たかどうかはわかりませんが。これが役立つかどうか私に知らせてください。.NETCoreの提案に感謝します。また、私の回答が役に立ったと思ったら、投票を追加してください。
Jonny Boy、

受け入れ済みとマークした答えで、ずっと前に問題を解決しました。あなたがそれらが有用であるとわかった場合に備えて、私はいくつかの警告をさらに指摘していました。私の最初の質問は2011年だったので、これは今ではかなり古いです。
Nick Albrecht

@NickAlbrechtありがとうございます。あなたがそれを解決したことに気づいていませんでした、それは非常に古い投稿だと思いました。非同期関連の回答を探しているときにこのフォーラムを見つけました。非同期プロセスは初めてです。私は私の問題を解決することができたので、共有しました。お時間ありがとうございます。
Jonny Boy、

0

アクションメソッドは、ファイルのストリーム、byte []、または仮想パスを使用してFileResultを返す必要があります。また、ダウンロードするファイルのコンテンツタイプも知っておく必要があります。これは、サンプルの(quick / dirty)ユーティリティメソッドです。サンプルビデオリンク asp.netコアを使用してファイルをダウンロードする方法

[Route("api/[controller]")]
public class DownloadController : Controller
{
    [HttpGet]
    public async Task<IActionResult> Download()
    {
        var path = @"C:\Vetrivel\winforms.png";
        var memory = new MemoryStream();
        using (var stream = new FileStream(path, FileMode.Open))
        {
            await stream.CopyToAsync(memory);
        }
        memory.Position = 0;
        var ext = Path.GetExtension(path).ToLowerInvariant();
        return File(memory, GetMimeTypes()[ext], Path.GetFileName(path));
    }

    private Dictionary<string, string> GetMimeTypes()
    {
        return new Dictionary<string, string>
        {
            {".txt", "text/plain"},
            {".pdf", "application/pdf"},
            {".doc", "application/vnd.ms-word"},
            {".docx", "application/vnd.ms-word"},
            {".png", "image/png"},
            {".jpg", "image/jpeg"},
            ...
        };
    }
}

自分のものであることを開示せずに、自分が関連しているもの(ライブラリ、ツール、製品、チュートリアル、またはWebサイトなど)にリンクすると、Stack Overflowでスパムと見なされます。参照:「良い」自己宣伝を意味するものは何ですか?自己宣伝に関するいくつかのヒントやアドバイススタックオーバーフローのための「スパム」の正確な定義は何ですか?、そして何かがスパムになるもの
サミュエルLiew

0

私のように、Brazorの学習中にRazorコンポーネントを介してこのトピックにアクセスした場合は、この問題を解決するために、ボックスの外側でもう少し考える必要があることがわかります。(私と同じように)BlazorがMVCタイプの世界への最初のforrayである場合、ドキュメントはそのような「平凡な」タスクにはあまり役に立たないので、それは地雷のようなものです。

したがって、執筆時点では、ファイルのダウンロード部分を処理するMVCコントローラーを埋め込むことなくバニラBlazor / Razorを使用してこれを実現することはできません。その例を以下に示します。

using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;

[Route("api/[controller]")]
[ApiController]
public class FileHandlingController : ControllerBase
{
    [HttpGet]
    public FileContentResult Download(int attachmentId)
    {
        TaskAttachment taskFile = null;

        if (attachmentId > 0)
        {
            // taskFile = <your code to get the file>
            // which assumes it's an object with relevant properties as required below

            if (taskFile != null)
            {
                var cd = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
                {
                    FileNameStar = taskFile.Filename
                };

                Response.Headers.Add(HeaderNames.ContentDisposition, cd.ToString());
            }
        }

        return new FileContentResult(taskFile?.FileData, taskFile?.FileContentType);
    }
}

次に、アプリケーションのスタートアップ(Startup.cs)がMVCを正しく使用するように構成されており、次の行が存在することを確認します(そうでない場合は追加します)。

        services.AddMvc();

..そして、最後に、たとえば、コントローラーにリンクするようにコンポーネントを変更します(カスタムクラスを使用した反復ベースの例):

    <tbody>
        @foreach (var attachment in yourAttachments)
        {
        <tr>
            <td><a href="api/FileHandling?attachmentId=@attachment.TaskAttachmentId" target="_blank">@attachment.Filename</a> </td>
            <td>@attachment.CreatedUser</td>
            <td>@attachment.Created?.ToString("dd MMM yyyy")</td>
            <td><ul><li class="oi oi-circle-x delete-attachment"></li></ul></td>
        </tr>
        }
        </tbody>

うまくいけば、これは(私のように!)苦労していたすべての人が、Blazorの領域でこの一見単純そうな質問に適切な答えを得るのに役立ちます...!


これは、同じ問題に遭遇した他の人にとっては役立つかもしれませんが、Blazorに固有のものであることを示すタイトルを付けて独自の質問を投稿し、自分で答え、ここにコメントを追加すると、ブレザーに関する問題のこの質問はあなたのリンクをチェックしてください。この元の質問がASP.NET MVCであることにさえ到達し、その回答をASP.NET Coreに関連するように調整していると感じました。あなたが発見したように、ブレイザーは完全に別の獣です。
ニックアルブレヒト
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.