จะส่งคืนไฟล์โดยใช้ Web API ได้อย่างไร


101

ฉันใช้เว็บ ASP.NET API
ฉันต้องการดาวน์โหลด PDF ด้วย C # จาก API (ที่ API สร้างขึ้น)

ฉันขอ API ส่งคืนได้byte[]ไหม และสำหรับแอปพลิเคชัน C # ฉันสามารถทำได้:

byte[] pdf = client.DownloadData("urlToAPI");? 

และ

File.WriteAllBytes()?

"Web API"? คุณหมายถึงอะไรกันแน่? โปรดอ่านtinyurl.com/so-hintsและแก้ไขคำถามของคุณ
Jon Skeet

1
@JonSkeet: Web API เป็นคุณลักษณะใหม่ใน ASP.NET เวอร์ชันล่าสุด ดูasp.net/whitepapers/mvc4-release-notes#_Toc317096197
Robert Harvey

1
@ โรเบิร์ต: ขอบคุณ - แท็กทำให้ชัดเจนขึ้นแม้ว่าการอ้างถึง "ASP.NET Web API" จะชัดเจนกว่าก็ตาม ส่วนหนึ่งเป็นความผิดของ MS สำหรับชื่อสามัญที่น่าขยะแขยงเช่นกัน :)
Jon Skeet


ใครก็ตามที่ต้องการคืนสตรีมผ่าน web api และ IHTTPActionResult โปรดดูที่นี่: nodogmablog.bryanhogan.net/2017/02/…
IbrarMumtaz

คำตอบ:


173

ดีกว่าที่จะส่งคืน HttpResponseMessage ด้วย StreamContent ภายใน

นี่คือตัวอย่าง:

public HttpResponseMessage GetFile(string id)
{
    if (String.IsNullOrEmpty(id))
        return Request.CreateResponse(HttpStatusCode.BadRequest);

    string fileName;
    string localFilePath;
    int fileSize;

    localFilePath = getFileFromID(id, out fileName, out fileSize);

    HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
    response.Content = new StreamContent(new FileStream(localFilePath, FileMode.Open, FileAccess.Read));
    response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
    response.Content.Headers.ContentDisposition.FileName = fileName;
    response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");

    return response;
}

UPDจากความคิดเห็นโดยpatridge : หากคนอื่นมาที่นี่เพื่อต้องการส่งคำตอบจากอาร์เรย์ไบต์แทนที่จะเป็นไฟล์จริงคุณจะต้องใช้ ByteArrayContent (someData) ใหม่แทน StreamContent (ดูที่นี่ )


1
สิ่งแรก - รหัสนี้จะทำให้เกิดข้อยกเว้นเนื่องจากคุณกำลังสร้าง FileStream สองวัตถุที่ชี้ไปที่ไฟล์เดียวกัน สิ่งที่สองคือคุณไม่ต้องการใช้คำสั่ง "ใช้" เพราะทันทีที่ตัวแปรอยู่นอกขอบเขต. NET จะกำจัดมันและคุณจะได้รับข้อความแสดงข้อผิดพลาดเกี่ยวกับการเชื่อมต่อที่อยู่ภายใต้การปิด
Brandon Montgomery

48
หากคนอื่นมาที่นี่เพื่อต้องการส่งการตอบกลับจากอาร์เรย์ไบต์แทนที่จะเป็นไฟล์จริงคุณจะต้องใช้new ByteArrayContent(someData)แทนStreamContent(ดูที่นี่ )
patridge

คุณอาจต้องการลบล้างการกำจัดฐาน () เพื่อให้คุณสามารถจัดการทรัพยากรของคุณได้อย่างถูกต้องเมื่อเฟรมเวิร์กเรียกใช้
Phil Cooper

2
ฉันต้องการจะชี้ให้เห็นว่า MediaTypeHeaderValue ที่ถูกต้องมีความสำคัญและเพื่อให้ได้ไดนามิกหากคุณมีไฟล์ประเภทต่างๆคุณสามารถทำได้เช่นนี้ (โดยที่ fileName เป็นสตริงและมีประเภทไฟล์ลงท้ายเช่น. jpg, .pdf, docx เป็นต้น .. ) var contentType = MimeMapping.GetMimeMapping (fileName); response.Content.Headers.ContentType = MediaTypeHeaderValue ใหม่ (contentType);
JimiSweden

1
FileStream ถูกกำจัดโดยอัตโนมัติหรือไม่?
Brian Tacker

37

ฉันได้ดำเนินการดังต่อไปนี้:

[HttpGet]
[Route("api/DownloadPdfFile/{id}")]
public HttpResponseMessage DownloadPdfFile(long id)
{
    HttpResponseMessage result = null;
    try
    {
        SQL.File file = db.Files.Where(b => b.ID == id).SingleOrDefault();

        if (file == null)
        {
            result = Request.CreateResponse(HttpStatusCode.Gone);
        }
        else
        {
            // sendo file to client
            byte[] bytes = Convert.FromBase64String(file.pdfBase64);


            result = Request.CreateResponse(HttpStatusCode.OK);
            result.Content = new ByteArrayContent(bytes);
            result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
            result.Content.Headers.ContentDisposition.FileName = file.name + ".pdf";
        }

        return result;
    }
    catch (Exception ex)
    {
        return Request.CreateResponse(HttpStatusCode.Gone);
    }
}

นี่ตอบคำถามได้จริง
มิก

