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
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
Client request reaches server
Server populates the JavaScript widget using a C# wrapper
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
So start a new STA thread and create the WebBrowser control in it.
Add the HTML content and scripts required for the widget in WebBrowser control
Serialize the C# wrapper to equivalent JavaScript as string and add it to the WebBrowser control
Execute the serialized JavaScript code using InvokeScript method of WebBrowser control. This will render the widget in WebBrowser control
Use DrawToBitmap method of WebBrowser control to draw the image of JavaScript widget in a Bitmap
Save and dispose the bitmap. Dispose the WebBrowser control. Execution of STA thread completes now
Now thread of action method should be available. Get the data from the widget image which was saved in STA thread
Delete the image from server after reading the data
Allow the image to be downloaded in client using Response.OutputStream