Web Page Screen Shot for ASP.Net

9/29/2008

If you've looked at my site and scrolled over any of the links, you've probably noticed that a popup comes up (well in IE it does any way, if you have javascript turned on).  So maybe you haven't seen it, but if you have you'll notice that it's a service by Snap. You add in their javascript and they'll automatically add a contextual popup (usually an image of the site) for each link. Depending on your view, this is either very annoying or fantastic.  The main issue with the service that Snap provides is it can't view an internal website. So what can you do if you want to have the same functionality on an intranet?

In a recent post, I mentioned how to take a screenshot of a web page on the fly and convert it to a PDF. We can use the same code (so go download it), to create a similar functionality to the one Snap provides.

LinkVisualizerExtender.cs

   1: using System;
   2: using System.Web.UI.WebControls;
   3: using System.Web.UI;
   4: using System.ComponentModel;
   5: using System.ComponentModel.Design;
   6: using AjaxControlToolkit;
   7:  
   8: [assembly: System.Web.UI.WebResource("AJAXControls.LinkVisualizer.LinkVisualizerBehavior.js", "text/javascript")]
   9:  
  10: namespace AJAXControls.LinkVisualizer
  11: {
  12:     [Designer(typeof(LinkVisualizerDesigner))]
  13:     [ClientScriptResource("AJAXControls.LinkVisualizer.LinkVisualizerBehavior", "AJAXControls.LinkVisualizer.LinkVisualizerBehavior.js")]
  14:     [TargetControlType(typeof(Control))]
  15:     public class LinkVisualizerExtender : ExtenderControlBase
  16:     {
  17:         // TODO: Add your property accessors here.
  18:         //
  19:     }
  20: }

LinkVisualizerBehavior.js

   1: // README
   2: //
   3: // There are two steps to adding a property:
   4: //
   5: // 1. Create a member variable to store your property
   6: // 2. Add the get_ and set_ accessors for your property
   7: //
   8: // Remember that both are case sensitive!
   9:  
  10:  
  11: /// <reference name="MicrosoftAjaxTimer.debug.js" />
  12: /// <reference name="MicrosoftAjaxWebForms.debug.js" />
  13: /// <reference name="AjaxControlToolkit.ExtenderBase.BaseScripts.js" assembly="AjaxControlToolkit" />
  14:  
  15:  
  16: Type.registerNamespace('AJAXControls.LinkVisualizer');
  17:  
  18: AJAXControls.LinkVisualizer.LinkVisualizerBehavior = function(element) {
  19:     AJAXControls.LinkVisualizer.LinkVisualizerBehavior.initializeBase(this, [element]);
  20:  
  21:     // TODO : (Step 1) Add your property variables here
  22:     var PopupDiv = null;
  23:     var ChildImage = null;
  24: }
  25: AJAXControls.LinkVisualizer.LinkVisualizerBehavior.prototype = {
  26:     initialize: function() {
  27:         AJAXControls.LinkVisualizer.LinkVisualizerBehavior.callBaseMethod(this, 'initialize');
  28:  
  29:         // TODO: Add your initalization code here
  30:         var Elements = this.get_element().getElementsByTagName("a");
  31:         for (var x = 0; x < Elements.length; ++x) {
  32:             Sys.UI.DomEvent.addHandler(Elements[x], 'mouseover',
  33:                 Function.createDelegate(this, this._onmouseover));
  34:             Sys.UI.DomEvent.addHandler(Elements[x], 'mouseout',
  35:                 Function.createDelegate(this, this._onmouseout));
  36:         }
  37:         this.PopupDiv = document.createElement("div");
  38:         this.PopupDiv.style.display = "none";
  39:         this.PopupDiv.style.height = "500px";
  40:         this.PopupDiv.style.position = "absolute";
  41:         this.ChildImage = document.createElement("img");
  42:         this.ChildImage.src = "";
  43:         this.PopupDiv.appendChild(this.ChildImage);
  44:         document.body.appendChild(this.PopupDiv);
  45:     },
  46:  
  47:     cumulativeOffset: function(Element) {
  48:         var Left = 0;
  49:         var Top = 0;
  50:         do {
  51:             Top += Element.offsetTop || 0;
  52:             Left += Element.offsetLeft || 0;
  53:             Element = Element.offsetParent;
  54:         } while (Element);
  55:         var ReturnValues = [Left, Top];
  56:         return ReturnValues;
  57:     },
  58:  
  59:     _onmouseover: function(EventElement) {
  60:         var Positions = this.cumulativeOffset(EventElement.target);
  61:         this.PopupDiv.style.left = Positions[0] + "px";
  62:         this.PopupDiv.style.top = Positions[1]+EventElement.target.offsetHeight + "px";
  63:         this.PopupDiv.style.display = "block";
  64:         this.ChildImage.src = "PageToImage.axd?MaxSize=500&Page=" + EventElement.target.href;
  65:     },
  66:  
  67:     _onmouseout: function(EventElement) {
  68:         this.PopupDiv.style.display = "none";
  69:     },
  70:  
  71:     dispose: function() {
  72:         // TODO: Add your cleanup code here
  73:  
  74:         AJAXControls.LinkVisualizer.LinkVisualizerBehavior.callBaseMethod(this, 'dispose');
  75:     }
  76: }
  77: AJAXControls.LinkVisualizer.LinkVisualizerBehavior.registerClass('AJAXControls.LinkVisualizer.LinkVisualizerBehavior', AjaxControlToolkit.BehaviorBase);

