컨트롤러의 액션에서 XML을 ActionResult로 반환 하시겠습니까?


139

ASP.NET MVC의 컨트롤러 작업에서 XML을 반환하는 가장 좋은 방법은 무엇입니까? JSON을 반환하는 좋은 방법이 있지만 XML은 아닙니다. 실제로 XML을 View를 통해 라우팅해야합니까, 아니면 최선의 방법으로 Response를 작성해야합니까?

답변:


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 규칙을 따르는 경우이 클래스를 어디에 배치 하시겠습니까? 컨트롤러 폴더? ViewModel을 넣은 곳과 같은 곳일까요?
p.campbell

7
@ pcampbel, 나는 모든 종류의 수업을 위해 프로젝트 루트에 별도의 폴더를 만드는 것을 선호합니다 : 결과, 필터, 라우팅 등
Anthony Serdyukov

사용 XmlSerialiser및 멤버 주석을 유지 관리하기 어려울 수 있습니다. Luke가이 답변을 게시 한 이후 (약 4 년 전) Linq to XML은 가장 일반적인 시나리오를보다 우아하고 강력하게 대체 한 것으로 입증되었습니다. 이 작업을 수행하는 방법에 대한 예는 내 답변 을 확인하십시오 .
Drew Noakes

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

1
와우, 이것은 정말로 나에게 도움이되었지만, 나는 단지 MVC를 다루기 시작했습니다.
Denis Valeev

Linq to XML을 사용하여 작업하는 경우 문서의 문자열 형식을 만드는 것은 낭비 입니다. 스트림을 사용하는 것이 좋습니다 .
Drew Noakes

2
@Drew Noakes : 아닙니다. HttpContext.Response.Output 스트림에 직접 쓰면 WinXP 기반 서버에서 YSOD를 얻게됩니다. Vista +에서 수정 된 것 같습니다. Windows 7에서 개발하고 Windows XP (Server 2003?)에 배포하는 경우 특히 문제가됩니다. 그렇다면 메모리 스트림에 먼저 기록한 다음 메모리 스트림을 출력 스트림에 복사해야합니다.
Stefan Steiger

6
@Quandary, ok 요점을 다시 말씀 드리겠습니다. 오류가있는 11 세의 컴퓨팅 시스템에서 작업 하지 않는 한 스트림을 사용하여 할당 / 수집 / 메모리 부족 예외를 피할 수 있으면 문자열을 만드는 것이 낭비 입니다.
Drew Noakes

1
application/xml대신 mimetype 을 사용하고 싶을 수도 있습니다 .
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 컨트롤러에서 사용하기 위해 이것을 수정할 수 있다고 생각하십니까?
Ray Ackley

@ RayAckley, 새로운 웹 API를 아직 시도하지 않았으므로 알 수 없습니다. 알게되면 알려주십시오.
Drew Noakes

API 컨트롤러 질문에 잘못 착각했다고 생각합니다 (일반적으로 MVC 작업은하지 않습니다). 방금 일반 컨트롤러로 구현했으며 훌륭하게 작동했습니다.
Ray Ackley

대단한 Drew. 내 요구 사항에 XmlActionResult의 맛을 사용하고 있습니다. 내 개발 환경 : ASP.NET 4 MVC ajax에서 컨트롤러의 메소드 (MS-Excel 용 변환 된 XML을 포함하는 XmlActionResult 반환)를 호출합니다. Ajax Success 함수에는 변환 된 xml이 포함 된 데이터 매개 변수가 있습니다. 이 데이터 매개 변수를 사용하여 브라우저 창을 시작하고 SaveAs 대화 상자를 표시하거나 Excel을 여는 방법은 무엇입니까?
sheir

@ sheir, 브라우저에서 파일을 시작하려면 AJAX를 통해 파일을로드하지 않아야합니다. 행동 방법으로 바로 이동하십시오. MIME 유형에 따라 브라우저가 처리하는 방식이 결정됩니다. application/octet-stream강제로 다운로드하는 것과 같은 것을 사용합니다 . MIME 형식으로 Excel을 시작하는 방법을 모르지만 온라인에서 쉽게 찾을 수 있어야합니다.
Drew Noakes

26

요청을 통해서만 XML을 반환하고 xml "청크"가있는 경우 컨트롤러에서 작업으로 수행 할 수 있습니다.

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


4

최근에 Sitecore 항목 및 해당 하위 항목에서 XmlDocument를 작성하고 컨트롤러 ActionResult에서 파일로이를 리턴하는 메소드를 사용하는 Sitecore 프로젝트에 대해이 작업을 수행해야했습니다. 내 해결책 :

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

2

마지막 으로이 작업을 수행하고 다른 사람들이 고통을 덜기 위해 어떻게 여기에 문서화 할 것이라고 생각했습니다.

환경

  • VS2012
  • SQL Server 2008R2
  • .NET 4.5
  • ASP.NET MVC4 (레이저)
  • 윈도우 7

지원되는 웹 브라우저

  • 파이어 폭스 23
  • IE 10
  • 크롬 29
  • 오페라 16
  • Safari 5.1.7 (Windows 용 마지막 것)

내 작업은 ui 버튼 클릭으로 내 컨트롤러에서 메소드를 호출하고 (매개 변수 포함) xslt 변환을 통해 MS-Excel XML을 반환하도록합니다. 반환 된 MS-Excel XML은 브라우저가 열기 / 저장 대화 상자를 팝업하게합니다. 이것은 위에 나열된 모든 브라우저에서 작동해야했습니다.

처음에는 Ajax로 파일 이름에 대한 "다운로드"속성을 가진 동적 앵커를 만들려고 시도했지만 IE 또는 Safari가 아닌 5 개의 브라우저 (FF, Chrome, Opera) 중 약 3 개에서만 작동했습니다. 그리고 앵커의 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>

자바 스크립트

//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 (코드 조각) @Drew는 XmlActionResult라는 사용자 지정 ActionResult를 만들었습니다.

컨트롤러의 액션에서 XML을 ActionResult로 반환 하시겠습니까?

내 컨트롤러 방법 (ActionResult 반환)

  • XML을 생성하는 SQL Server 저장 프로 시저에 keys 매개 변수를 전달합니다.
  • 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

를 수행 ms.Position=0하면 원래 메모리 스트림을 반환 할 수 있습니다. 그럼 당신은return new FileStreamResult(ms,"text/xml");
카터 메들린

0

XDocument의 Save () 메소드를 사용하는 Drew Noakes 답변 의 작은 변형 .

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)
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.