JavaScript / jQuery เพื่อดาวน์โหลดไฟล์ผ่าน POST ด้วยข้อมูล JSON


250

ฉันมี webapp แบบหน้าเดียวแบบ jquery มันสื่อสารกับบริการเว็บสงบผ่านสาย AJAX

ฉันพยายามทำสิ่งต่อไปนี้ให้สำเร็จ:

  1. ส่ง POST ที่มีข้อมูล JSON ไปยัง URL REST
  2. หากคำขอระบุการตอบสนอง JSON ดังนั้น JSON จะถูกส่งกลับ
  3. หากคำขอระบุการตอบสนอง PDF / XLS / etc จะส่งกลับไบนารีที่ดาวน์โหลดได้

ฉันทำงานได้ 1 & 2 ในขณะนี้และแอป jquery ของลูกค้าจะแสดงข้อมูลที่ส่งคืนในหน้าเว็บด้วยการสร้างองค์ประกอบ DOM ตามข้อมูล JSON ฉันยังมี # 3 ทำงานจากมุมมองของเว็บเซอร์วิสซึ่งหมายความว่ามันจะสร้างและส่งคืนไฟล์ไบนารีหากกำหนดพารามิเตอร์ JSON ที่ถูกต้อง แต่ฉันไม่แน่ใจวิธีที่ดีที่สุดในการจัดการกับ # 3 ในรหัสจาวาสคริปต์ของลูกค้า

เป็นไปได้ไหมที่จะรับไฟล์ที่ดาวน์โหลดได้จากการโทร ajax เช่นนี้? ฉันจะรับเบราว์เซอร์เพื่อดาวน์โหลดและบันทึกไฟล์ได้อย่างไร

$.ajax({
    type: "POST",
    url: "/services/test",
    contentType: "application/json",
    data: JSON.stringify({category: 42, sort: 3, type: "pdf"}),
    dataType: "json",
    success: function(json, status){
        if (status != "success") {
            log("Error loading data");
            return;
        }
        log("Data loaded!");
    },
    error: function(result, status, err) {
        log("Error loading data");
        return;
    }
});

เซิร์ฟเวอร์ตอบสนองด้วยส่วนหัวต่อไปนี้:

Content-Disposition:attachment; filename=export-1282022272283.pdf
Content-Length:5120
Content-Type:application/pdf
Server:Jetty(6.1.11)

อีกแนวคิดหนึ่งคือการสร้าง PDF และเก็บไว้ในเซิร์ฟเวอร์และส่งคืน JSON ที่มี URL ไปยังไฟล์ จากนั้นออกการโทรอื่นในตัวจัดการความสำเร็จ ajax เพื่อทำสิ่งต่อไปนี้:

success: function(json,status) {
    window.location.href = json.url;
}

แต่การทำเช่นนั้นหมายความว่าฉันจะต้องโทรมากกว่าหนึ่งครั้งไปยังเซิร์ฟเวอร์และเซิร์ฟเวอร์ของฉันจะต้องสร้างไฟล์ที่ดาวน์โหลดได้จัดเก็บไว้ที่ใดที่หนึ่งจากนั้นจึงล้างพื้นที่เก็บข้อมูลเป็นระยะ

ต้องมีวิธีที่ง่ายกว่าในการทำสิ่งนี้ให้สำเร็จ ไอเดีย?


แก้ไข: หลังจากตรวจสอบเอกสารสำหรับ $ .ajax ฉันเห็นว่า dataType สามารถตอบสนองได้เพียงหนึ่งxml, html, script, json, jsonp, textดังนั้นฉันเดาว่าจะไม่มีวิธีดาวน์โหลดไฟล์โดยตรงโดยใช้คำขอ ajax เว้นแต่ฉันจะฝังไฟล์ไบนารีในการใช้ โครงการ Data URI ตามที่แนะนำในคำตอบ @VinayC (ซึ่งไม่ใช่สิ่งที่ฉันต้องการจะทำ)

