วิธีคืนไฟล์ (FileContentResult) ใน ASP.NET WebAPI


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"]}]}

และมีการโพสต์ที่คล้ายกันใน SO: กลับแฟ้มไบนารีจากตัวควบคุมใน 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
สวัสดีขอบคุณสำหรับการแบ่งปันมีคำถามง่าย ๆ (ฉันเดา) ฉันมีส่วนหน้า C # ที่ได้รับการส่งข้อความตอบกลับ ฉันจะแยกสตรีมเนื้อหาและทำให้พร้อมใช้งานเพื่อให้ผู้ใช้สามารถบันทึกลงในดิสก์หรืออะไร (และฉันสามารถรับไฟล์จริง) ขอบคุณ!
Ronald

7
ฉันพยายามดาวน์โหลดไฟล์ excel ที่สร้างขึ้นเอง ใช้ stream.GetBuffer () ส่งคืน excel ที่เสียหายเสมอ ถ้าฉันใช้ stream.ToArray () แทนไฟล์จะถูกสร้างขึ้นโดยไม่มีปัญหา หวังว่านี่จะช่วยใครซักคน
afnpires

4
@AlexandrePires นั่นเป็นเพราะMemoryStream.GetBuffer()จริง ๆ แล้วส่งคืนบัฟเฟอร์ของ MemoryStream ซึ่งโดยปกติจะใหญ่กว่าเนื้อหาของสตรีม (เพื่อให้การแทรกมีประสิทธิภาพ) MemoryStream.ToArray()ส่งคืนบัฟเฟอร์ที่ถูกตัดให้เป็นขนาดเนื้อหา
M.Stramm

19
โปรดหยุดทำสิ่งนี้ การใช้หน่วยความจำประเภทนี้ในทางที่ผิดทำให้โค้ดไม่สามารถปรับขนาดได้และไม่สนใจวัตถุประสงค์ของสตรีมอย่างสมบูรณ์ คิดว่า: ทำไมทุกอย่างไม่ได้ถูกเปิดเผยในฐานะ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 refactor ของรหัสนี้จะเป็นการย้ายการเรียก IHttpActionResult ที่กำหนดเองเช่นที่อยู่ในรายการที่: stackoverflow.com/questions/23768596//
จอช

โพสต์นี้แสดงให้เห็นถึงการใช้งานที่เป็นระเบียบเรียบร้อยดี ในกรณีของฉันวิธีการช่วยเหลือที่ระบุไว้ในลิงค์ด้านบนได้รับการพิสูจน์แล้วว่ามีประโยชน์มากขึ้น
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;          
}

ดูมาร์กอัป Html (ด้วยเหตุการณ์การคลิกและ URL แบบง่าย):

<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 อ่านมิฉะนั้นคุณอาจพบไฟล์ที่ใช้งานอยู่ยกเว้น
Jeremy Bell

หากคุณแทนที่ด้วยหน่วยความจำสตรีมมันจะไม่ทำงาน?
aleha

@ JeremyBell เป็นเพียงตัวอย่างที่ไม่มีใครพูดถึงที่นี่เกี่ยวกับการผลิตและรุ่นที่ไม่ปลอดภัย
aleha

1
@Blaise ดูด้านล่างสำหรับเหตุผลที่รหัสนี้ทำงานร่วมกับแต่ล้มเหลวด้วยFileStream เป็นพื้นได้จะทำอย่างไรกับกระแสของMemoryStream Position
M.Stramm

9

นี่คือการใช้งานที่สตรีมเนื้อหาของไฟล์โดยไม่บัฟเฟอร์ (การบัฟเฟอร์ในไบต์ [] / 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);
    }
}

คุณจะลบไฟล์หลังจากดาวน์โหลดเสร็จสิ้นได้อย่างไร? มีตะขอใดบ้างที่จะได้รับการแจ้งเตือนเมื่อการดาวน์โหลดเสร็จสิ้น?
costa

ตกลงดูเหมือนว่าคำตอบนั้นจะนำไปใช้กับแอ็ตทริบิวต์ตัวกรองการกระทำและลบไฟล์ในวิธีการ OnActionExecuted
costa

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 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 (StreamContent ใหม่ (stream)) ส่งคืนการแทน JSON ของ StreamContent
Dmytro Zakharov

อัปเดตรหัส คำตอบนี้ตอบคำถามที่ลึกซึ้งยิ่งขึ้นว่า 'ทำไมโซลูชั่นแบบง่าย ๆ ทำงานกับ FileStream แต่ไม่ใช่ MemoryStream' แทนที่จะส่งคืนไฟล์ใน WebApi
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");

คนแรกที่ส่งคืนการแสดง JSON ของ StringContent: {"ส่วนหัว": [{"คีย์": "ประเภทเนื้อหา", "ค่า": ["แอปพลิเคชัน / octet-stream; charset = utf-8"]}]}

ในขณะที่ไฟล์ที่สองส่งคืนไฟล์ที่ถูกต้อง

ดูเหมือนว่า Request.CreateResponse มีการโอเวอร์โหลดที่ใช้สตริงเป็นพารามิเตอร์ที่สองและดูเหมือนว่าจะเป็นสาเหตุที่ทำให้วัตถุ StringContent แสดงเป็นสตริงแทนที่จะเป็นเนื้อหาจริง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.