ส่งคืน XML จากการกระทำของตัวควบคุมในฐานะที่เป็น ActionResult?


139

วิธีที่ดีที่สุดในการคืน XML จากการกระทำของตัวควบคุมใน ASP.NET MVC คืออะไร? มีวิธีที่ดีในการส่งคืน JSON แต่ไม่ใช่สำหรับ XML ฉันจำเป็นต้องกำหนดเส้นทาง XML ผ่านมุมมองจริง ๆ หรือฉันควรทำวิธีการที่ไม่เหมาะสมที่สุดในการเผชิญเหตุตอบกลับหรือไม่

คำตอบ:


114

ใช้การกระทำ XmlResult ของMVCContrib

สำหรับการอ้างอิงนี่คือรหัสของพวกเขา:

public class XmlResult : ActionResult
{
    private object objectToSerialize;

    /// <summary>
    /// Initializes a new instance of the <see cref="XmlResult"/> class.
    /// </summary>
    /// <param name="objectToSerialize">The object to serialize to XML.</param>
    public XmlResult(object objectToSerialize)
    {
        this.objectToSerialize = objectToSerialize;
    }

    /// <summary>
    /// Gets the object to be serialized to XML.
    /// </summary>
    public object ObjectToSerialize
    {
        get { return this.objectToSerialize; }
    }

    /// <summary>
    /// Serialises the object that was passed into the constructor to XML and writes the corresponding XML to the result stream.
    /// </summary>
    /// <param name="context">The controller context for the current request.</param>
    public override void ExecuteResult(ControllerContext context)
    {
        if (this.objectToSerialize != null)
        {
            context.HttpContext.Response.Clear();
            var xs = new System.Xml.Serialization.XmlSerializer(this.objectToSerialize.GetType());
            context.HttpContext.Response.ContentType = "text/xml";
            xs.Serialize(context.HttpContext.Response.Output, this.objectToSerialize);
        }
    }
}

12
ชั้นเรียนที่นี่นำตรงจากโครงการ MVC Contrib ไม่แน่ใจว่าเป็นสิ่งที่มีคุณสมบัติเช่นเดียวกับการกลิ้งของคุณเอง
เรือใบยูโด

3
คุณจะให้คลาสนี้อยู่ที่ไหนถ้าคุณทำตามการประชุม ASP.NET MVC โฟลเดอร์ตัวควบคุม? ที่เดียวที่คุณใส่ ViewModels
p.campbell

7
@pcampbel ฉันชอบสร้างโฟลเดอร์แยกกันในรูทโปรเจคของฉันสำหรับคลาสทุกประเภท: ผลลัพธ์ตัวกรองการกำหนดเส้นทางและอื่น ๆ
Anthony Serdyukov

การใช้XmlSerialiserและการเพิ่มความคิดเห็นของสมาชิกอาจทำได้ยาก ตั้งแต่ลุคโพสต์คำตอบนี้ (ประมาณสี่ปีที่แล้ว) Linq เป็น XML ได้พิสูจน์ตัวเองว่าเป็นสิ่งทดแทนที่หรูหราและทรงพลังสำหรับสถานการณ์ที่พบบ่อยที่สุด ตรวจสอบคำตอบของฉันสำหรับตัวอย่างของวิธีการทำเช่นนี้
Drew Noakes

133
return this.Content(xmlString, "text/xml");

1
ว้าวสิ่งนี้ช่วยฉันได้จริงๆ แต่จากนั้นฉันก็เป็นเพียงผู้เริ่มใช้ทิงเกอร์ MVC
เดนิส Valeev

หากคุณกำลังทำงานกับ Linq เพื่อ XML, การสร้างรูปแบบสตริงของเอกสารคือสิ้นเปลือง - มันดีกว่าที่จะทำงานร่วมกับลำธาร
Drew Noakes

2
@ ดึง Noakes: ไม่มันไม่ได้ หากคุณเขียนโดยตรงไปที่สตรีม HttpContext.Response.Output คุณจะได้รับ YSOD บนเซิร์ฟเวอร์ที่ใช้ WinXP ดูเหมือนว่าจะได้รับการแก้ไขใน Vista + ซึ่งเป็นปัญหาโดยเฉพาะอย่างยิ่งถ้าคุณพัฒนาบน Windows 7 และปรับใช้กับ Windows XP (Server 2003?) ถ้าคุณทำคุณจะต้องเขียนไปยังสตรีมหน่วยความจำก่อนแล้วจึงคัดลอกสตรีมหน่วยความจำไปยังสตรีมเอาท์พุท ...
Stefan Steiger

