ASP.NET 웹 API의 컨트롤러에서 이진 파일 반환


323

바이너리 파일, 주로 파일을 제공하는 ASP.NET MVC의 새로운 WebAPI를 사용하여 웹 서비스를 개발 .cab하고 .exe있습니다.

다음 컨트롤러 메소드가 작동하는 것 같습니다. 즉, 파일을 리턴하지만 컨텐츠 유형을 application/json다음과 같이 설정합니다 .

public HttpResponseMessage<Stream> Post(string version, string environment, string filetype)
{
    var path = @"C:\Temp\test.exe";
    var stream = new FileStream(path, FileMode.Open);
    return new HttpResponseMessage<Stream>(stream, new MediaTypeHeaderValue("application/octet-stream"));
}

더 좋은 방법이 있습니까?


2
원하는 토지 누구나 웹 API를 통해 스트림을 통해 바이트 배열을 반환하고 IHTTPActionResult는 여기를 참조하십시오 nodogmablog.bryanhogan.net/2017/02/...
IbrarMumtaz

답변:


516

속성을 다음과 같이 설정하여 simple HttpResponseMessage을 사용해보십시오 .ContentStreamContent

// using System.IO;
// using System.Net.Http;
// using System.Net.Http.Headers;

public HttpResponseMessage Post(string version, string environment,
    string filetype)
{
    var path = @"C:\Temp\test.exe";
    HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
    var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
    result.Content = new StreamContent(stream);
    result.Content.Headers.ContentType = 
        new MediaTypeHeaderValue("application/octet-stream");
    return result;
}

stream사용 에 대해 몇 가지 참고할 사항 :

  • stream.Dispose()웹 메소드가 컨트롤러 메소드의 result데이터를 클라이언트로 다시 전송 하도록 처리 할 때 여전히 웹 API에 액세스 할 수 있어야하기 때문에을 호출하지 않아야합니다 . 따라서 using (var stream = …)블록을 사용하지 마십시오 . 웹 API가 스트림을 처리합니다.

  • 스트림의 현재 위치가 0으로 설정되어 있는지 확인하십시오 (예 : 스트림 데이터의 시작). 위의 예에서는 파일을 방금 연 경우에만 제공됩니다. 그러나 다른 시나리오 (예 : 바이너리 데이터를 처음에 쓰는 경우 MemoryStream)에서 확인 stream.Seek(0, SeekOrigin.Begin);하거나 설정하십시오.stream.Position = 0;

  • 파일 스트림에서 FileAccess.Read권한을 명시 적으로 지정하면 웹 서버의 액세스 권한 문제를 방지 할 수 있습니다. IIS 응용 프로그램 풀 계정에는 종종 wwwroot에 대한 읽기 / 목록 / 실행 액세스 권한 만 부여됩니다.


37
스트림이 닫히는 시점을 알고 있습니까? 프레임 워크가 궁극적으로 HttpResponseMessage.Dispose ()를 호출하고 스트림을 효과적으로 닫는 HttpResponseMessage.Content.Dispose ()를 호출한다고 가정합니다.
Steve Guidi

41
Steve-FileStream에 중단 점을 추가 하여이 코드를 올바르게 실행하고 확인했습니다. 프레임 워크는 HttpResponseMessage.Dispose를 호출하고, StreamContent.Dispose를 호출하고 FileStream.Dispose를 호출합니다.
Dan Gartner

15
메소드 외부에서 여전히 사용되므로 using결과 ( HttpResponseMessage) 또는 스트림 자체에 실제로 a 를 추가 할 수 없습니다 . @ Dan이 언급했듯이 클라이언트에 응답을 보낸 후에 프레임 워크에 의해 처리됩니다.
carlosfigueira 2016 년

2
@ B.ClayShannon 그래, 그게 다야. 클라이언트에 관한 한 HTTP 응답의 내용에는 바이트 수에 불과합니다. 클라이언트는 로컬 파일에 저장하는 것을 포함하여 원하는 바이트로 처리 할 수 ​​있습니다.
carlosfigueira

5
@carlosfigueira, 안녕, 바이트가 모두 전송 된 후 파일을 삭제하는 방법을 알고 있습니까?
Zach

137

내용은 웹 API 2 , 당신은 구현할 수 있습니다 IHttpActionResult. 내 꺼야 :

using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;

class FileResult : IHttpActionResult
{
    private readonly string _filePath;
    private readonly string _contentType;

    public FileResult(string filePath, string contentType = null)
    {
        if (filePath == null) throw new ArgumentNullException("filePath");

        _filePath = filePath;
        _contentType = contentType;
    }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var response = new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StreamContent(File.OpenRead(_filePath))
        };

        var contentType = _contentType ?? MimeMapping.GetMimeMapping(Path.GetExtension(_filePath));
        response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);

        return Task.FromResult(response);
    }
}

그런 다음 컨트롤러에서 다음과 같이하십시오.

[Route("Images/{*imagePath}")]
public IHttpActionResult GetImage(string imagePath)
{
    var serverPath = Path.Combine(_rootPath, imagePath);
    var fileInfo = new FileInfo(serverPath);

    return !fileInfo.Exists
        ? (IHttpActionResult) NotFound()
        : new FileResult(fileInfo.FullName);
}

