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





Используйте действие 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); } } }
Где бы вы поместили этот класс, если вы следуете соглашению ASP.NET MVC? Папка контроллеров? Возможно, там же, где вы бы разместили свои ViewModels?
@pcampbel, я предпочитаю создавать отдельные папки в корне моего проекта для всех типов классов: результатов, фильтров, маршрутизации и т. д.
Использование XmlSerialiser и аннотаций членов может быть трудным в обслуживании. С тех пор, как Люк опубликовал этот ответ (около четырех лет назад), Linq to XML зарекомендовал себя как более элегантная и мощная замена для наиболее распространенных сценариев. Посмотрите на мой ответ пример того, как это сделать.
В MVC Contrib есть XmlResult (и многое другое). Взгляните на http://www.codeplex.com/MVCContrib
Если вас интересует только возврат xml через запрос, и у вас есть «кусок» xml, вы можете просто сделать (как действие в вашем контроллере):
public string Xml()
{
Response.ContentType = "text/xml";
return yourXmlChunk;
}
return this.Content(xmlString, "text/xml");
Вау, это мне действительно помогло, но тогда я только начинаю возиться с MVC.
Если вы работаете с Linq to XML, создание строковой формы документа будет расточительным - это лучше работать с потоками.
@ Дрю Ноукс: Нет, это не так. Если вы пишете напрямую в поток HttpContext.Response.Output, вы получите YSOD на серверах на базе WinXP. Кажется, это исправлено в Vista +, что особенно проблематично, если вы разрабатываете в Windows 7 и развертываете в Windows XP (Server 2003?). Если вы это сделаете, вам нужно сначала записать в поток памяти, а затем скопировать поток памяти в выходной поток ...
@Quandary, хорошо, я повторю мысль: создание строк расточительно, если вы можете избежать исключений выделения / сбора / нехватки памяти с помощью потоков, пока не вы работаете на вычислительных системах 11-летней давности, которые показывают ошибку.
Вместо этого вы можете использовать mimetype application/xml.
Можно ли с помощью этого установить кодировку UTF-8? Я попытался вернуть this.Content (xmlString, "text / xml", System.Text.Encoding.UTF8), но объявление XML всегда UTF-16
Если вы создаете 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?
@RayAckley, я не знаю, потому что я еще не пробовал новые возможности веб-API. Если вы узнаете, дайте нам знать.
Я думаю, что ошибся с вопросом о контроллере API (обычно я не занимаюсь MVC). Я просто реализовал его как обычный контроллер, и он отлично работал.
Отличная работа Дрю. Я использую ваш XmlActionResult для своих требований. Моя среда разработки: ASP.NET 4 MVC Я вызываю свой метод контроллера (возвращает XmlActionResult - содержащий преобразованный XML для MS-Excel) из ajax. Функция Ajax Success имеет параметр данных, содержащий преобразованный xml. Как использовать этот параметр данных для запуска окна браузера и отображения диалогового окна «Сохранить как» или просто открытия Excel?
@sheir, если вы хотите, чтобы браузер запускал файл, вам не следует загружать его через AJAX. Просто перейдите прямо к своему методу действия. Тип MIME будет определять, как он обрабатывается браузером. Использование чего-то вроде application/octet-stream для принудительной загрузки. Я не знаю, какой тип MIME запускает Excel, но вы легко сможете найти его в Интернете.
@DrewNoakes, только что опубликовал свое «решение» в ответе ниже. Еще раз спасибо за класс XmlActionResult.
Текущий код излучает метку порядка байтов (BOM) в начале файла. Используйте new UTF8Encoding(false), чтобы удалить его.
@ivarne, вы также можете кэшировать экземпляр, чтобы уменьшить количество запросов.
Да, но если вы используете Linq-to-xml, удаление единственного выделения, вероятно, будет несущественным
Наконец-то мне удалось получить эту работу, и я подумал, что запишу, как здесь, в надежде спасти других от боли.
Среда
Поддерживаемые веб-браузеры
Моя задача заключалась в нажатии кнопки пользовательского интерфейса, вызове метода на моем контроллере (с некоторыми параметрами), а затем возвращении 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# СЕРВЕРНАЯ СТОРОНА (фрагмент кода) @Drew создал настраиваемый ActionResult под названием XmlActionResult, который я изменил для своих целей.
Вернуть XML из действия контроллера в виде ActionResult?
Мой метод контроллера (возвращает ActionResult)
создает экземпляр измененного 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);
}
Вот и все. Надеюсь, это поможет другим.
Простой вариант, который позволит вам использовать потоки и все такое - return File(stream, "text/xml");.
Вот простой способ сделать это:
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 напрямую, а не копировать его на новый? Оба объекта будут иметь одинаковое время жизни.
Сделайте ms.Position=0, и вы сможете вернуть исходный поток памяти. Тогда можно return new FileStreamResult(ms,"text/xml");
Недавно мне пришлось сделать это для проекта Sitecore, который использует метод для создания XmlDocument из элемента Sitecore и его дочерних элементов и возвращает его из контроллера ActionResult в виде файла. Мое решение:
public virtual ActionResult ReturnXml()
{
return File(Encoding.UTF8.GetBytes(GenerateXmlFeed().OuterXml), "text/xml");
}
Небольшая вариация ответ от Дрю Ноукс, использующая метод 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)
}
}
используйте один из этих методов
public ContentResult GetXml()
{
string xmlString = "your xml data";
return Content(xmlString, "text/xml");
}
или же
public string GetXml()
{
string xmlString = "your xml data";
Response.ContentType = "text/xml";
return xmlString;
}
Класс здесь взят прямо из проекта MVC Contrib. Не уверен, что это можно считать вашим собственным.