ASP.NET WebAPI에서 파일 (FileContentResult)을 반환하는 방법


173

일반 MVC 컨트롤러에서는을 사용하여 pdf를 출력 할 수 있습니다 FileContentResult.

public FileContentResult Test(TestViewModel vm)
{
    var stream = new MemoryStream();
    //... add content to the stream.

    return File(stream.GetBuffer(), "application/pdf", "test.pdf");
}

그러나 어떻게 그것을로 바꿀 수 ApiController있습니까?

[HttpPost]
public IHttpActionResult Test(TestViewModel vm)
{
     //...
     return Ok(pdfOutput);
}

여기에 내가 시도한 것이 있지만 작동하지 않는 것 같습니다.

[HttpGet]
public IHttpActionResult Test()
{
    var stream = new MemoryStream();
    //...
    var content = new StreamContent(stream);
    content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
    content.Headers.ContentLength = stream.GetBuffer().Length;
    return Ok(content);            
}

브라우저에 표시되는 결과는 다음과 같습니다.

{"Headers":[{"Key":"Content-Type","Value":["application/pdf"]},{"Key":"Content-Length","Value":["152844"]}]}

그리고 비슷한 게시물이 있습니다 : ASP.NET Web API의 컨트롤러에서 바이너리 파일 반환 . 기존 파일 출력에 대해 설명합니다. 그러나 스트림으로 작동시키지 못했습니다.

어떤 제안?


1
이 게시물이 도움이되었습니다. stackoverflow.com/a/23768883/585552
Greg

답변:


199

대신에 반환하는 StreamContent으로 Content, 나는 그것이 작동 할 수 있습니다 ByteArrayContent.

[HttpGet]
public HttpResponseMessage Generate()
{
    var stream = new MemoryStream();
    // processing the stream.

    var result = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new ByteArrayContent(stream.ToArray())
    };
    result.Content.Headers.ContentDisposition =
        new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
    {
        FileName = "CertificationCard.pdf"
    };
    result.Content.Headers.ContentType =
        new MediaTypeHeaderValue("application/octet-stream");

    return result;
}

2
상위 절반이 질문에 대한 답변을 제공하는 경우 답변으로 만 게시하십시오. 후반은 다른 질문으로 보입니다. 새 질문을 게시하십시오.
gunr2171

3
안녕하세요, 공유해 주셔서 감사합니다. 간단한 질문이 있습니다. httpresponsemessage를받는 C # 프런트 엔드가 있습니다. 사용자가 스트림 콘텐츠를 추출하여 사용 가능하게하여 사용자가 디스크 나 다른 곳에 파일을 저장하고 실제 파일을 얻을 수 있도록하려면 어떻게해야합니까? 감사!
Ronald

7
자체 생성 된 Excel 파일을 다운로드하려고했습니다. stream.GetBuffer ()를 사용하면 항상 손상된 Excel을 반환합니다. 대신 stream.ToArray ()를 사용하면 파일이 문제없이 생성됩니다. 이것이 누군가를 돕기를 바랍니다.
afnpires

4
@AlexandrePires MemoryStream.GetBuffer()실제로 MemoryStream의 버퍼를 반환 하기 때문이다 . MemoryStream의 버퍼는 일반적으로 스트림의 내용보다 크기 때문에 삽입이 효율적이다. MemoryStream.ToArray()내용 크기로 잘린 버퍼를 반환합니다.
M.Stramm

19
이 작업을 중단하십시오. 이러한 종류의 MemoryStream 남용은 확장 불가능한 코드를 야기하며 스트림의 목적을 완전히 무시합니다. 생각하십시오 : 왜 모든 것이 byte[]대신 버퍼로 노출되지 않습니까? 사용자는 메모리에서 응용 프로그램을 쉽게 실행할 수 있습니다.
makhdumi

97

돌아가려면 IHttpActionResult다음과 같이하십시오.

[HttpGet]
public IHttpActionResult Test()
{
    var stream = new MemoryStream();

    var result = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new ByteArrayContent(stream.GetBuffer())
    };
    result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
    {
        FileName = "test.pdf"
    };
    result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

    var response = ResponseMessage(result);

    return response;
}

3
IHttpActionResult 반환 유형을 표시하도록 업데이트되었습니다. 이 코드의 리 팩터는 stackoverflow.com/questions/23768596/…에
Josh

이 포스트는 깔끔한 단일 사용 구현을 보여줍니다. 제 경우에는 위의 링크에 나열된 도우미 방법이 더 도움이되었습니다
hanzolo

45

질문이 도움 되었습니다.

따라서 이것을 시도하십시오 :

컨트롤러 코드 :

[HttpGet]
public HttpResponseMessage Test()
{
    var path = System.Web.HttpContext.Current.Server.MapPath("~/Content/test.docx");;
    HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
    var stream = new FileStream(path, FileMode.Open);
    result.Content = new StreamContent(stream);
    result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
    result.Content.Headers.ContentDisposition.FileName = Path.GetFileName(path);
    result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
    result.Content.Headers.ContentLength = stream.Length;
    return result;          
}

클릭 이벤트 및 간단한 URL과 함께 HTML 마크 업보기 :

<script type="text/javascript">
    $(document).ready(function () {
        $("#btn").click(function () {
            // httproute = "" - using this to construct proper web api links.
            window.location.href = "@Url.Action("GetFile", "Data", new { httproute = "" })";
        });
    });
