Restituire XML dall'azione di un controller come ActionResult?


139

Qual è il modo migliore per restituire XML dall'azione di un controller in ASP.NET MVC? C'è un buon modo per restituire JSON, ma non per XML. Devo davvero instradare l'XML attraverso una vista o dovrei fare il modo non best practice di Response. Scriverlo?

Risposte:


114

Usa MvcContrib s' XmlResult azione.

Per riferimento qui è il loro codice:

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
La classe qui è presa direttamente dal progetto MVC Contrib. Non sono sicuro se questo è ciò che si qualifica come il proprio.
Vela Judo

3
Dove inseriresti questa classe, se stai seguendo la convenzione ASP.NET MVC? Cartella dei controller? Nello stesso posto dove avresti messo i tuoi ViewModels, forse?
p

7
@pcampbel, preferisco creare cartelle separate nella radice del mio progetto per ogni tipo di classe: risultati, filtri, routing, ecc.
Anthony Serdyukov,

L'uso XmlSerialisere le annotazioni dei membri possono essere difficili da mantenere. Da quando Luke ha pubblicato questa risposta (circa quattro anni fa), Linq a XML si è dimostrato un sostituto più elegante e potente per gli scenari più comuni. Controlla la mia risposta per un esempio di come farlo.
Drew Noakes,

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

1
Caspita, questo mi ha davvero aiutato, ma poi sto solo iniziando a armeggiare con la cosa MVC.
Denis Valeev,

Se lavori con Linq su XML, la creazione di una forma stringa del documento è dispendiosa: è meglio lavorare con i flussi .
Drew Noakes,

2
@Drew Noakes: No non lo è. Se si scrive direttamente sul flusso HttpContext.Response.Output, si otterrà un YSOD su server basati su WinXP. Sembra essere risolto su Vista +, il che è particolarmente problematico se si sviluppa su Windows 7 e si distribuisce su Windows XP (Server 2003?). Se lo fai, devi prima scrivere su un flusso di memoria, quindi copiare il flusso di memoria sul flusso di output ...
Stefan Steiger

6
@Quandary, ok ribadirò il punto: la creazione di stringhe è dispendiosa quando si potrebbero evitare eccezioni di allocazione / raccolta / memoria insufficiente utilizzando i flussi, a meno che non si stia lavorando su sistemi informatici di 11 anni che presentano un errore.
Drew Noakes,

1
In application/xmlalternativa, potresti voler utilizzare il mimetype.
Fred,

32

Se stai costruendo l'XML usando l'eccellente framework Linq-to-XML, questo approccio sarà utile.

Creo un XDocumentmetodo di azione.

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

Questo riutilizzabile personalizzato ActionResultserializza l'XML per te.

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

È possibile specificare un tipo MIME (come application/rss+xml) e se l'output deve essere rientrato se necessario. Entrambe le proprietà hanno impostazioni predefinite ragionevoli.

Se hai bisogno di una codifica diversa da UTF8, è semplice aggiungere una proprietà anche per quello.


Pensi che sia possibile modificarlo per l'uso in un controller API?
Ray Ackley,

@RayAckley, non lo so perché non ho ancora provato le nuove cose sull'API Web. Se lo scopri, faccelo sapere.
Drew Noakes,

Penso di essere stato sulla strada sbagliata con la domanda del controller API (normalmente non faccio cose MVC). L'ho appena implementato come un normale controller e ha funzionato alla grande.
Ray Ackley,

Ottimo lavoro Drew. Sto usando un sapore del tuo XmlActionResult per le mie esigenze. Il mio ambiente di sviluppo: ASP.NET 4 MVC chiamo il metodo del mio controller (restituisce XmlActionResult - contenente un XML trasformato per MS-Excel) da Ajax. La funzione Ajax Success ha un parametro di dati che contiene l'xml trasformato. Come utilizzare questo parametro di dati per avviare una finestra del browser e visualizzare una finestra di dialogo Salva come o semplicemente aprire Excel?
Sheir

@sheir, se vuoi che il browser avvii il file, non dovresti caricarlo tramite AJAX. Basta passare direttamente al metodo di azione. Il tipo MIME determinerà come viene gestito dal browser. Usando qualcosa di simile application/octet-streamper costringerlo a scaricare. Non so quale tipo MIME avvii Excel, ma dovresti riuscire a trovarlo online abbastanza facilmente.
Drew Noakes,

26

Se sei interessato solo a restituire xml attraverso una richiesta e hai il tuo "chunk" xml, puoi semplicemente fare (come azione nel tuo controller):

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


4

Ho dovuto farlo di recente per un progetto Sitecore che utilizza un metodo per creare un documento Xml da un elemento Sitecore e dai suoi figli e lo restituisce dal controller ActionResult come file. La mia soluzione:

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

2

Finalmente riesco a ottenere questo lavoro e ho pensato di documentare come qui nella speranza di salvare il dolore agli altri.

Ambiente

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

Browser Web supportati

  • FireFox 23
  • IE 10
  • Chrome 29
  • Opera 16
  • Safari 5.1.7 (l'ultimo per Windows?)

La mia attività era su un clic del pulsante dell'interfaccia utente, chiamare un metodo sul mio controller (con alcuni parametri) e quindi restituire un XML MS-Excel tramite una trasformazione xslt. L'XML di MS-Excel restituito causerebbe quindi il popup della finestra di dialogo Apri / Salva. Questo ha dovuto funzionare in tutti i browser (elencati sopra).

All'inizio ho provato con Ajax e per creare un Anchor dinamico con l'attributo "download" per il nome file, ma ha funzionato solo per circa 3 dei 5 browser (FF, Chrome, Opera) e non per IE o Safari. E ci sono stati problemi con il tentativo di attivare a livello di codice l'evento Click dell'ancora per causare il vero "download".

Quello che ho finito è stato usare un IFRAME "invisibile" e ha funzionato per tutti e 5 i browser!

Quindi, ecco cosa mi è venuto in mente: [tieni presente che non sono affatto un guru html / javascript e ho incluso solo il codice pertinente]

HTML (frammento di bit pertinenti)

<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 (snippet di codice) @Drew ha creato un ActionResult personalizzato chiamato XmlActionResult che ho modificato per il mio scopo.

Restituire XML dall'azione di un controller come ActionResult?

Metodo My Controller (restituisce ActionResult)

  • passa il parametro keys a un proc memorizzato di SQL Server che genera un XML
  • tale XML viene quindi trasformato tramite xslt in un xml MS-Excel (XmlDocument)
  • crea l'istanza di XmlActionResult modificata e la restituisce

    Risultato XmlActionResult = nuovo XmlActionResult (excelXML, "application / vnd.ms-excel"); string version = DateTime.Now.ToString ("dd_MMM_yyyy_hhmmsstt"); string fileMask = "LabelExport_ {0} .xml";
    result.DownloadFilename = string.Format (fileMask, versione); risultato di ritorno;

La modifica principale alla classe XmlActionResult creata da @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);
}

Era praticamente tutto. Spero che aiuti gli altri.


1

Una semplice opzione che ti permetterà di usare i flussi e tutto il resto return File(stream, "text/xml");.


0

Ecco un modo semplice per farlo:

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

Perché questo crea due flussi di memoria? Perché non passare msdirettamente, invece di copiarlo in uno nuovo? Entrambi gli oggetti avranno la stessa durata.
jpaugh

Fai un ms.Position=0e puoi restituire il memorystream originale. Quindi puoireturn new FileStreamResult(ms,"text/xml");
Carter Medlin il

0

Una piccola variazione della risposta di Drew Noakes che utilizza il metodo Save () di 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)
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.