none
Sending an in-memory word document to client,preferably in the sandbox

    Question

  • My task : Create a web part that

    • Allows the user to fill in a date.
    • On the press of a button generates a report with data from a sharepoint 2010 list.
    • Gives the user a download dialog in the browser with options to save, open ..(just standard download dialog)

    I have successfully used openxml tools to create the word document, although it may be corrupt it still opens as should. 

    Problem 1 : When i implement the webpart as a farm solution I am able to generate and open the report, but only once.  After the button is clicked and the download dialog completed, no buttons on the page are responding at all.  Only after i refresh the page is everything back to normal.

    Here is some relevant code from my button click event handler.

    MemoryStream ms = new MemoryStream();
    Report.CreatePackage(ms);
    
    Page.Response.ContentType = "application/ms-word";
    Page.Response.ContentEncoding=System.Text.Encoding.Unicode;
    Page.Response.AppendHeader("content-disposition", "attachment; filename=" + "Rapport.docx");
    Page.Response.AppendHeader("content-length", ms.GetBuffer().Length.ToString());
    Page.Response.BinaryWrite(ms.GetBuffer());
    
    ms.Flush();
    ms.Close();
    ms.Dispose();
    
    Page.Response.Flush();
    Page.Response.End();
    
    

    Problem 2 : When implementing the web part as a sandbox solution I am unable to send the file to the client.  Something funny with the Page object i gather.  Are there any other ways to do the file transfer in the sandbox?  I think a sandbox solution will avoid the issue in problem 1.

    I could post more code, but I think it would crowd the question.  Hope someone knows how to deal with this.

    Wednesday, December 21, 2011 11:40 AM

Answers

  • Using the javascript workaround still messes up the transaction state.  The work around that I have found to work now is using an invisible iframe that points to a ashx file.  This provides a new transaction object to leave in whatever state you wish.

    The file to be transferred must be temporarily stored somewhere so that only the path is included in the url to the ashx.  The easyest way to store it I found to be a document library on the current site.

    In the webpart code:

                string FileID = null,WebUrl = null;
                try
                {
                    FileID = saveToCurrentDocumentLibrary(FileName, AllBytes);
                    WebUrl = SPContext.Current.Web.Url;
                }
                catch   { }
                LiteralControl lc = new LiteralControl("<iframe Height='0' Width='0' src='/_layouts/DownloadPopUp.ashx?fileid=" + FileID + "&url=" + WebUrl + "'></iframe>");
                Controls.Add(lc);
    

     

    In the ashx file :

    public class Handler : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            string FileID = context.Request.QueryString["fileid"];
            string WebUrl = context.Request.QueryString["url"];
            if (string.IsNullOrEmpty(FileID))
                FileID = "null/empty";
            
            try
            {
                Guid UID = new Guid(FileID);
            }
            catch
            {
                context.Response.ContentType = "text/plain";
                context.Response.Write("Invalid file-id: " + FileID);
                return;
            }
    
            SPWeb Web = null;
            using (SPSite Site = new SPSite(WebUrl))
            {
                Web = Site.OpenWeb();
            }
            Web.AllowUnsafeUpdates = true;
            SPFile file = null;
            byte[] bytes = null;
            try
            {
                file = Web.GetFile(new Guid(FileID));
                bytes = file.OpenBinary();
                file.Delete();
                file.Update();
            }
            catch
            {
                context.Response.ContentType = "text/plain";
                context.Response.Write("Can not retrieve file with id: " + FileID);
                return;
            }
            finally
            {
                if (Web != null)
                    Web.Dispose();
            }
    
            context.Response.Clear();
            context.Response.BufferOutput = true;
            context.Response.ContentType = "application/ms-word";//ms-word
            context.Response.ContentEncoding = System.Text.Encoding.Unicode;
            context.Response.AppendHeader("content-disposition", "attachment; filename=" + "Rapport.docx");
            context.Response.AppendHeader("content-length", bytes.Length.ToString());
            context.Response.BinaryWrite(bytes);
            context.Response.Flush();
            context.Response.End();
        }
        public bool IsReusable { get { return false; } } }
    

    This works and does not break the page object.

    • Marked as answer by chwill Monday, January 23, 2012 10:15 AM
    • Unmarked as answer by chwill Monday, January 23, 2012 10:19 AM
    • Marked as answer by Margriet BruggemanMVP Tuesday, April 17, 2012 1:03 PM
    Monday, January 23, 2012 10:15 AM

