June 2008 - Posts

I have created an ASP.NET page to automate the Microsoft Source Code Analyzer for SQL Injection command line tool. It would be tedious to craft a command for every page in a large Classic ASP web site. I was unable to scan my entire site until I developed this ASP.NET page.

Why an ASP.NET page and not a console application? Well "I've learned to use a hammer, so I'm gonna hammer everything" as Jeff Atwood would say. LOL. But seriously, I wanted to generate a HTML report so it may as well be a single ASP.NET page. You can easily convert it to a console application or even a Windows application. I did learn how to redirect standard input, output, and error streams which is good to know.

msscasi.aspx

   1: <%@ Page Language="C#" Theme="Granite" MasterPageFile="~/AppMaster.master" AutoEventWireup="true" CodeFile="msscasi.aspx.cs" Inherits="msscasi" Title="ASP Web Site Parser" %>
   2: <asp:Content ID="Content1" ContentPlaceHolderID="mainCopy" Runat="Server">
   3:     <div class="container">
   4:         <h2>ASP Web Site Parser</h2>
   5:         <p class="teaser">
   6:             Scans all ASP pages in a web site for SQL Injection vulnerabilities.</p>
   7:         <p>Enter ASP site file location:&nbsp;<asp:TextBox ID="txtPath" runat="server"></asp:TextBox></p>            
   8:         <p><asp:Button ID="btnSubmit" Text="Submit" runat="server" OnClick="btnSubmit_Click" /></p>
   9:         <p><asp:Label ID="lblMessage" runat="server"></asp:Label></p>
  10:         <p><asp:Label ID="lblErrorMessage" ForeColor="red" runat="server"></asp:Label></p>
  11:     </div>        
  12: </asp:Content>
  13: <asp:Content ID="Content2" ContentPlaceHolderID="leftColumn" Runat="Server">
  14: </asp:Content>
  15: <asp:Content ID="Content3" ContentPlaceHolderID="rightColumn" Runat="Server">
  16: </asp:Content>

msscasi.aspx.cs

   1: using System;
   2: using System.Data;
   3: using System.Configuration;
   4: using System.Collections;
   5: using System.Web;
   6: using System.Web.Security;
   7: using System.Web.UI;
   8: using System.Web.UI.WebControls;
   9: using System.Web.UI.WebControls.WebParts;
  10: using System.Web.UI.HtmlControls;
  11: using System.IO;
  12: using System.Diagnostics;
  13: using System.Text;
  14:  
  15: public partial class msscasi : System.Web.UI.Page
  16: {
  17:  
  18:     StringBuilder sbStandardOutput = new StringBuilder();
  19:     StringBuilder sbStandardError = new StringBuilder();
  20:     public const string MSSCASI_PATH = "\"G:\\msscasi\\msscasi_asp.exe\"";
  21:  
  22:     /// <summary>
  23:     /// Handles the Click event of the btnSubmit control.
  24:     /// </summary>
  25:     /// <param name="sender">The source of the event.</param>
  26:     /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
  27:     protected void btnSubmit_Click(object sender, EventArgs e)
  28:     {
  29:         if (String.IsNullOrEmpty(txtPath.Text))
  30:         {
  31:             lblErrorMessage.Text = "You neglected to enter a file path to the ASP site!";
  32:             return;
  33:         }
  34:         else
  35:         {
  36:             // proceed with recursive file processing
  37:             try
  38:             {
  39:                 DirectoryInfo objDirectoryInfo = new DirectoryInfo(txtPath.Text);
  40:                 ListDirectoryFiles(objDirectoryInfo);
  41:                 lblMessage.Text = sbStandardOutput.ToString();
  42:                 lblErrorMessage.Text = sbStandardError.ToString();
  43:                 lblErrorMessage.Visible = true;
  44:  
  45:                 // write a report to a file as well
  46:                 FileInfo objFileInfo = new FileInfo(Server.MapPath("App_Data\\report.html"));
  47:                 StreamWriter objStreamWriter = objFileInfo.CreateText();
  48:                 objStreamWriter.Write(sbStandardOutput.ToString());
  49:                 objStreamWriter.Flush();
  50:                 objStreamWriter.Close();
  51:                 objStreamWriter.Dispose();
  52:             }
  53:             catch (Exception err)
  54:             {
  55:                 // display error
  56:                 lblErrorMessage.Text = err.ToString();
  57:                 lblErrorMessage.Visible = true;
  58:             }
  59:  
  60:         }
  61:     }
  62:  
  63:     /// <summary>
  64:     /// Lists the directory files.
  65:     /// </summary>
  66:     /// <param name="objDirectoryInfo">The obj directory info.</param>
  67:     /// <remarks>This is a recursive function.</remarks>
  68:     public void ListDirectoryFiles(DirectoryInfo objDirectoryInfo)
  69:     {
  70:         string strGlobalAsa = "";
  71:         // loop through files
  72:         foreach (FileSystemInfo objFileSystemInfo in objDirectoryInfo.GetFileSystemInfos())
  73:         {
  74:             if (objFileSystemInfo is FileInfo)
  75:             {
  76:                 FileInfo objFileInfo = ((FileInfo)objFileSystemInfo);
  77:                 if (objFileInfo.Extension == ".asp" || objFileInfo.Extension == ".asa")
  78:                 {
  79:                     // determine the file path to the global.asa file
  80:                     if (objFileInfo.Name.ToLower().Trim() == "global.asa")
  81:                     {
  82:                         strGlobalAsa = objFileInfo.FullName;
  83:                     }
  84:                     // shell command
  85:                     System.Diagnostics.Process objProcess = new System.Diagnostics.Process();
  86:                     objProcess.StartInfo.FileName = MSSCASI_PATH;
  87:                     objProcess.StartInfo.Arguments = "/GlobalAsaPath=" + strGlobalAsa;
  88:                     objProcess.StartInfo.Arguments = "/input=" + objFileInfo.FullName;
  89:                     // Setting this property to false enables you to redirect input, output, and error streams
  90:                     objProcess.StartInfo.UseShellExecute = false;
  91:                     objProcess.StartInfo.CreateNoWindow = true;
  92:                     objProcess.StartInfo.RedirectStandardInput = true;
  93:                     objProcess.StartInfo.RedirectStandardOutput = true;
  94:                     objProcess.StartInfo.RedirectStandardError = true;
  95:                     objProcess.Start();
  96:  
  97:                     StreamWriter sIn = objProcess.StandardInput;
  98:                     StreamReader sOut = objProcess.StandardOutput;
  99:                     StreamReader sErr = objProcess.StandardError;
 100:  
 101:                     string s = sOut.ReadToEnd();
 102:                     string e = sErr.ReadToEnd();
 103:  
 104:                     if (String.IsNullOrEmpty(s) == false)
 105:                     {
 106:                         // highlight warnings in red
 107:                         if (s.Contains("warning"))
 108:                         {
 109:                             sbStandardOutput.Append("<font color=#FF0000>");
 110:                         }
 111:                         else
 112:                         {
 113:                             sbStandardOutput.Append("<font color=#000000>");
 114:                         }
 115:                         sbStandardOutput.Append("<br><b>");
 116:                         sbStandardOutput.Append(objFileInfo.FullName);
 117:                         sbStandardOutput.Append("</b><br>");
 118:                         sbStandardOutput.Append(s);
 119:                         sbStandardOutput.Append("</font><br>");
 120:                         Debug.WriteLine(s);
 121:                     }
 122:                      if (String.IsNullOrEmpty(e) == false)
 123:                     {
 124:                         sbStandardError.Append("<br><b>");
 125:                         sbStandardError.Append("<br><b>");
 126:                         sbStandardError.Append(objFileInfo.FullName);
 127:                         sbStandardError.Append("</b><br>");
 128:                         sbStandardError.Append(e);
 129:                         Debug.WriteLine(e);
 130:                     }
 131:  
 132:                     sIn.Close();
 133:                     sOut.Close();
 134:                     objProcess.Close();
 135:                 }
 136:             }
 137:         }
 138:         // loop through directories
 139:         foreach (DirectoryInfo objSubDirectoryInfo in objDirectoryInfo.GetDirectories("*.*"))
 140:         {
 141:             if (objSubDirectoryInfo.Name.Substring(0,1) == "_")
 142:             {
 143:                 // skip FrontPage Server Extension directories
 144:             }
 145:             else
 146:             {
 147:                 ListDirectoryFiles(objSubDirectoryInfo);
 148:             }
 149:         }
 150:  
 151:     }
 152: }