1
นี่ไม่ใช่ความคิดที่ดีสำหรับไฟล์ขนาดใหญ่เนื่องจากจะโหลดภาพทั้งหมดลงในหน่วยความจำ ตัวเลือกสตรีมจะดีกว่า
Paul Reedy

@PaulReedy Perfect ... แต่ในหลาย ๆ กรณีคุณไม่จำเป็นต้องจัดการกับไฟล์ขนาดใหญ่ แต่ฉันเห็นด้วยกับประเด็นของคุณ!
André de Mattos Ferraz

16

ตัวอย่างด้วยIHttpActionResultในApiController.

[HttpGet]
[Route("file/{id}/")]
public IHttpActionResult GetFileForCustomer(int id)
{
    if (id == 0)
      return BadRequest();

    var file = GetFile(id);

    IHttpActionResult response;
    HttpResponseMessage responseMsg = new HttpResponseMessage(HttpStatusCode.OK);
    responseMsg.Content = new ByteArrayContent(file.SomeData);
    responseMsg.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
    responseMsg.Content.Headers.ContentDisposition.FileName = file.FileName;
    responseMsg.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
    response = ResponseMessage(responseMsg);
    return response;
}

หากคุณไม่ต้องการดาวน์โหลด PDF และใช้เบราว์เซอร์ที่สร้างขึ้นในโปรแกรมดู PDF ให้ลบสองบรรทัดต่อไปนี้:

responseMsg.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
responseMsg.Content.Headers.ContentDisposition.FileName = file.FileName;

@ElbertJohnFelipe ใช่คุณได้รับไฟล์ด้วยไฟล์var file = GetFile(id);. file.SomeDataเป็น Byte Array ( byte[]) และfile.FileNameคือstring.
Ogglas

ขอบคุณสำหรับโพสต์ของคุณ 'HttpResponseMessage' ใช้ไม่ได้สำหรับฉันใน ApiController ดังนั้นคุณจึงช่วยฉันไว้
สูงสุด

16

หมายเหตุสำหรับ.Net Core: เราสามารถใช้FileContentResultและตั้งค่า contentType เป็นapplication/octet-streamถ้าเราต้องการส่งไบต์ดิบ ตัวอย่าง:

[HttpGet("{id}")]
public IActionResult GetDocumentBytes(int id)
{
    byte[] byteArray = GetDocumentByteArray(id);
    return new FileContentResult(byteArray, "application/octet-stream");
}

1
วิธีนี้ใช้งานได้ดีนอกจากนี้หากคุณต้องการควบคุมชื่อไฟล์มีคุณสมบัติที่FileContentResultเรียกFileDownloadNameเพื่อระบุชื่อไฟล์
สัปดาห์ที่

@weeksdev อ่าไม่รู้เรื่องนั้น ขอบคุณสำหรับความคิดเห็น
Amir Shirazi

ขอบคุณครับ นอกจากนี้ความคิดเห็นจาก weeksdev ยังมีประโยชน์มาก
fragg

1

ฉันเคยสงสัยว่ามีวิธีง่ายๆในการดาวน์โหลดไฟล์ด้วยวิธีอื่น ๆ ... "ทั่วไป" หรือไม่ ฉันคิดสิ่งนี้ขึ้นมา

เป็นเรื่องง่ายActionResultที่จะช่วยให้คุณดาวน์โหลดไฟล์จากการเรียกตัวควบคุมที่ส่งคืนIHttpActionResultไฟล์. ไฟล์จะถูกเก็บไว้ในไฟล์byte[] Content. คุณสามารถเปลี่ยนเป็นสตรีมได้หากต้องการ

ฉันใช้สิ่งนี้เพื่อส่งคืนไฟล์ที่เก็บไว้ในคอลัมน์ varbinary ของฐานข้อมูล

    public class FileHttpActionResult : IHttpActionResult
    {
        public HttpRequestMessage Request { get; set; }

        public string FileName { get; set; }
        public string MediaType { get; set; }
        public HttpStatusCode StatusCode { get; set; }

        public byte[] Content { get; set; }

        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            HttpResponseMessage response = new HttpResponseMessage(StatusCode);

            response.StatusCode = StatusCode;
            response.Content = new StreamContent(new MemoryStream(Content));
            response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
            response.Content.Headers.ContentDisposition.FileName = FileName;
            response.Content.Headers.ContentType = new MediaTypeHeaderValue(MediaType);

            return Task.FromResult(response);
        }
    }

คำอธิบายสั้น ๆ ว่าโค้ดของคุณแก้ไขปัญหาของ OP ได้อย่างไรจะช่วยเพิ่มคุณภาพของคำตอบของคุณ
Adrian Mole

0

อีกวิธีในการดาวน์โหลดไฟล์คือการเขียนเนื้อหาสตรีมลงในเนื้อหาของการตอบสนองโดยตรง:

[HttpGet("pdfstream/{id}")]
public async Task  GetFile(long id)
{        
    var stream = GetStream(id);
    Response.StatusCode = (int)HttpStatusCode.OK;
    Response.Headers.Add( HeaderNames.ContentDisposition, $"attachment; filename=\"{Guid.NewGuid()}.pdf\"" );
    Response.Headers.Add( HeaderNames.ContentType, "application/pdf"  );            
    await stream.CopyToAsync(Response.Body);
    await Response.Body.FlushAsync();           
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.