XML von der Aktion eines Controllers als ActionResult zurückgeben?

139

Was ist der beste Weg, um XML von der Aktion eines Controllers in ASP.NET MVC zurückzugeben? Es gibt eine gute Möglichkeit, JSON zurückzugeben, jedoch nicht für XML. Muss ich das XML wirklich durch eine Ansicht routen oder sollte ich die nicht bewährte Methode für die Antwort ausführen.

Ken Randall
quelle

Antworten:

114

Verwenden Sie die XmlResult-Aktion von MVCContrib.

Als Referenz hier ist ihr Code:

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
quelle
12
Die Klasse hier stammt direkt aus dem MVC Contrib-Projekt. Ich bin mir nicht sicher, ob es das ist, was als Rolling Your Own qualifiziert ist.
Segeln Judo
3
Wo würden Sie diese Klasse ablegen, wenn Sie der ASP.NET MVC-Konvention folgen? Controller-Ordner? Der gleiche Ort, an dem Sie vielleicht Ihre ViewModels platzieren würden?
p.campbell
7
@pcampbel, ich bevorzuge es, separate Ordner in meinem Projektstamm für jede Art von Klassen zu erstellen: Ergebnisse, Filter, Routing usw.
Anthony Serdyukov
Die Verwendung von XmlSerialiserund Elementanmerkungen kann schwierig zu pflegen sein. Seit Luke diese Antwort veröffentlicht hat (vor ungefähr vier Jahren), hat sich Linq to XML als eleganter und leistungsfähigerer Ersatz für die meisten gängigen Szenarien erwiesen. In meiner Antwort finden Sie ein Beispiel dafür.
Drew Noakes
133
return this.Content(xmlString, "text/xml");
Petr
quelle
1
Wow, das hat mir wirklich geholfen, aber dann fange ich erst an, an dem MVC-Ding zu basteln.
Denis Valeev
Wenn Sie mit Linq to XML arbeiten, ist das Erstellen einer Zeichenfolgenform des Dokuments verschwenderisch - es ist besser, mit Streams zu arbeiten .
Drew Noakes
2
@Drew Noakes: Nein, ist es nicht. Wenn Sie direkt in den Stream HttpContext.Response.Output schreiben, erhalten Sie auf WinXP-basierten Servern einen YSOD. Es scheint unter Vista + behoben zu sein, was besonders problematisch ist, wenn Sie unter Windows 7 entwickeln und unter Windows XP (Server 2003?) Bereitstellen. Wenn Sie dies tun, müssen Sie zuerst in einen Speicherstrom schreiben und dann den Speicherstrom in den Ausgabestream kopieren ...
Stefan Steiger
6
@Quandary, ok, ich werde den Punkt noch einmal wiederholen: Das Erstellen von Zeichenfolgen ist verschwenderisch, wenn Sie durch die Verwendung von Streams Ausnahmen bei der Zuweisung / Sammlung / Speicherausfall vermeiden können, es sei denn, Sie arbeiten an 11 Jahre alten Computersystemen, die einen Fehler aufweisen.
Drew Noakes
1
Möglicherweise möchten Sie application/xmlstattdessen den Mimetyp verwenden.
Fred
32

Wenn Sie das XML mit dem hervorragenden Linq-to-XML-Framework erstellen, ist dieser Ansatz hilfreich.

Ich erstelle eine XDocumentin der Aktion Methode.

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

Diese wiederverwendbare, benutzerdefinierte ActionResultSerialisierung des XML für Sie.

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

Sie können einen MIME-Typ (z. B. application/rss+xml) angeben und festlegen, ob die Ausgabe bei Bedarf eingerückt werden soll. Beide Eigenschaften haben sinnvolle Standardeinstellungen.

Wenn Sie eine andere Codierung als UTF8 benötigen, können Sie auch dafür einfach eine Eigenschaft hinzufügen.

Drew Noakes
quelle
Denken Sie, dass es möglich ist, dies für die Verwendung in einem API-Controller zu ändern?
Ray Ackley
@ RayAckley, ich weiß es nicht, da ich das neue Web-API-Zeug noch nicht ausprobiert habe. Wenn Sie es herausfinden, lassen Sie es uns wissen.
Drew Noakes
Ich glaube, ich war mit der Frage nach dem API-Controller auf dem falschen Weg (normalerweise mache ich keine MVC-Sachen). Ich habe es gerade als regulären Controller implementiert und es hat großartig funktioniert.
Ray Ackley
Großartige Arbeit, Drew. Ich verwende eine Variante Ihres XmlActionResult für meine Anforderung. Meine Entwicklungsumgebung: ASP.NET 4 MVC Ich rufe die Methode meines Controllers (gibt XmlActionResult zurück - enthält eine transformierte XML für MS-Excel) von Ajax auf. Die Ajax Success-Funktion verfügt über einen Datenparameter, der die transformierte XML enthält. Wie kann ich mit diesem Datenparameter ein Browserfenster starten und entweder ein SaveAs-Dialogfeld anzeigen oder einfach Excel öffnen?
Sheir
@sheir, wenn der Browser die Datei starten soll, sollten Sie sie nicht über AJAX laden. Navigieren Sie einfach direkt zu Ihrer Aktionsmethode. Der MIME-Typ bestimmt, wie er vom Browser behandelt wird. Verwenden Sie so etwas application/octet-stream, um das Herunterladen zu erzwingen. Ich weiß nicht, welcher MIME-Typ Excel startet, aber Sie sollten ihn online leicht finden können.
Drew Noakes
26

