Trả lại XML từ hành động của bộ điều khiển dưới dạng ActionResult?


139

Cách tốt nhất để trả về XML từ hành động của bộ điều khiển trong ASP.NET MVC là gì? Có một cách hay để trả về JSON, nhưng không phải cho XML. Tôi có thực sự cần định tuyến XML thông qua Chế độ xem hay tôi nên thực hiện cách Phản hồi không thực tế nhất. Viết lại?

Câu trả lời:


114

Sử dụng MVCContrib 's XmlResult Action.

Để tham khảo ở đây là mã của họ:

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
Lớp học ở đây được lấy thẳng từ dự án MVC Contrib. Không chắc chắn nếu đó là những gì đủ điều kiện như lăn của riêng bạn.
Đi thuyền Judo

3
Bạn sẽ đặt lớp này ở đâu, nếu bạn đang theo quy ước ASP.NET MVC? Bộ điều khiển thư mục? Có lẽ cùng một nơi bạn đặt ViewModels của bạn, có lẽ?
p.campbell 2

7
@pcampbel, tôi thích tạo các thư mục riêng trong gốc dự án của mình cho mọi loại lớp: Kết quả, Bộ lọc, Định tuyến, v.v.
Anthony Serdyukov

Sử dụng XmlSerialiservà chú thích thành viên có thể khó duy trì. Kể từ khi Luke đăng câu trả lời này (khoảng bốn năm trước), Linq to XML đã chứng tỏ mình là một sự thay thế thanh lịch và mạnh mẽ hơn cho hầu hết các kịch bản phổ biến. Kiểm tra câu trả lời của tôi cho một ví dụ về cách làm điều này.
vẽ Noakes

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

1
Wow, điều này thực sự đã giúp tôi, nhưng sau đó tôi chỉ bắt đầu mày mò với MVC.
Denis Valeev

Nếu bạn đang làm việc với Linq to XML, việc tạo một dạng chuỗi tài liệu là lãng phí - tốt hơn là làm việc với các luồng .
vẽ Noakes

2
@Drew Noakes: Không, không. Nếu bạn viết trực tiếp vào luồng HTTPContext.Response.Output, bạn sẽ nhận được YSOD trên các máy chủ dựa trên WinXP. Nó dường như được sửa trên Vista +, điều này đặc biệt có vấn đề nếu bạn phát triển trên Windows 7 và triển khai lên Windows XP (Server 2003?). Nếu bạn làm như vậy, trước tiên bạn cần ghi vào luồng bộ nhớ, sau đó sao chép luồng bộ nhớ vào luồng đầu ra ...
Stefan Steiger

6
@Quandary, ok Tôi sẽ nói lại quan điểm: việc tạo chuỗi là lãng phí khi bạn có thể tránh các trường hợp ngoại lệ cấp phát / bộ sưu tập / bộ nhớ ngoài bằng cách sử dụng các luồng, trừ khi bạn làm việc trên các hệ thống máy tính 11 tuổi có lỗi.
Drew Noakes

1
Bạn có thể muốn sử dụng application/xmlmimetype thay thế.
Fred

32

Nếu bạn đang xây dựng XML bằng cách sử dụng khung Linq-to-XML tuyệt vời, thì cách tiếp cận này sẽ hữu ích.

Tôi tạo một XDocumentphương thức hành động.

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

Điều này có thể tái sử dụng, tùy chỉnh ActionResultnối tiếp XML cho bạn.

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

Bạn có thể chỉ định loại MIME (chẳng hạn như application/rss+xml) và liệu đầu ra có nên được thụt lề nếu bạn cần. Cả hai tính chất có mặc định hợp lý.

Nếu bạn cần một mã hóa khác ngoài UTF8, thì thật đơn giản để thêm một thuộc tính cho điều đó.


Bạn có nghĩ rằng có thể sửa đổi điều này để sử dụng trong bộ điều khiển API không?
Ray Ackley

@RayAckley, tôi không biết vì tôi chưa thử các công cụ API Web mới. Nếu bạn tìm ra, hãy cho chúng tôi biết.
Drew Noakes

Tôi nghĩ rằng tôi đã đi sai hướng với câu hỏi về bộ điều khiển API (tôi thường không làm công cụ MVC). Tôi chỉ thực hiện nó như một bộ điều khiển thông thường và nó hoạt động rất tốt.
Ray Ackley

Công việc tuyệt vời Drew. Tôi đang sử dụng hương vị XmlActionResult của bạn cho yêu cầu của tôi. Môi trường dev của tôi: ASP.NET 4 MVC Tôi gọi phương thức của trình điều khiển của mình (trả về XmlActionResult - chứa xml đã chuyển đổi cho MS-Excel) từ ajax. Hàm Ajax Thành công có một tham số dữ liệu chứa xml được chuyển đổi. Làm cách nào để sử dụng tham số dữ liệu này để khởi chạy cửa sổ trình duyệt và hiển thị hộp thoại SaveAs hoặc chỉ mở Excel?
thừa kế