ดังนั้นฉันเดาตัวเลือกของฉันคือ:

  1. ไม่ใช้ Ajax และส่งการโพสต์แบบฟอร์มและฝังข้อมูล JSON ของฉันลงในค่าฟอร์ม อาจจะต้องยุ่งกับ iframes ที่ซ่อนอยู่และเช่นนั้น

  2. อย่าใช้ Ajax และแปลงข้อมูล JSON ของฉันเป็นสตริงข้อความค้นหาเพื่อสร้างคำขอ GET มาตรฐานและตั้ง window.location.href เป็น URL นี้ อาจต้องใช้ event.preventDefault () ในตัวจัดการคลิกของฉันเพื่อไม่ให้เบราว์เซอร์เปลี่ยนจาก URL แอปพลิเคชัน

  3. ใช้แนวคิดอื่นของฉันด้านบน แต่ปรับปรุงด้วยคำแนะนำจากคำตอบ @naikus ส่งคำขอ AJAX ด้วยพารามิเตอร์บางอย่างที่ช่วยให้เว็บเซอร์วิสทราบว่ากำลังถูกเรียกผ่าน ajax call หากบริการเว็บถูกเรียกจากการโทร ajax เพียงส่งคืน JSON ด้วย URL ไปยังทรัพยากรที่สร้างขึ้น หากเรียกใช้ทรัพยากรโดยตรงให้ส่งคืนไฟล์ไบนารีจริง

ยิ่งฉันคิดถึงมันมากเท่าไหร่ฉันก็ยิ่งชอบตัวเลือกสุดท้ายเท่านั้น วิธีนี้ฉันสามารถรับข้อมูลกลับเกี่ยวกับคำขอ (เวลาในการสร้างขนาดไฟล์ข้อความผิดพลาด ฯลฯ ) และฉันสามารถดำเนินการกับข้อมูลนั้นก่อนที่จะเริ่มดาวน์โหลด ข้อเสียคือการจัดการไฟล์พิเศษบนเซิร์ฟเวอร์

มีวิธีอื่นอีกไหมในการทำสิ่งนี้ให้สำเร็จ ข้อดี / ข้อเสียของวิธีการเหล่านี้ฉันควรทราบหรือไม่?


อาจเป็นไปได้ซ้ำกับการดาวน์โหลดไฟล์ Handle จาก ajax post
IsmailS

6
ก่อนหน้านี้เป็น "บิต" ดังนั้นจึงเป็นวิธีที่ซ้ำกัน
GiM

1
url = 'http://localhost/file.php?file='+ $('input').val(); window.open(url); วิธีง่ายๆในการรับผลลัพธ์เดียวกัน ใส่ส่วนหัวในไฟล์ php ไม่จำเป็นต้องส่ง ajax หรือรับคำขอ
abhishek bagul

คำตอบ:


171

โซลูชันของletronjeใช้งานได้กับหน้าเว็บที่เรียบง่ายเท่านั้น document.body.innerHTML +=นำข้อความ HTML ของเนื้อหาผนวก iframe HTML และตั้งค่า innerHTML ของหน้าเป็นสตริงนั้น วิธีนี้จะลบล้างเหตุการณ์ใด ๆ ที่ผูกหน้าของคุณและสิ่งอื่น ๆ สร้างองค์ประกอบและใช้appendChildแทน

$.post('/create_binary_file.php', postData, function(retData) {
  var iframe = document.createElement("iframe");
  iframe.setAttribute("src", retData.url);
  iframe.setAttribute("style", "display: none");
  document.body.appendChild(iframe);
}); 

หรือใช้ jQuery

$.post('/create_binary_file.php', postData, function(retData) {
  $("body").append("<iframe src='" + retData.url+ "' style='display: none;' ></iframe>");
}); 

สิ่งนี้ทำจริง: ดำเนินการโพสต์ไปยัง /create_binary_file.php ด้วยข้อมูลในตัวแปร postData; หากโพสต์นั้นเสร็จสมบูรณ์ให้เพิ่ม iframe ใหม่ให้กับเนื้อความของหน้า สมมติฐานคือการตอบสนองจาก /create_binary_file.php จะมีค่า 'url' ซึ่งเป็น URL ที่สามารถดาวน์โหลดไฟล์ PDF / XLS / etc ที่สร้างขึ้นได้ การเพิ่ม iframe ให้กับเพจที่อ้างถึง URL นั้นจะส่งผลให้เบราว์เซอร์โปรโมตผู้ใช้ให้ดาวน์โหลดไฟล์โดยสมมติว่าเว็บเซิร์ฟเวอร์มีการกำหนดค่าประเภท mime ที่เหมาะสม