You can customize the design of a web service's test page with a single line of code in the web.config file. But like most things in ASP.NET it is not that easy if you actually try it!

Although you can find instructions on how to specify a custom wsdlHelpGenerator page it does not seem as if any developer has gone through with it and actually customized the design. They just tell you about the obscure web.config tag and leave it at that. This reflects the typical programmer's indifference to design. Although, to be honest, you really don't need to customize the design of the web service's test page because this is rarely exposed to the public.

The first thing you should do is copy the default wsdlHelpGenerator template which is buried in the framework's CONFIG folder. On my system I found it at: C:\WINNT\Microsoft.NET\Framework\v2.0.50727\CONFIG\DefaultWsdlHelpGenerator.aspx

When you have a copy of that file in your web root, edit your web.config file so your ASP.NET site will use your copy of the template page instead of the default template:

   1: <configuration>
   2:    <system.web>
   3:       <webServices>
   4:          <wsdlHelpGenerator href="WSHelpPage.aspx"/>
   5:       </webServices>
   6:    </system.web>
   7: </configuration>

The DefaultWsdlHelpGenerator.aspx page is coded in C# using a script block marked runat=server so don't try this with a VB.NET project. This is one of those cases where VB.NET developers are really treated like second class citizens of the .NET Framework world because there is no VB.NET version and you would have a tough time converting the code. It is a lot of code.

