Man kann relativ einfach auf seiner Seite einen Excel-Export bei tabellarischen Daten anbieten ohne großartige SDKs zu wälzen. Excel versteht von Haus aus auch HTML Tabellen. Man ist zwar eingeschränkt, aber es ist schnell gemacht. Bei einer ASP.NET MVC Anwendung wäre es nun noch schön, dass man das Markup der HTML Tabelle als View speichert. Über eine kleine Extension kann man sich den View auch als String ausgeben lassen.
Unser Ziel: Excel Export
Wir möchten auf unserer Webseite einen Excelexport anbieten. Etwas ähnliches, nur etwas kurioser gemacht, habe ich auch schonmal auf dem Blog geschrieben.
Was muss man dafür tun?
Im Grunde reicht es aus, wenn man in die Response eine HTML Tabelle rendert und als Contenttype "application/ms-excel" angibt. Wir möchten aber die Tabelle als View speichern um Sie einfacher editieren zu können und nicht hardcoded im Source Code zu pflegen.
RenderViewToString
Wir benötigen zu aller erst eine Komponente die einen View als String uns zurückgibt. Wichtig dabei ist, dass die Response während des Vorgangs nicht schon zum Client geschickt wird (Response.Flush()), da man ansonsten den Contenttyp nicht mehr ändern kann! Eine der Lösungen in diesem Thread hat auch funktioniert - in ASP.NET MVC 2.0 muss man allerdings eine kleine Sache ändern -> Siehe Stackoverflow.
/// <summary>Renders a view to string.</summary> public static string RenderViewToString(this Controller controller, string viewName, object viewData) { //Create memory writer var sb = new StringBuilder(); var memWriter = new StringWriter(sb); //Create fake http context to render the view var fakeResponse = new HttpResponse(memWriter); var fakeContext = new HttpContext(HttpContext.Current.Request, fakeResponse); var fakeControllerContext = new ControllerContext( new HttpContextWrapper(fakeContext), controller.ControllerContext.RouteData, controller.ControllerContext.Controller); var oldContext = HttpContext.Current; HttpContext.Current = fakeContext; //Use HtmlHelper to render partial view to fake context var html = new HtmlHelper(new ViewContext(fakeControllerContext, new FakeView(), new ViewDataDictionary(), new TempDataDictionary()), new ViewPage()); html.RenderPartial(viewName, viewData); //Restore context HttpContext.Current = oldContext; //Flush memory and return output memWriter.Flush(); return sb.ToString(); } /// <summary>Fake IView implementation used to instantiate an HtmlHelper.</summary> public class FakeView : IView { #region IView Members public void Render(ViewContext viewContext, System.IO.TextWriter writer) { throw new NotImplementedException(); } #endregion }
Nun können wir über diese Methode einen String aus einem View erzeugen - schonmal nicht schlecht.
Jetzt kann man entweder direkt das ContentResult nutzen oder man baut sich sein eigens ExcelResult:
public class ExcelResult : ActionResult { public string FileName { get; set; } public string Content { get; set; } public ExcelResult(string filename, string content) { this.FileName = filename; this.Content = content; } public override void ExecuteResult(ControllerContext context) { WriteFile(this.FileName, "application/ms-excel", this.Content); } private static void WriteFile(string fileName, string contentType, string content) { HttpContext context = HttpContext.Current; context.Response.Clear(); context.Response.AddHeader("content-disposition", "attachment;filename=" + fileName); context.Response.Charset = ""; context.Response.Cache.SetCacheability(HttpCacheability.NoCache); context.Response.ContentType = contentType; context.Response.Write(content); context.Response.End(); } }
Die Anwendung
Folgender View ist unser Exceltemplate (mit einem ViewModel)
%@ Import Namespace="MvcRenderToString.Models"%> <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IList<ExcelData>>" %> <table> <tr> <% foreach(ExcelData excel in Model) { %> <td><%=excel.Foobar %></td> <% } %> </tr> </table>
Und der Controller:
public ExcelResult Excel() { List<ExcelData> foobars = new List<ExcelData>(); foobars.Add(new ExcelData() { Foobar = "HelloWorld!"}); foobars.Add(new ExcelData() { Foobar = "HelloWorld!" }); foobars.Add(new ExcelData() { Foobar = "HelloWorld!" }); foobars.Add(new ExcelData() { Foobar = "HelloWorld!" }); string content = this.RenderViewToString("Excel", foobars); return new ExcelResult("Foobar.xls", content); }
Das Ergebnis:
Einziges Manko ist, dass Excel dem nicht ganz vertraut. Leider ist das ByDesign so:
Bei Bestätigung auf "Ja":
Fetzt, oder?
Weitere Anwendungsmöglichkeiten
Dadurch das wir eine HTML Tabelle benutzen, können wir die Tabelle auch genauso auf unserer normalen Webseite benutzen. Hierbei Rufe ich dann einfach RenderPartial etc. auf.
Man könnte so auch Email Templates als View ablegen. Die Möglichkeiten sind jedenfalls da :)