그리고 다음은 IIS가 확장명을 가진 요청을 무시하도록 요청하여 컨트롤러에 요청하도록하는 한 가지 방법입니다.

<!-- web.config -->
<system.webServer>
  <modules runAllManagedModulesForAllRequests="true"/>

1
좋은 대답은 항상 붙여 넣은 직후와 다른 경우 (다른 파일)에 대해 SO 코드가 실행되는 것은 아닙니다.
Krzysztof Morcinek

1
@JonyAdamit 감사합니다. 다른 옵션은 async메소드 서명에 수정자를 배치 하고 작업 생성을 완전히 제거하는 것입니다. gist.github.com/ronnieoverby/ae0982c7832c531a9022
Ronnie

4
IIS7 +를 실행하는 사람이라면 누구에게나 도움이 될 것입니다. runAllManagedModulesForAllRequests를 이제 생략 할 수 있습니다 .
Index

1
@BendEg 한 번에 소스를 확인한 것처럼 보입니다. 그리고 그것은해야한다는 것이 합리적입니다. 프레임 워크의 소스를 제어 할 수 없으므로이 질문에 대한 답변은 시간이 지남에 따라 변경 될 수 있습니다.
Ronnie Overby

1
실제로 이미 내장 된 FileResult (및 FileStreamResult) 클래스가 있습니다.
BrainSlugs83

12

.NET Core를 사용하는 사용자 :

API 컨트롤러 메서드에서 IActionResult 인터페이스를 사용할 수 있습니다.

    [HttpGet("GetReportData/{year}")]
    public async Task<IActionResult> GetReportData(int year)
    {
        // Render Excel document in memory and return as Byte[]
        Byte[] file = await this._reportDao.RenderReportAsExcel(year);

        return File(file, "application/vnd.openxmlformats", "fileName.xlsx");
    }

이 예제는 단순화되었지만 그 점을 알아야합니다. .NET 핵심에서이 과정은 그래서 .NET의 이전 버전보다 훨씬 간단합니다 - 즉, 어떤 설정 응답 형식, 내용, 헤더 등

또한 파일 및 확장명에 대한 MIME 유형은 개별 요구에 따라 다릅니다.

참조 : @NKosi의 SO 게시물 답변


1
참고로 이미지 인 경우 직접 URL에 액세스 할 수있는 브라우저에서 이미지를 볼 수있게하려면 파일 이름을 제공하지 마십시오.
명왕성

9

제안 된 솔루션은 정상적으로 작동하지만 컨트롤러에서 바이트 배열을 반환하는 다른 방법이 있으며 응답 스트림의 형식이 적절합니다.

  • 요청에서 "Accept : application / octet-stream"헤더를 설정하십시오.
  • 서버 측에서이 MIME 유형을 지원하기 위해 매체 유형 포맷터를 추가하십시오.

불행히도 WebApi에는 "application / octet-stream"에 대한 포맷터가 포함되어 있지 않습니다. GitHub 에는 BinaryMediaTypeFormatter 구현이 있습니다 (webapi 2에서 작동하도록 메소드가 약간 변경되어 메소드 서명이 변경되었습니다).

이 포맷터를 전역 설정에 추가 할 수 있습니다 :

HttpConfiguration config;
// ...
config.Formatters.Add(new BinaryMediaTypeFormatter(false));

BinaryMediaTypeFormatter요청이 올바른 Accept 헤더를 지정하면 WebApi를 사용해야합니다 .

byte []를 반환하는 액션 컨트롤러가 테스트하기에 더 편하기 때문에이 솔루션을 선호합니다. 그러나 다른 솔루션을 사용하면 "application / octet-stream"(예 : "image / gif") 이외의 다른 컨텐츠 유형을 리턴하려는 경우 더 많은 제어가 가능합니다.


8

허용 된 답변의 메소드를 사용하여 상당히 큰 파일을 다운로드하는 동안 API가 두 번 이상 호출되는 데 문제가있는 사람은 응답 버퍼링을 true로 설정하십시오. System.Web.HttpContext.Current.Response.Buffer = true;

이를 통해 전체 바이너리 컨텐츠가 클라이언트로 전송되기 전에 서버 측에서 버퍼링됩니다. 그렇지 않으면 컨트롤러에 여러 요청이 전송되는 것을 볼 수 있으며 제대로 처리하지 않으면 파일이 손상됩니다.


3
Buffer속성은 더 이상 사용되지 찬성 BufferOutput. 기본값은 true입니다.
decates

6

사용중인 과부하는 직렬화 포맷터의 열거를 설정합니다. 컨텐츠 유형을 다음과 같이 명시 적으로 지정해야합니다.

httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

3
답장을 보내 주셔서 감사합니다. 나는 이것을 시도했지만 여전히 Content Type: application/jsonFiddler에서 보고 있습니다. 은 Content Type내가 돌아 오기 전에 어기면 올바르게 설정 것으로 보인다 httpResponseMessage응답을. 더 이상의 아이디어?
Josh Earl

3

당신은 시도 할 수 있습니다

httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.