ASP.NET MVC에서보기 / 다운로드로 파일 반환


304

데이터베이스에 저장된 파일을 ASP.NET MVC의 사용자에게 다시 보내는 데 문제가 있습니다. 내가 원하는 것은 두 개의 링크를 나열하는보기입니다. 하나는 파일을보고 브라우저로 전송 된 mimetype이 처리 방법을 결정하고 다른 하나는 강제로 다운로드하도록합니다.

호출 된 파일을 보려고 선택 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 (Full Framework) 앱 에서이 문제에 대한 가장 최근의 화신에서 얻은 것이지만 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 ()를 통해 광산을 실행했습니다).
Keith Williams

22
참고 : 이것을 사용하고 설정 하면 파일 이름을 3 매개 변수 Inline = true로 사용하는 3 매개 변수 과부하를 사용하지 않아야합니다 File(). 그것은 것입니다 IE에서 작동하지만 크롬은 중복 헤더를보고 이미지를 제공 거부합니다.
Faust

74
var document = ...은 어떤 타입입니까?
TTT

7
@ user1103990, 도메인 모델입니다.
Darin Dimitrov

3
MVC 5를 사용하면 컨텐츠 처리 헤더가 이미 응답 헤더의 일부이므로 더 이상 컨텐츠 처리 헤더가 필요하지 않습니다. 그러나 크롬과 IE에는 대화 상자가없는 FF로만 다운로드 대화 상자가 나타납니다.
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
문서 변수는 반환하려는 문서에 대한 정보를 나타내는 클래스 (POCO) 일뿐입니다. 그것은 받아 들인 대답에 대해서도 물었습니다. ORM, 수동으로 빌드 된 SQL 쿼리, 파일 시스템 (정보를 가져 오는대로) 또는 기타 데이터 저장소에서 올 수 있습니다. 문서 바이트 / 파일 이름 / mime 유형이 유래 한 원래 질문과 관련이 없으므로 코드를 흐릿하게 만들지 않았습니다. 파일 시스템 만 사용하여 예제를 제공해 주셔서 감사합니다.
Nick Albrecht

1
파일 이름에 US-ASCII 외부의 국제 문자가 포함되어 있으면이 솔루션이 제대로 작동하지 않습니다.
Oskar Berggren

1
고마워, 내 하루를
구했다

1
에 대한 대안은 AppDomain.CurrentDomain.BaseDirectory있다 System.Web.HttpContext.Current.Server.MapPath("~")이 로컬 컴퓨터에 비해 실제 서버에 더 잘 작동 할 수 있습니다.
크리스 톰슨

15

Darin Dimitrov의 대답 은 맞습니다. 단지 추가 :

Response.AppendHeader("Content-Disposition", cd.ToString());응답에 이미 "Content-Disposition"헤더가 포함되어 있으면 브라우저에서 파일 렌더링에 실패 할 수 있습니다. 이 경우 다음을 사용할 수 있습니다.

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

나는 콘텐츠 형식도에 대한 영향을 미칠 것을 발견 pdf나는이 내용 유형을 설정하면, 파일 System.Net.Mime.MediaTypeNames.Application.Octet은 내가 설정 한 경우에도 다운로드를 강제하는 것입니다, Inline = true하지만이 같은 설정하면 Response.ContentType = MimeMapping.GetMimeMapping(filePath),입니다 application/pdf, 오히려 다운로드보다 정확하게 열 수
유를 Yang Jian

Response.Headers.AddIIS 통합 파이프 라인 모드가 필요합니다. 또한 앱 풀이 통합으로 설정되어 있어도 예외가 발생합니다. 해결책. 사용하십시오 Response.AddHeader. SO 스레드 : stackoverflow.com/questions/22313167/…
roland

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
권장하지는 않지만 내용 처리는 명확성과 호환성을 위해 선호되는 방법입니다. stackoverflow.com/questions/10615797/…
Nick Albrecht

고마워 MIME 형식을 변경했지만 application/octet-stream여전히 파일이 아닌 다운로드되는 파일이 생겼으며 호환되는 것으로 보입니다.
Serj Sagan

