Kembalikan XML dari tindakan pengontrol sebagai ActionResult?

139

Apa cara terbaik untuk mengembalikan XML dari aksi pengontrol di ASP.NET MVC? Ada cara yang bagus untuk mengembalikan JSON, tetapi tidak untuk XML. Apakah saya benar-benar perlu mengarahkan XML melalui Tampilan, atau haruskah saya melakukan cara respons yang tidak terbaik? Menulisnya?

Ken Randall
sumber

Jawaban:

114

Gunakan MVCContrib 's XmlResult Aksi.

Untuk referensi di sini adalah kode mereka:

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);
        }
    }
}
Luke Smith
sumber
12
Kelas di sini diambil langsung dari proyek MVC Contrib. Tidak yakin apakah itu yang memenuhi syarat untuk bergulir sendiri.
Berlayar Judo
3
Di mana Anda akan meletakkan kelas ini, jika Anda mengikuti konvensi ASP.NET MVC? Folder pengontrol? Tempat yang sama dengan yang Anda taruh ViewModels Anda, mungkin?
p.campbell
7
@ pcampbel, saya lebih suka membuat folder terpisah di root proyek saya untuk setiap jenis kelas: Hasil, Filter, Perutean, dll.
Anthony Serdyukov
Penggunaan XmlSerialiserdan anotasi anggota mungkin sulit dipertahankan. Sejak Luke memposting jawaban ini (sekitar empat tahun lalu), Linq to XML telah membuktikan dirinya sebagai pengganti yang lebih elegan dan kuat untuk skenario paling umum. Lihatlah jawaban saya untuk contoh bagaimana melakukan ini.
Drew Noakes
133
return this.Content(xmlString, "text/xml");
Petr
sumber
1
Wow, ini benar-benar membantu saya, tetapi kemudian saya hanya mulai bermain-main dengan MVC thingy.
Denis Valeev
Jika Anda bekerja dengan Linq ke XML, membuat bentuk string dari dokumen itu boros - lebih baik bekerja dengan stream .
Drew Noakes
2
@Drew Noakes: Tidak, tidak. Jika Anda menulis langsung ke stream HttpContext.Response.Output, Anda akan mendapatkan YSOD di server berbasis WinXP. Tampaknya diperbaiki pada Vista +, yang sangat bermasalah jika Anda mengembangkan pada Windows 7 dan menyebarkan ke Windows XP (Server 2003?). Jika ya, Anda harus menulis ke aliran memori terlebih dahulu, lalu salin aliran memori ke aliran output ...
Stefan Steiger
6
@Quandary, ok saya akan nyatakan kembali poinnya: membuat string adalah pemborosan ketika Anda bisa menghindari alokasi / pengumpulan / kehabisan memori-pengecualian dengan menggunakan stream, kecuali jika Anda sedang bekerja pada sistem komputasi berusia 11 tahun yang menunjukkan kesalahan.
Drew Noakes
1
Anda mungkin ingin menggunakan application/xmlmimetype sebagai gantinya.
Fred
32

Jika Anda sedang membangun XML menggunakan kerangka kerja Linq-to-XML yang sangat baik, maka pendekatan ini akan sangat membantu.

Saya membuat XDocumentmetode aksi.

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

Ini dapat digunakan kembali, kustom membuat ActionResultserialisasi XML untuk Anda.

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

Anda dapat menentukan tipe MIME (seperti application/rss+xml) dan apakah output harus diindentasi jika perlu. Kedua properti memiliki default yang masuk akal.

Jika Anda memerlukan penyandian selain UTF8, maka mudah untuk menambahkan properti untuk itu juga.

Drew Noakes
sumber
Apakah Anda pikir mungkin untuk memodifikasi ini untuk digunakan dalam pengontrol API?
Ray Ackley
@RayAckley, saya tidak tahu karena saya belum mencoba hal-hal Web API baru. Jika Anda mengetahuinya, beri tahu kami.
Drew Noakes
Saya pikir saya berada di jalur yang salah dengan pertanyaan pengontrol API (saya biasanya tidak melakukan hal-hal MVC). Saya baru saja mengimplementasikannya sebagai pengontrol biasa dan bekerja dengan baik.
Ray Ackley
Kerja bagus Drew. Saya menggunakan rasa XmlActionResult Anda untuk kebutuhan saya. Lingkungan dev saya: ASP.NET 4 MVC Saya memanggil metode pengontrol saya (mengembalikan XmlActionResult - berisi xml yang diubah untuk MS-Excel) dari ajax. Fungsi Ajax Success memiliki parameter data yang berisi xml yang diubah. Bagaimana cara menggunakan parameter data ini untuk meluncurkan jendela browser dan menampilkan dialog SaveAs atau hanya membuka Excel?
sheir
@sheir, jika Anda ingin browser meluncurkan file maka Anda tidak harus memuatnya melalui AJAX. Navigasi langsung ke metode tindakan Anda. Jenis MIME akan menentukan cara penanganannya oleh browser. Menggunakan sesuatu seperti application/octet-streammemaksanya untuk mengunduh. Saya tidak tahu tipe MIME apa yang meluncurkan Excel, tetapi Anda harus dapat menemukannya secara online dengan cukup mudah.
Drew Noakes
26

