W końcu udało mi się zdobyć tę pracę i pomyślałem, że udokumentuję, jak tutaj w nadziei uratowania bólu innym.
Środowisko
- VS2012
- SQL Server 2008R2
- .NET 4.5
- ASP.NET MVC4 (Razor)
- System Windows 7
Obsługiwane przeglądarki internetowe
- FireFox 23
- IE 10
- Chrome 29
- Opera 16
- Safari 5.1.7 (ostatnia dla Windows?)
Moje zadanie polegało na kliknięciu przycisku interfejsu użytkownika, wywołaniu metody na moim kontrolerze (z kilkoma parametrami), a następnie zwróceniu pliku XML MS-Excel za pośrednictwem transformacji xslt. Zwrócony plik XML MS-Excel spowodowałby wówczas wyświetlenie okna dialogowego Otwórz / Zapisz w przeglądarce. Musiało to działać we wszystkich przeglądarkach (wymienionych powyżej).
Na początku próbowałem z Ajaxem i stworzyć dynamiczną kotwicę z atrybutem „download” dla nazwy pliku, ale działało to tylko dla około 3 z 5 przeglądarek (FF, Chrome, Opera), a nie dla IE czy Safari. Występowały też problemy z próbą programowego wywołania zdarzenia Click kotwicy w celu spowodowania faktycznego „pobrania”.
Skończyło się na tym, że użyłem „niewidzialnej” ramki IFRAME i zadziałało we wszystkich 5 przeglądarkach!
Oto, co wymyśliłem: [pamiętaj, że w żadnym wypadku nie jestem guru html / javascript i włączyłem tylko odpowiedni kod]
HTML (fragment odpowiednich bitów)
<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 (fragment kodu) @Drew utworzył niestandardową ActionResult o nazwie XmlActionResult, którą zmodyfikowałem w swoim celu.
Zwrócić XML z akcji kontrolera w postaci ActionResult?
Metoda My Controller (zwraca ActionResult)
- przekazuje parametr keys do przechowywanego procesu SQL Server, który generuje XML
- ten XML jest następnie przekształcany przez xslt w plik XML MS-Excel (XmlDocument)
tworzy wystąpienie zmodyfikowanego XmlActionResult i zwraca je
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, wersja); wynik zwrotu;
Główna modyfikacja klasy XmlActionResult utworzonej przez @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);
}
To było w zasadzie to. Mam nadzieję, że to pomaga innym.