1
콘텐츠 유형에 대한 거짓말은 정말 나쁜 생각처럼 들립니다. 일부 브라우저는 "저장 또는 열기"대화 상자에서 사용자에게 응용 프로그램을 제안하기 위해 올바른 컨텐츠 유형에 의존합니다.
Oskar Berggren

당신의 의도는 브라우저가 응용 프로그램을 제안하는 것이라면 괜찮습니다,하지만이 질문은 다운로드를 강제로하는 것에 관한 것입니다 ...
Serj Sagan

@SerjSagan 필자는 저장 / 열기 사이의 선택을 제공하는 대신 특정 파일 유형을 직접 보거나 플러그인을 사용하도록 기본 설정하는 브라우저 동작을 피하는 것이 더 중요하다고 생각합니다. 지금 JPEG와 같은 시도를하지 않았으므로 정확한 동작을 확신하지 못합니다.
Oskar Berggren

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

1
난 당신이 필요로 믿습니다 @CrashOverride
Bishoy Hanna

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를 사용하는 한 솔루션이 작동한다고 언급해야합니다. .NET Core에 대해 실행중인 경우 가장 좋은 방법은 다음과 같습니다.Microsoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider
Nick Albrecht

@NickAlbrecht 나는 .Net Core를 사용하지 않았습니다-위의 예는 브라우저에서 pdf 응답을위한 것입니다. 그러나 파일을 다운로드하려면 File (fileContentBytes, System.Net.Mime.MediaTypeNames.Application.Pdf, "your file name")을 시도하십시오. 당신이 대답을 얻었는지 확실하지 않습니다. .Net Core 제안에 감사드립니다. 또한 당신이 내 답변을 유용하게 찾았다면 투표를 추가하십시오.
Jonny Boy

나는 이미 승인 된 것으로 표시된 답변으로 오랫동안 문제를 해결했습니다. 유용하다고 생각되는 경우 몇 가지주의 사항을 더 지적했습니다. 내 원래 질문은 2011 이었으므로 지금까지 날짜가 꽤 있습니다.
Nick Albrecht

@NickAlbrecht 감사합니다. 난 당신이 그것을 해결 몰랐어요, 아주 오래된 게시물을 보았습니다. 비동기 관련 답변을 찾는 동안이 포럼을 찾았습니다. 비동기 프로세스를 처음 사용합니다. 내 문제를 해결할 수 있었으므로 공유했습니다. 시간보다 당신보다.
Jonny Boy

0

동작 메소드는 파일의 스트림, 바이트 [] 또는 가상 경로와 함께 FileResult를 리턴해야합니다. 또한 다운로드중인 파일의 컨텐츠 유형을 알아야합니다. 다음은 샘플 (빠른 / 더러운) 유틸리티 방법입니다. 샘플 비디오 링크 asp.net core를 사용하여 파일을 다운로드하는 방법

[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"},
            ...
        };
    }
}

자신의 것을 공개하지 않고 귀하가 소속 된 대상 (예 : 라이브러리, 도구, 제품, 자습서 또는 웹 사이트) 에 연결하는 것은 스택 오버플로에서 스팸으로 간주됩니다. 참조 : "좋은"자기 홍보를 의미한다 무엇? , 자체 프로모션에 대한 몇 가지 팁과 조언 , 스택 오버플로에 대한 "스팸"의 정확한 정의는 무엇입니까? , 무엇을 스팸으로 만드는가 .
Samuel Liew

0

나처럼 Blazor를 배우면서 Razor 구성 요소를 통해이 주제에 도달했다면이 문제를 해결하기 위해 상자 밖에서 조금 더 생각해야한다는 것을 알게 될 것입니다. 문서가 그러한 '정신적 인'작업에 도움이되지 않기 때문에 Blazor가 MVC 유형의 세계에 처음으로 진출한 경우 그것은 약간의 지뢰밭입니다.

따라서 작성 당시 파일 다운로드 부분을 처리하기 위해 MVC 컨트롤러를 포함시키지 않으면 서 vanilla 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와 관련하여 그 대답을 조정한다고 느꼈습니다. Blazor는 완전히 다른 짐승입니다.
닉 알브레히트
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.