コントローラーのアクションからXMLをActionResultとして返しますか?


139

ASP.NET MVCでコントローラーのアクションからXMLを返す最良の方法は何ですか?JSONを返すには良い方法がありますが、XMLにはありません。ビューを介してXMLをルーティングする必要がありますか、それともResponse.Write-ingのベストプラクティスではない方法を実行する必要がありますか?

回答:


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規約に従っている場合、このクラスはどこに配置しますか?Controllersフォルダー?ViewModelを配置する場所と同じですか?
p.campbell

7
@pcampbel、すべての種類のクラス(プロジェクト結果、フィルター、ルーティングなど)のプロジェクトルートに個別のフォルダーを作成することを好む
Anthony Serdyukov 2010

XmlSerialiserとメンバーの注釈の使用は、維持するのが難しい場合があります。Lukeがこの回答を投稿してから(約4年前)、Linq to XMLは、最も一般的なシナリオに代わる、よりエレガントで強力な代替品としての地位を確立しています。これを行う方法の例については、私の答えを確認しください。
Drew Noakes、2012年

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

1
うわー、これは本当に私に役立ちましたが、それから私はMVCのことをいじくり始めたばかりです。
Denis Valeev、2011年

Linq to XMLを使用している場合、ドキュメントの文字列形式を作成するのは無駄です。ストリームを使用する方が適切です。
Drew Noakes、2012年

2
@Drew Noakes:いいえ、そうではありません。HttpContext.Response.Outputストリームに直接書き込むと、WinXPベースのサーバーでYSODを取得します。これはVista +で修正されているようですが、Windows 7で開発してWindows XP(Server 2003?)に展開する場合は特に問題があります。その場合、最初にメモリストリームに書き込み、次にメモリストリームを出力ストリームにコピーする必要があります...
Stefan Steiger

6
@Quandary、okポイントを言い直します。エラーを示す11年前のコンピューティングシステムで作業しているのでない限り、ストリームを使用して割り当て/収集/メモリ不足の例外を回避できる場合、文字列の作成は無駄です。
Drew Noakes 2013

1
application/xml代わりに、mimetype を使用することもできます。
フレッド

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を試したことがないのでわかりません。判明した場合はお知らせください。
Drew Noakes

私はAPIコントローラーの質問で間違った方向に進んでいたと思います(通常、MVCに関することはしません)。私はそれを通常のコントローラーとして実装しましたが、うまくいきました。
Ray Ackley

素晴らしい仕事ドリュー。私の要件には、XmlActionResultのフレーバーを使用しています。私の開発環境:ASP.NET 4 MVC ajaxからコントローラーのメソッド(XmlActionResultを返す-MS-Excel用に変換されたxmlを含む)を呼び出します。Ajax Success関数には、変換されたxmlを含むデータパラメータがあります。このデータパラメーターを使用してブラウザーウィンドウを起動し、[名前を付けて保存]ダイアログを表示するか、Excelを開く方法は?
2013

@sheir、ブラウザでファイルを起動する場合は、AJAX経由でロードしないでください。アクションメソッドに直接移動するだけです。MIMEタイプによって、ブラウザでの処理方法が決まります。application/octet-streamダウンロードを強制するようなものを使用します。Excelを起動するMIMEタイプはわかりませんが、オンラインで簡単に見つけることができるはずです。
Drew Noakes 2013

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(かみそり)
  • Windows 7

サポートされているWebブラウザー

  • FireFox 23
  • IE 10
  • Chrome 29
  • オペラ16
  • Safari 5.1.7(Windowsの最後のもの?)

私のタスクは、UIボタンをクリックして、コントローラーのメソッドを(いくつかのパラメーターを使用して)呼び出し、xslt変換を介してMS-Excel XMLを返すようにすることでした。返されたMS-Excel XMLは、ブラウザに[開く/保存]ダイアログをポップアップさせます。これはすべてのブラウザー(上記にリスト)で機能する必要がありました。

最初にAjaxを試し、ファイル名に「download」属性を使用して動的アンカーを作成しようとしましたが、これは5つのブラウザー(FF、Chrome、Opera)のうち約3つでしか機能せず、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>

ジャバスクリプト

//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ストアドプロシージャにキーパラメータを渡します
  • そのXMLは、xsltを介してMS-Excel xml(XmlDocument)に変換されます。
  • 変更されたXmlActionResultのインスタンスを作成し、それを返します

    XmlActionResult result = new XmlActionResult(excelXML、 "application / vnd.ms-excel"); string version = 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);
}

基本的にはそれでした。それが他の人を助けることを願っています。


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

なぜこれが2つのメモリストリームを構築するのですか?msそれを新しいものにコピーするのではなく、直接渡すだけではどうですか?両方のオブジェクトの寿命は同じです。
jpaugh 2018年

a 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.