ASP.NET Web API'sindeki denetleyiciden ikili dosya döndürme


323

ASP.NET MVC'nin çoğunlukla .cabve .exedosyaları ikili dosyaları sunacak yeni WebAPI kullanarak bir web hizmeti üzerinde çalışıyorum .

Aşağıdaki denetleyici yöntemi çalışıyor gibi görünüyor, yani bir dosya döndürüyor, ancak içerik türünü şu şekilde ayarlıyor 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"));
}

Bunu yapmanın daha iyi bir yolu var mı?


2
Web api ve IHTTPActionResult aracılığıyla akış yoluyla bir bayt dizisi döndürmek isteyen herkes o zaman buraya bakın: nodogmablog.bryanhogan.net/2017/02/…
IbrarMumtaz

Yanıtlar:


516

Bir basit kullanmayı deneyin HttpResponseMessageonun ile Contentbir mülk kümesi StreamContent:

// 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;
}

streamKullanılmış hakkında dikkat edilmesi gereken birkaç şey :

  • Sen deme stream.Dispose()o denetleyici yöntemin işlerken Web API hala erişime bunu yapabilmek gerekiyor, çünkü resultgöndermek için veri müşteriye geri. Bu nedenle, bir using (var stream = …)blok kullanmayın . Web API akışı sizin için atacaktır.

  • Akışın mevcut konumunun 0 olarak ayarlandığından emin olun (yani akış verilerinin başlangıcı). Yukarıdaki örnekte, dosyayı yeni açtığınızdan bu verilmiştir. Ancak, (örneğin ilk olarak bir bazı ikili veri yazarken diğer senaryolarda MemoryStream), emin olun stream.Seek(0, SeekOrigin.Begin);seti veyastream.Position = 0;

  • Dosya akışlarında, açıkça FileAccess.Readizin belirtmek web sunucularındaki erişim hakları sorunlarını önlemeye yardımcı olabilir; IIS uygulama havuzu hesaplarına genellikle wwwroot için yalnızca okuma / listeleme / yürütme erişim hakları verilir.


37
Akışın ne zaman kapanacağını biliyor musun? Çerçeve sonuçta etkili bir şekilde akışı kapatma HttpResponseMessage.Content.Dispose () çağırır HttpResponseMessage.Dispose () çağırır varsayıyorum.
Steve Guidi

41
Steve - haklısın ve FileStream'e bir kesme noktası ekleyerek doğrulandım. Çerçeve, FileStream.Dispose öğesini çağıran StreamContent.Dispose öğesini çağıran HttpResponseMessage.Dispose öğesini çağırır.
Dan Gartner

15
usingSonuçta ( HttpResponseMessage) veya akışın kendisine gerçekten a ekleyemezsiniz , çünkü bunlar yöntemin dışında kullanılmaya devam eder. @Dan'ın belirttiği gibi, istemciye yanıt gönderildikten sonra çerçeve tarafından bertaraf edilirler.
carlosfigueira

2
@ B.ClayShannon evet, hepsi bu. İstemci söz konusu olduğunda, HTTP yanıtının içeriğinde sadece bir bayt bayttır. İstemci bu baytlarla yerel bir dosyaya kaydetmek de dahil olmak üzere seçtikleri her şeyi yapabilir.
carlosfigueira

5
@carlosfigueira, merhaba, baytlar gönderildikten sonra dosyayı nasıl sileceğinizi biliyor musunuz?
Zach

137

İçin Web API 2 , sen uygulayabilir IHttpActionResult. Benimki burada:

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);
    }
}

Sonra kontrol cihazınızda böyle bir şey:

[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);
}

Ve işte IIS'ye bir uzantıya sahip istekleri yoksaymasını söylemenin bir yolu, böylece istek denetleyiciye bunu yapar:

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

1
Güzel cevap, her zaman değil SO kodu yapıştırma hemen sonra ve farklı durumlarda (farklı dosyalar) çalışır.
Krzysztof Morcinek

