Повернути XML з дії контролера у вигляді ActionResult?


139

Який найкращий спосіб повернути XML з дії контролера в ASP.NET MVC? Є хороший спосіб повернути JSON, але не для XML. Чи дійсно мені потрібно направляти XML через Перегляд, або я повинен робити не найкращий спосіб реагування? Написати його?

Відповіді:


114

Використовуйте MVCContrib 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);
        }
    }
}

12
Клас тут взято прямо з проекту MVC Contrib. Не впевнений, чи є те, що визначається як прокатка вашого власного.
Вітрильний спорт по дзюдо

3
Куди б ви поставили цей клас, якщо ви дотримуєтесь конвенції ASP.NET MVC? Папка контролерів? Можливо, там же, де ви помістили б свої ViewModels?
p.campbell

7
@pcampbel, я вважаю за краще створювати окремі папки в корені свого проекту для всіх типів занять: результати, фільтри, маршрутизація тощо
Ентоні Сердюков

Використання XmlSerialiserта примітки членів може бути важким для підтримки. Відтоді, як Лука опублікував цю відповідь (близько чотирьох років тому), Linq до XML зарекомендував себе як більш елегантна і потужна заміна для найбільш поширених сценаріїв. Перегляньте мою відповідь на прикладі того, як це зробити.
Дрю Ноакс

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

1
Нічого собі, це мені справді допомогло, але тоді я тільки починаю повозитися з MVC речі.
Денис Валєєв

Якщо ви працюєте з Linq до XML, створення рядкової форми документа є марною - краще працювати з потоками .
Дрю Ноакс

2
@Drew Noakes: Ні, це не так. Якщо ви пишете безпосередньо в потік HttpContext.Response.Output, ви отримаєте YSOD на серверах, заснованих на WinXP. Здається, це виправлено на Vista +, що особливо проблематично, якщо ви розробляєте в Windows 7 і розгортаєте до Windows XP (Server 2003?). Якщо це зробити, вам потрібно спочатку записатись у потік пам'яті, а потім скопіювати потік пам'яті у вихідний потік ...
Стефан Штайгер

6
@ Quandary, добре, я повторю точку: створення рядків є марним, коли ви можете уникнути виділення / збирання / винятків пам'яті, використовуючи потоки, якщо ви не працюєте над 11-річними обчислювальними системами, які виявляють помилку.
Дрю Ноакс

1
Ви можете application/xmlзамість цього використати міметик.
Фред

32

Якщо ви будуєте XML, використовуючи чудову рамку Linq-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?
Рей Еклі

@RayAckley, я не знаю, оскільки я ще не пробував нові речі веб-API. Якщо ви дізнаєтесь, повідомте нам про це.
Дрю Ноакс

Я думаю, що я пішов неправильно з питанням про контролер API (я зазвичай не роблю MVC). Я просто реалізував це як звичайний контролер, і він чудово працював.
Рей Еклі

Чудова робота Дрю. Я використовую аромат вашого XmlActionResult для моєї вимоги. Моє середовище розробників: ASP.NET 4 MVC Я викликаю метод свого контролера (повертає XmlActionResult - містить перетворений xml для MS-Excel) від ajax. Функція Ajax Success має параметр даних, який містить перетворений xml. Як використовувати цей параметр даних для запуску вікна браузера та відображення діалогового вікна SaveAs або просто відкриття Excel?
шейр

@sheir, якщо ви хочете, щоб браузер запустив файл, тоді вам не слід завантажувати його через AJAX. Просто перейдіть безпосередньо до способу дії. Тип MIME визначатиме спосіб обробки браузером. Використовуючи щось на кшталт application/octet-streamзмусити його завантажити. Я не знаю, який тип MIME запускає Excel, але ви зможете знайти його в Інтернеті досить легко.
Дрю Ноакс

26

Якщо вам цікаво лише повернути xml через запит, і у вас є "xunk" 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
  • Chrome 29
  • Опера 16
  • Safari 5.1.7 (останній для Windows?)

Моє завдання полягало в натисканні кнопки ui, виклику методу на моєму контролері (з деякими параметрами), а потім повернути йому MS-Excel XML через перетворення xslt. Після цього повернутий MSML-Excel XML призведе до появи браузера у діалоговому вікні "Відкрити / Зберегти". Це мало працювати у всіх браузерах (перелічених вище).

Спочатку я намагався з Ajax і створити динамічний якір з атрибутом "скачати" для імені файлу, але це працювало лише для приблизно 3 з 5 браузерів (FF, Chrome, Opera), а не для IE або Safari. І виникли проблеми зі спробою програмного запуску події Click якоря, щоб викликати фактичне "завантаження".

Що я в кінцевому підсумку робив - використовував "невидимий" IFRAME, і він працював у всіх 5 браузерах!

Тож ось що я придумав: [будь ласка, зверніть увагу, що я аж ніяк не html / javascript guru і включив лише відповідний код]

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 в MS-Excel xml (XmlDocument)
  • створює екземпляр зміненого XmlActionResult і повертає його

    XmlActionResult результат = новий XmlActionResult (excelXML, "application / vnd.ms-excel"); версія рядка = DateTime.Now.ToString ("dd_MMM_yyyy_hhmmsstt"); string 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");

Чому це будує два потоки пам'яті? Чому б просто не передати msбезпосередньо, а не скопіювати його на новий? Обидва об'єкти матимуть однаковий термін експлуатації.
jpaugh

Зробіть a, ms.Position=0і ви можете повернути початковий потік пам'яті. Тоді можнаreturn new FileStreamResult(ms,"text/xml");
Картер Медлін

0

Невелика варіація відповіді від Drew Noakes, що використовують метод 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)
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.