asp.net - por - ¿Devuelve XML de la acción de un controlador en ActionResult?
peticion ajax mvc (10)
¿Cuál es la mejor forma de devolver XML desde la acción de un controlador en ASP.NET MVC? Hay una buena manera de devolver JSON, pero no para XML. ¿De verdad necesito enrutar el XML a través de una Vista, o debería hacer la mejor forma de Response.Write-ing?
Aquí hay una manera simple de hacerlo:
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");
Finalmente logré obtener este trabajo y pensé que documentaría cómo hacerlo con la esperanza de salvar a otros el dolor.
Ambiente
- VS2012
- SQL Server 2008R2
- .NET 4.5
- ASP.NET MVC4 (Razor)
- Windows 7
Navegadores web compatibles
- FireFox 23
- IE 10
- Chrome 29
- Opera 16
- Safari 5.1.7 (¿el último para Windows?)
Mi tarea consistía en hacer clic en el botón de una ui, llamar un método en mi Controlador (con algunos parámetros) y luego hacer que devolviera un XML de MS-Excel a través de una transformación xslt. El XML MS-Excel devuelto haría que el navegador muestre el diálogo Abrir / Guardar. Esto tuvo que funcionar en todos los navegadores (mencionados anteriormente).
Primero intenté con Ajax y crear un Anchor dinámico con el atributo "descargar" para el nombre de archivo, pero eso solo funcionó para 3 de los 5 navegadores (FF, Chrome, Opera) y no para IE o Safari. Y hubo problemas al intentar activar mediante programación el evento Click del anclaje para provocar la "descarga" real.
¡Lo que terminé haciendo fue usar un IFRAME "invisible" y funcionó para los 5 navegadores!
Así que esto es lo que se me ocurrió: [tenga en cuenta que de ninguna manera soy un gurú de html / javascript y solo he incluido el código correspondiente]
HTML (fragmento de bits relevantes)
<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 (fragmento de código) @Drew creó un ActionResult personalizado llamado XmlActionResult que modifiqué para mi propósito.
¿Devuelve XML de la acción de un controlador en ActionResult?
Mi método de controlador (devuelve ActionResult)
- pasa el parámetro de las claves a un proceso almacenado de SQL Server que genera un XML
- ese XML se transforma a través de xslt en un xml de MS-Excel (XmlDocument)
crea una instancia del XmlActionResult modificado y lo devuelve
XmlActionResult result = new XmlActionResult (excelXML, "application / vnd.ms-excel"); string version = DateTime.Now.ToString ("dd_MMM_aaaa_hhmmsstt"); string fileMask = "LabelExport_ {0} .xml";
result.DownloadFilename = string.Format (fileMask, version); resultado de devolución;
La modificación principal de la clase XmlActionResult que creó @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);
}
Eso fue básicamente eso. Espero que ayude a otros.
Hay un XmlResult (y mucho más) en MVC Contrib. Eche un vistazo a MVCContrib
He tenido que hacer esto recientemente para un proyecto de Sitecore que utiliza un método para crear un XmlDocument a partir de un elemento de Sitecore y sus hijos y lo devuelve desde el controlador ActionResult como un archivo. Mi solución:
public virtual ActionResult ReturnXml()
{
return File(Encoding.UTF8.GetBytes(GenerateXmlFeed().OuterXml), "text/xml");
}
Si está construyendo el XML utilizando el excelente marco Linq-to-XML, entonces este enfoque será útil.
Creo un XDocument
en el método de acción.
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);
}
Este ActionResult
personalizado y ActionResult
serializa el XML por usted.
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);
}
}
Puede especificar un tipo MIME (como application/rss+xml
) y si la salida debe sangrarse si es necesario. Ambas propiedades tienen valores predeterminados razonables.
Si necesita una codificación que no sea UTF8, también es fácil agregar una propiedad para eso.
Si solo está interesado en devolver xml mediante una solicitud, y tiene su "fragmento" xml, puede hacerlo (como una acción en su controlador):
public string Xml()
{
Response.ContentType = "text/xml";
return yourXmlChunk;
}
Una opción simple que te permitirá usar streams y todo lo que es return File(stream, "text/xml");
.
Una pequeña variación de la respuesta de Drew Noakes que usa el método Save () de 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)
}
}
Use la acción XmlResult de MVCContrib.
Para referencia aquí está su código:
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); } } }
return this.Content(xmlString, "text/xml");