Tuesday, August 31, 2010

IFRAME based file download – Silverlight and beyond

Problem statement: You have to initiate file download in the browser with target = _self and in case of error on the server side you should not navigate away from the current page.

Why there is a problem: You initiate navigation to a download page. In case of successfully obtaining file from a server – you stay on the original page and FF/IE download window is shown. In case when there is an error on server side – result of your navigation with target _self will be an error page shown.

Though code provided here has some specific to Silverlight pieces, the idea is generic enough.

First, lets prepare a client side on the “initiator” page:

    <script type="text/javascript">
        $(document).ready(function () {
            $('#form1').append('<iframe id="downloadFrame" style="visibility: hidden; height: 0px; width: 0px; border: 0px"/>');
        });
 
        function InitiateDownload(url) {
            $('#downloadFrame').attr('src', url);
        }
    </script>

Lets introduce a tiny helper class to wrap a call to InitiateDownload for Silverlight:

    public static class JavascriptHelper
    {
        public const string DownloadFunctionName = "InitiateDownload";
 
        public static void StartDownload(Uri targetUri)
        {
            HtmlPage.Window.Invoke(DownloadFunctionName, new object[] {targetUri.ToString()});
        }
    }

Now, we can call InitiateDownload from Silverlight (via a command here which is not required and don’t forget about cross domain hurdles if it is your case):

DownloadCommand = new DelegateCommand<string>(g => 
JavascriptHelper.StartDownload(downloadUrl));

That is enough to get to the download dialog:

filedownload2

But lets add an ability to show a message box in case of a server-side error:

download

Following shows an example of amended download page (providing a placeholder for error message):

<head runat="server">
    <script src="scripts/jquery-1.4.2.min.js" type="text/javascript"></script>
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <p runat="server" id="ErrorHolder"></p>
    </div>
    </form>
    <script type="text/javascript">
        var errorText = $('p#ErrorHolder').text();
        alert(errorText);
    </script>
</body>
</html>

With code behind concept:

protected void Page_Load(object sender, EventArgs e)
        {
            try
            {
                // Do your regular download stuff
            }
            catch(Exception ex)
            {
                // TODO: Log and only show user friendly sanitized message
                ErrorHolder.InnerText = ex.Message;
            }
        }
This is it and happy coding to You!

No comments: