从控制器的动作返回XML作为ActionResult?


139

从ASP.NET MVC中的控制器操作返回XML的最佳方法是什么?有一种不错的方法可以返回JSON,但不能返回XML。我真的需要通过View路由XML,还是应该采用Response的最佳实践方式?

Answers:


114

使用MVCContrib的XmlResult操作。

供参考的是他们的代码:

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和成员注释可能很难维护。自从Luke发布这个答案(大约四年前)以来,Linq to XML已经证明自己是大多数常见场景的更优雅,更强大的替代品。查看我的答案,以获取有关如何执行此操作的示例。
德鲁·诺阿克斯

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

1
哇,这确实帮了我大忙,但是后来我才开始对MVC进行修补。
丹尼斯·瓦列夫

如果您使用的是Linq to XML,则创建文档的字符串形式是很浪费的- 最好使用stream
德鲁·诺阿克斯

2
@Drew Noakes:不,不是。如果直接写入HttpContext.Response.Output流,则会在基于WinXP的服务器上获得YSOD。它似乎已在Vista +上修复,如果在Windows 7上进行开发并部署到Windows XP(Server 2003?),则尤其容易出现问题。如果这样做,则需要先写入内存流,然后将内存流复制到输出流...
Stefan Steiger 2013年

6
@Quandary,好吧,我要重申一下这一点:创建字符串是很浪费的,因为您可以通过使用流避免分配/集合/内存不足异常,除非您使用的是存在11年错误的计算系统。
Drew Noakes

1
您可能要改用application/xmlmimetype。
Fred

32

如果您要使用出色的Linq-to-XML框架构建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,我不知道,因为我还没有尝试过新的Web API。如果您找到答案,请告诉我们。
德鲁·诺阿克斯

我认为我在API控制器问题上走错了路(我通常不做MVC的工作)。我只是将其实现为常规控制器,效果很好。
雷·阿克莱

干得好Drew。我正在使用符合您要求的XmlActionResult。我的开发环境:ASP.NET 4 MVC我从ajax调用了我控制器的方法(返回XmlActionResult-包含用于MS-Excel的转换后的xml)。Ajax Success函数具有一个包含转换后的xml的数据参数。如何使用此数据参数启动浏览器窗口并显示“另存为”对话框或仅打开Excel?
继承人

@sheir,如果您希望浏览器启动文件,则不应通过AJAX加载它。只需直接导航到您的操作方法即可。MIME类型将确定浏览器如何处理它。使用类似application/octet-stream的强迫它下载。我不知道哪种MIME类型会启动Excel,但是您应该能够足够容易地在网上找到它。
Drew Noakes 2013年

26

如果您只想通过请求返回xml,并且您的xml为“块”,则可以这样做(作为控制器中的操作):

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


4

最近,我不得不对Sitecore项目执行此操作,该项目使用一种方法从Sitecore项及其子项创建XmlDocument,并将其从控制器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的

支持的网页浏览器

  • 火狐23
  • IE 10
  • 铬29
  • 歌剧16
  • Safari 5.1.7(适用于Windows的最后一个?)

我的任务是单击ui按钮,在我的Controller上调用一个方法(带有一些参数),然后让它通过xslt转换返回MS-Excel XML。然后,返回的MS-Excel XML将使浏览器弹出“打开/保存”对话框。这必须在所有浏览器(上面列出)中都有效。

最初,我尝试使用Ajax并为文件名创建一个具有“下载”属性的动态锚点,但是它仅适用于5种浏览器中的大约3种(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创建了一个名为XmlActionResult的自定义ActionResult,我为此目的对其进行了修改。

从控制器的动作返回XML作为ActionResult?

我的控制器方法(返回ActionResult)

  • 将keys参数传递给生成XML的SQL Server存储的proc
  • 然后将XML通过xslt转换为MS-Excel xml(XmlDocument)
  • 创建修改后的XmlActionResult的实例并返回

    XmlActionResult结果=新的XmlActionResult(excelXML,“ application / vnd.ms-excel”); 字符串版本= DateTime.Now.ToString(“ dd_MMM_yyyy_hhmmsstt”); 字符串fileMask =“ LabelExport_ {0} .xml”;
    result.DownloadFilename = string.Format(fileMask,版本); 返回结果;

@Drew创建的XmlActionResult类的主要修改。

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使用XDocument的Save()方法的答案的一个小变化。

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)
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.