【问题标题】:Return XML from a controller's action in as an ActionResult?从控制器的操作中返回 XML 作为 ActionResult?
【发布时间】:2010-09-13 04:33:43
【问题描述】:

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

【问题讨论】:

    标签: asp.net .net xml asp.net-mvc


    【解决方案1】:
    return this.Content(xmlString, "text/xml");
    

    【讨论】:

    • 哇,这对我很有帮助,但我才刚刚开始修补 MVC 的东西。
    • 如果您使用 Linq to XML,创建文档的字符串形式是一种浪费——它是 better to work with streams
    • @Drew Noakes:不,不是。如果您直接写入 HttpContext.Response.Output 流,您将在基于 WinXP 的服务器上获得 YSOD。它似乎在 Vista+ 上已修复,如果您在 Windows 7 上开发并部署到 Windows XP(Server 2003?),这尤其成问题。如果这样做,则需要先写入内存流,然后将内存流复制到输出流...
    • @Quandary,好的,我将重申这一点:当您可以通过使用流来避免分配/收集/内存不足异常时,创建字符串是一种浪费,除非您'正在研究出现错误的 11 年历史的计算系统。
    • 您可能想改用application/xml mimetype。
    【解决方案2】:

    使用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);
            }
        }
    }
    

    【讨论】:

    • 这里的类直接取自 MVC Contrib 项目。不确定这是否有资格滚动您自己。
    • 如果您遵循 ASP.NET MVC 约定,您会将此类放在哪里?控制器文件夹?可能是你放置 ViewModel 的地方?
    • @pcampbel,我更喜欢在我的项目根目录中为各种类创建单独的文件夹:结果、过滤器、路由等。
    • 使用XmlSerialiser 和成员注释可能很难维护。自从 Luke 发布了这个答案(大约四年前)以来,Linq to XML 已经证明自己是大多数常见场景的更优雅、更强大的替代品。查看my answer 了解如何执行此操作的示例。
    • 链接断开,走向死胡同。
    【解决方案3】:

    如果您使用出色的 Linq-to-XML 框架构建 XML,那么这种方法会很有帮助。

    我在 action 方法中创建了一个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 的事情)。我刚刚将它实现为常规控制器,效果很好。
    • 伟大的工作德鲁。我正在使用您的 XmlActionResult 来满足我的要求。我的开发环境:ASP.NET 4 MVC 我从 ajax 调用控制器的方法(返回 XmlActionResult - 包含用于 MS-Excel 的转换 xml)。 Ajax Success 函数有一个包含转换后的xml 的数据参数。如何使用此数据参数启动浏览器窗口并显示另存为对话框或仅打开 Excel?
    • @sheir,如果您希望浏览器启动文件,那么您不应该通过 AJAX 加载它。只需直接导航到您的操作方法即可。 MIME 类型将决定浏览器如何处理它。使用 application/octet-stream 之类的东西来强制下载。我不知道哪种 MIME 类型会启动 Excel,但您应该可以很容易地在网上找到它。
    【解决方案4】:

    如果你只对通过请求返回 xml 感兴趣,并且你有你的 xml“块”,你可以这样做(作为你的控制器中的一个动作):

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

    【讨论】:

      【解决方案5】:

      在 MVC Contrib 中有一个 XmlResult(以及更多)。看看http://www.codeplex.com/MVCContrib

      【讨论】:

        【解决方案6】:

        我最近不得不为一个 Sitecore 项目执行此操作,该项目使用一种方法从 Sitecore 项及其子项创建 XmlDocument,并将其从控制器 ActionResult 作为文件返回。我的解决方案:

        public virtual ActionResult ReturnXml()
        {
            return File(Encoding.UTF8.GetBytes(GenerateXmlFeed().OuterXml), "text/xml");
        }
        

        【讨论】:

          【解决方案7】:

          终于设法完成了这项工作,并认为我会在这里记录下如何在这里为他人省去痛苦。

          环境

          • VS2012
          • SQL Server 2008R2
          • .NET 4.5
          • ASP.NET MVC4 (Razor)
          • Windows 7

          支持的网络浏览器

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

          我的任务是单击 ui 按钮,在我的控制器上调用一个方法(带有一些参数),然后让它通过 xslt 转换返回一个 MS-Excel XML。返回的 MS-Excel XML 然后会导致浏览器弹出打开/保存对话框。这必须适用于所有浏览器(如上所列)。

          起初我尝试使用 Ajax 并使用文件名的“下载”属性创建一个动态锚, 但这仅适用于 5 种浏览器中的 3 种(FF、Chrome、Opera),不适用于 IE 或 Safari。 尝试以编程方式触发锚点的 Click 事件以导致实际的“下载”存在问题。

          我最终做的是使用“不可见”的 IFRAME,它适用于所有 5 种浏览器!

          所以这就是我想出的: [请注意,我绝不是 html/javascript 大师,仅包含相关代码]

          HTML(相关位的sn-p)

          <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# 服务器端(代码 sn-p) @Drew 创建了一个名为 XmlActionResult 的自定义 ActionResult,我根据自己的目的对其进行了修改。

          Return XML from a controller's action in as an ActionResult?

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

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

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

          @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);
          }
          

          基本上就是这样。 希望对其他人有所帮助。

          【讨论】:

            【解决方案8】:

            一个简单的选项,让您可以使用流和return File(stream, "text/xml");

            【讨论】:

              【解决方案9】:

              这是一个简单的方法:

                      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");
              【解决方案10】:

              answer from 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)
                  }
              }
              

              【讨论】:

                【解决方案11】:

                使用其中一种方法

                    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;
                    }
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2014-01-15
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多