6
@ ข้อสงสัยโอเคฉันจะพูดซ้ำจุด: การสร้างสตริงนั้นสิ้นเปลืองเมื่อคุณสามารถหลีกเลี่ยงการจัดสรร / การรวบรวม / การยกเว้นหน่วยความจำหมดโดยใช้สตรีมเว้นแต่ว่าคุณกำลังทำงานกับระบบคอมพิวเตอร์อายุ 11 ปีที่แสดงข้อผิดพลาด
Drew Noakes

1
คุณอาจต้องการใช้ชนิดapplication/xmlmime แทน
Fred

32

หากคุณกำลังสร้าง XML โดยใช้กรอบ Linq-to-XML ที่ยอดเยี่ยมแนวทางนี้จะเป็นประโยชน์

ฉันสร้างXDocumentในวิธีการดำเนินการ

public ActionResult MyXmlAction()
{
    // Create your own XDocument according to your requirements
    var xml = new XDocument(
        new XElement("root",
            new XAttribute("version", "2.0"),
            new XElement("child", "Hello World!")));

    return new XmlActionResult(xml);
}

ActionResultXML ที่กำหนดเองนี้สามารถใช้ซ้ำได้สำหรับคุณ

public sealed class XmlActionResult : ActionResult
{
    private readonly XDocument _document;

    public Formatting Formatting { get; set; }
    public string MimeType { get; set; }

    public XmlActionResult(XDocument document)
    {
        if (document == null)
            throw new ArgumentNullException("document");

        _document = document;

        // Default values
        MimeType = "text/xml";
        Formatting = Formatting.None;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        context.HttpContext.Response.Clear();
        context.HttpContext.Response.ContentType = MimeType;

        using (var writer = new XmlTextWriter(context.HttpContext.Response.OutputStream, Encoding.UTF8) { Formatting = Formatting })
            _document.WriteTo(writer);
    }
}

คุณสามารถระบุประเภท MIME (เช่นapplication/rss+xml) และระบุว่าควรจะเยื้องเอาต์พุตหรือไม่หากคุณต้องการ คุณสมบัติทั้งสองมีค่าเริ่มต้นที่สมเหตุสมผล

หากคุณต้องการการเข้ารหัสที่นอกเหนือจาก UTF8 คุณสามารถเพิ่มคุณสมบัติสำหรับสิ่งนั้นได้ง่าย


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

@ RayAckley ฉันไม่รู้เพราะฉันยังไม่ได้ลองใช้ Web API ใหม่ หากคุณรู้แจ้งให้เราทราบ
Drew Noakes

ฉันคิดว่าฉันผิดคำถามเกี่ยวกับตัวควบคุม API (ปกติฉันไม่ได้ทำสิ่ง MVC) ฉันเพิ่งใช้มันเป็นคอนโทรลเลอร์ปกติและใช้งานได้ดี
เรย์แอคคลี่ย์

ทำงานได้ดีเยี่ยม ฉันกำลังใช้ XmlActionResult สำหรับความต้องการของฉัน สภาพแวดล้อมการพัฒนาของฉัน: ASP.NET 4 MVC ฉันเรียกวิธีการควบคุมของฉัน (คืน XmlActionResult - มี xml ที่แปลงสภาพสำหรับ MS-Excel) จาก ajax ฟังก์ชัน Ajax Success มีพารามิเตอร์ข้อมูลที่มีการแปลง xml วิธีใช้พารามิเตอร์ข้อมูลนี้เพื่อเปิดหน้าต่างเบราว์เซอร์และแสดงไดอะล็อก SaveAs หรือเพียงแค่เปิด Excel
ทายาท

@sheir หากคุณต้องการให้เบราว์เซอร์เปิดไฟล์คุณไม่ควรโหลดผ่าน AJAX เพียงนำทางไปยังวิธีการกระทำของคุณโดยตรง ประเภท MIME จะเป็นตัวกำหนดวิธีจัดการเบราว์เซอร์ ใช้สิ่งที่ต้องการapplication/octet-streamบังคับให้ดาวน์โหลด ฉันไม่รู้ว่า MIME ชนิดใดเปิดตัว Excel แต่คุณควรจะหามันออนไลน์ได้ง่ายพอ
Drew Noakes

26

หากคุณสนใจที่จะคืนค่า xml ผ่านการร้องขอและคุณมี "chunk" xml ของคุณคุณสามารถทำได้ (เป็นการกระทำในคอนโทรลเลอร์ของคุณ):

