ActionResult olarak bir denetleyicinin eyleminden XML döndürülsün mü?


139

ASP.NET MVC'de bir denetleyicinin eyleminden XML döndürmenin en iyi yolu nedir? JSON'u döndürmenin güzel bir yolu var, ancak XML için değil. XML'i gerçekten bir Görünüm üzerinden yönlendirmem gerekiyor mu yoksa en iyi olmayan Yanıt yöntemini mi kullanmalıyım?

Yanıtlar:


114

MVCContrib'in XmlResult Eylemini kullanın .

Referans için kodları:

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
Buradaki sınıf doğrudan MVC Katkı projesinden alınır. Kendi haddeleme olarak nitelendirilen bu olup olmadığından emin değilim.
Yelken Judo

3
ASP.NET MVC kuralını izliyorsanız bu sınıfı nereye koyardınız? Kontrolörler klasörü? Belki de ViewModels'ınızı koyacağınız yer?
p.campbell

7
@pcampbel, her tür sınıf için proje kökümde ayrı klasörler oluşturmayı tercih ediyorum: Sonuçlar, Filtreler, Yönlendirme, vb.
Anthony Serdyukov

Kullanım XmlSerialiserve üye ek açıklamalarının bakımı zor olabilir. Luke bu cevabı gönderdiğinden (yaklaşık dört yıl önce), Linq to XML, en yaygın senaryoların yerini daha zarif ve güçlü bir yerine koydu. Bunun nasıl yapılacağına dair bir örnek için cevabımı kontrol et.
Drew Noakes

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

1
Vay be, bu gerçekten bana yardımcı oldu, ama sonra sadece MVC şeyle uğraşmaya başlıyorum.
Denis Valeev

Linq to XML ile çalışıyorsanız, belgenin dize biçimini oluşturmak israftır - akışlarla çalışmak daha iyidir .
Drew Noakes

2
@ Çekti Noakes: Hayır, değil. Doğrudan HttpContext.Response.Output akışına yazarsanız, WinXP tabanlı sunucularda bir YSOD alırsınız. Windows 7'de geliştirip Windows XP'ye (Server 2003?) Dağıtırsanız özellikle sorunlu olan Vista + üzerinde düzeltilmiş gibi görünüyor. Bunu yaparsanız, önce bir bellek akışına yazmanız ve ardından bellek akışını çıkış akışına kopyalamanız gerekir ...
Stefan Steiger

6
@Quandary, tamam Bir noktaya değineceğim: Bir hata gösteren 11 yaşındaki bilgi işlem sistemleri üzerinde çalışmadığınız sürece , akışları kullanarak ayırma / toplama / bellek dışı özel durumlardan kaçınabildiğinizde dizeler oluşturmak israftır .
Drew Noakes

1
Bunun application/xmlyerine mime türünü kullanmak isteyebilirsiniz .
Fred

32

XML'i mükemmel Linq-XML çerçevesini kullanarak oluşturuyorsanız, bu yaklaşım yardımcı olacaktır.

Bir XDocumenteylem yöntemi oluşturmak .

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

Bu yeniden kullanılabilir, özel ActionResultXML sizin için serileştirir.

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

Bir MIME türü (örneğin application/rss+xml) ve gerekirse çıktının girintili olup olmayacağını belirtebilirsiniz. Her iki özelliğin de varsayılan varsayılanları vardır.

UTF8 dışında bir kodlamaya ihtiyacınız varsa, bunun için bir özellik eklemek de basittir.


Bunu bir API denetleyicisinde kullanmak üzere değiştirmenin mümkün olduğunu düşünüyor musunuz?
Ray Ackley

@ RayAckley, yeni Web API'sini henüz denemediğim için bilmiyorum. Öğrenirseniz, bize bildirin.
Drew Noakes

Ben API denetleyicisi soru ile yanlış yolda olduğunu düşünüyorum (Ben normalde MVC şeyler yapmıyorum). Ben sadece düzenli bir denetleyici olarak uyguladım ve harika çalıştı.
Ray Ackley

İyi işti Drew. Gereksinimim için XmlActionResult cihazınızın bir lezzetini kullanıyorum. Geliştirme ortamım: ASP.NET 4 MVC Denetleyicimin yöntemini çağırıyorum (MS-Excel için dönüştürülmüş bir xml içeren XmlActionResult döndürür). Ajax Başarı işlevi, dönüştürülmüş xml içeren bir veri parametresine sahiptir. Bir tarayıcı penceresi başlatmak ve bir SaveAs iletişim kutusunu görüntülemek veya sadece Excel'i açmak için bu veri parametresini nasıl kullanabilirim?
Şeyh

