title: Problem with using server-side clipboard in ASPX webapp
body: <p>I've got a reporting application that runs server-side that reads a stored BMP from my database (as a byte[]), converts it back to an image, and then places it into an Excel spreadsheet that forms the base for this report (this report is ultimately delivered to the client for download.) To do this I'm trying to use the server-side clipboard to handle the 'pasting' of the image into a specific range in the worksheet.  Here's the code snippet - </p>
  
  <pre><code>System.Drawing.Image image;
  Bitmap bm;
  Graphics g;
  Excel.Range range;
  
  MemoryStream ms = new MemoryStream(graphRecs.ElementAt(0).Graph, 0, 
     graphRecs.ElementAt(0).Graph.Length);
  ms.Write(graphRecs.ElementAt(0).Graph, 0, graphRecs.ElementAt(0).Graph.Length);
  image = System.Drawing.Image.FromStream(ms, true);
  
  bm = new Bitmap(413, 130);
  g = Graphics.FromImage(bm);
  g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
  g.DrawImage(image, 0, 1, 413, 130);
  
  Clipboard.SetDataObject(bm, false, 4, 250);
  range = ws.get_Range(cBlkPtr[6, 2], cBlkPtr[6, 2]);
  ws.Paste(range, bm);
  Clipboard.Clear();
  </code></pre>
  
  <p>Running this in debug mode under VS2008 seems to work fine - the image is converted, added to the clipboard, and pasted into the specified range with no problems.  After I publish the webapp to my IIS server, this fails on the 'Clipboard.SetDataObject' statement with the following exception - </p>
  
  <pre><code>Requested Clipboard operation did not succeed.
  at System.Windows.Forms.Clipboard.ThrowIfFailed(Int32 hr)
  at System.Windows.Forms.Clipboard.SetDataObject(Object data, Boolean copy, Int32 
         retryTimes, Int32 retryDelay) 
  at ReportGenerate.buildPvsClinicSections(Worksheet ws, Object j, patientRecord p, String 
         patientStatus, String programType)
  </code></pre>
  
  <p>I am under the assumption that this error has to do with NOT being in a SingleThreadApartment.  I've added the 'AspCompat=true' directive to my ASPX page with no change (didn't think it would help as AspCompat is more for ASP than ASPX).  Since I can't add the [STAThread] to my 'main' (that would be IIS), I'm at a loss on how to proceed.  I'm also open to changing the approach I'm using in adding the image to the spreadsheet, as long as I can explicitly specify (via the range) where to place it.  Using Shape.AddPicture for instance doesn't allow me to do this.</p>
  
  <p>Any ideas?</p>
  
  <p>Thanks.</p>
  
  <p>Update  </p>
  
  <p>I've updated the code snippet to start a second thread with the correct ApartmentState - </p>
  
  <pre><code>range = ws.get_Range(cBlkPtr[6, 2], cBlkPtr[6, 2]);
  
  ClipboardModel cbm = new ClipboardModel(bm, range, ws);
  System.Threading.Thread cbThread = new System.Threading.Thread(new
       System.Threading.ParameterizedThreadStart(DoClipboardStuff));
  cbThread.SetApartmentState(System.Threading.ApartmentState.STA);
  cbThread.Start(cbm);
  cbThread.Join();
  </code></pre>
  
  <p>The 'DoClipboardStuff' method looks like this - </p>
  
  <pre><code>[STAThread]
  protected void DoClipboardStuff(object o)
  {
    try
    {
      ClipboardModel cbm = (ClipboardModel)o;
  
      Clipboard.SetDataObject(cbm.bm, false, 4, 250);
      cbm.ws.Paste(cbm.range, cbm.bm);
      Clipboard.Clear();
    }
    catch (Exception e)
    {
      StreamWriter sw = new StreamWriter(@"C:\Myopia\Log.txt");
      sw.WriteLine(e.Message);
      sw.WriteLine(e.StackTrace);
      sw.Flush();
      sw.Close();
      throw e;
    }
  }
  </code></pre>
  
  <p>I'm now getting the exact same error as before, only now in this method.  I'm beginning to suspect that it's not the ApartmentState, but rather the lack of a 'UI'.  I don't know if the normal Win32 interface would be any better, but that's my next approach (unless someone else has a more, .NET'ish solution.)</p>
  
  <p>Update #2  </p>
  
  <p>While I haven't been able to resolve the issue with IIS 6 and the clipboard, I've managed to work around this problem by writing the reconstructed BMP to a temp file, then using the Shapes.AddPicture to place it where I need it to be - </p>
  
  <pre><code>g.DrawImage(image, 0, 1, 400, 75);
  
  bm.Save(@"c:\Myopia\temp.bmp");
  
  Excel.Shape xlShape = ws.Shapes.Item("Rectangle 2");
  float left = xlShape.Left;
  float top = Convert.ToSingle(ws.get_Range("A1", cBlkPtr[5, 2]).Height);
  float width = xlShape.Width;
  float height = xlShape.Height;
  
  xlShape = ws.Shapes.AddPicture(@"c:\Myopia\temp.bmp", 
     Microsoft.Office.Core.MsoTriState.msoFalse, Microsoft.Office.Core.MsoTriState.msoCTrue,
          left, top, width, height);
  </code></pre>
  
  <p>Not an ideal solution, but one that works for now.  The only issue with this approach is that I seem to have a loss of resolution between reconstructing the BMP from the byte[], saving it to the temp.bmp file, and then adding it back in - the bmp looks 'fuzzy'.  May have to look for a less 'lossy' format to use.</p>