Schaffe es endlich, diese Arbeit zu bekommen und dachte, ich würde dokumentieren, wie hier in der Hoffnung, anderen den Schmerz zu ersparen.
Umgebung
- VS2012
- SQL Server 2008R2
- .NET 4.5
- ASP.NET MVC4 (Rasiermesser)
- Windows 7
Unterstützte Webbrowser
- FireFox 23
- IE 10
- Chrome 29
- Oper 16
- Safari 5.1.7 (letzte für Windows?)
Meine Aufgabe bestand darin, auf eine UI-Schaltfläche zu klicken, eine Methode auf meinem Controller aufzurufen (mit einigen Parametern) und dann ein MS-Excel-XML über eine xslt-Transformation zurückzugeben. Das zurückgegebene MS-Excel-XML würde dann dazu führen, dass der Browser das Dialogfeld Öffnen / Speichern öffnet. Dies musste in allen Browsern (oben aufgeführt) funktionieren.
Zuerst habe ich mit Ajax versucht, einen dynamischen Anker mit dem Attribut "Download" für den Dateinamen zu erstellen, aber das funktionierte nur für etwa 3 der 5 Browser (FF, Chrome, Opera) und nicht für IE oder Safari. Und es gab Probleme beim Versuch, das Click-Ereignis des Ankers programmgesteuert auszulösen, um den tatsächlichen "Download" zu verursachen.
Am Ende habe ich einen "unsichtbaren" IFRAME verwendet, der für alle 5 Browser funktioniert hat!
Also hier ist, was ich mir ausgedacht habe: [Bitte beachten Sie, dass ich keineswegs ein HTML / Javascript-Guru bin und nur den relevanten Code eingefügt habe]
HTML (Ausschnitt relevanter Bits)
<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 (Code-Snippet) @Drew hat ein benutzerdefiniertes ActionResult namens XmlActionResult erstellt, das ich für meinen Zweck geändert habe.
XML von der Aktion eines Controllers als ActionResult zurückgeben?
Meine Controller-Methode (gibt ActionResult zurück)
- Übergibt den Schlüsselparameter an einen gespeicherten SQL Server-Prozess, der ein XML generiert
- Dieses XML wird dann über xslt in eine MS-Excel-XML (XmlDocument) umgewandelt.
Erstellt eine Instanz des geänderten XmlActionResult und gibt sie zurück
XmlActionResult-Ergebnis = neues XmlActionResult (excelXML, "application / vnd.ms-excel"); Zeichenfolge version = DateTime.Now.ToString ("dd_MMM_yyyy_hhmmsstt"); string fileMask = "LabelExport_ {0} .xml";
result.DownloadFilename = string.Format (fileMask, version); Ergebnis zurückgeben;
Die Hauptänderung an der XmlActionResult-Klasse, die @Drew erstellt hat.
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);
}
Das war es im Grunde. Hoffe es hilft anderen.