1
ฉันยอมรับสิ่งนี้ดีกว่าการใช้งาน+=และฉันจะอัปเดตแอปพลิเคชันของฉันตามนั้น ขอบคุณ!
Tauren

3
ผมชอบแนวคิดนี้ แต่ Chrome Resource interpreted as Document but transferred with MIME type application/pdfมีข้อความนี้อยู่ในคอนโซล: จากนั้นยังเตือนฉันว่าไฟล์อาจเป็นอันตราย ถ้าฉันไปที่ retData.url โดยตรงไม่มีคำเตือนหรือปัญหา
Michael Irey

ดูจากอินเทอร์เน็ตดูเหมือนว่าคุณจะมีปัญหากับ PDF ใน iFrames หากเซิร์ฟเวอร์ของคุณไม่ได้ตั้งค่า Content-Type และ Content-Disposition อย่างเหมาะสม ฉันไม่ได้ใช้โซลูชันนี้จริง ๆ เพียงชี้แจงโซลูชันก่อนหน้านี้ดังนั้นไม่มีคำแนะนำอื่น ๆ ที่ฉันกลัว
SamStephens

1
@Tauren: หากคุณยอมรับว่านี่เป็นคำตอบที่ดีกว่าโปรดทราบว่าคุณสามารถเปลี่ยนคำตอบที่ยอมรับได้ตลอดเวลา - หรือแม้แต่ลบเครื่องหมายยอมรับทั้งหมด เพียงตรวจสอบคำตอบใหม่แล้วเครื่องหมายตอบรับจะถูกโอน (คำถามเก่า แต่มันถูกนำขึ้นมาในธงเมื่อเร็ว ๆ นี้)
BoltClock

5
retData.url คืออะไรควรเป็นอย่างไร
Mark Thien

48

ฉันได้เล่นกับตัวเลือกอื่นที่ใช้ blobs ฉันจัดการเพื่อดาวน์โหลดเอกสารข้อความและฉันดาวน์โหลดไฟล์ PDF (อย่างไรก็ตามเอกสารเหล่านั้นเสียหาย)

การใช้ blob API คุณจะสามารถทำสิ่งต่อไปนี้:

$.post(/*...*/,function (result)
{
    var blob=new Blob([result]);
    var link=document.createElement('a');
    link.href=window.URL.createObjectURL(blob);
    link.download="myFileName.txt";
    link.click();

});

นี่คือ IE 10+, Chrome 8+, FF 4+ ดูhttps://developer.mozilla.org/en-US/docs/Web/API/URL.createObjectURL

มันจะดาวน์โหลดไฟล์ใน Chrome, Firefox และ Opera เท่านั้น สิ่งนี้ใช้คุณลักษณะการดาวน์โหลดบนแท็กจุดยึดเพื่อบังคับเบราว์เซอร์ให้ดาวน์โหลด


