Enfin réussir à obtenir ce travail et j'ai pensé que je documenterais comment ici dans l'espoir de sauver les autres de la douleur.
Environnement
- VS2012
- SQL Server 2008R2
- .NET 4.5
- ASP.NET MVC4 (rasoir)
- Windows 7
Navigateurs Web pris en charge
- Renard de feu 23
- IE 10
- Chrome 29
- Opéra 16
- Safari 5.1.7 (le dernier pour Windows?)
Ma tâche consistait à cliquer sur un bouton d'interface utilisateur, à appeler une méthode sur mon contrôleur (avec quelques paramètres), puis à lui faire renvoyer un XML MS-Excel via une transformation xslt. Le XML MS-Excel renvoyé amènerait alors le navigateur à faire apparaître la boîte de dialogue Ouvrir / Enregistrer. Cela devait fonctionner dans tous les navigateurs (listés ci-dessus).
Au début, j'ai essayé avec Ajax et de créer une ancre dynamique avec l'attribut "download" pour le nom de fichier, mais cela ne fonctionnait que pour environ 3 des 5 navigateurs (FF, Chrome, Opera) et non pour IE ou Safari. Et il y avait des problèmes avec la tentative de déclencher par programme l'événement Click de l'ancre pour provoquer le "téléchargement" réel.
Ce que j'ai fini par faire était d'utiliser un IFRAME "invisible" et cela a fonctionné pour les 5 navigateurs!
Voici donc ce que j'ai proposé: [veuillez noter que je ne suis en aucun cas un gourou html / javascript et que je n'ai inclus que le code pertinent]
HTML (extrait de bits pertinents)
<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 (extrait de code) @Drew a créé un ActionResult personnalisé appelé XmlActionResult que j'ai modifié à mes fins.
Renvoyer du XML à partir de l'action d'un contrôleur en tant qu'ActionResult?
Ma méthode Controller (retourne ActionResult)
- transmet le paramètre keys à un proc stocké SQL Server qui génère un XML
- ce XML est ensuite transformé via xslt en un XML MS-Excel (XmlDocument)
crée une instance du XmlActionResult modifié et la renvoie
XmlActionResult result = new XmlActionResult (excelXML, "application / vnd.ms-excel"); version de la chaîne = DateTime.Now.ToString ("jj_MMM_aaaa_hhmmsstt"); string fileMask = "LabelExport_ {0} .xml";
result.DownloadFilename = string.Format (fileMask, version); résultat de retour;
La principale modification de la classe XmlActionResult créée par @Drew.
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);
}
C'était essentiellement ça. J'espère que cela aide les autres.