public string Xml()
{
    Response.ContentType = "text/xml";
    return yourXmlChunk;
}


4

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

public virtual ActionResult ReturnXml()
{
    return File(Encoding.UTF8.GetBytes(GenerateXmlFeed().OuterXml), "text/xml");
}

2

ในที่สุดก็จัดการเพื่อให้ได้งานนี้และคิดว่าฉันจะบันทึกว่าที่นี่ด้วยความหวังที่จะช่วยคนอื่นให้เจ็บปวด

สิ่งแวดล้อม

  • VS2012
  • SQL Server 2008R2
  • .NET 4.5
  • ASP.NET MVC4 (มีดโกน)
  • วินโดว 7

เว็บเบราเซอร์ที่รองรับ

  • FireFox 23
  • IE 10
  • Chrome 29
  • Opera 16
  • Safari 5.1.7 (อันสุดท้ายสำหรับ Windows?)

งานของฉันอยู่ที่การคลิกปุ่ม ui เรียกวิธีการในคอนโทรลเลอร์ของฉัน (มี params บางส่วน) จากนั้นให้ส่งคืน MS-Excel XML ผ่านการแปลง xslt MS-Excel XML ที่ส่งคืนจะทำให้เบราว์เซอร์ป๊อปอัพโต้ตอบเปิด / บันทึก สิ่งนี้ต้องใช้กับเบราว์เซอร์ทั้งหมด (ตามที่แสดงไว้ด้านบน)

ตอนแรกฉันลองกับ Ajax และสร้าง Anchor แบบไดนามิกด้วยคุณสมบัติ "ดาวน์โหลด" สำหรับชื่อไฟล์ แต่ใช้งานได้กับเบราว์เซอร์เพียง 3 ใน 5 (FF, Chrome, Opera) และไม่ใช่ IE หรือ Safari และมีปัญหาในการพยายามไฟเหตุการณ์คลิกของจุดยึดโดยทางโปรแกรมเพื่อให้เกิด "ดาวน์โหลด" จริง

สิ่งที่ฉันทำคือใช้ IFRAME แบบ "ล่องหน" และใช้งานได้กับเบราว์เซอร์ทั้ง 5!

ดังนั้นนี่คือสิ่งที่ฉันมาด้วย: [โปรดทราบว่าฉันไม่เคยเป็นกูรู html / javascript และรวมรหัสที่เกี่ยวข้องเท่านั้น]

HTML (ตัวอย่างของบิตที่เกี่ยวข้อง)

<div id="docxOutput">
<iframe id="ifOffice" name="ifOffice" width="0" height="0"
    hidden="hidden" seamless='seamless' frameBorder="0" scrolling="no"></iframe></div>

JAVASCRIPT

//url to call in the controller to get MS-Excel xml
var _lnkToControllerExcel = '@Url.Action("ExportToExcel", "Home")';
$("#btExportToExcel").on("click", function (event) {
    event.preventDefault();

    $("#ProgressDialog").show();//like an ajax loader gif

    //grab the basket as xml                
    var keys = GetMyKeys();//returns delimited list of keys (for selected items from UI) 

    //potential problem - the querystring might be too long??
    //2K in IE8
    //4096 characters in ASP.Net
    //parameter key names must match signature of Controller method
    var qsParams = [
    'keys=' + keys,
    'locale=' + '@locale'               
    ].join('&');

    //The element with id="ifOffice"
    var officeFrame = $("#ifOffice")[0];

    //construct the url for the iframe
    var srcUrl = _lnkToControllerExcel + '?' + qsParams;

    try {
        if (officeFrame != null) {
            //Controller method can take up to 4 seconds to return
            officeFrame.setAttribute("src", srcUrl);
        }
        else {
            alert('ExportToExcel - failed to get reference to the office iframe!');
        }
    } catch (ex) {
        var errMsg = "ExportToExcel Button Click Handler Error: ";
        HandleException(ex, errMsg);
    }
    finally {
        //Need a small 3 second ( delay for the generated MS-Excel XML to come down from server)
        setTimeout(function () {
            //after the timeout then hide the loader graphic
            $("#ProgressDialog").hide();
        }, 3000);

        //clean up
        officeFrame = null;
        srcUrl = null;
        qsParams = null;
        keys = null;
    }
});

C # SERVER-SIDE (code snippet) @Drew สร้าง ActionResult แบบกำหนดเองชื่อ XmlActionResult ซึ่งฉันแก้ไขเพื่อวัตถุประสงค์ของฉัน