Jika Anda hanya tertarik untuk mengembalikan xml melalui permintaan, dan Anda memiliki xml "chunk", Anda bisa melakukannya (sebagai tindakan di controller Anda):

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

Saya harus melakukan ini baru-baru ini untuk proyek Sitecore yang menggunakan metode untuk membuat XmlDocument dari Item Sitecore dan anak-anaknya dan mengembalikannya dari controller ActionResult sebagai File. Solusi saya:

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

Akhirnya berhasil mendapatkan pekerjaan ini dan berpikir saya akan mendokumentasikan bagaimana di sini dengan harapan menyelamatkan orang lain dari rasa sakit.

Lingkungan Hidup

  • VS2012
  • SQL Server 2008R2
  • .NET 4.5
  • ASP.NET MVC4 (Razor)
  • Windows 7

Browser Web yang didukung

  • FireFox 23
  • IE 10
  • Chrome 29
  • Opera 16
  • Safari 5.1.7 (yang terakhir untuk Windows?)

Tugas saya adalah klik tombol ui, panggil metode pada Kontroler saya (dengan beberapa params) dan kemudian kembalikan XML MS-Excel melalui transformasi xslt. MS-Excel XML yang dikembalikan kemudian akan menyebabkan browser popup dialog Open / Save. Ini harus berfungsi di semua browser (tercantum di atas).

Pada awalnya saya mencoba dengan Ajax dan membuat Anchor dinamis dengan atribut "unduh" untuk nama file, tetapi itu hanya bekerja untuk sekitar 3 dari 5 browser (FF, Chrome, Opera) dan bukan untuk IE atau Safari. Dan ada masalah dengan mencoba memrogram acara Click jangkar secara terprogram untuk menyebabkan "unduhan" yang sebenarnya.

Apa yang akhirnya saya lakukan adalah menggunakan IFRAME "tidak terlihat" dan bekerja untuk semua 5 browser!

Jadi inilah yang saya kemukakan: [harap dicatat bahwa saya tidak berarti guru html / javascript dan hanya menyertakan kode yang relevan]

HTML (potongan bit yang relevan)

<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 (potongan kode) @Drew membuat ActionResult kustom yang disebut XmlActionResult yang saya modifikasi untuk tujuan saya.

Kembalikan XML dari tindakan pengontrol sebagai ActionResult?

Metode Pengontrol Saya (mengembalikan ActionResult)

  • melewati parameter kunci ke proc SQL Server tersimpan yang menghasilkan XML
  • XML itu kemudian ditransformasikan melalui xslt menjadi MS-Excel xml (XmlDocument)
  • menciptakan instance XmlActionResult yang dimodifikasi dan mengembalikannya

    Hasil XmlActionResult = XmlActionResult baru (excelXML, "application / vnd.ms-excel"); versi string = DateTime.Now.ToString ("dd_MMM_yyyy_hhmmsstt"); string fileMask = "LabelExport_ {0} .xml";
    result.DownloadFilename = string.Format (fileMask, versi); hasil pengembalian;

Modifikasi utama ke kelas XmlActionResult yang dibuat @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);
}

Itu pada dasarnya itu. Semoga ini bisa membantu orang lain.

pewaris
sumber
1

Opsi sederhana yang memungkinkan Anda menggunakan stream dan sebagainya return File(stream, "text/xml");.

Casey
sumber
0

Inilah cara sederhana untuk melakukannya:

        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");
pengguna2670714
sumber
Mengapa ini membangun dua aliran memori? Mengapa tidak mslangsung saja lulus , alih-alih menyalinnya ke yang baru? Kedua benda akan memiliki umur yang sama.
jpaugh
Lakukan ms.Position=0dan Anda dapat mengembalikan MemoryStream asli. Maka Anda bisareturn new FileStreamResult(ms,"text/xml");
Carter Medlin
0

Variasi kecil jawaban dari Drew Noakes yang menggunakan metode Save () dari 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)
    }
}
Nelson Lopez Centeno
sumber