1
ไม่รองรับคุณสมบัติการดาวน์โหลดบน IE และ Safari :( ( caniuse.com/#feat=download ) คุณสามารถเปิดหน้าต่างใหม่และใส่เนื้อหาที่นั่น แต่ไม่แน่ใจเกี่ยวกับ PDF หรือ Excel ...
Marçal Juan

1
คุณควรเพิ่มหน่วยความจำของเบราว์เซอร์หลังจากโทรclick:window.URL.revokeObjectURL(link.href);
Edward Brey

@JoshBerke นี่เก่า แต่ใช้ได้ในตอนท้าย ฉันจะเปิดไดเรกทอรีของฉันทันทีเพื่อบันทึกแทนที่จะบันทึกลงในเบราว์เซอร์ของฉันได้อย่างไร ยังมีวิธีการกำหนดชื่อของไฟล์ที่เข้ามาหรือไม่
Jo Ko

2
มันจะไฟล์ไบนารีเสียหายเพราะพวกเขาได้รับการแปลงสตริงโดยใช้การเข้ารหัสและผลที่ไม่ได้เป็นมากใกล้เคียงกับไบนารีเดิม ...
Gobol

2
อาจใช้ mimetypes เพื่อไม่ให้ข้อมูลเสียหายตามที่ใช้กับ PDF: alexhadik.com/blog/2016/7/7/l8ztp8kr5lbctf5qns4l8t3646npqh
Mateo Tibaquira

18

ฉันรู้ว่าของเก่าแบบนี้ แต่ฉันคิดว่าฉันคิดวิธีที่สง่างามกว่า ฉันมีปัญหาเดียวกันแน่นอน ปัญหาที่ฉันมีกับวิธีแก้ปัญหาที่แนะนำคือพวกเขาต้องการไฟล์ที่ถูกบันทึกไว้ในเซิร์ฟเวอร์ แต่ฉันไม่ต้องการบันทึกไฟล์บนเซิร์ฟเวอร์เพราะมันแนะนำปัญหาอื่น ๆ (ความปลอดภัย: ไฟล์นั้นสามารถเข้าถึงได้โดย ผู้ใช้ที่ไม่ผ่านการตรวจสอบสิทธิ์การล้างข้อมูล: คุณกำจัดไฟล์อย่างไรและเมื่อไร) เช่นเดียวกับคุณข้อมูลของฉันมีความซับซ้อนวัตถุ JSON ซ้อนกันซึ่งยากที่จะใส่ลงในแบบฟอร์ม

สิ่งที่ฉันทำคือสร้างฟังก์ชันเซิร์ฟเวอร์สองฟังก์ชัน คนแรกที่ตรวจสอบข้อมูล หากมีข้อผิดพลาดมันจะถูกส่งกลับ หากไม่ใช่ข้อผิดพลาดฉันกลับพารามิเตอร์ทั้งหมดที่ทำให้เป็นอนุกรม / เข้ารหัสเป็นสตริงเบส 64 จากนั้นในไคลเอนต์ฉันมีรูปแบบที่มีเพียงหนึ่งอินพุตที่ซ่อนอยู่และโพสต์ไปยังฟังก์ชั่นเซิร์ฟเวอร์ที่สอง ฉันตั้งค่าอินพุตที่ซ่อนเป็นสตริง base64 และส่งรูปแบบ ฟังก์ชั่นเซิร์ฟเวอร์ตัวที่สองถอดรหัส / ยกเลิกการกำหนดพารามิเตอร์และสร้างไฟล์ แบบฟอร์มสามารถส่งไปที่หน้าต่างใหม่หรือ iframe บนหน้าและไฟล์จะเปิดขึ้น

มีงานเกี่ยวข้องเพิ่มขึ้นอีกเล็กน้อยและอาจจะมีการประมวลผลเพิ่มขึ้นเล็กน้อย แต่โดยรวมแล้วฉันรู้สึกดีขึ้นมากเมื่อใช้โซลูชันนี้

รหัสอยู่ใน C # / MVC

    public JsonResult Validate(int reportId, string format, ReportParamModel[] parameters)
    {
        // TODO: do validation

        if (valid)
        {
            GenerateParams generateParams = new GenerateParams(reportId, format, parameters);

            string data = new EntityBase64Converter<GenerateParams>().ToBase64(generateParams);

            return Json(new { State = "Success", Data = data });
        }

        return Json(new { State = "Error", Data = "Error message" });
    }

    public ActionResult Generate(string data)
    {
        GenerateParams generateParams = new EntityBase64Converter<GenerateParams>().ToEntity(data);

        // TODO: Generate file

        return File(bytes, mimeType);
    }

บนลูกค้า

    function generate(reportId, format, parameters)
    {
        var data = {
            reportId: reportId,
            format: format,
            params: params
        };

        $.ajax(
        {
            url: "/Validate",
            type: 'POST',
            data: JSON.stringify(data),
            dataType: 'json',
            contentType: 'application/json; charset=utf-8',
            success: generateComplete
        });
    }

    function generateComplete(result)
    {
        if (result.State == "Success")
        {
            // this could/should already be set in the HTML
            formGenerate.action = "/Generate";
            formGenerate.target = iframeFile;

            hidData = result.Data;
            formGenerate.submit();
        }
        else
            // TODO: display error messages
    }

1
ฉันยังไม่ได้ดูวิธีแก้ปัญหานี้ได้ดี แต่มันก็คุ้มค่าที่จะได้เห็นว่าไม่มีอะไรเกี่ยวกับโซลูชันที่ใช้ create_binary_file.php ต้องมีไฟล์ที่จะบันทึกลงดิสก์ มันจะเป็นไปได้ทั้งหมดที่จะสร้าง create_binary_file.php ไฟล์ไบนารีในหน่วยความจำ
SamStephens

11

มีวิธีที่ง่ายกว่าสร้างแบบฟอร์มและโพสต์สิ่งนี้จะเสี่ยงต่อการรีเซ็ตหน้าเว็บหากประเภทการส่งคืน mime เป็นสิ่งที่เบราว์เซอร์จะเปิด แต่สำหรับ csv และมันสมบูรณ์แบบ

ตัวอย่างต้องการขีดล่างและ jquery

var postData = {
    filename:filename,
    filecontent:filecontent
};
var fakeFormHtmlFragment = "<form style='display: none;' method='POST' action='"+SAVEAS_PHP_MODE_URL+"'>";
_.each(postData, function(postValue, postKey){
    var escapedKey = postKey.replace("\\", "\\\\").replace("'", "\'");
    var escapedValue = postValue.replace("\\", "\\\\").replace("'", "\'");
    fakeFormHtmlFragment += "<input type='hidden' name='"+escapedKey+"' value='"+escapedValue+"'>";
});
fakeFormHtmlFragment += "</form>";
$fakeFormDom = $(fakeFormHtmlFragment);
$("body").append($fakeFormDom);
$fakeFormDom.submit();

สำหรับสิ่งต่างๆเช่น html ข้อความและอื่น ๆ ตรวจสอบให้แน่ใจว่า mimetype นั้นเป็นแอปพลิเคชั่น / octet-stream

รหัส php

<?php
/**
 * get HTTP POST variable which is a string ?foo=bar
 * @param string $param
 * @param bool $required
 * @return string
 */
function getHTTPPostString ($param, $required = false) {
    if(!isset($_POST[$param])) {
        if($required) {
            echo "required POST param '$param' missing";
            exit 1;
        } else {
            return "";
        }
    }
    return trim($_POST[$param]);
}

$filename = getHTTPPostString("filename", true);
$filecontent = getHTTPPostString("filecontent", true);

header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"$filename\"");
echo $filecontent;

8

ในระยะสั้นไม่มีวิธีที่ง่ายกว่า คุณต้องขอเซิร์ฟเวอร์อื่นเพื่อแสดงไฟล์ PDF แม้ว่าจะมีตัวเลือกน้อย แต่ก็ไม่สมบูรณ์แบบและจะไม่ทำงานบนเบราว์เซอร์ทั้งหมด:

  1. ดูที่ข้อมูลโครงการ URI หากข้อมูลไบนารีมีขนาดเล็กคุณอาจใช้จาวาสคริปต์เพื่อเปิดหน้าต่างผ่านข้อมูลใน URI
  2. วิธีแก้ปัญหาเฉพาะ Windows / IE คือการมี. NET control หรือ FileSystemObject เพื่อบันทึกข้อมูลบนระบบไฟล์โลคัลและเปิดจากที่นั่น

ขอบคุณฉันไม่ได้ตระหนักถึงรูปแบบ Data URI ดูเหมือนว่าอาจเป็นวิธีเดียวที่จะทำได้ในคำขอเดียว แต่มันไม่ใช่ทิศทางที่ฉันต้องการจริงๆ
Tauren

เนื่องจากความต้องการของลูกค้าฉันสิ้นสุดการแก้ปัญหานี้โดยใช้ส่วนหัวของเซิร์ฟเวอร์ในการตอบสนอง ( Content-Disposition: attachment;filename="file.txt") แต่ฉันต้องการลองวิธีนี้ในครั้งต่อไป ฉันเคยใช้URIข้อมูลสำหรับรูปขนาดย่อมาก่อน แต่มันไม่เคยเกิดขึ้นกับฉันเพื่อใช้สำหรับการดาวน์โหลด - ขอบคุณสำหรับการแบ่งปันนักเก็ตนี้
brichins

8

คำถามนี้ถูกถามมานานแล้ว แต่ฉันมีความท้าทายเดียวกันและต้องการแบ่งปันวิธีแก้ปัญหาของฉัน มันใช้องค์ประกอบจากคำตอบอื่น ๆ แต่ฉันไม่สามารถค้นหาได้ทั้งหมด มันไม่ได้ใช้แบบฟอร์มหรือ iframe แต่มันต้องมีคู่โพสต์ / รับคำขอ แทนที่จะบันทึกไฟล์ระหว่างคำขอมันจะบันทึกข้อมูลโพสต์ ดูเหมือนว่าทั้งง่ายและมีประสิทธิภาพ

ลูกค้า

var apples = new Array(); 
// construct data - replace with your own
$.ajax({
   type: "POST",
   url: '/Home/Download',
   data: JSON.stringify(apples),
   contentType: "application/json",
   dataType: "text",

   success: function (data) {
      var url = '/Home/Download?id=' + data;
      window.location = url;
   });
});