ส่งคืน XML จากการกระทำของตัวควบคุมในฐานะที่เป็น ActionResult?

วิธีการควบคุมของฉัน (ผลตอบแทน ActionResult)

  • ส่งผ่านพารามิเตอร์ keys ไปยัง SQL Server ที่จัดเก็บ proc ที่สร้าง XML
  • XML นั้นจะถูกแปลงผ่าน xslt ไปเป็น MS-Excel xml (XmlDocument)
  • สร้างตัวอย่างของ XmlActionResult ที่แก้ไขแล้วส่งคืน

    XmlActionResult result = new XmlActionResult (excelXML, "application / vnd.ms-excel"); รุ่นสตริง = DateTime.Now.ToString ("dd_MMM_yyyy_hhmmsstt"); string fileMask = "LabelExport_ {0} .xml";
    result.DownloadFilename = string.Format (fileMask, version); ผลตอบแทน;

การแก้ไขหลักกับคลาส XmlActionResult ที่ @Drew สร้างขึ้น

public override void ExecuteResult(ControllerContext context)
{
    string lastModDate = DateTime.Now.ToString("R");

    //Content-Disposition: attachment; filename="<file name.xml>" 
    // must set the Content-Disposition so that the web browser will pop the open/save dialog
    string disposition = "attachment; " +
                        "filename=\"" + this.DownloadFilename + "\"; ";

    context.HttpContext.Response.Clear();
    context.HttpContext.Response.ClearContent();
    context.HttpContext.Response.ClearHeaders();
    context.HttpContext.Response.Cookies.Clear();
    context.HttpContext.Response.Cache.SetCacheability(System.Web.HttpCacheability.NoCache);// Stop Caching in IE
    context.HttpContext.Response.Cache.SetNoStore();// Stop Caching in Firefox
    context.HttpContext.Response.Cache.SetMaxAge(TimeSpan.Zero);
    context.HttpContext.Response.CacheControl = "private";
    context.HttpContext.Response.Cache.SetLastModified(DateTime.Now.ToUniversalTime());
    context.HttpContext.Response.ContentType = this.MimeType;
    context.HttpContext.Response.Charset = System.Text.UTF8Encoding.UTF8.WebName;

    //context.HttpContext.Response.Headers.Add("name", "value");
    context.HttpContext.Response.Headers.Add("Last-Modified", lastModDate);
    context.HttpContext.Response.Headers.Add("Pragma", "no-cache"); // HTTP 1.0.
    context.HttpContext.Response.Headers.Add("Expires", "0"); // Proxies.

    context.HttpContext.Response.AppendHeader("Content-Disposition", disposition);

    using (var writer = new XmlTextWriter(context.HttpContext.Response.OutputStream, this.Encoding)
    { Formatting = this.Formatting })
        this.Document.WriteTo(writer);
}

นั่นเป็นพื้นมัน หวังว่ามันจะช่วยให้ผู้อื่น



0

นี่เป็นวิธีง่ายๆในการทำ:

        var xml = new XDocument(
            new XElement("root",
            new XAttribute("version", "2.0"),
            new XElement("child", "Hello World!")));
        MemoryStream ms = new MemoryStream();
        xml.Save(ms);
        return File(new MemoryStream(ms.ToArray()), "text/xml", "HelloWorld.xml");

ทำไมสิ่งนี้ถึงสร้างสตรีมหน่วยความจำสองแห่ง ทำไมไม่เพียงส่งmsโดยตรงแทนที่จะคัดลอกไปที่ใหม่ วัตถุทั้งสองจะมีอายุการใช้งานเท่ากัน
jpaugh

ทำms.Position=0และคุณสามารถคืนค่าหน่วยความจำดั้งเดิม จากนั้นคุณสามารถreturn new FileStreamResult(ms,"text/xml");
Carter Medlin

0

ชุดรูปแบบขนาดเล็กของคำตอบจาก Drew Noakesที่ใช้เมธอด Save () ของ XDocument

public sealed class XmlActionResult : ActionResult
{
    private readonly XDocument _document;
    public string MimeType { get; set; }

    public XmlActionResult(XDocument document)
    {
        if (document == null)
            throw new ArgumentNullException("document");

        _document = document;

        // Default values
        MimeType = "text/xml";
    }

    public override void ExecuteResult(ControllerContext context)
    {
        context.HttpContext.Response.Clear();
        context.HttpContext.Response.ContentType = MimeType;
        _document.Save(context.HttpContext.Response.OutputStream)
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.