Вернуть XML из действия контроллера в виде ActionResult?

Как лучше всего вернуть XML из действия контроллера в ASP.NET MVC? Есть хороший способ вернуть JSON, но не для XML. Действительно ли мне нужно маршрутизировать XML через представление, или я должен использовать не самый лучший способ ответа.


person Ken Randall    schedule 25.09.2008    source источник


Ответы (11)


Используйте действие 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);
        }
    }
}
person Luke Smith    schedule 25.09.2008
comment
Класс здесь взят прямо из проекта MVC Contrib. Не уверен, что это можно считать вашим собственным. - person Sailing Judo; 12.02.2009
comment
Где бы вы поместили этот класс, если вы следуете соглашению ASP.NET MVC? Папка контроллеров? Возможно, там же, где вы бы разместили свои ViewModels? - person p.campbell; 03.09.2009
comment
@pcampbel, я предпочитаю создавать отдельные папки в корне моего проекта для всех типов классов: результатов, фильтров, маршрутизации и т. д. - person Anthony Serdyukov; 06.04.2010
comment
Использование аннотаций XmlSerialiser и членов может быть трудным в обслуживании. С тех пор, как Люк опубликовал этот ответ (около четырех лет назад), Linq to XML зарекомендовал себя как более элегантная и мощная замена для большинства распространенных сценариев. Посмотрите в моем ответе, как это сделать. - person Drew Noakes; 04.10.2012

Если вы создаете 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);
}

Этот многоразовый пользовательский ActionResult сериализует XML за вас.

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, то для нее тоже просто добавить свойство.

person Drew Noakes    schedule 03.10.2012
comment
Как вы думаете, можно ли изменить это для использования в контроллере API? - person Ray Ackley; 01.11.2012
comment
@RayAckley, я не знаю, потому что я еще не пробовал новые возможности веб-API. Если вы узнаете, дайте нам знать. - person Drew Noakes; 03.11.2012
comment
Я думаю, что ошибся с вопросом о контроллере API (обычно я не занимаюсь MVC). Я просто реализовал его как обычный контроллер, и он отлично работал. - person Ray Ackley; 23.11.2012
comment
Отличная работа Дрю. Я использую ваш XmlActionResult для своих требований. Моя среда разработки: ASP.NET 4 MVC Я вызываю свой метод контроллера (возвращает XmlActionResult - содержащий преобразованный XML для MS-Excel) из ajax. Функция Ajax Success имеет параметр данных, содержащий преобразованный xml. Как использовать этот параметр данных для запуска окна браузера и отображения диалогового окна «Сохранить как» или просто открытия Excel? - person sheir; 19.09.2013
comment
@sheir, если вы хотите, чтобы браузер запускал файл, вам не следует загружать его через AJAX. Просто перейдите прямо к своему методу действия. Тип MIME будет определять, как он обрабатывается браузером. Использование чего-то вроде application/octet-stream для принудительной загрузки. Я не знаю, какой тип MIME запускает Excel, но вы легко сможете найти его в Интернете. - person Drew Noakes; 19.09.2013
comment
@DrewNoakes, только что опубликовал свое решение в ответе ниже. Еще раз спасибо за класс XmlActionResult. - person sheir; 26.09.2013
comment
Текущий код содержит метку порядка байтов (BOM) в начале файла. Используйте new UTF8Encoding(false), чтобы удалить его. - person ivarne; 06.09.2019
comment
@ivarne, вы также можете кэшировать экземпляр, чтобы уменьшить количество запросов. - person Drew Noakes; 08.09.2019
comment
Да, но если вы используете Linq-to-xml, удаление единственного выделения, вероятно, будет несущественным - person ivarne; 09.09.2019

Если вас интересует только возврат xml через запрос, и у вас есть «кусок» xml, вы можете просто сделать (как действие в вашем контроллере):

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

В MVC Contrib есть XmlResult (и многое другое). Взгляните на http://www.codeplex.com/MVCContrib

person Mahdi Taghizadeh    schedule 29.10.2008

Недавно мне пришлось сделать это для проекта Sitecore, который использует метод для создания XmlDocument из элемента Sitecore и его дочерних элементов и возвращает его из контроллера ActionResult в виде файла. Мое решение:

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

Наконец-то мне удалось получить эту работу, и я подумал, что запишу, как здесь, в надежде спасти других от боли.