เซิร์ฟเวอร์

[HttpPost]
// called first
public ActionResult Download(Apple[] apples)
{
   string json = new JavaScriptSerializer().Serialize(apples);
   string id = Guid.NewGuid().ToString();
   string path = Server.MapPath(string.Format("~/temp/{0}.json", id));
   System.IO.File.WriteAllText(path, json);

   return Content(id);
}

// called next
public ActionResult Download(string id)
{
   string path = Server.MapPath(string.Format("~/temp/{0}.json", id));
   string json = System.IO.File.ReadAllText(path);
   System.IO.File.Delete(path);
   Apple[] apples = new JavaScriptSerializer().Deserialize<Apple[]>(json);

   // work with apples to build your file in memory
   byte[] file = createPdf(apples); 

   Response.AddHeader("Content-Disposition", "attachment; filename=juicy.pdf");
   return File(file, "application/pdf");
}

1
ฉันคิดว่าฉันชอบวิธีนี้ดีที่สุด ในกรณีของฉันแม้ว่าฉันกำลังสร้างไฟล์ดังนั้นฉันคิดว่าฉันจะพยายามเก็บไฟล์ไว้ในหน่วยความจำจนกว่าจะมีการเข้าถึงและเพิ่มหน่วยความจำหลังจากดาวน์โหลดเสร็จดังนั้นฉันจึงไม่ต้องเขียนลงดิสก์
BVernon