Eğer tarayıcının dosyayı başlatmasını istiyorsanız AJAX ile yüklememelisiniz. Doğrudan eylem yönteminize gidin. MIME türü tarayıcı tarafından nasıl ele alınacağını belirler. application/octet-streamİndirmeye zorlamak gibi bir şey kullanmak . MIME türünün Excel'i başlattığını bilmiyorum, ancak çevrimiçi olarak yeterince kolayca bulabilmeniz gerekir.
Drew Noakes

26

Yalnızca bir istekle xml döndürmekle ilgileniyorsanız ve xml "chunk" değeriniz varsa, bunu yapabilirsiniz (denetleyicinizde bir eylem olarak):

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


4

Son zamanlarda bir Sitecore Öğe ve alt öğelerinden bir XmlDocument oluşturmak için bir yöntem kullanan ve denetleyici ActionResult bir Dosya olarak döndürür bir Sitecore projesi için bunu yapmak zorunda kaldı. Çözümüm:

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

2

Sonunda bu işi almayı başar ve burada başkalarını acıyı kurtarmak umuduyla nasıl belgeleyeceğimi düşündüm.

çevre

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

Desteklenen Web Tarayıcıları

  • FireFox 23
  • IE 10
  • Chrome 29
  • Opera 16
  • Safari 5.1.7 (Windows için sonuncusu?)

Görevim bir ui düğmesine tıklamaktı, Denetleyicimde bir yöntem çağırın (bazı parametrelerle) ve ardından bir xslt dönüşümü yoluyla bir MS-Excel XML döndürmesini sağlayın. Döndürülen MS-Excel XML, tarayıcının Aç / Kaydet iletişim kutusunu açmasına neden olur. Bu, tüm tarayıcılarda (yukarıda listelenmiştir) çalışmak zorundaydı.

İlk başta Ajax ile denedim ve dosya adı için "download" özniteliğine sahip dinamik bir Anchor oluşturmaya çalıştım, ancak bu IE veya Safari için değil, 5 tarayıcıdan (FF, Chrome, Opera) sadece 3 tanesinde çalıştı. Ve gerçek "indirmeye" neden olmak için çapanın Click olayını programlı olarak tetiklemeye çalışırken sorunlar vardı.

Yaptığım şey "görünmez" bir IFRAME kullanmaktı ve 5 tarayıcıda da işe yaradı!

İşte burada ne ile geldi: [ben hiçbir şekilde bir html / javascript guru olduğumu ve sadece ilgili kodu dahil unutmayın lütfen]

HTML (alakalı bitlerin snippet'i)

<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 (kod snippet) @Drew, amacım için değiştirdiğim XmlActionResult adlı özel bir ActionResult oluşturdu.

ActionResult olarak bir denetleyicinin eyleminden XML döndürülsün mü?

Denetleyicim yöntemi (ActionResult döndürür)

  • keys parametresini XML üreten SQL Server'da saklanan bir proc'a geçirir
  • bu XML daha sonra xslt aracılığıyla bir MS-Excel xml (XmlDocument) biçimine dönüştürülür.
  • değiştirilmiş XmlActionResult örneğini oluşturur ve döndürür

    XmlActionResult sonucu = yeni XmlActionResult (excelXML, "application / vnd.ms-excel"); string version = DateTime.Now.ToString ("dd_MMM_yyyy_hhmmsstt"); string fileMask = "LabelExport_ {0} .xml";
    results.DownloadFilename = string.Format (fileMask, sürüm); sonuç;

@Drew tarafından oluşturulan XmlActionResult sınıfında ana değişiklik.

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

Temelde buydu. Umarım başkalarına yardımcı olur.


1

Akışları ve her şeyi kullanmanıza izin veren basit bir seçenek return File(stream, "text/xml");.


0

İşte bunu yapmanın basit bir yolu:

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

Bu neden iki bellek akışı oluşturuyor? Neden sadece yenisine mskopyalamak yerine doğrudan geçmiyorsunuz ? Her iki nesnenin de ömrü aynı olacaktır.
jpaugh

Bir yapın ms.Position=0ve orijinal bellek akışını döndürebilirsiniz. Sonra yapabilirsinizreturn new FileStreamResult(ms,"text/xml");
Carter Medlin

0

XDocument'in Save () yöntemini kullanan Drew Noakes'ın cevabının küçük bir varyasyonu .

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)
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.