Вернуть 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);
        }
    }
}
Люк Смит
источник
13
Класс здесь взят прямо из проекта MVC Contrib. Не уверен, что это можно считать вашим собственным.
Парусное дзюдо,
3
Где бы вы поместили этот класс, если вы следуете соглашению ASP.NET MVC? Папка контроллеров? Возможно, там же, где вы бы разместили свои ViewModels?
p.campbell
7
@pcampbel, я предпочитаю создавать отдельные папки в корне моего проекта для всех типов классов: Результаты, Фильтры, Маршрутизация и т. д.
Энтони Сердюков
Использование XmlSerialiserаннотаций и членов может быть сложно поддерживать. С тех пор, как Люк опубликовал этот ответ (около четырех лет назад), Linq to XML зарекомендовал себя как более элегантная и мощная замена для большинства распространенных сценариев. Посмотрите мой ответ, чтобы увидеть, как это сделать.
Дрю Ноукс
135
return this.Content(xmlString, "text/xml");
Петр
источник
1
Вау, это мне действительно помогло, но тогда я только начинаю возиться с MVC.
Денис Валеев
Если вы работаете с Linq to XML, создание строковой формы документа расточительно - лучше работать с потоками .
Дрю Ноукс
2
@ Дрю Ноукс: Нет, это не так. Если вы пишете напрямую в поток HttpContext.Response.Output, вы получите YSOD на серверах на базе WinXP. Кажется, это исправлено в Vista +, что особенно проблематично, если вы разрабатываете в Windows 7 и развертываете в Windows XP (Server 2003?). Если вы это сделаете, вам нужно сначала записать в поток памяти, а затем скопировать поток памяти в выходной поток ...
Стефан Штайгер
6
@Quandary, хорошо, я повторю точку зрения: создание строк расточительно, если вы можете избежать исключений выделения / сбора / нехватки памяти с помощью потоков, если вы не работаете на вычислительных системах 11-летней давности, которые показывают ошибку.
Дрю Ноукс
1
application/xmlВместо этого вы можете использовать mimetype.
Фред
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);
}

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

Дрю Ноукс
источник
Как вы думаете, можно ли изменить это для использования в контроллере API?
Ray Ackley
@RayAckley, я не знаю, потому что я еще не пробовал новые возможности веб-API. Если вы узнаете, дайте нам знать.
Дрю Ноукс
Я думаю, что ошибся с вопросом о контроллере API (обычно я не занимаюсь MVC). Я просто реализовал его как обычный контроллер, и он отлично работал.
Ray Ackley
Отличная работа Дрю. Я использую ваш XmlActionResult для своих требований. Моя среда разработки: ASP.NET 4 MVC. Я вызываю метод своего контроллера (возвращает XmlActionResult - содержащий преобразованный XML для MS-Excel) из ajax. Функция Ajax Success имеет параметр данных, который содержит преобразованный xml. Как использовать этот параметр данных для запуска окна браузера и отображения диалогового окна «Сохранить как» или просто открытия Excel?
sheir
@sheir, если вы хотите, чтобы браузер запускал файл, вам не следует загружать его через AJAX. Просто перейдите прямо к своему методу действия. Тип MIME будет определять, как он обрабатывается браузером. Используя что-то вроде application/octet-streamпринудительного скачивания. Я не знаю, какой тип MIME запускает Excel, но вы сможете достаточно легко найти его в Интернете.
Дрю Ноукс,
26

Если вас интересует только возврат xml через запрос, и у вас есть «кусок» 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 (бритва)
  • Windows 7

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

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

Моя задача заключалась в нажатии кнопки пользовательского интерфейса, вызове метода на моем контроллере (с некоторыми параметрами), а затем в том, чтобы он возвращал XML MS-Excel через преобразование 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, версия); вернуть результат;

Основная модификация класса 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);
}

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

наследница
источник
1

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

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

Небольшой вариант ответа Дрю Ноукса , использующий метод 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)
    }
}
Нельсон Лопес Сентено
источник