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.