ASP.NET WebAPI'de dosya (FileContentResult) döndürme


173

Düzenli bir MVC denetleyicisinde, pdf ile a FileContentResult.

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

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

Ama bunu nasıl bir haline dönüştürebiliriz ApiController?

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

İşte denedim ama işe yaramıyor.

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

Tarayıcıda görüntülenen döndürülen sonuç:

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

Ve benzer bir yazı SO: ASP.NET Web API denetleyiciden ikili dosya döndürme . Mevcut bir dosyanın çıktısından bahsediyor. Ama bunu bir akarsu ile çalıştıramadım.

Baska öneri?


1
Bu yazı bana yardımcı oldu: stackoverflow.com/a/23768883/585552
Greg

Yanıtlar:


199

StreamContentOlarak dönmek yerine Content, ben çalışmasını sağlayabilir 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
Üst yarı sorunuzu cevaplıyorsa, lütfen bunu yalnızca yanıt olarak gönderin. İkinci yarı farklı bir soru gibi görünüyor - bunun için yeni bir soru gönderin.
gunr2171

3
Merhaba, paylaştığın için teşekkürler, basit bir sorum var (sanırım). Ben httpresponsemessage alır bir C # ön uç var. Akış içeriğini nasıl ayıklayabilir ve kullanılabilir hale getirebilirim, böylece bir kullanıcı diske veya başka bir şeye kaydedebilir (ve gerçek dosyayı alabilirim)? Teşekkürler!
Ronald

7
Kendi kendine oluşturulan bir excel dosyası indirmeye çalışıyordum. Stream.GetBuffer () yöntemi her zaman bozuk bir excel döndürdü. Bunun yerine stream.ToArray () kullanırsanız dosya sorunsuz bir şekilde oluşturulur. Umarım bu birine yardımcı olur.
afnpires

4
@AlexandrePires Bunun nedeni MemoryStream.GetBuffer()aslında genellikle akış içeriğinden daha büyük olan (eklemeleri verimli hale getirmek için) MemoryStream arabelleğini döndürür. MemoryStream.ToArray()içerik boyutuna kesilen arabelleği döndürür.
M.Stramm

19
Lütfen bunu yapmayı kes. MemoryStream bu tür kötüye kullanım nedenleri, ölçeklenemeyen kodlar ve Akışların amacını tamamen yok sayar. Düşün: neden her şey byte[]bunun yerine tampon olarak gösterilmiyor ? Kullanıcılarınız uygulamanızı belleği yetersiz bir şekilde çalıştırabilir.
makhdumi

97

Eğer geri dönmek IHttpActionResultisterseniz bunu şu şekilde yapabilirsiniz:

[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 dönüş türünü göstermek için iyi güncelleme. Bu kodun bir refactoru, aşağıdaki gibi listelenen gibi özel bir IHttpActionResult çağrısını taşımak için olacaktır: stackoverflow.com/questions/23768596/…
Josh

Bu yazı güzel ve düzenli bir tek kullanımlık uygulama göstermektedir. Benim durumumda, yukarıdaki bağlantıda listelenen yardımcı yöntem daha yararlı oldu
hanzolo

45

Bu soru bana yardımcı oldu.

Bu yüzden şunu deneyin:

Denetleyici kodu:

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

Html işaretlemesini görüntüleme (tıklama etkinliği ve basit URL ile):

<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
Burada FileStreamsunucudaki mevcut bir dosya için kullanıyorsunuz . Biraz farklı MemoryStream. Ama girdi için teşekkürler.
Blaise

4
Bir web sunucusundaki bir dosyadan okursanız, FileShare.Read için aşırı yük kullandığınızdan emin olun, aksi takdirde kullanım istisnalarıyla karşılaşabilirsiniz.
Jeremy Bell

bellek akışı ile değiştirirseniz, çalışmaz?
aleha

@JeremyBell sadece basitleştirilmiş bir örnek, kimse burada üretim ve arıza korumalı versiyon hakkında konuşmuyor.
aleha

1
@Blaise Bu kodun neden çalıştığı FileStreamancak başarısız olduğu için aşağıya bakın MemoryStream. Temelde Stream'lerle ilgili Position.
M.Stramm

9

Burada, dosyanın içeriğini arabelleğe almadan akışla aktaran bir uygulama (bayt [] / MemoryStream vb .'de arabelleğe alma, büyük bir dosyaysa bir sunucu sorunu olabilir).

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

Basitçe şu şekilde kullanılabilir:

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

İndirme işlemi tamamlandıktan sonra dosyayı nasıl silebilirsiniz? İndirme tamamlandığında bildirilecek kanca var mı?
costa

Tamam, yanıt bir eylem filtresi özniteliği uygulamak ve OnActionExecuted yönteminde dosyayı kaldırmak gibi görünüyor.
costa

5
Bu yayını bulundu Risord'un cevabı: stackoverflow.com/questions/2041717/… . Bunun var fs = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.None, 4096, FileOptions.DeleteOnClose);yerine bu satırı kullanabilirsinizFile.OpenRead(FilePath)
costa

7

Hangi bölümün suçlanacağından tam olarak emin değilim, ama işte neden MemoryStreamsizin için çalışmıyor:

Siz yazarken MemoryStream, Positionözelliğini artırır . Yapıcısı StreamContentdikkate streamin akımını alır Position. Bu nedenle, akışa yazar ve sonra iletirseniz StreamContent, yanıt akışın sonundaki hiçlikten başlar.

Bunu düzgün bir şekilde düzeltmenin iki yolu vardır:

1) içerik oluşturmak, akışa yazmak

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

2) akışa yazma, konumu sıfırlama, içerik oluşturma

[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) yeni bir Akışınız varsa biraz daha iyi görünür, 1) akışınız 0'dan başlamazsa daha basittir


Bu kod aslında soruya değinilen aynı yaklaşımı kullandığından soruna herhangi bir çözüm sağlamaz. Soru zaten bunun işe yaramadığını belirtiyor ve bunu onaylayabilirim. return Ok (yeni StreamContent (akış)), StreamContent'in JSON temsilini döndürür.
Dmytro Zakharov

Kod güncellendi. Bu yanıt aslında WebApi'de bir Dosyayı nasıl döndürmektense 'basit çözüm neden FileStream ile çalışıyor ancak MemoryStream ile çalışmıyor?
M.Stramm

3

Benim için fark

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

ve

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

İlki, StringContent'in JSON temsilini döndürüyordu: {"Headers": [{"Key": "Content-Type", "Value": ["application / octet-stream; charset = utf-8"]}]}

İkincisi dosyayı düzgün döndürürken.

Request.CreateResponse, ikinci parametre olarak bir dizeyi alan bir aşırı yüklemeye sahip gibi görünüyor ve bu, StringContent nesnesinin gerçek içerik yerine dize olarak işlenmesine neden olan şey gibi görünüyor.

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.