Wenn Sie nur daran interessiert sind, XML über eine Anfrage zurückzugeben, und Sie Ihren XML-Block haben, können Sie dies einfach tun (als Aktion in Ihrem Controller):

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

Ich musste dies kürzlich für ein Sitecore-Projekt tun, das eine Methode verwendet, um ein XmlDocument aus einem Sitecore-Element und seinen untergeordneten Elementen zu erstellen und es vom Controller ActionResult als Datei zurückzugeben. Meine Lösung:

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

Schaffe es endlich, diese Arbeit zu bekommen und dachte, ich würde dokumentieren, wie hier in der Hoffnung, anderen den Schmerz zu ersparen.

Umgebung

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

Unterstützte Webbrowser

  • FireFox 23
  • IE 10
  • Chrome 29
  • Oper 16
  • Safari 5.1.7 (letzte für Windows?)

Meine Aufgabe bestand darin, auf eine UI-Schaltfläche zu klicken, eine Methode auf meinem Controller aufzurufen (mit einigen Parametern) und dann ein MS-Excel-XML über eine xslt-Transformation zurückzugeben. Das zurückgegebene MS-Excel-XML würde dann dazu führen, dass der Browser das Dialogfeld Öffnen / Speichern öffnet. Dies musste in allen Browsern (oben aufgeführt) funktionieren.

Zuerst habe ich mit Ajax versucht, einen dynamischen Anker mit dem Attribut "Download" für den Dateinamen zu erstellen, aber das funktionierte nur für etwa 3 der 5 Browser (FF, Chrome, Opera) und nicht für IE oder Safari. Und es gab Probleme beim Versuch, das Click-Ereignis des Ankers programmgesteuert auszulösen, um den tatsächlichen "Download" zu verursachen.

Am Ende habe ich einen "unsichtbaren" IFRAME verwendet, der für alle 5 Browser funktioniert hat!

Also hier ist, was ich mir ausgedacht habe: [Bitte beachten Sie, dass ich keineswegs ein HTML / Javascript-Guru bin und nur den relevanten Code eingefügt habe]

HTML (Ausschnitt relevanter Bits)

<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 hat ein benutzerdefiniertes ActionResult namens XmlActionResult erstellt, das ich für meinen Zweck geändert habe.

XML von der Aktion eines Controllers als ActionResult zurückgeben?

Meine Controller-Methode (gibt ActionResult zurück)

  • Übergibt den Schlüsselparameter an einen gespeicherten SQL Server-Prozess, der ein XML generiert
  • Dieses XML wird dann über xslt in eine MS-Excel-XML (XmlDocument) umgewandelt.
  • Erstellt eine Instanz des geänderten XmlActionResult und gibt sie zurück

    XmlActionResult-Ergebnis = neues XmlActionResult (excelXML, "application / vnd.ms-excel"); Zeichenfolge version = DateTime.Now.ToString ("dd_MMM_yyyy_hhmmsstt"); string fileMask = "LabelExport_ {0} .xml";
    result.DownloadFilename = string.Format (fileMask, version); Ergebnis zurückgeben;

Die Hauptänderung an der XmlActionResult-Klasse, die @Drew erstellt hat.

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

Das war es im Grunde. Hoffe es hilft anderen.

Sheir
quelle
1

Eine einfache Option, mit der Sie Streams und alles, was ist, verwenden können return File(stream, "text/xml");.

Casey
quelle
0

Hier ist eine einfache Möglichkeit:

        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");
user2670714
quelle
Warum werden dadurch zwei Speicherströme erstellt? Warum nicht einfach msdirekt übergeben, anstatt es auf ein neues zu kopieren? Beide Objekte haben die gleiche Lebensdauer.
Jpaugh
Haben ein ms.Position=0und Sie können die Original - Memorystream zurück. Dann können Siereturn new FileStreamResult(ms,"text/xml");
Carter Medlin
0

Eine kleine Variation der Antwort von Drew Noakes , die die Methode Save () von XDocument verwenden.

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
quelle