How to paste a screenshot from Google Chrome to ASP.NET MVC

If you’re like me, someone who has been building web applications for 15 years or so, then like me, you probably freaked out the first time you pasted a screenshot into your gmail.  You thought, “what just happened?”  You thought, “wait, this shouldn’t be possible!”  And your immediate next thought was, “omg, how do I do that?”

This is not a ubiquitous functionality at the moment–I’m not able to paste a screenshot into Yahoo! Mail or WordPress right now, nor did I have a need to figure out a way to paste anything using Internet Explorer or Firefox.  In building a Knowledge Base for our www.securevideo.com support team to be able to serve content onto our website, we decided to implement the ability for them to paste a screenshot to our server using AJAX, have the server show the URL, and then allow them to upload HTML and include the screenshots by creating image tags using the TinyMCE HTML Editor.

Anyone can implement TinyMCE by googling, but the tricky part was getting the paste and AJAX to work, and mind you, as of the time of this writing, this only works in Chrome.  That’s fine for me since our support team uses Chrome, but if you can’t control the browser choice, then this method will not be as valuable to you.

First, you need to capture the paste event on your web page.  This is done using some Chrome-specific Javascript to handle the paste event, and jquery to send the image to the server via AJAX.

        
   document.onkeydown = function (e) { return on_keyboard_action(e); }
   document.onkeyup = function (e) { return on_keyboardup_action(e); }

   var ctrl_pressed = false;

   function on_keyboard_action(event) {
       k = event.keyCode;
       //ctrl
       if (k == 17) {
           if (ctrl_pressed == false)
               ctrl_pressed = true;
           if (!window.Clipboard)
               pasteCatcher.focus();
       }
   }
   function on_keyboardup_action(event) {
       //ctrl
       if (k == 17)
           ctrl_pressed = false;
   }

   // Paste in from Chrome clipboard
   window.addEventListener("paste", pasteHandler);
   function pasteHandler(e) {
       if (e.clipboardData) {
           var items = e.clipboardData.items;
           if (items) {
               for (var i = 0; i < items.length; i++) {
                   // Only process anything if we have an image
                   if (items[i].type.indexOf("image") !== -1) {
                       // Get the pasted item as a File Blob
                       var blob = items[i].getAsFile();

                       // Reader will read the file
                       var reader = new FileReader();

                       // This fires after we have a base64 load of the file 
                       reader.onload = function (event) {
                           // Once reader loads, sent the blob to the server
                           $.ajax({
                               type: "POST",
                               url: '/Knowledge/Screencap',
                               data: event.target.result,
                               success: function (resultHtml) {
                                   // Show the uploaded image
                                   $("#screencap-container").html(resultHtml);
                               }
                           });
                       };
                       // Convert the blob from clipboard to base64
                       // After this finishes, reader.onload event will fire
                       reader.readAsDataURL(blob);
                   }
               }
           }
       }
   }

Once you’ve got the paste and AJAX calls set up, the user pastes an image, and then the AJAX call sends your base64 encoded image to the server.  Here’s the actual content sent in the HTTP POST:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABVYAAA...

On the ASP.NET MVC side, I was not able to get the controller to automatically bind the posted data into a controller parameter.  It’s probably possible, but I’m under some time pressure, so I just examined the HTTP Request’s Input Stream, and picked the image from there.

      
   public ActionResult Screencap()
   {
      // Get the raw input stream (return to the start of the stream first!)
      Request.InputStream.Position = 0;
      string payload = new StreamReader(Request.InputStream).ReadToEnd();

      string indicator = "base64,";
      int imageStartIdx = payload.IndexOf(indicator);
      if (imageStartIdx >= 0)
      {
          string base64Image = payload.Substring(imageStartIdx + indicator.Length);
          byte[] fileBytes = Convert.FromBase64String(base64Image);
          System.IO.File.WriteAllBytes(saveToPath, fileBytes);
      }
      // Return the URL of the newly saved file for display on the browser
      return Content(PathManager.ToUrl(saveToPath));
   }

Now my support staff can add Knowledge Articles, including lots and lots of screenshots (a good thing), without ever leaving the browser window!