However, if you just want to do something simple like change the color of the heading then you can leave the code alone and just edit the CSS. But even this is not that easy. The actual style for the heading appears to be coming from a default local resource. I was unable to figure out exactly where the string is coming from. Maybe some ASP.NET guru can enlighten us on that.

   1: .heading1 { <%#GetLocalizedText("Styleheading1")%> }

But a simple solution to this problem is to look at the HTML source in the browser, copy the actual CSS generated, and then edit that for the color. Replace the localized text string with this CSS:

   1: .heading1 { 
   2:     color: #ffffff; 
   3:     font-family: Tahoma; 
   4:     font-size: 26px; 
   5:     font-weight: normal; 
   6:     background-color: #A10D0D; 
   7:     margin-top: 0px; 
   8:     margin-bottom:     0px; 
   9:     margin-left: -30px; 
  10:     padding-top: 10px; 
  11:     padding-bottom: 3px; 
  12:     padding-left: 15px; 
  13:     width: 105%; 
  14: }
  15:  

Now let's get really ambitious and apply a Master Page and theme to the web service's test page. You definitely don't want to try this with a VB.NET project because the C# code will cause you a lot of problems. First create a new content page with your selected master page. Copy all the HTML in the DefaultWsdlHelpGenerator.aspx page between <div id="content"> and the last </span> into the appropriate ContentPlaceHolder. Add the missing closing </div>. Add every using directive from the DefaultWsdlHelpGenerator.aspx file to the content page's code behind file and the content page itself. Copy all the C# code from the script block into the code file. Change the access modifier of every method and property in the code to public. I was lazy and made everything in sight public. You can take the time to figure out which properties really need to be public if you like. The title of the content page is a bit tricky but it can be specified using single value data binding:

   1: Title='<%#ServiceName + " " + GetLocalizedText("WebService")%>' 

As an example of how beautiful you can make your web service test page, I've used the Commerce template from the ASP.NET Master Pages Template Set to apply a really fancy design to my file downloader web service's test page. Click the image to see the larger image.

Yesterday I added a progress bar to my ASP.NET page for downloading YouTube videos. There was no visual indication that the download was taking place because the page just appeared to be taking a long time to load. I would have to open the download file location to verify that something was being downloaded.

Fortunately the WebClient class does have a DownloadProgressChanged method which will return the percentage of the download that has been completed. So it was just a matter of displaying that information in the ASP.NET page. First I moved the code for performing the download to a web service. That made more sense than using an asynchronous task initiated in the code behind for the page. The web service has a method for passing in the download url and file name and a method to return the download progress percentage. I used a Script Manager to create the JavaScript proxy for the web service so I could call the web service methods using JavaScript.

For the progress bar, I used Matt Berseth's ASP.NET AJAX Progress Bar Control which is based on the WebAppers Simple Javascript Progress Bar with CSS. This had to be recompiled for ASP.NET 2.0 and the 1.0.11119.0 version of AjaxControlToolkit. The ASP.NET page does not require any code in its code behind file. Everything is done through AJAX. A JavaScript setInterval is used to make repeated calls to the web service method to get the download progress. 

I have created a generic version which can be used to download any file. You can download the necessary files at: http://www.williamsportwebdeveloper.com/AjaxDownloader.zip However I did not include the web.config file or the complete project solution because I was using one of my development sites with a lot of other prototype crap.

Last evening I tried out two of the tools that Joe Stagner wrote about in his blog post Tools to block and eradicate SQL injection. Since I manage a classic ASP web application that was completely lacking in security and potentially vulnerable to these attacks, I thought I'd share my experiences.

The Source Code Analyzer for SQL Injection tool looks promising but it is a command line tool and you have to feed it the path to each of your ASP web pages. I was only able to test a few pages. You will need to find some way to automate the process of feeding it ASP file paths and gathering the output. I suppose this could be done by piping multiple commands together or through custom scripting.

The HP Scrawlr does not appear to crawl a site very deeply and may not use the particular SQL Injection attack that is causing so much grief. It did not find any vulnerabilities in my classic ASP web application. It also did not find any potential problems with the e-commerce software I customize.

Coincidentally, I had an user complain that he could not log in to the classic ASP web application I secured. His email address included the string "exec" and I was not accepting that as input. I had to remove that SQL keyword from the filter. That just goes to show that filters are not the way to handle this problem. I also used a lot of stored procedures with parameters.

Unfortunately, my classic ASP site uses many iframes and include files which makes it difficult to be confident about finding every vulnerable script. I just loaded the site in Visual Studio 2005 and did searches for SQL strings to find vulnerabilities.

I was planning on creating my own web crawler that would attack my development sites with the actual exploit which may not be caught by some filters.  I may still do that. I'd like a more thorough web crawler that generates a detailed report.

Currently I'm using ASP.NET in an unconventional manner to write scripts that download files. Asynchronous tasks and the WebClient object's DownloadFile method are proving to be a very convenient combination of features which makes it easy to automate the process of downloading a lot of files. 

For example, I was recently researching some CSS 3 features supported by the Safari browser and came across an article on A List Apart about the rebirth of web fonts. Digging a little deeper I found there was a large number of TrueType fonts freely available for use on the web. You can find a long list of them referenced in a CSS file but it would be tedious to download them all. So I wrote an ASP.NET page to parse the CSS file and download all the fonts automatically. Well they did say the fonts were freely available for use on the web so I assume this is permissible.

I'm also using this technique to download YouTube videos. I'm not sure if Google would approve of this, but I now have an ASP.NET page that will download a YouTube video for me as a FLV file. There are many applications available to do this but my ASP.NET page has an advantage in that it names the file using the video title instead of the meaningless video id. That really saves me a lot of time that I waste renaming video files. Videos frequently disappear from YouTube because they are removed for copyright violations or the neurotic user takes them down in a fit of rage or despair. When you have an emotional investment in this content it becomes rather annoying to have it disappear on a regular basis. Therefore it is important to be able to preserve content that you fear will be taken away from you. Sometimes videos disappear for illegitimate reasons as when trolls file false DMCAs to cause grief for a user. There have also been accusations that various groups file false DMCAs to silence their critics. I feel that being able to save a video is as important as being able to save a web page.

Recently I installed the new Adobe Media Player. It is just another example of the growing number of media clients available. I really don't care that much about the war between the Rich Internet Application frameworks (today I learned that Apple is getting into the game with something called SproutCore) but I may spend some time learning Flash because it is the technology behind a lot of what is happening with Internet video now. I don't need to limit myself to Microsoft technology but I do need to leverage my expertise. Since I have a lot of expertise in JavaScript I may favor the RIA framework that offers the simplest way to leverage that skill set.

I have found a random PHP script for creating captcha images using the GD Library and I've converted it to C# as promised in my previous blog post. I learned something new on this project. The first color in a new image will be the background color but only if the image is not using TrueColor.

I have to admit that this is just an idle exercise. I have no real need for this. I waste a lot of time on idle exercises, although sometimes previous research is useful later on. I think I need to be more focused on billable client work and creative work. One of the major pitfalls of intellect is that a very intelligent person is inclined to waste considerable time on pointless intellectual exercises, i.e abstractions. I think this is a trap that many IT professionals fall into because it is easy to get lost in technical details that serve no purpose. For example, how often do you find yourself struggling to install something on Linux which you are never going to use?

I have a list of some other idle exercises I was considering but I should probably focus on something else:

  1. Querying Index Server catalog using ASP.NET, maybe using LINQ. Like, why bother?
  2. Querying Active Directory using ASP.NET with LINQ. What for?
  3. Write an ASP.NET page using COBOL. This might be worthwhile if only to horrify other developers.
   1: <%@ WebHandler Language="C#" Class="CaptchaImages" %>
   2:  
   3: /*
   4: * File: CaptchaImages.ashx
   5: * Author: Simon Jarvis
   6: * Copyright: 2006 Simon Jarvis
   7: * Date: 03/08/06
   8: * Updated: 07/02/07
   9: * Requirements: PHP 4/5 with GD and FreeType libraries
  10: * Link: http://www.white-hat-web-design.co.uk/articles/php-captcha.php
  11: * 
  12: * This program is free software; you can redistribute it and/or 
  13: * modify it under the terms of the GNU General Public License 
  14: * as published by the Free Software Foundation; either version 2 
  15: * of the License, or (at your option) any later version.
  16: * 
  17: * This program is distributed in the hope that it will be useful, 
  18: * but WITHOUT ANY WARRANTY; without even the implied warranty of 
  19: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
  20: * GNU General Public License for more details: 
  21: * http://www.gnu.org/licenses/gpl.html
  22: *
  23:  * C# Version for ASP.NET
  24:  * Author: Robert S. Robbins
  25:  * Modified: 06/10/08
  26:  * Requirements: ASP.NET 2.0 with GD-Sharp
  27: */
  28:  
  29: using System;
  30: using System.Web;
  31: using System.Diagnostics;
  32: using System.Collections;
  33: using System.IO;
  34: using System.Drawing;
  35: using Ntx.GD;
  36:  
  37: public class CaptchaImages : IHttpHandler {
  38:  
  39:     private string font_file = @"C:\\WINDOWS\\Fonts\\bauhs93.ttf";
  40:     private string font_name = "Bauhaus 93";
  41:     private string mime_type = "image/jpeg";
  42:     private bool transparent_background = true;
  43:     
  44:     public void ProcessRequest (HttpContext context) {
  45:         // create trace listener file for debugging purposes
  46:         System.IO.Stream objFile = System.IO.File.Create(@"C:\Inetpub\wwwroot\study\App_Data\trace2.txt");
  47:         TextWriterTraceListener objTextListener = new TextWriterTraceListener(objFile);
  48:         Trace.Listeners.Add(objTextListener);
  49:         Trace.AutoFlush = true;
  50:  
  51:         GD image = CaptchaSecurityImage(120, 40, 6);
  52:  
  53:         /* output captcha image to browser */
  54:         context.Response.ContentType = mime_type;
  55:         MemoryStream ms = new MemoryStream();
  56:         image.Save(GD.FileType.Jpeg, ms);
  57:         byte[] binary = ms.ToArray();
  58:         context.Response.BinaryWrite(binary);
  59:         context.Response.Flush();        
  60:         
  61:         Trace.Close();
  62:         Trace.Flush();
  63:     }
  64:  
  65:     /// <summary>
  66:     /// Generates a random code with the specified number of characters.
  67:     /// </summary>
  68:     /// <param name="characters">The number of characters to generate.</param>
  69:     /// <returns>A random code.</returns>
  70:     public string generateCode(int characters) 
  71:     {
  72:         /* list all possible characters, similar looking characters and vowels have been removed */
  73:         string possible = "23456789bcdfghjkmnpqrstvwxyz";
  74:         string code = "";
  75:         int i = 0;
  76:         Random random = new Random();
  77:         while (i < characters)
  78:         {
  79:             int intRandomNumber = random.Next(0, possible.Length);
  80:             Trace.WriteLine(intRandomNumber.ToString(), "intRandomNumber");
  81:             code += possible.Substring(intRandomNumber, 1);
  82:             i++;
  83:         }
  84:         return code;
  85:     }
  86:     
  87:     public GD CaptchaSecurityImage(int width, int height, int characters)
  88:     {
  89:         string code = generateCode(characters);
  90:         /* font size will be 75% of the image height */
  91:         int font_size = Convert.ToInt32(height * 0.75);
  92:         // This is the equivalent of calling ImageCreate
  93:         GD image = new GD(width, height, false);
  94:         /* set the colours */
  95:         /* The first color in a new image should be the background color
  96:          * but it requires trueColor to be set to false*/
  97:         GDColor background_color = image.ColorAllocate(255, 255, 255);
  98:         GDColor text_color = image.ColorAllocate(20, 40, 100);
  99:         GDColor noise_color = image.ColorAllocate(100, 120, 180);
 100:         Random random = new Random();
 101:         /* generate random dots in background */
 102:         for (int i = 0; i < (width * height) / 3; i++)
 103:         {
 104:             int intRandomNumberX = random.Next(0, width);
 105:             int intRandomNumberY = random.Next(0, height);
 106:             image.FilledEllipse(intRandomNumberX, intRandomNumberY, 2, 2, noise_color);
 107:         }       
 108:         /* generate random lines in background */
 109:         for (int i = 0; i < (width * height) / 150; i++)
 110:         {
 111:             int intRandomNumberX = random.Next(0, width);
 112:             int intRandomNumberY = random.Next(0, height);
 113:             int intRandomNumberX2 = random.Next(0, width);
 114:             int intRandomNumberY2 = random.Next(0, height);
 115:             image.Line(intRandomNumberX, intRandomNumberY, intRandomNumberX2, intRandomNumberY2, noise_color);
 116:         }          
 117:         /* create textbox and add text */
 118:         int[] textbox = ImageTTFBBox(font_name, font_size, code);
 119:         // bounding rectangle
 120:         ArrayList br = new ArrayList();
 121:         br.Add(new Ntx.GD.Point(0, 0));
 122:         br.Add(new Ntx.GD.Point(textbox[0], 0));
 123:         br.Add(new Ntx.GD.Point(textbox[0], textbox[1]));
 124:         br.Add(new Ntx.GD.Point(0, textbox[1]));
 125:         /* font size will be 75% of the image height */
 126:         font_size = Convert.ToInt32(textbox[1] * 0.75);
 127:         // This is the equivalent of calling ImageTTFText
 128:         string result = image.StringFT(br, text_color, font_file, font_size, 0, 0, font_size, code, true);
 129:         Trace.WriteLine(result, "result");
 130:         
 131:         // set transparency
 132:         if (transparent_background)
 133:         {
 134:             image.ColorTransparent(background_color);
 135:         }
 136:  
 137:         return image;
 138:     }
 139:  
 140:     /// <summary>
 141:     /// Give the bounding box of a text using TrueType fonts
 142:     /// </summary>
 143:     /// <param name="font">The name of a TTF font.</param>
 144:     /// <param name="size">The size of the font.</param>
 145:     /// <param name="text">The text to be displayed using the font.</param>
 146:     /// <returns>An integer array containing the height and width.</returns>
 147:     /// <remarks>Not exactly the same as the original function, but the best we can do.</remarks>
 148:     public int[] ImageTTFBBox(string font, int size, string text)
 149:     {
 150:         Bitmap objBmpImage = new Bitmap(1, 1);
 151:  
 152:         int intWidth = 0;
 153:         int intHeight = 0;
 154:  
 155:         System.Drawing.Font objFont = new System.Drawing.Font(font, size, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Pixel);
 156:  
 157:         // Create a graphics object to measure the text’s width and height.  
 158:         Graphics objGraphics = Graphics.FromImage(objBmpImage);
 159:  
 160:         // Determine the bitmap size.  
 161:         /* Need to multiply the width by 2.0. 
 162:          * This is an ugly hack.
 163:          * The System.Drawing width will not match the GD Library drawing width.
 164:          */
 165:         intWidth = Convert.ToInt32(objGraphics.MeasureString(text, objFont).Width * 2.0);
 166:         intHeight = Convert.ToInt32(objGraphics.MeasureString(text, objFont).Height);
 167:         Trace.WriteLine(intWidth.ToString(), "intWidth");
 168:         Trace.WriteLine(intHeight.ToString(), "intHeight");
 169:         int[] box = { intWidth, intHeight };
 170:         return box;
 171:     }
 172:  
 173:     public bool IsReusable {
 174:         get {
 175:             return false;
 176:         }
 177:     }
 178:  
 179: }

There is a PHP script on A List Apart for dynamically generating images for paragraph headings using fonts. I have successfully translated this script into C#.

The Dynamic Text Replacement article by Stewart Rosenberger provides a PHP script that uses the GD graphics library to create images using the specified font and text. The article claims "Although we used PHP to construct the images in this implementation, your website does not need to be actively using PHP to take advantage of this technique." but I doubt that anyone has bothered to provide another implementation of this technique. Fortunately, there is a .NET wrapper for the GD Library known as GD-Sharp so it is possible to use this on an ASP.NET powered site.

This is very useful if your web hosting company does not have the GD Library enabled in their php.ini file. My web site supports PHP scripting and the GD Library but I was unable to get this script to work there. Fortunately, my ASP.NET version provides an alternative and I was able to get it working on my web site at: http://www.williamsportwebdeveloper.com/heading.ashx?text=ASP.NET%20WebLog. If you come across any other interesting PHP scripts that rely upon the GD Library to generate images you should be able to use GD-Sharp to do something similar using ASP.NET. For example, there are PHP scripts to generate captcha images this way. I'll probably convert one to C# in a later blog post.

To use this on your ASP.NET site you must import bgd.dll, GD-Import.dll, and GD-Sharp.dll into your bin directory. Since I used a trace listener to log errors to a text file you will also need to add a section to your web.config file:

   1: <system.codedom>
   2:     <compilers>
   3:         <compiler language="c#;cs;csharp" extension=".cs"
   4:         compilerOptions="/d:TRACE" 
   5:         type="Microsoft.CSharp.CSharpCodeProvider, System, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
   6:         warningLevel="1"/>
   7:         <compiler language="VB" extension=".vb"
   8:         compilerOptions="/d:Trace=true" 
   9:         type="Microsoft.VisualBasic.VBCodeProvider, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
  10:     </compilers>
  11: </system.codedom>

Then you need to create directories for the image cache and the trace file and give those directories write permission for the ASP.NET user account. It was difficult to translate the PHP code into C# and I was unable to find a good alternative for the ImageTTFBBox method so forgive the ugly hack:

   1: <%@ WebHandler Language="C#" Class="heading" %>
   2: /*
   3:     Dynamic Heading Generator
   4:     By Stewart Rosenberger
   5:     http://www.stewartspeak.com/headings/  
   6:  * 
   7:  *  C#.NET conversion by Robert S. Robbins
   8: 
   9:     This script generates PNG images of text, written in
  10:     the font/size that you specify. These PNG images are passed
  11:     back to the browser. Optionally, they can be cached for later use. 
  12:     If a cached image is found, a new image will not be generated,
  13:     and the existing copy will be sent to the browser.
  14: 
  15:     Additional documentation on PHP's image handling capabilities can
  16:     be found at http://www.php.net/image/    
  17:  * 
  18:  *  Additional documentation on the GD-Sharp .NET wrapper for the GD Library can
  19:  *  be found at http://gd-sharp.sourceforge.net/
  20: */
  21: using System;
  22: using System.Web;
  23: using System.Diagnostics;
  24: using System.Runtime.InteropServices;
  25: using System.IO;
  26: using System.Drawing;
  27: using Ntx.GD;
  28: using System.Collections;
  29: using System.Security.Cryptography;
  30: using System.Text;
  31:  
  32: public class heading : IHttpHandler {
  33:  
  34:     private string font_file = @"C:\WINDOWS\Fonts\comic.ttf";
  35:     private string font_name = "Comic Sans MS";
  36:     private int font_size = 30;
  37:     private string font_color = "#000000";
  38:     private string background_color = "#ffffff";
  39:     private bool transparent_background = true;
  40:     private bool cache_images = true;
  41:     private string cache_folder = @"D:\inetpub\williamsportwebdeveloper\cache";
  42:  
  43:     /*
  44:       ---------------------------------------------------------------------------
  45:        For basic usage, you should not need to edit anything below this comment.
  46:        If you need to further customize this script's abilities, make sure you
  47:        are familiar with PHP and C#.NET and its image handling capabilities.
  48:       ---------------------------------------------------------------------------
  49:     */
  50:     
  51:     private string mime_type = "image/png";
  52:     private string extension = ".png";
  53:     private int send_buffer_size = 4096;
  54:         
  55:     public void ProcessRequest (HttpContext context) {
  56:         // create trace listener file for debugging purposes
  57:         System.IO.Stream objFile = System.IO.File.Create(@"D:\inetpub\williamsportwebdeveloper\app_data\trace.txt");
  58:         TextWriterTraceListener objTextListener = new TextWriterTraceListener(objFile);
  59:         Trace.Listeners.Add(objTextListener);
  60:         Trace.AutoFlush = true;
  61:  
  62:         try
  63:         {
  64:             // This is the equivalent of calling ImageCreate
  65:             GD img = new GD(1, 1, true);
  66:             
  67:             if (HttpContext.Current.Request.QueryString["text"] == null)
  68:             {
  69:                 Trace.WriteLine("Fatal Error: No text specified.");
  70:             }
  71:  
  72:             // clean up text
  73:             string text = HttpContext.Current.Request.QueryString["text"];
  74:             text = text.Replace(@"\", "");
  75:  
  76:             // look for cached copy, send if it exists
  77:             string hash = GenerateMD5Hash(font_name, font_size.ToString(), font_color, background_color, transparent_background.ToString(), text);
  78:             string cache_filename = cache_folder + @"\" + hash + extension;
  79:             if (cache_images)
  80:             {
  81:                 // check image file availability
  82:                 if (File.Exists(cache_filename))
  83:                 {
  84:                     Trace.WriteLine("Serving image file: " + cache_filename + " from the cache");
  85:                     context.Response.ContentType = mime_type;
  86:  
  87:                     FileStream fs = File.OpenRead(cache_filename);
  88:                     byte[] buffer = ReadFully(fs);
  89:                     context.Response.BinaryWrite(buffer);
  90:                     context.Response.Flush();
  91:                     
  92:                     // exit immediately
  93:                     Trace.Close();
  94:                     Trace.Flush();
  95:                     return;
  96:                 }
  97:             } 
  98:             
  99:             // check font availability
 100:             if (File.Exists(font_file) == false)
 101:             {
 102:                 Trace.WriteLine("Fatal Error: The server is missing the specified font.");
 103:             }
 104:  
 105:             // create image
 106:             int dip = get_dip(font_name, font_size);
 107:             int[] box = ImageTTFBBox(font_name, font_size, text);
 108:             // This is the equivalent of calling ImageCreate
 109:             GD image = new GD(box[0], box[1], true);
 110:             if (image == null)
 111:             {
 112:                 Trace.WriteLine("Fatal Error: The server could not create this heading image.");
 113:             }
 114:             
 115:             // allocate colors and draw text
 116:             Color font_rgb = Color.FromArgb(ColorTranslator.FromHtml(font_color).ToArgb());
 117:             GDColor fg = image.ColorAllocate(font_rgb.R, font_rgb.G, font_rgb.B);
 118:             Color background_rgb = Color.FromArgb(ColorTranslator.FromHtml(background_color).ToArgb());
 119:             GDColor bg = image.ColorAllocate(background_rgb.R, background_rgb.G, background_rgb.B);
 120:             image.FilledRectangle(0, 0, box[0], box[0], bg);
 121:             // bounding rectangle
 122:             ArrayList br = new ArrayList();
 123:             br.Add(new Ntx.GD.Point(0, 0));
 124:             br.Add(new Ntx.GD.Point(box[0], 0));
 125:             br.Add(new Ntx.GD.Point(box[0], box[1]));
 126:             br.Add(new Ntx.GD.Point(0, box[1]));
 127:             // This is the equivalent of calling ImageTTFText
 128:             string result = image.StringFT(br, fg, font_file, font_size, 0, 0, font_size, text, true);
 129:             Trace.WriteLine(result, "result");
 130:              
 131:             // set transparency
 132:             if (transparent_background)
 133:             {
 134:                 image.ColorTransparent(bg);
 135:             }
 136:             
 137:             // save copy of image for cache
 138:             if(cache_images)
 139:             {
 140:                 image.Save(GD.FileType.Png, cache_filename, 1);
 141:             }
 142:             
 143:             context.Response.ContentType = mime_type;
 144:             MemoryStream ms = new MemoryStream();
 145:             image.Save(GD.FileType.Png, ms);
 146:             byte[] binary = ms.ToArray();
 147:             context.Response.BinaryWrite(binary);
 148:             context.Response.Flush();           
 149:         }
 150:         catch (Exception ex)
 151:         {
 152:             Trace.WriteLine("Fatal Error: Server does not support PHP image generation");
 153:             Trace.WriteLine(ex.ToString());
 154:         }
 155:         finally
 156:         {
 157:               Trace.Close();
 158:             Trace.Flush();  
 159:         }    
 160:     }
 161:  
 162:     /// <summary>
 163:     /// Try to determine the "dip" (pixels dropped below baseline) of this
 164:     /// font for this size.
 165:     /// </summary>
 166:     /// <param name="font">The name of a TTF font.</param>
 167:     /// <param name="size">The size of the font.</param>
 168:     /// <returns>CellDescent, the height below base line.</returns>
 169:     /// <remarks>Not exactly the same as the original function, but the best we can do.</remarks>
 170:     public int get_dip(string font, int size)
 171:     {
 172:         // Create the Font object for the font at that size   
 173:         System.Drawing.Font objFont = new System.Drawing.Font(font, size, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Pixel);  
 174:        
 175:         // Determine the CellDescent
 176:         int intCellDescent = size * objFont.FontFamily.GetCellDescent(objFont.Style) / objFont.FontFamily.GetEmHeight(objFont.Style);
 177:         Trace.WriteLine(size.ToString(), "size");
 178:         Trace.WriteLine(objFont.FontFamily.GetCellDescent(objFont.Style).ToString(), "CellDescent");
 179:         Trace.WriteLine(objFont.FontFamily.GetEmHeight(objFont.Style).ToString(), "GetEmHeight");
 180:         Trace.WriteLine(intCellDescent.ToString(), "intCellDescent");
 181:         return intCellDescent;
 182:     }
 183:  
 184:     /// <summary>
 185:     /// Give the bounding box of a text using TrueType fonts
 186:     /// </summary>
 187:     /// <param name="font">The name of a TTF font.</param>
 188:     /// <param name="size">The size of the font.</param>
 189:     /// <param name="text">The text to be displayed using the font.</param>
 190:     /// <returns>An integer array containing the height and width.</returns>
 191:     /// <remarks>Not exactly the same as the original function, but the best we can do.</remarks>
 192:     public int[] ImageTTFBBox(string font, int size, string text)
 193:     {
 194:         Bitmap objBmpImage = new Bitmap(1, 1);   
 195:      
 196:         int intWidth = 0;   
 197:         int intHeight = 0;   
 198:      
 199:         System.Drawing.Font objFont = new System.Drawing.Font(font, size, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Pixel);  
 200:     
 201:         // Create a graphics object to measure the text’s width and height.  
 202:         Graphics objGraphics = Graphics.FromImage(objBmpImage);  
 203:        
 204:         // Determine the bitmap size.  
 205:         /* Need to multiply the width by 1.25. 
 206:          * This is an ugly hack.
 207:          * The System.Drawing width will not match the GD Library drawing width.
 208:          */
 209:         intWidth = Convert.ToInt32(objGraphics.MeasureString(text, objFont).Width * 1.25);
 210:         intHeight = Convert.ToInt32(objGraphics.MeasureString(text, objFont).Height);
 211:         Trace.WriteLine(intWidth.ToString(), "intWidth");
 212:         Trace.WriteLine(intHeight.ToString(), "intHeight");
 213:         int[] box = { intWidth, intHeight };
 214:         return box;
 215:     }
 216:  
 217:     /// <summary>
 218:     /// Generates a MD5 hash for an unique file name
 219:     /// </summary>
 220:     /// <param name="font_name">The name of a TTF font.</param>
 221:     /// <param name="font_size">The size of the font.</param>
 222:     /// <param name="font_color">The color of the font.</param>
 223:     /// <param name="background_color">The background color of the text image.</param>
 224:     /// <param name="transparent_color">The transparent color of the text image.</param>
 225:     /// <param name="text">The text of the text image.</param>
 226:     /// <returns>A string to be used as an unique file name.</returns>
 227:     private string GenerateMD5Hash(string font_name, string font_size, string font_color, string background_color, string transparent_background, string text)
 228:     {
 229:         // Create an instance of the MD5CryptoServiceProvider class
 230:         MD5CryptoServiceProvider md5Hasher = new MD5CryptoServiceProvider();
 231:         // The array of bytes that will contain the encrypted value 
 232:         byte[] hashedBytes;
 233:         UTF8Encoding encoder = new UTF8Encoding();
 234:         // Call ComputeHash, passing in the plain-text string as an array of bytes
 235:         // The return value is the encrypted value, converted to a string
 236:         hashedBytes = md5Hasher.ComputeHash(encoder.GetBytes((font_name + font_size + font_color + background_color + transparent_background + text)));
 237:         Trace.WriteLine(BitConverter.ToString(hashedBytes).Replace("-", "").ToLower(), "BitConverter");
 238:         return BitConverter.ToString(hashedBytes).Replace("-", "").ToLower();
 239:     }
 240:  
 241:     /// <summary>
 242:     /// Reads data from a stream until the end is reached. The
 243:     /// data is returned as a byte array. An IOException is
 244:     /// thrown if any of the underlying IO calls fail.
 245:     /// </summary>
 246:     /// <param name="stream">The stream to read data from</param>
 247:     public static byte[] ReadFully(Stream stream)
 248:     {
 249:         byte[] buffer = new byte[32768];
 250:         using (MemoryStream ms = new MemoryStream())
 251:         {
 252:             while (true)
 253:             {
 254:                 int read = stream.Read(buffer, 0, buffer.Length);
 255:                 if (read <= 0)
 256:                     return ms.ToArray();
 257:                 ms.Write(buffer, 0, read);
 258:             }
 259:         }
 260:     }
 261:  
 262:     public bool IsReusable {
 263:         get {
 264:             return false;
 265:         }
 266:     }
 267:  
 268: }

I've been exploring asynchronous operations in ASP.NET 2.0 and while I don't have anything original to contribute you might find how I used this technique interesting. Even when the code is well documented it can be useful to know how it might be applicable to a specific problem.

Awhile ago I figured out how to authenticate for Seesmic API calls using ASP.NET 2.0 based on some PHP sample code. I continued to work with the Seesmic API and expanded my code to get user videos after successfully authenticating. I noticed that in addition to providing a thumbnail of the user video, Seesmic also gives you the url to a FLV file (i.e. Flash Video). This is probably intended for a video player but I decided that I wanted my code to download the videos.

Since downloading a video file can be time consuming and may cause an ASP.NET page to timeout, I wanted the video download to occur asynchronously. However this required making asynchronous web requests in a loop and all of the sample code I found did not attempt to make more than one asynchronous web service request. Initially I tried the old method of doing asynchronous operations which requires a callback delegate. However my ASP.NET page was timing out and there is a new way to do this in ASP.NET 2.0, see the MSDN article Asynchronous Pages in ASP.NET 2.0. I had better luck with the new method of creating asynchronous pages. I was able to successfully download 20 video files totaling 29 MB without causing the ASP.NET page to timeout. I think that is pretty impressive.

Below is my sample code for this little experiment. You will need a Seesmic account and since it is still an "alpha release" this means you have to request an account. If the code appears cut off remember that it is still there, merely hidden, and will be there when you copy and paste. There is an username hard coded in the GetUserVideos function so you may want to change that.

I'm now working on a new project involving converting a PHP script, which uses the GD Library to create dynamic images, into ASP.NET. So far it looks doable and it should be quite innovative.

ASPX Code

   1: <%@ Page Language="VB" AutoEventWireup="false" CodeFile="SeesmicAuth.aspx.vb" Inherits="SeesmicAuth" title="Seesmic API Authentication" Async="true" AsyncTimeout="3000" %>
   2: <asp:Content ID="Content1" ContentPlaceHolderID="cphFull" Runat="Server">
   3: <div class="Box">
   4:   <div class="BoxTitle">
   5:     <asp:Label ID="Label1" runat="server">Seesmic API Authentication Login</asp:Label>
   6:   </div>
   7:   <div class="BoxContent">
   8:   <!-- page content begins -->
   9:   <asp:PlaceHolder ID="phLoginForm" runat="server"> 
  10:         <h2>Please login using your seesmic account</h2>
  11:         <div style="width:200px;float:left;text-align:right;">
  12:         <p>Enter username: <asp:TextBox ID="txtUserName" runat="server"></asp:TextBox></p>
  13:         <p>Enter password: <asp:TextBox ID="txtPassword" runat="server"></asp:TextBox></p>
  14:         <asp:Button ID="btnSubmit" Text="Submit" runat="server" />
  15:         <br clear="all" /><br />no information is stored on this server in this process
  16:   </asp:PlaceHolder>
  17:   <asp:PlaceHolder ID="phSuccess" runat="server" Visible="false"> 
  18:         <h2>You have authenticated successfully!</h2>
  19:         Your current sid is: <asp:Label ID="lblSID" Font-Bold="true" runat="server"></asp:Label> and the session_id is <asp:Label ID="lblSessionID" Font-Bold="true" runat="server"></asp:Label>
  20:   </asp:PlaceHolder>
  21:   <asp:PlaceHolder ID="phFailure" runat="server" Visible="false"> 
  22:         <h2 style="color:red;">Seesmic login failed</h2>
  23:   </asp:PlaceHolder>
  24:   <br /><br />
  25:   <asp:PlaceHolder ID="phVideos" runat="server" Visible="false"> 
  26:   </asp:PlaceHolder>
  27:   <!-- page content ends -->
  28:    </div>
  29: </div>  
  30: </asp:Content>
  31:  
  32:  

VB.NET Code

   1: Imports System.Net
   2: Imports System.IO
   3: Imports System.Text
   4: Imports System.Security.Cryptography
   5: Imports System.Web.Script.Serialization
   6: Imports System.Diagnostics
   7: Imports System.Collections.Generic
   8: Imports System.Xml
   9: Imports System.Threading
  10:  
  11: Partial Class SeesmicAuth
  12:     Inherits System.Web.UI.Page
  13:  
  14:     Private Delegate Sub DownloadFLVDelegate(ByVal urlFlv As String)
  15:  
  16:     Protected Sub btnSubmit_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnSubmit.Click
  17:         Dim seesmicAuthUrl As String = ""
  18:         seesmicAuthUrl = "http://api.seesmic.com/login/auth?username=" & txtUserName.Text & "&password=" & GenerateMD5Hash(txtUserName.Text, txtPassword.Text)
  19:         Debug.WriteLine(seesmicAuthUrl, "seesmicAuthUrl")
  20:  
  21:         ' Make a web request
  22:         Dim HttpSite As Uri = New Uri(seesmicAuthUrl)
  23:         Dim objHttpWebRequest As HttpWebRequest = CType(WebRequest.Create(HttpSite), HttpWebRequest)
  24:         objHttpWebRequest.KeepAlive = False
  25:         objHttpWebRequest.Timeout = 30000
  26:  
  27:         '  Get the response
  28:         Dim objWebResponse As WebResponse = objHttpWebRequest.GetResponse()
  29:         Dim objStreamReader As StreamReader = New StreamReader(objWebResponse.GetResponseStream(), System.Text.Encoding.UTF8)
  30:  
  31:         Dim strJsonResponse = String.Empty
  32:         Dim js As New JavaScriptSerializer
  33:         Dim objDictionary As Dictionary(Of String, Object)
  34:  
  35:         ' Read the response using a stream reader
  36:         strJsonResponse = objStreamReader.ReadToEnd()
  37:         ' Deserialize the JSON string into a dictionary object
  38:         objDictionary = js.DeserializeObject(strJsonResponse)
  39:         Debug.WriteLine(strJsonResponse, "strJsonResponse")
  40:  
  41:         ' The value of the login key is another dictionary object
  42:         Dim objSubDictionary As Dictionary(Of String, Object) = objDictionary("login")
  43:  
  44:         ' Note: Make sure the dictionary contains a key before trying to reference it!
  45:         If objSubDictionary.ContainsKey("success") Then
  46:             If objSubDictionary("success") = "true" Then
  47:                 ' Authenticated, show sid and session_id values
  48:                 phLoginForm.Visible = False
  49:                 phSuccess.Visible = True
  50:                 If objSubDictionary.ContainsKey("sid") Then
  51:                     lblSID.Text = objSubDictionary("sid")
  52:                 End If
  53:                 If objSubDictionary.ContainsKey("session_id") Then
  54:                     lblSessionID.Text = objSubDictionary("session_id")
  55:                 End If
  56:                 ' Make an API request
  57:                 GetUserVideos(objSubDictionary("sid"))
  58:             Else
  59:                 ' Authentication failed, show a different message
  60:                 phLoginForm.Visible = False
  61:                 phFailure.Visible = True
  62:             End If
  63:         End If
  64:  
  65:         ' Debugging authentication failures
  66:         If objSubDictionary.ContainsKey("success") Then
  67:             Debug.WriteLine(objSubDictionary("success"), "success")
  68:         End If
  69:         If objSubDictionary.ContainsKey("reason") Then
  70:             Debug.WriteLine(objSubDictionary("reason"), "reason")
  71:         End If
  72:  
  73:         ' Code to figure out what objects JavaScriptSerializer.DeserializeObject returns
  74:         'For Each obj As Object In d
  75:         '    Debug.WriteLine(obj.GetType(), "Object Type")
  76:         '    Dim c As KeyValuePair(Of String, Object) = DirectCast(obj, KeyValuePair(Of String, Object))
  77:         '    Debug.WriteLine(c.Key, "Key")
  78:         '    Debug.WriteLine(c.Value, "Value")
  79:         '    Dim f As Dictionary(Of String, Object) = c.Value
  80:         '    Debug.WriteLine(f.Count, "Value")
  81:         '    Debug.WriteLine(f("reason"), "reason")
  82:         'Next
  83:  
  84:         objStreamReader.Close()
  85:         objWebResponse.Close()
  86:     End Sub
  87:  
  88:     Private Function GenerateMD5Hash(ByVal username As String, ByVal password As String) As String
  89:         'Create an instance of the MD5CryptoServiceProvider class
  90:         Dim md5Hasher As New MD5CryptoServiceProvider()
  91:         'The array of bytes that will contain the encrypted value 
  92:         Dim hashedBytes As Byte()
  93:         'The encoder class used to convert plain text to an array of bytes
  94:         Dim encoder As New UTF8Encoding()
  95:         'Call ComputeHash, passing in the plain-text string as an array of bytes
  96:         'The return value is the encrypted value, as an array of bytes
  97:         hashedBytes = md5Hasher.ComputeHash(encoder.GetBytes(username & password))
  98:         Debug.WriteLine(BitConverter.ToString(hashedBytes).Replace("-", "").ToLower(), "BitConverter")
  99:         Return BitConverter.ToString(hashedBytes).Replace("-", "").ToLower()
 100:     End Function
 101:  
 102:     Public Sub GetUserVideos(ByVal strSid As String)
 103:         Dim seesmicAPIUrl As String = ""
 104:         seesmicAPIUrl = "http://api.seesmic.com/users/renetto/videos.xml?sid=" & strSid
 105:         Debug.WriteLine(seesmicAPIUrl, "seesmicAPIUrl")
 106:  
 107:         ' Make a web request
 108:         Dim HttpSite As Uri = New Uri(seesmicAPIUrl)
 109:         Dim objHttpWebRequest As HttpWebRequest = CType(WebRequest.Create(HttpSite), HttpWebRequest)
 110:         objHttpWebRequest.KeepAlive = False
 111:         objHttpWebRequest.Timeout = 900000
 112:  
 113:         '  Get the response
 114:         Dim objWebResponse As WebResponse = objHttpWebRequest.GetResponse()
 115:         Dim objStreamReader As StreamReader = New StreamReader(objWebResponse.GetResponseStream(), System.Text.Encoding.UTF8)
 116:         Dim objXmlTextReader As XmlTextReader = New XmlTextReader(objStreamReader)
 117:         Dim objXmlDocument As XmlDocument = New XmlDocument()
 118:         objXmlDocument.Load(objXmlTextReader)
 119:  
 120:         Dim nsMgr = New XmlNamespaceManager(objXmlDocument.NameTable)
 121:         Dim sbHTML As New System.Text.StringBuilder("")
 122:  
 123:         sbHTML.Append("<ul>")
 124:         sbHTML.Append(Environment.NewLine)
 125:  
 126:         Dim strTitle As String = ""
 127:         Dim strUserName As String = ""
 128:         Dim strUrlPlayer As String = ""
 129:         Dim strUrlThumbnail As String = ""
 130:         Dim strCreatedDate As String = ""
 131:  
 132:         Dim objRecordNodeList = objXmlDocument.SelectNodes("records/record", nsMgr)
 133:         For Each objXmlNode As XmlNode In objRecordNodeList
 134:             sbHTML.Append("<li class=""VideoRecord"">")
 135:             sbHTML.Append(Environment.NewLine)
 136:  
 137:             For Each objXmlElement As XmlElement In objXmlNode.ChildNodes
 138:                 If objXmlElement.Name = "title" Then
 139:                     strTitle = objXmlElement.InnerText
 140:                 End If
 141:                 If objXmlElement.Name = "username" Then
 142:                     strUserName = objXmlElement.InnerText
 143:                 End If
 144:                 If objXmlElement.Name = "url-player" Then
 145:                     strUrlPlayer = objXmlElement.InnerText
 146:                 End If
 147:                 If objXmlElement.Name = "url-thumbnail" Then
 148:                     strUrlThumbnail = objXmlElement.InnerText
 149:                 End If
 150:                 If objXmlElement.Name = "created-at" Then
 151:                     strCreatedDate = objXmlElement.InnerText
 152:                 End If
 153:                 If objXmlElement.Name = "url-flv" Then
 154:                     ' download the flv file using an asynchronous page task
 155:                     Dim urlFlv As Uri = New Uri(objXmlElement.InnerText)
 156:                     ' Pass additional parameter using the state object
 157:                     Dim state As Object
 158:                     state = urlFlv.ToString()
 159:                     Dim bh As New BeginEventHandler(AddressOf BeginAsyncTask)
 160:                     Dim eh As New EndEventHandler(AddressOf EndAsyncTask)
 161:                     Dim th As New EndEventHandler(AddressOf TimeoutAsyncOperation)
 162:                     Dim objTask As New PageAsyncTask(bh, eh, th, state)
 163:                     RegisterAsyncTask(objTask)
 164:                 End If
 165:             Next
 166:  
 167:             sbHTML.Append("<h3 class=""VideoTitle"">" & strTitle & "</h3>")
 168:             sbHTML.Append(Environment.NewLine)
 169:             sbHTML.Append("<p>" & strUserName & "</p>")
 170:             sbHTML.Append(Environment.NewLine)
 171:             sbHTML.Append("<a href=""" & strUrlPlayer & """>")
 172:             sbHTML.Append(Environment.NewLine)
 173:             sbHTML.Append("<img src=""" & strUrlThumbnail & """>")
 174:             sbHTML.Append(Environment.NewLine)
 175:             sbHTML.Append("</a>")
 176:             sbHTML.Append(Environment.NewLine)
 177:             sbHTML.Append("<br>")
 178:             sbHTML.Append(Environment.NewLine)
 179:             sbHTML.Append(Convert.ToDateTime(strCreatedDate).ToShortDateString & " " & Convert.ToDateTime(strCreatedDate).ToShortTimeString)
 180:             sbHTML.Append(Environment.NewLine)
 181:  
 182:             sbHTML.Append("</li>")
 183:             sbHTML.Append(Environment.NewLine)
 184:         Next
 185:  
 186:         sbHTML.Append("</ul>")
 187:         sbHTML.Append(Environment.NewLine)
 188:  
 189:         ' save the xml file for future reference
 190:         objXmlDocument.Save(Server.MapPath("App_Data/" & strUserName & "-videos.xml"))
 191:  
 192:         Dim objGenericControl As HtmlControls.HtmlGenericControl = New HtmlControls.HtmlGenericControl
 193:         objGenericControl.InnerHtml = sbHTML.ToString()
 194:         phVideos.Controls.Add(objGenericControl)
 195:         phVideos.Visible = True
 196:  
 197:         objWebResponse.Close()
 198:         objHttpWebRequest.Abort()
 199:     End Sub
 200:  
 201:     Function BeginAsyncTask(ByVal sender As Object, ByVal e As EventArgs, ByVal cb As AsyncCallback, ByVal state As Object) As IAsyncResult
 202:         Dim urlFlv As String = DirectCast(state, String)
 203:         Dim download As New DownloadFLVDelegate(AddressOf DownloadFLV)
 204:         Return download.BeginInvoke(urlFlv, cb, state)
 205:     End Function
 206:  
 207:     Sub EndAsyncTask(ByVal ar As IAsyncResult)
 208:         Debug.WriteLine("End of Asynchronous Task")
 209:     End Sub
 210:  
 211:     Sub TimeoutAsyncOperation(ByVal ar As IAsyncResult)
 212:         Debug.WriteLine("Asynchronous Task timed out downloading file: " & DirectCast(ar.AsyncState, String))
 213:     End Sub
 214:  
 215:     Sub DownloadFLV(ByVal urlFlv As String)
 216:         Dim objWebClient As WebClient = New WebClient()
 217:         Dim strFlvFileName = urlFlv.ToString.Replace("http://v.seesmic.com/flv/", "")
 218:         Try
 219:             Debug.WriteLine("Downloading..." & strFlvFileName)
 220:             objWebClient.DownloadFile(urlFlv, Server.MapPath("App_Data/" & strFlvFileName))
 221:         Catch ex As WebException
 222:             Debug.WriteLine("Error downloading file.." & strFlvFileName)
 223:             Debug.WriteLine(ex.ToString())
 224:         End Try
 225:     End Sub
 226:  
 227: End Class

Here is some simple code for creating a list of all your installed fonts in order to create a visual reference. It may be of more interest to a web designer. I suggest you print the web page so you can easily find an appropriate font. Some applications have font drop down menus that display font names using that font but I would rather have a handy print out.

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Text;
using System.Drawing;

/// <summary>
/// Lists all the installed fonts for a quick visual reference.
/// </summary>
public partial class ListFonts : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        try
        {
            StringBuilder sb = new StringBuilder();

            foreach (FontFamily family in FontFamily.Families)
            {
                sb.Append("<h2 style='color:blue;font-family:Arial;'>' + family.Name + "</h2>");
                sb.Append("<font face='" + family.Name + "' size='6'>");
                sb.Append("The quick brown fox jumps over the lazy dog. 1234567890");
                sb.Append("</font><hr/>");
                sb.Append(Environment.NewLine);
            }

            lblFontList.Text = sb.ToString();
        }
        catch (Exception ex)
        {
            lblErrorMessage.Text = ex.ToString();
        }
    }
}

I'm not doing anything interesting with ASP.NET at the moment. I recently got an iBook so I've been busy exploring OS X. I'm also reading my second book on After Effects because I want to get back to creative work. If you are a geek and a big movie fan then you should really explore the world of digital video editing and motion graphics. The complexity of the software will satisfy your geek side. Many of the high end video editing systems have steep learning curves. As a digital film geek you'll get to network with amateur filmmakers and videographers who are increasingly interested in promoting their work online and therefore involved in social networking, media startups, etc.

I will be exploring asynchronous web requests because I've run into several situations where my code is waiting too long for a web request. There are some technical challenges in making multiple asynchronous web requests in a loop and dealing with their staggered completion.

More Posts