</script>


<button id="btn">
    Button text
</button>

<a href=" @Url.Action("GetFile", "Data", new { httproute = "" }) ">Data</a>

1
여기서는 FileStream서버의 기존 파일을 사용하고 있습니다. 와 조금 다릅니다 MemoryStream. 그러나 입력 주셔서 감사합니다.
Blaise

4
웹 서버의 파일에서 읽는 경우 FileShare.Read에 과부하를 사용해야합니다. 그렇지 않으면 파일 사용 예외가 발생할 수 있습니다.
Jeremy Bell

메모리 스트림으로 교체하면 작동하지 않습니까?
aleha

@ JeeremyBell 그것은 단순화 된 예일뿐입니다. 생산 및 페일 세이프 버전에 대해서는 아무도 이야기하지 않습니다.
aleha

1
@Blaise이 코드가 작동 FileStream하지만 실패하는 이유는 아래를 참조하십시오 MemoryStream. 기본적으로 Stream과 관련이 Position있습니다.
M.Stramm

9

다음은 파일을 버퍼링하지 않고 파일의 내용을 스트리밍하는 구현입니다 (byte [] / MemoryStream 등의 버퍼링은 큰 파일 인 경우 서버 문제 일 수 있음).

public class FileResult : IHttpActionResult
{
    public FileResult(string filePath)
    {
        if (filePath == null)
            throw new ArgumentNullException(nameof(filePath));

        FilePath = filePath;
    }

    public string FilePath { get; }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var response = new HttpResponseMessage(HttpStatusCode.OK);
        response.Content = new StreamContent(File.OpenRead(FilePath));
        var contentType = MimeMapping.GetMimeMapping(Path.GetExtension(FilePath));
        response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
        return Task.FromResult(response);
    }
}

다음과 같이 간단하게 사용할 수 있습니다.

public class MyController : ApiController
{
    public IHttpActionResult Get()
    {
        string filePath = GetSomeValidFilePath();
        return new FileResult(filePath);
    }
}

다운로드가 완료된 후 파일을 어떻게 삭제 하시겠습니까? 다운로드가 완료되면 알려줄 후크가 있습니까?
코스타

대답은 액션 필터 속성을 구현하고 OnActionExecuted 메서드에서 파일을 제거하는 것 같습니다.
코스타

5
이 게시물 Risord의 답변을 찾았습니다 : stackoverflow.com/questions/2041717/… . var fs = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.None, 4096, FileOptions.DeleteOnClose);대신 에이 줄을 사용할 수 있습니다File.OpenRead(FilePath)
costa

7

어떤 부분을 비난해야할지 확실 MemoryStream하지 않지만 다음은 왜 효과 가 없는지입니다.

에 쓰면 속성 MemoryStream이 증가합니다 Position. 의 생성자 StreamContent는 스트림의 current를 고려합니다 Position. 따라서 스트림에 쓴 다음에 전달 StreamContent하면 스트림 끝의 무에서 응답이 시작됩니다.

이를 올바르게 수정하는 방법에는 두 가지가 있습니다.

1) 컨텐츠 구성, 스트림 쓰기

[HttpGet]
public HttpResponseMessage Test()
{
    var stream = new MemoryStream();
    var response = Request.CreateResponse(HttpStatusCode.OK);
    response.Content = new StreamContent(stream);
    // ...
    // stream.Write(...);
    // ...
    return response;
}

2) 스트림에 쓰기, 위치 재설정, 컨텐츠 구성

[HttpGet]
public HttpResponseMessage Test()
{
    var stream = new MemoryStream();
    // ...
    // stream.Write(...);
    // ...
    stream.Position = 0;

    var response = Request.CreateResponse(HttpStatusCode.OK);
    response.Content = new StreamContent(stream);
    return response;
}

2) 신선한 스트림이 있으면 조금 더 좋아 보입니다. 1) 스트림이 0에서 시작하지 않으면 더 간단합니다.


이 코드는 실제로 질문에 언급 된 것과 동일한 접근법을 사용하기 때문에 문제에 대한 해결책을 제공하지 않습니다. 문제는 이미 이것이 작동하지 않는다는 것을 나타내며 확인할 수 있습니다. return Ok (new StreamContent (stream))은 StreamContent의 JSON 표현을 반환합니다.
Dmytro Zakharov

코드를 업데이트했습니다. 이 답변은 실제로 WebApi에서 파일을 반환하는 방법보다는 '단순한 솔루션이 FileStream과 함께 작동하지만 MemoryStream이 아닌 이유'에 대한 더 미묘한 질문에 대답합니다.
M.Stramm

3

나를 위해 그것은 차이점이었다

var response = Request.CreateResponse(HttpStatusCode.OK, new StringContent(log, System.Text.Encoding.UTF8, "application/octet-stream");

var response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(log, System.Text.Encoding.UTF8, "application/octet-stream");

첫 번째는 StringContent의 JSON 표현을 리턴했습니다. { "Headers": [{ "Key": "Content-Type", "Value": [ "application / octet-stream; charset = utf-8"]}]}

두 번째 파일이 올바른 파일을 반환하는 동안.

Request.CreateResponse에 문자열을 두 번째 매개 변수로 사용하는 오버로드가있는 것 같습니다. 이것은 StringContent 객체 자체가 실제 내용 대신 문자열로 렌더링되게하는 것으로 보입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.