Окружающая обстановка

  • VS2012
  • SQL Server 2008R2
  • .NET 4.5
  • ASP.NET MVC4 (бритва)
  • Windows 7

Поддерживаемые веб-браузеры

  • FireFox 23
  • IE 10
  • Хром 29
  • Опера 16
  • Safari 5.1.7 (последняя версия для Windows?)

Моя задача заключалась в нажатии кнопки пользовательского интерфейса, вызове метода на моем контроллере (с некоторыми параметрами), а затем возвращении MS-Excel XML через преобразование xslt. После этого возвращенный MS-Excel XML вызовет в браузере всплывающее диалоговое окно «Открыть / сохранить». Это должно было работать во всех браузерах (перечисленных выше).

Сначала я попытался использовать Ajax и создать динамический якорь с атрибутом «загрузить» для имени файла, но это сработало только для 3 из 5 браузеров (FF, Chrome, Opera), а не для IE или Safari. И были проблемы с попыткой программно запустить событие Click привязки, чтобы вызвать фактическую «загрузку».

В итоге я использовал «невидимый» 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 (фрагмент кода) @Drew создал настраиваемый ActionResult под названием XmlActionResult, который я модифицировал для своих целей.

Вернуть XML из действия контроллера в виде ActionResult?

Мой метод контроллера (возвращает ActionResult)

  • передает параметр ключей в хранимую процедуру SQL Server, которая генерирует XML
  • этот XML затем преобразуется через xslt в XML-файл MS-Excel (XmlDocument)
  • создает экземпляр измененного XmlActionResult и возвращает его

    Результат XmlActionResult = новый результат XmlActionResult (excelXML, «application / vnd.ms-excel»); строка version = DateTime.Now.ToString ("dd_MMM_yyyy_hhmmsstt"); строка 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);
}

Вот и все. Надеюсь, это поможет другим.

person sheir    schedule 26.09.2013

Простой вариант, который позволит вам использовать потоки и все такое return File(stream, "text/xml");.

person Casey    schedule 10.02.2014

Вот простой способ сделать это:

        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");
person user2670714    schedule 25.02.2014
comment
Почему это создает два потока памяти? Почему бы просто не передать ms напрямую, а не копировать его в новый? Оба объекта будут иметь одинаковое время жизни. - person jpaugh; 04.10.2018
comment
Сделайте ms.Position=0, и вы сможете вернуть исходный поток памяти. Тогда вы можете return new FileStreamResult(ms,"text/xml"); - person Carter Medlin; 28.01.2019

Небольшая вариация ответа Дрю Ноукса, использующего метод 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)
    }
}
person Nelson Lopez Centeno    schedule 09.08.2016

используйте один из этих методов

    public ContentResult GetXml()
    {
        string xmlString  = "your xml data";
        return Content(xmlString, "text/xml");
    }

or

    public string GetXml()
    {
        string xmlString = "your xml data";
        Response.ContentType = "text/xml";
        return xmlString;
    }
person Hamid Jolany    schedule 05.12.2020

person    schedule
comment
Вау, это мне действительно помогло, но тогда я только начинаю возиться с MVC. - person Denis Valeev; 27.05.2011
comment
Если вы работаете с Linq to XML, создание строковой формы документа расточительно - лучше работать с потоками. - person Drew Noakes; 04.10.2012
comment
@ Дрю Ноукс: Нет, это не так. Если вы пишете напрямую в поток HttpContext.Response.Output, вы получите YSOD на серверах на базе WinXP. Кажется, это исправлено в Vista +, что особенно проблематично, если вы разрабатываете в Windows 7 и развертываете в Windows XP (Server 2003?). Если вы это сделаете, вам нужно сначала записать в поток памяти, а затем скопировать поток памяти в выходной поток ... - person Stefan Steiger; 17.04.2013
comment
@Quandary, хорошо, я повторю мысль: создание строк бесполезно, если вы можете избежать выделения / сбора / нехватки памяти с помощью потоков, если вы не работаете над вычислениями 11-летней давности системы, которые показывают ошибку. - person Drew Noakes; 17.04.2013
comment
Вместо этого вы можете использовать application/xml mimetype. - person Fred; 02.09.2014
comment
Можно ли с помощью этого установить кодировку UTF-8? Я попытался вернуть this.Content (xmlString, text / xml, System.Text.Encoding.UTF8), но объявление XML всегда UTF-16 - person Andy B; 13.03.2019