@sheir, nếu bạn muốn trình duyệt khởi chạy tệp thì bạn không nên tải nó qua AJAX. Chỉ cần điều hướng trực tiếp đến phương pháp hành động của bạn. Loại MIME sẽ xác định cách nó được trình duyệt xử lý. Sử dụng một cái gì đó như application/octet-streamđể buộc nó để tải về. Tôi không biết loại MIME nào khởi chạy Excel, nhưng bạn sẽ có thể tìm thấy nó trực tuyến đủ dễ dàng.
Drew Noakes

26

Nếu bạn chỉ muốn trả lại xml thông qua một yêu cầu và bạn có "chunk" xml của mình, bạn có thể thực hiện (như một hành động trong bộ điều khiển của bạn):

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


4

Gần đây tôi đã phải làm điều này cho một dự án Sitecore sử dụng một phương thức để tạo XmlDocument từ một mục Sitecore và các phần tử con của nó và trả về nó từ bộ điều khiển ActionResult dưới dạng tệp. Giải pháp của tôi:

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

2

Cuối cùng quản lý để có được công việc này và nghĩ rằng tôi sẽ ghi lại làm thế nào ở đây với hy vọng cứu người khác nỗi đau.

Môi trường

  • VS2012
  • Máy chủ SQL 2008R2
  • .NET 4.5
  • ASP.NET MVC4 (Dao cạo)
  • Windows 7

Trình duyệt web được hỗ trợ

  • Lửa chữa cháy 23
  • IE 10
  • Chrome 29
  • Opera 16
  • Safari 5.1.7 (cái cuối cùng cho Windows?)

Nhiệm vụ của tôi là nhấp chuột vào nút ui, gọi một phương thức trên Trình điều khiển của tôi (với một số thông số) và sau đó nó sẽ trả về một MS-Excel XML thông qua một biến đổi xslt. MS-Excel XML được trả lại sau đó sẽ khiến trình duyệt bật lên hộp thoại Mở / Lưu. Điều này đã phải làm việc trong tất cả các trình duyệt (được liệt kê ở trên).

Lúc đầu, tôi đã thử với Ajax và để tạo một Anchor động với thuộc tính "download" cho tên tệp, nhưng nó chỉ hoạt động với khoảng 3 trong số 5 trình duyệt (FF, Chrome, Opera) chứ không phải cho IE hoặc Safari. Và đã có vấn đề với việc cố gắng lập trình kích hoạt sự kiện Click của neo để gây ra "tải xuống" thực sự.

Điều cuối cùng tôi đã làm là sử dụng IFRAME "vô hình" và nó hoạt động cho cả 5 trình duyệt!

Vì vậy, đây là những gì tôi nghĩ ra: [xin lưu ý rằng tôi không phải là một guru html / javascript và chỉ bao gồm các mã có liên quan]

HTML (đoạn mã liên quan)

<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 (đoạn mã) @Drew đã tạo một ActionResult tùy chỉnh có tên XmlActionResult mà tôi đã sửa đổi cho mục đích của mình.

Trả lại XML từ hành động của bộ điều khiển dưới dạng ActionResult?

Phương thức điều khiển của tôi (trả về ActionResult)

  • chuyển tham số khóa cho một máy chủ lưu trữ SQL Server tạo ra XML
  • XML đó sau đó được chuyển đổi qua xslt thành xml MS-Excel (XmlDocument)
  • tạo bản sao của XmlActionResult đã sửa đổi và trả về nó

    Kết quả XmlActionResult = new XmlActionResult (excelXML, "application / vnd.ms-excel"); chuỗi phiên bản = DateTime.Now.ToString ("dd_MMM_yyyy_hhmmsstt"); chuỗi fileMask = "LabelExport_ {0} .xml";
    result.DoadFilename = string.Format (fileMask, phiên bản); kết quả trả về;

Sửa đổi chính cho lớp XmlActionResult mà @Drew đã tạo.

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

Về cơ bản là như vậy. Hy vọng nó sẽ giúp người khác.


1

Một tùy chọn đơn giản sẽ cho phép bạn sử dụng các luồng và tất cả chỉ có thế return File(stream, "text/xml");.


0

Đây là một cách đơn giản để làm điều đó:

        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");

Tại sao điều này xây dựng hai luồng bộ nhớ? Tại sao không chỉ truyền mstrực tiếp, thay vì sao chép nó sang một cái mới? Cả hai đối tượng sẽ có cùng một cuộc đời.
jpaugh

Làm một ms.Position=0và bạn có thể trả về bộ nhớ ban đầu. Sau đó, bạn có thểreturn new FileStreamResult(ms,"text/xml");
Carter Medlin

0

Một biến thể nhỏ của câu trả lời từ Drew Noakes sử dụng phương thức Save () của 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)
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.