1
@JonyAdamit Teşekkürler. Başka bir seçenek, asyncyöntem imzasına bir değiştirici yerleştirmek ve bir görevin oluşturulmasını tamamen kaldırmaktır: gist.github.com/ronnieoverby/ae0982c7832c531a9022
Ronnie Overby

4
Sadece bu çalışan IIS7 + 'dan gelen herkes için bir kafa. Artık runAllManagedModulesForAllRequests atlanabilir .
İndeks

1
@BendEg Bir keresinde kaynağı kontrol ettim ve yaptı. Ve olması gerektiği mantıklı. Çerçevenin kaynağını kontrol edememek, bu soruya verilecek herhangi bir cevap zaman içinde değişebilir.
Ronnie Overby

1
Aslında zaten yerleşik bir FileResult (ve hatta FileStreamResult) sınıfı var.
BrainSlugs83

12

.NET Core'u kullananlar için:

IActionResult arabirimini, bunun gibi bir API denetleyicisi yönteminde kullanabilirsiniz ...

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

Bu örnek basitleştirilmiştir, ancak konuyu ele almalıdır. .NET Çekirdek bu süreç böylece .NET önceki sürümlerinde çok daha basit - yani hiçbir ayar tepkisi tipi, içerik, başlıklar, vb

Ayrıca, elbette dosya ve uzantı için MIME türü bireysel ihtiyaçlara bağlı olacaktır.

Referans: SO Post Tarafından cevap @NKosi


1
Yalnızca bir not, bu bir resimse ve doğrudan URL erişimi olan bir tarayıcıda görüntülenmesini istiyorsanız, bir dosya adı vermeyin.
Pluto

9

Önerilen çözüm iyi çalışıyor olsa da, yanıt akışı düzgün biçimlendirilmiş olarak denetleyiciden bir bayt dizisi döndürmenin başka bir yolu daha vardır:

  • İstekte, "Kabul et: application / octet-stream" üstbilgisini ayarlayın.
  • Sunucu tarafında, bu mime türünü desteklemek için bir ortam türü biçimlendiricisi ekleyin.

Ne yazık ki, WebApi "application / octet-stream" için herhangi bir formatlayıcı içermemektedir. Burada GitHub'da bir uygulama var: BinaryMediaTypeFormatter (webapi 2 için çalışmasını sağlamak için küçük uyarlamalar var, yöntem imzaları değişti).

Bu biçimlendiriciyi genel yapılandırmanıza ekleyebilirsiniz:

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

WebApi artık BinaryMediaTypeFormatteristek doğru Accept üstbilgisini belirtiyorsa kullanmalıdır .

Bu çözümü tercih ediyorum, çünkü byte [] döndüren bir eylem denetleyicisinin sınanması daha rahat. Yine de, diğer çözüm "application / octet-stream" (örneğin "image / gif") dışında başka bir içerik türü döndürmek istiyorsanız daha fazla denetime izin verir.


8

Kabul edilen yanıttaki yöntemi kullanarak oldukça büyük bir dosyayı indirirken API sorunu birden çok kez çağrılırsa, lütfen yanıt arabelleğini true System.Web.HttpContext.Current.Response.Buffer = true olarak ayarlayın;

Bu, istemciye gönderilmeden önce tüm ikili içeriğin sunucu tarafında arabelleğe alınmasını sağlar. Aksi takdirde, denetleyiciye birden çok istek gönderildiğini görürsünüz ve düzgün işlemezseniz dosya bozulur.


3
BufferMülkiyet dışı bırakılmıştır lehine BufferOutput. Varsayılan olarak true.
decates

6

Kullandığınız aşırı yük, serileştirme biçimlendiricilerinin numaralandırmasını ayarlar. İçerik türünü açıkça aşağıdaki gibi belirtmeniz gerekir:

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

3
Cevap için teşekkürler. Bunu denedim ve hala Content Type: application/jsonFiddler'de görüyorum . Content TypeBen dönmeden önce bölerseniz doğru ayarlanmış gibi görünüyor httpResponseMessageyanıtını. Başka fikir var mı?
Josh Earl

3

Deneyebilirsin

httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.