All replies

  •             tag:"form" att:"onsubmit" val:"return _spFormOnSubmitWrapper()"
                blocks async postbacks after the first one
                not calling "_spFormOnSubmitWrapper()" breaks all postbacks
                returning true all the time, somewhat defeats the purpose of the
                _spFormOnSubmitWrapper() which is to block repetitive postbacks,

                but it allows MS AJAX Extensions to work properly

     

     

    function _spFormOnSubmitWrapper() {
        if (_spSuppressFormOnSubmitWrapper) {
            return true;
        }
        if (_spFormOnSubmitCalled) {
            return true;
        }
        if (typeof (_spFormOnSubmit) == "function") {
            var retval = _spFormOnSubmit();
            var testval = false;
            if (typeof (retval) == typeof (testval) && retval == testval) {
                return false;
            }
        }

        RestoreToOriginalFormAction();
        _spFormOnSubmitCalled = true;
        return true;
    }

     

     

    Thursday, December 22, 2011 12:17 PM
  • Solution to Problem 1.

    In webpart class

    protected override void OnLoad(EventArgs e)
            {
                base.OnLoad(e);
                Page.ClientScript.RegisterClientScriptInclude("jQuery", "/_layouts/ListReporterV02/SubmitPatcher.js");
            }
    

     

    In webpart class

    string Script = "<script type='text/javascript'>try{_spFormOnSubmitWrapper();}catch(e){alert(e);}" + "</script>";
                LiteralControl lit = new LiteralControl(Script);
                Controls.Add(lit);
    

     

    SubmitPatcher.js

    function _spFormOnSubmitWrapper() {
        if (_spSuppressFormOnSubmitWrapper) {
            return true;
        }
        if (_spFormOnSubmitCalled) {
            return true;
        }
        if (typeof (_spFormOnSubmit) == "function") {
            var retval = _spFormOnSubmit();
            var testval = false;
            if (typeof (retval) == typeof (testval) && retval == testval) {
                return false;
            }
        }
    
        RestoreToOriginalFormAction();
        _spFormOnSubmitCalled = true;
        return true;
    }
    


    This solution was given to me by "gonadn" and it works.

    • Marked as answer by chwill Wednesday, January 04, 2012 11:44 AM
    • Unmarked as answer by chwill Monday, January 23, 2012 10:17 AM
    Wednesday, January 04, 2012 11:43 AM
  •  

    function myFunction() {

        window.WebForm_OnSubmit = function ()
        { return true; };

    }

    • Proposed as answer by gonadn Friday, January 06, 2012 1:07 PM
    Friday, January 06, 2012 1:07 PM
  • Using the javascript workaround still messes up the transaction state.  The work around that I have found to work now is using an invisible iframe that points to a ashx file.  This provides a new transaction object to leave in whatever state you wish.

    The file to be transferred must be temporarily stored somewhere so that only the path is included in the url to the ashx.  The easyest way to store it I found to be a document library on the current site.

    In the webpart code:

                string FileID = null,WebUrl = null;
                try
                {
                    FileID = saveToCurrentDocumentLibrary(FileName, AllBytes);
                    WebUrl = SPContext.Current.Web.Url;
                }
                catch   { }
                LiteralControl lc = new LiteralControl("<iframe Height='0' Width='0' src='/_layouts/DownloadPopUp.ashx?fileid=" + FileID + "&url=" + WebUrl + "'></iframe>");
                Controls.Add(lc);
    

     

    In the ashx file :

    public class Handler : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            string FileID = context.Request.QueryString["fileid"];
            string WebUrl = context.Request.QueryString["url"];
            if (string.IsNullOrEmpty(FileID))
                FileID = "null/empty";
            
            try
            {
                Guid UID = new Guid(FileID);
            }
            catch
            {
                context.Response.ContentType = "text/plain";
                context.Response.Write("Invalid file-id: " + FileID);
                return;
            }
    
            SPWeb Web = null;
            using (SPSite Site = new SPSite(WebUrl))
            {
                Web = Site.OpenWeb();
            }
            Web.AllowUnsafeUpdates = true;
            SPFile file = null;
            byte[] bytes = null;
            try
            {
                file = Web.GetFile(new Guid(FileID));
                bytes = file.OpenBinary();
                file.Delete();
                file.Update();
            }
            catch
            {
                context.Response.ContentType = "text/plain";
                context.Response.Write("Can not retrieve file with id: " + FileID);
                return;
            }
            finally
            {
                if (Web != null)
                    Web.Dispose();
            }
    
            context.Response.Clear();
            context.Response.BufferOutput = true;
            context.Response.ContentType = "application/ms-word";//ms-word
            context.Response.ContentEncoding = System.Text.Encoding.Unicode;
            context.Response.AppendHeader("content-disposition", "attachment; filename=" + "Rapport.docx");
            context.Response.AppendHeader("content-length", bytes.Length.ToString());
            context.Response.BinaryWrite(bytes);
            context.Response.Flush();
            context.Response.End();
        }
        public bool IsReusable { get { return false; } } }
    

    This works and does not break the page object.

    • Marked as answer by chwill Monday, January 23, 2012 10:15 AM
    • Unmarked as answer by chwill Monday, January 23, 2012 10:19 AM
    • Marked as answer by Margriet BruggemanMVP Tuesday, April 17, 2012 1:03 PM
    Monday, January 23, 2012 10:15 AM
  • Hi all,

    I have an ashx handler that feeds file content to an iframe just like the code here that only work in IE and FireFox but not in Chrome (iframe shows blank). Any ideas?

    Monday, May 21, 2012 7:22 AM