5
$scope.downloadSearchAsCSV = function(httpOptions) {
  var httpOptions = _.extend({
    method: 'POST',
    url:    '',
    data:   null
  }, httpOptions);
  $http(httpOptions).then(function(response) {
    if( response.status >= 400 ) {
      alert(response.status + " - Server Error \nUnable to download CSV from POST\n" + JSON.stringify(httpOptions.data));
    } else {
      $scope.downloadResponseAsCSVFile(response)
    }
  })
};
/**
 * @source: https://github.com/asafdav/ng-csv/blob/master/src/ng-csv/directives/ng-csv.js
 * @param response
 */
$scope.downloadResponseAsCSVFile = function(response) {
  var charset = "utf-8";
  var filename = "search_results.csv";
  var blob = new Blob([response.data], {
    type: "text/csv;charset="+ charset + ";"
  });

  if (window.navigator.msSaveOrOpenBlob) {
    navigator.msSaveBlob(blob, filename); // @untested
  } else {
    var downloadContainer = angular.element('<div data-tap-disabled="true"><a></a></div>');
    var downloadLink      = angular.element(downloadContainer.children()[0]);
    downloadLink.attr('href', window.URL.createObjectURL(blob));
    downloadLink.attr('download', "search_results.csv");
    downloadLink.attr('target', '_blank');

    $document.find('body').append(downloadContainer);

    $timeout(function() {
      downloadLink[0].click();
      downloadLink.remove();
    }, null);
  }

  //// Gets blocked by Chrome popup-blocker
  //var csv_window = window.open("","","");
  //csv_window.document.write('<meta name="content-type" content="text/csv">');
  //csv_window.document.write('<meta name="content-disposition" content="attachment;  filename=data.csv">  ');
  //csv_window.document.write(response.data);
};

3

ไม่ใช่คำตอบทั้งหมดของโพสต์ต้นฉบับ แต่เป็นวิธีแก้ปัญหาที่รวดเร็วและสกปรกสำหรับการโพสต์ json-object ไปยังเซิร์ฟเวอร์และสร้างการดาวน์โหลดแบบไดนามิก

jQuery ฝั่งไคลเอ็นต์:

var download = function(resource, payload) {
     $("#downloadFormPoster").remove();
     $("<div id='downloadFormPoster' style='display: none;'><iframe name='downloadFormPosterIframe'></iframe></div>").appendTo('body');
     $("<form action='" + resource + "' target='downloadFormPosterIframe' method='post'>" +
      "<input type='hidden' name='jsonstring' value='" + JSON.stringify(payload) + "'/>" +
      "</form>")
      .appendTo("#downloadFormPoster")
      .submit();
}

.. แล้วถอดรหัส json-string ที่ส่วนเซิร์ฟเวอร์และตั้งค่าส่วนหัวสำหรับดาวน์โหลด (ตัวอย่าง PHP):

$request = json_decode($_POST['jsonstring']), true);
header('Content-Type: application/csv');
header('Content-Disposition: attachment; filename=export.csv');
header('Pragma: no-cache');

ฉันชอบวิธีนี้ ในคำถามของ OP ดูเหมือนว่าผู้ร้องขอจะรู้ว่าคาดว่าจะมีการดาวน์โหลดไฟล์หรือข้อมูล JSON เพื่อให้เขาสามารถตัดสินใจได้ที่ปลายไคลเอนต์และโพสต์ไปยัง URL อื่นแทนที่จะตัดสินใจที่ปลายเซิร์ฟเวอร์
แซม

2

ฉันคิดว่าวิธีที่ดีที่สุดคือการใช้ชุดค่าผสมวิธีที่สองของคุณดูเหมือนจะเป็นโซลูชันที่หรูหราที่เบราว์เซอร์มีส่วนร่วม

ดังนั้นขึ้นอยู่กับวิธีการโทร (ไม่ว่าจะเป็นเบราว์เซอร์หรือการบริการผ่านเว็บ) คุณสามารถใช้ทั้งสองอย่างร่วมกันโดยส่ง URL ไปยังเบราว์เซอร์และส่งข้อมูลดิบไปยังไคลเอนต์บริการเว็บอื่น ๆ


วิธีที่สองของฉันก็ดูน่าดึงดูดสำหรับฉันเช่นกัน ขอบคุณสำหรับการยืนยันว่าเป็นวิธีแก้ปัญหาที่คุ้มค่า คุณแนะนำให้ฉันส่งค่าเพิ่มเติมในวัตถุ json ที่ระบุว่าคำขอนี้ทำจากเบราว์เซอร์เป็นการโทร ajax แทนที่จะเป็นการเรียกใช้บริการเว็บหรือไม่ มีหลายวิธีที่จะทำให้สิ่งนี้สำเร็จที่ฉันคิดได้ แต่เทคนิคอะไรที่คุณจะใช้?
Tauren

ไม่สามารถกำหนดได้โดยส่วนหัว http ผู้ใช้ - ตัวแทน หรือส่วนหัว http อื่น ๆ
naikus

1

ฉันตื่นมาสองวันแล้วตอนนี้พยายามหาวิธีดาวน์โหลดไฟล์โดยใช้ jquery ด้วย ajax call การสนับสนุนทั้งหมดที่ฉันได้รับไม่สามารถช่วยสถานการณ์ของฉันได้จนกว่าฉันจะลองทำสิ่งนี้

ด้านลูกค้า

function exportStaffCSV(t) {
   
    var postData = { checkOne: t };
    $.ajax({
        type: "POST",
        url: "/Admin/Staff/exportStaffAsCSV",
        data: postData,
        success: function (data) {
            SuccessMessage("file download will start in few second..");
            var url = '/Admin/Staff/DownloadCSV?data=' + data;
            window.location = url;
        },
       
        traditional: true,
        error: function (xhr, status, p3, p4) {
            var err = "Error " + " " + status + " " + p3 + " " + p4;
            if (xhr.responseText && xhr.responseText[0] == "{")
                err = JSON.parse(xhr.responseText).Message;
            ErrorMessage(err);
        }
    });

}

ฝั่งเซิร์ฟเวอร์

 [HttpPost]
    public string exportStaffAsCSV(IEnumerable<string> checkOne)
    {
        StringWriter sw = new StringWriter();
        try
        {
            var data = _db.staffInfoes.Where(t => checkOne.Contains(t.staffID)).ToList();
            sw.WriteLine("\"First Name\",\"Last Name\",\"Other Name\",\"Phone Number\",\"Email Address\",\"Contact Address\",\"Date of Joining\"");
            foreach (var item in data)
            {
                sw.WriteLine(string.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\"",
                    item.firstName,
                    item.lastName,
                    item.otherName,
                    item.phone,
                    item.email,
                    item.contact_Address,
                    item.doj
                    ));
            }
        }
        catch (Exception e)
        {

        }
        return sw.ToString();

    }

    //On ajax success request, it will be redirected to this method as a Get verb request with the returned date(string)
    public FileContentResult DownloadCSV(string data)
    {
        return File(new System.Text.UTF8Encoding().GetBytes(data), System.Net.Mime.MediaTypeNames.Application.Octet, filename);
        //this method will now return the file for download or open.
    }

