Search code examples
asp.net-mvcmultithreadingdownloadresponse

Download png image using a sta thread in MVC


In my MVC application, I am using web browser control in STA thread. I want to send a png image to client at the end of the STA thread.

This is my code

         public void Export()    //Action method for exporting
         {
             //Saving widget as image using web browser control
             System.Threading.Thread tr = new System.Threading.Thread(() => ExportWidget());
             tr.SetApartmentState(ApartmentState.STA);
             tr.Start();
         }

         public void ExportWidget() //STA method for downloading
         {

             WebBrowser browser = new WebBrowser() { ScriptErrorsSuppressed=true, WebBrowserShortcutsEnabled = false, Size = new System.Drawing.Size(1000, 1000) };

             browser.DocumentText = new StringBuilder()
                 //Add header for HTML document
                 .Append("<!DOCTYPE html><html><head><meta http-equiv='X-UA-Compatible' content='IE=edge' />"

                 //Add scripts required for rendering widget in browser control
                 + AddScripts(browser, widgetOption)

                 + "</head><body><div id='widgetContainer'></div></body></html>").ToString();


             //Check browser is loaded or not. If it is not ready wait until browser loading is complete
             while (browser.ReadyState == WebBrowserReadyState.Loading)
             {
                 Application.DoEvents();
                 Thread.Sleep(5);
             }

             MemoryStream stream = new MemoryStream();

             using (Bitmap img = new Bitmap(ParseInt(bounds[1]), ParseInt(bounds[0])))
             {
                 browser.DrawToBitmap(img, new Rectangle(ParseInt(bounds[2]), ParseInt(bounds[3]), img.Width, img.Height));          
                 img.Save(stream, ImageFormat.Png);

                 browser.Dispose();                
             }

             Response.Clear();

             stream.WriteTo(Response.OutputStream);
             stream.Dispose();
             string fileName = "WidgetExport.png";
             Response.ContentType = "application/octet-stream";

             //System.ArgumentException throws here
             Response.AddHeader("Content-Disposition", "attachment;filename=" + fileName);
             Response.Flush();
         }

I am checking the possibility of exporting a JavaScript widget in server-side.

Is this the correct approach ? Above code throws System.ArgumentException when setting "Content-Disposition" in header

Please share your suggestions


Solution

  • After analyzing for a few hours, I came up with the following solution to download a JavaScript widget completely in server-side.

    It is not elegant but currently this is the only solution I know that works

             public void Export()    //Action method for exporting
             {
                 //Saving widget as image using web browser control
                 System.Threading.Thread tr = new System.Threading.Thread(() => ExportWidget());
                 tr.SetApartmentState(ApartmentState.STA);
                 tr.Start();
    
                 string fileName = Server.MapPath(@"\WidgetExport.png");            
    
                 //Make current thread waits until widget image is ready in STA thread
                 while(tr.IsAlive)
                 {
                     Thread.Sleep(5);
                 }
    
                 //Read widget image as bytes
                 byte[] file = System.IO.File.ReadAllBytes(fileName);
    
                 //Delete the file after reading
                 System.IO.File.Delete(fileName);
    
                 //Allowing client to download the image
                 Response.OutputStream.Write(file, 0, file.Length);
                 Response.ContentType = "application/octet-stream";             
                 Response.AddHeader("Content-Disposition", "attachment;filename=" + fileName);
                 Response.Flush(); 
             }
    
         public void ExportWidget() //STA thread method for downloading
         {
    
             WebBrowser browser = new WebBrowser() { ScriptErrorsSuppressed=true, WebBrowserShortcutsEnabled = false, Size = new System.Drawing.Size(1000, 1000) };
    
             browser.DocumentText = new StringBuilder()
                 //Add header for HTML document
                 .Append("<!DOCTYPE html><html><head><meta http-equiv='X-UA-Compatible' content='IE=edge' />"
    
                 //Add scripts required for rendering widget in browser control
                 + AddScripts(browser, widgetOption)
    
                 + "</head><body><div id='widgetContainer'></div></body></html>").ToString();
    
    
             //Check browser is loaded or not. If it is not ready wait until browser loading is complete
             while (browser.ReadyState == WebBrowserReadyState.Loading)
             {
                 Application.DoEvents();
                 Thread.Sleep(5);
             }
    
             MemoryStream stream = new MemoryStream();
    
             using (Bitmap img = new Bitmap(ParseInt(bounds[1]), ParseInt(bounds[0])))
             {
                 browser.DrawToBitmap(img, new Rectangle(ParseInt(bounds[2]), ParseInt(bounds[3]), img.Width, img.Height));          
                 img.Save(Server.MapPath(@"\WidgetExport.png"), ImageFormat.Png);
    
                 browser.Dispose();                
             }
         }
    

    Above code does the following

    1. Client request reaches server

    2. Server populates the JavaScript widget using a C# wrapper

    3. WebBrowser control does not work in the thread of action method because it only works in STA thread. The thread of action method should be stopped till the STA thread completes

    4. So start a new STA thread and create the WebBrowser control in it.

    5. Add the HTML content and scripts required for the widget in WebBrowser control

    6. Serialize the C# wrapper to equivalent JavaScript as string and add it to the WebBrowser control

    7. Execute the serialized JavaScript code using InvokeScript method of WebBrowser control. This will render the widget in WebBrowser control

    8. Use DrawToBitmap method of WebBrowser control to draw the image of JavaScript widget in a Bitmap

    9. Save and dispose the bitmap. Dispose the WebBrowser control. Execution of STA thread completes now

    10. Now thread of action method should be available. Get the data from the widget image which was saved in STA thread

    11. Delete the image from server after reading the data

    12. Allow the image to be downloaded in client using Response.OutputStream