PageToImageHandler.cs

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.Web;
   6: using System.IO;
   7:  
   8: namespace HTTPHandlers
   9: {
  10:     public class PageToImageHandler:IHttpHandler
  11:     {
  12:         #region IHttpHandler Members
  13:  
  14:         public bool IsReusable
  15:         {
  16:             get { return false; }
  17:         }
  18:  
  19:         public void ProcessRequest(HttpContext context)
  20:         {
  21:             if (string.IsNullOrEmpty(context.Request.QueryString["Page"]))
  22:                 return;
  23:             string FileName = context.Request.QueryString["Page"];
  24:             int Size = 0;
  25:             if (!string.IsNullOrEmpty(context.Request.QueryString["MaxSize"]))
  26:             {
  27:                 if (int.TryParse(context.Request.QueryString["MaxSize"], out Size))
  28:                 {
  29:                     if (Size > 0 && Size < 1000)
  30:                     {
  31:  
  32:                         string Folder = "Location To Save Images";
  33:                         FileInfo Info = new FileInfo(context.Server.MapPath(Folder + Size.ToString() + FileName.GetHashCode().ToString() + ".bmp"));
  34:                         if (!Info.Exists)
  35:                         {
  36:                             Utils.WebPageThumbnail.WebPageThumbnail Thumbnail = new Utils.WebPageThumbnail.WebPageThumbnail();
  37:                             Thumbnail.GenerateBitmap(context.Server.MapPath(Folder + FileName.GetHashCode().ToString() + ".bmp"), FileName, -1, -1);
  38:                             Utils.Image.ResizeImage(context.Server.MapPath(Folder + FileName.GetHashCode().ToString() + ".bmp"), context.Server.MapPath(Folder + Size.ToString() + FileName.GetHashCode().ToString() + ".bmp"), Size);
  39:                         }
  40:  
  41:                         FileName = context.Server.MapPath(Folder + Size.ToString() + FileName.GetHashCode().ToString() + ".bmp");
  42:  
  43:                         Info = new FileInfo(FileName);
  44:                         if (Info.Exists)
  45:                         {
  46:                             int Index = FileName.LastIndexOf(".") + 1;
  47:                             string FileExtension = FileName.Substring(Index).ToUpperInvariant();
  48:  
  49:                             // Fix for IE not handling jpg image types
  50:                             if (string.Compare(FileExtension, "JPG") == 0)
  51:                                 context.Response.ContentType = "image/jpeg";
  52:                             else
  53:                                 context.Response.ContentType = "image/" + FileExtension;
  54:  
  55:                             string ETag = "\"" + Info.CreationTimeUtc.GetHashCode() + "\"";
  56:                             string IncomingEtag = context.Request.Headers["If-None-Match"];
  57:                             string ModifiedSince = context.Request.Headers["If-Modified-Since"];
  58:                             DateTime ModifiedSinceDate = DateTime.Now;
  59:                             if (!DateTime.TryParse(ModifiedSince, out ModifiedSinceDate))
  60:                             {
  61:                                 ModifiedSinceDate = DateTime.MinValue;
  62:                             }
  63:  
  64:                             DateTime date = DateTime.Now;
  65:                             if (context.Cache[FileName + "date"] != null)
  66:                             {
  67:                                 date = (DateTime)context.Cache[FileName + "date"];
  68:                             }
  69:                             else
  70:                             {
  71:                                 context.Cache[FileName + "date"] = date;
  72:                             }
  73:  
  74:                             if (Info.LastWriteTime > date)
  75:                             {
  76:                                 date = DateTime.Now;
  77:                                 context.Cache[FileName + "date"] = date;
  78:                             }
  79:  
  80:                             date = DateTime.Parse(date.ToString("r"));
  81:  
  82:                             context.Response.Cache.SetCacheability(HttpCacheability.Public);
  83:                             context.Response.Cache.SetExpires(DateTime.Now.AddDays(14.0d));
  84:                             context.Response.Cache.SetLastModified(Info.CreationTimeUtc);
  85:                             context.Response.Cache.SetETag(ETag);
  86:                             if (String.Compare(IncomingEtag, ETag) == 0 || date.CompareTo(ModifiedSinceDate) <= 0)
  87:                             {
  88:                                 context.Response.StatusCode = (int)System.Net.HttpStatusCode.NotModified;
  89:                             }
  90:                             else
  91:                             {
  92:                                 context.Response.TransmitFile(Info.FullName);
  93:                             }
  94:                         }
  95:                         else
  96:                         {
  97:                             context.Response.Status = "404 Bad Request";
  98:                         }
  99:                     }
 100:                 }
 101:             }
 102:         }
 103:  
 104:         #endregion
 105:     }
 106: }

The code above is really two items. The first is an extender. This extender simply takes a div as input and will attach itself to every link inside that div (mouseover and mouseout events). When the mouseover event is fired, it positions a div underneath the link, and calls a handler (the second item in the zip file) to load an image. The handler is called PageToImage. This item takes in a max size for the resulting image and a URL which you want to change into an image. This handler uses the code from the other post to get the screenshot, so you'll need it as well.

Please note that this is very basic example and was intended more as a proof of concept than anything. I'd definately add a div as a secondary target that would hold the image of the link (so I could format it however I wanted), add a property to define the size of the image (at present it's static at a max size of 500 pixels), maybe another property to define where I wanted to place the item (top, bottom, left, etc.), and maybe some sort of animation if you really want. None of that would be too difficult to add really.

The handler also needs to be changed as it doesn't know where you want to place the images (there's a FolderName string object that needs to be filled in). Other than that though, it works fine (even caches the item on the client side as well as on the server). The only thing I might add there is some code to have the server side cached item die off after X number of days. That would be it though.

Anyway, download the code, try it out, and happy coding.



Comments