โชคดี.


2
สิ่งนี้จะใช้งานได้สำหรับไฟล์ขนาดเล็ก แต่ถ้าคุณมีไฟล์ขนาดใหญ่คุณจะพบปัญหาของ URL ที่ยาวเกินไป ลองกับ 1,000 บันทึกมันจะทำลายและส่งออกเพียงส่วนหนึ่งของข้อมูล
Michael Merrell

1

พบมันที่ไหนนานมาแล้วและมันทำงานได้อย่างสมบูรณ์แบบ!

let payload = {
  key: "val",
  key2: "val2"
};

let url = "path/to/api.php";
let form = $('<form>', {'method': 'POST', 'action': url}).hide();
$.each(payload, (k, v) => form.append($('<input>', {'type': 'hidden', 'name': k, 'value': v})) );
$('body').append(form);
form.submit();
form.remove();

0

อีกวิธีหนึ่งแทนการบันทึกไฟล์บนเซิร์ฟเวอร์และดึงข้อมูลกลับมาคือการใช้. NET 4.0+ ObjectCache ด้วยการหมดอายุสั้น ๆ จนกระทั่งแอ็คชันที่สอง เหตุผลที่ฉันต้องการใช้ JQuery Ajax โทรออกก็เพราะมันเป็นแบบอะซิงโครนัส การสร้างไฟล์ PDF แบบไดนามิกของฉันใช้เวลาค่อนข้างนานและฉันจะแสดงกล่องโต้ตอบสปินเนอร์ที่ไม่ว่างในช่วงเวลานั้น วิธีการใช้ข้อมูลที่ส่งคืนใน "ความสำเร็จ:" เพื่อสร้าง Blob นั้นไม่สามารถทำงานได้อย่างน่าเชื่อถือ ขึ้นอยู่กับเนื้อหาของไฟล์ PDF ข้อมูลเสียหายในการตอบกลับได้อย่างง่ายดายหากข้อความไม่สมบูรณ์ซึ่งเป็นสิ่งที่ Ajax สามารถจัดการได้


0

สารละลาย

เอกสารแนบ - การจัดการเนื้อหาดูเหมือนจะใช้งานได้สำหรับฉัน:

self.set_header("Content-Type", "application/json")
self.set_header("Content-Disposition", 'attachment; filename=learned_data.json')

วิธีแก้ปัญหา

application / octet สตรีม

ฉันมีสิ่งที่คล้ายกันเกิดขึ้นกับฉันด้วย JSON สำหรับฉันในฝั่งเซิร์ฟเวอร์ฉันตั้งค่าส่วนหัวเป็น self.set_header ("Content-Type", " application / json ") อย่างไรก็ตามเมื่อฉันเปลี่ยนเป็น:

self.set_header("Content-Type", "application/octet-stream")

มันดาวน์โหลดโดยอัตโนมัติ

โปรดทราบด้วยว่าเพื่อให้ไฟล์ยังคงเป็นส่วนต่อท้าย. json คุณจะต้องดำเนินการกับส่วนหัวของชื่อไฟล์:

self.set_header("Content-Disposition", 'filename=learned_data.json')

-1

ด้วย HTML5 คุณสามารถสร้างจุดยึดและคลิกได้ ไม่จำเป็นต้องเพิ่มลงในเอกสารในฐานะเด็ก

const a = document.createElement('a');
a.download = '';
a.href = urlForPdfFile;
a.click();

ทุกอย่างเสร็จเรียบร้อย.

หากคุณต้องการมีชื่อพิเศษสำหรับการดาวน์โหลดเพียงส่งผ่านในแอdownloadททริบิวต์:

const a = document.createElement('a');
a.download = 'my-special-name.pdf';
a.href = urlForPdfFile;
a.click();

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