|
 Monday, June 09, 2008

I Took The ASP.NET 3.5 Beta Exam, and then the red pill

At Tech-Ed last week they offered the ASP.NET 3.5 Beta Exam. The beta exam is not available in Phoenix, so I decided to give it a whirl on Thursday. I didn't plan to take the test so I didn't have a lot of time to review (maybe 30 minutes of review) and I just dove in and took it.

Normally MS Exams have about ~40 questions and they take a couple of hours for me to complete. I was expecting the same from this one...

Nope. This exam had 70 questions. Wowza. I'm assuming the reason for this is one of these scenarios:

  • This is a beta exam and they need to weed out some questions
  • They have bumped up the number of questions in the exam

So how hard was it?

Well ... it's a little harder than the 2.0 version. A lot of it is repeated from 2.0, but this new beta exam threw me for a loop in a few areas.

The areas that threw me for a loop were:

  • WCF Integration Questions (I'm very well vested in WCF, but I just was not expecting it in this exam - there's a WCF exam for that!)
  • Deep AJAX Questions (Don't do much AJAX stuff, lets be honest... JavaScript is not my favorite language)
  • Miscellaneous Mobile Questions

The main thing about MS exams is that they are LONG and tricky. Sometimes you'll get asked a question and all four answers look right. LOOK VERY CLOSE. You'll see that maybe one word is spelled wrong, or the line's are swapped, etc. They will confuse you. But the main thing to do is be alert. The test will wear on you, its long. By the 2.5 hours in, you'll get tired of the test and you'll want to just hurry through it, don't. Just take your time.

 

So How Did I do?

Since its a beta exam, I won't know for about 2-3 weeks. They send your results in the mail. I feel like I passed, but we'll see when the results come in.

... and no, I didn't take any pills before or after... :) I'm just joshin' ya'll.

#    Comments [0] |
 Sunday, May 11, 2008

Using iTextSharp To Watermark/Write Text To Existing PDF's

First off, yes, I know there are other tutorials on how to watermark PDF's with iTextSharp. Unfortunately none of them showed me exactly what I wanted to do - which is why I wrote this one.

Currently I'm involved with a project that utilizes PDF's as their main product - a legal document. When I arrived to this client they utilized iTextSharp to "watermark" their PDF's. I've been wanting to get my hands dirty with iTextSharp for awhile now. So I cracked open the code base one weekend to see how they were watermarking the PDF's. After poking around for about 2 minutes I saw how they were performing the "watermarking" and unfortunately it was not the best way to get the job done.

Problems with current implementation:

  • Images were used to write text.
    • Huh? An image with text was "underlain" into the PDF. The image had faded text that when inside of the finished product, looked watermarked. The image would say things similar to "Sample". The entire image looked like this:
    • image
  • The watermark orientation would always end up as Portrait.
    • This was because the watermark was an image and it was created for a portrait and it was coded to never detect the page orientation. If the page was landscape the watermarked page would look like this:
    • image
  • The watermarked text could never be changed.
    • Well, it could be, but it took a code change. The images were kept as resources in the library. So if they needed a new watermark, that meant a code change, testing and a redeploy.

 

So How Do We Fix This? What's the End Goal?

  • We want to be able to watermark the PDF document with Text, not images.
  • The code should detect page orientation.
  • We should be able to watermark the PDF with text that we provide at run time.
Current Document Expected Result
A Non watermarked document A watermarked document (red is below the text)
image image

The Solution

I'm very amazed with iTextSharp's ability to manipulate PDF's. From existing PDF's to new PDF's to content extraction, iTextSharp does it all. The best part about it is... ITS FREE (other than your development time, that is).

I've created a solution which I provide the download link for at the bottom of the post, but for now here's the code that does the dirty work:

 

FooTheoryPdf.cs

1 using System.IO; 2 using System.Text; 3 using iTextSharp.text; 4 using iTextSharp.text.pdf; 5 6 namespace FooTheory.iTextSharpLibrary 7 { 8 /// <summary> 9 /// Example PDF Class 10 /// </summary> 11 public class FooTheoryPdf 12 { 13 /// <summary> 14 /// Method that will utilize iTextSharp to write the <see cref="stringToWriteToPdf"/> to the 15 /// pdf on each page of the PDF. 16 /// </summary> 17 /// <param name="sourceFile">The PDf File</param> 18 /// <param name="stringToWriteToPdf">The text to write to the pdf</param> 19 /// <returns>The bytes of the newly updated PDF with <see cref="stringToWriteToPdf"/> in the pdf.</returns> 20 public static byte[] WriteToPdf(FileInfo sourceFile, string stringToWriteToPdf) 21 { 22 PdfReader reader = new PdfReader(sourceFile.FullName); 23 24 using (MemoryStream memoryStream = new MemoryStream()) 25 { 26 // 27 // PDFStamper is the class we use from iTextSharp to alter an existing PDF. 28 // 29 PdfStamper pdfStamper = new PdfStamper(reader, memoryStream); 30 31 for (int i = 1; i <= reader.NumberOfPages; i++) // Must start at 1 because 0 is not an actual page. 32 { 33 // 34 // If you ask for the page size with the method getPageSize(), you always get a 35 // Rectangle object without rotation (rot. 0 degrees)—in other words, the paper size 36 // without orientation. That’s fine if that’s what you’re expecting; but if you reuse 37 // the page, you need to know its orientation. You can ask for it separately with 38 // getPageRotation(), or you can use getPageSizeWithRotation(). - (Manning Java iText Book) 39 // 40 // 41 Rectangle pageSize = reader.GetPageSizeWithRotation(i); 42 43 // 44 // Gets the content ABOVE the PDF, Another option is GetUnderContent(...) 45 // which will place the text below the PDF content. 46 // 47 PdfContentByte pdfPageContents = pdfStamper.GetUnderContent(i); 48 pdfPageContents.BeginText(); // Start working with text. 49 50 // 51 // Create a font to work with 52 // 53 BaseFont baseFont = BaseFont.CreateFont(BaseFont.HELVETICA_BOLD, Encoding.ASCII.EncodingName, false); 54 pdfPageContents.SetFontAndSize(baseFont, 40); // 40 point font 55 pdfPageContents.SetRGBColorFill(255, 0, 0); // Sets the color of the font, RED in this instance 56 57 58 // 59 // Angle of the text. This will give us the angle so we can angle the text diagonally 60 // from the bottom left corner to the top right corner through the use of simple trigonometry. 61 // 62 float textAngle = 63 (float) FooTheoryMath.GetHypotenuseAngleInDegreesFrom(pageSize.Height, pageSize.Width); 64 65 // 66 // Note: The x,y of the Pdf Matrix is from bottom left corner. 67 // This command tells iTextSharp to write the text at a certain location with a certain angle. 68 // Again, this will angle the text from bottom left corner to top right corner and it will 69 // place the text in the middle of the page. 70 // 71 pdfPageContents.ShowTextAligned(PdfContentByte.ALIGN_CENTER, stringToWriteToPdf, 72 pageSize.Width/2, 73 pageSize.Height/2, 74 textAngle); 75 76 pdfPageContents.EndText(); // Done working with text 77 } 78 pdfStamper.FormFlattening = true; // enable this if you want the PDF flattened. 79 pdfStamper.Close(); // Always close the stamper or you'll have a 0 byte stream. 80 81 82 return memoryStream.ToArray(); 83 } 84 } 85 } 86 }

Then there is a small math library I created to get the angle of the text through some simple Trig.

FooTheoryMath.cs

1 using System; 2 3 namespace FooTheory.iTextSharpLibrary 4 { 5 /// <summary> 6 /// Math library 7 /// </summary> 8 public static class FooTheoryMath 9 { 10 public static double GetHypotenuseAngleInDegreesFrom(double opposite, double adjacent) 11 { 12 //http://www.regentsprep.org/Regents/Math/rtritrig/LtrigA.htm 13 // Tan <angle> = opposite/adjacent 14 // Math.Atan2: http://msdn.microsoft.com/en-us/library/system.math.atan2(VS.80).aspx 15 16 double radians = Math.Atan2(opposite, adjacent); // Get Radians for Atan2 17 double angle = radians*(180/Math.PI); // Change back to degrees 18 return angle; 19 } 20 } 21 }

 

The Results

Utilizing a simple interface:

image

We're able to generate PDF's that are watermarked on an angle from bottom left corner to top right corner.

Here is a screen shot of what the generated PDF looks like:

Clicking the Top button:

image

Clicking the Bottom Button:

image

I'm able to change the message:

image

This will produce:

image

 

Conclusion

I found the iTextSharp quite versatile for working with PDF's. This solution will help you write text to existing PDF's quite easily.

Possible implementations include:

  • Place this into a WCF service to create a watermarking service
  • Leave it as it is, implemented as a class library.
  • Create your own .NET PDF Client that you could sell (I've read somewhere - cant find the source - that a couple third party .NET PDF components are actually written on top of iTextSharp).

While working to accomplish what I have above I found the best reference for iTextSharp was actually the iText Book written by Bruno Lowagie. You can purchase it here on Amazon.com.

And Finally ... While writing this I kept thinking .....

Why would you want to watermark existing PDF's?

There are a few options.

  1. Accountability: Lets say you have a ton of PDF's stored from a long running highly confidential report process and these PDF's are stored nightly/weekly on a certain directory. Perhaps you want to make sure that when a user logs in and accesses one of these highly confidential PDF's you water mark it with the users name on each page. Therefore, if it makes it into the hands of the media, you can see where/who it came from.
  2. Licensing: Perhaps you main product is a PDF. Lets say you're a book publisher and you sell PDF versions of the book or you allow people who have already purchased the hard copy to download the PDF version.  What you can do is force the user to create a profile in order to download the PDF. Usually this also includes some type of security question like "turn to page 435 and give us the last word on the page" in order to validate the user actually HAS the book in hand. Once they are authorized to download the PDF book, you can watermark every page on the book with their name "Licensed to: <FirstName> <LastName>. Let's face it, people are MUCH less likely to give a PDF book away on Torrent or P2P software when every single page has their name on it. It's not the BEST method of licensing, but it works.
  3. Other: Perhaps Company A is acquired by Company B and Company B wants to make sure that all PDF's on Company A's site are amended with the text "Company A is a subsidiary of Company B". What if company A has 15,000 PDF's that they have on their site? Maybe its a resume assistance site that has over 15,000 sample resumes. Are you going to open each PDF and do this by hand? Nope. Not at all. You can intercept the call for the PDF's and then write the text at the bottom before they're delivered to the end user.
  4. The list could go on and on ...

 

Download The Code

FooTheory.iTextSharpLibrary.Example.zip (4.69 MB)

#    Comments [4] |
 Tuesday, April 29, 2008

3.5 Beta Exams Not Available In Phoenix

I meant to post this a couple of weeks ago but unfortunately I didn't get the chance to. Anyway... I was a little bummed out to find that the ASP.NET 3.5 Beta Exams were being offered to MCP holders until late April (I believe it was up until yesterday), but unfortunately NOT A SINGLE EXAM SITE IN PHOENIX can house this Beta Exam.

I spoke to a ProMetric rep who stated that the exam sites such as Interface, ValorIT, and even ProMetric's own testing facility were not a high enough site rating. Apparently they need a level 4 site rating to hold these beta exams, these sites only obtain a level 3 or less rating.

Upon further inspection, the closest testing facility that would allow me to take this Beta exam was at Yavapai College in Prescott, AZ (see map below). Look how far away that is:

Map image

<-- Here's where it is in relation to Phoenix.

The part that baffles me is how Phoenix can be in the top 5 largest cities in the USA (I can't find my reference for when we passed Philly, but I heard we did somewhere - If I'm wrong, oh well) and we don't have a testing facility that allows us to take Beta Exams. Don't get me wrong, I'm happy that we even have facilities that do this, but I'm amazed that a city of this size does NOT have a level 4 facility in the metro area. Literally, the metro area almost spans nearly ~100 miles in diameter.

Anyway, I'll hit up the exams when they're released. No big deal. :) I was just amazed that we didn't have the capacity to hold these exams.

Here's the links for the .NET 3.5 Study Guides:

ASP.NET Application Development (70-562)

ADO.NET Application Development (70-561)

#    Comments [0] |

Simple Download Counter HttpHandler

[Full code sample provided at the bottom of this post]

I use A-Drive from time to time to host small files that I want users to download for various personal projects (mainly to help conceptualize the market for entrepreneur start-up ideas). Lately I needed to use it for a project where I needed to track the number of downloads a particular file had. Normally this is something I could code up real quick, but I literally wanted to get this up and running within minutes. I'm running a site using the Graffiti CMS so I'm doing this to get some quick feedback cycles in regards to if my "product" I'm offering is worth the time to invest a full implementation. (MicroTesting).

I had uploaded the file, and then accessed it from three different networks to realize that the incremental counter was NOT incrementing. WTF?! I messed around with it some more... I deleted my cookies (thinking that maybe they wrote a cookie to verify I downloaded it so it didn't double dip in the count for my multiple downloads - aka: Unique Downloads). I contacted support, but it was the weekend so I didn't expect a reply.

Update

I received a reply from the company in regards to their download counter not working:

Dear Donn,
This is currently a bug in our system that we will resolve very soon.
Thanks,
-ADrive Support Team

So.. it was now time to create a simple HttpHandler to count downloads for me.

How To

Note: This is a very very very simple example of how you can count downloads (and no, it does capture unique user downloads). This can be altered very easily to do this.

My Simple Requirements.

- Persistence Medium For The Counter (XML)

- Increment the counter

- Allow the user to download the file

The best place to implement this functionality is inside of an HttpHandler by using the IHttpHandler Interface. The reasons are simple: I don't need a UI, I don't need complex business logic (remember - this is a super quick implementation to fix a simple problem).

Code

1 using System; 2 using System.IO; 3 using System.Text; 4 using System.Web; 5 using System.Xml; 6 7 /// <summary> 8 /// Increments a counter and forces a download to the user. 9 /// </summary> 10 public class DownloadHandler : IHttpHandler 11 { 12 private readonly string COUNTER_REPOSITORY = "App_Data/ExampleCounter.xml"; 13 private readonly string MY_FILE = "App_Data/Example.pdf"; 14 private readonly string SAMPLE_DOWNLOAD_COUNT_ELEMENT = "SampleDownloadCount"; 15 16 #region IHttpHandler Members 17 18 ///<summary> 19 ///Enables processing of HTTP Web requests 20 /// by a custom HttpHandler that implements 21 /// the <see cref="T:System.Web.IHttpHandler"></see> interface. 22 ///</summary> 23 ///<param name="context">An 24 /// <see cref="T:System.Web.HttpContext"></see> object that provides 25 /// references to the intrinsic server objects (for example, Request, 26 /// Response, Session, and Server) used to service HTTP requests. </param> 27 public void ProcessRequest(HttpContext context) 28 { 29 IncrementCounter(context); 30 SendFileToUser(context); 31 } 32 33 ///<summary> 34 ///Gets a value indicating whether another request can use 35 /// the <see cref="T:System.Web.IHttpHandler"></see> instance. 36 ///</summary> 37 /// 38 ///<returns> 39 ///true if the <see cref="T:System.Web.IHttpHandler"></see> instance 40 /// is reusable; otherwise, false. 41 ///</returns> 42 /// 43 public bool IsReusable 44 { 45 get { return false; } 46 } 47 48 #endregion 49 50 /// <summary> 51 /// Sends the file to the user. 52 /// </summary> 53 /// <param name="context">The <see cref="HttpContext"/> that 54 /// contains the request.</param> 55 private void SendFileToUser(HttpContext context) 56 { 57 // Send the file to the user. 58 FileInfo fileInfo = new FileInfo(context.Server.MapPath(MY_FILE)); 59 60 context.Response.ContentType = "application/pdf"; 61 context.Response.AddHeader( 62 "Content-Disposition", 63 "attachment; filename=" + fileInfo.Name); 64 context.Response.WriteFile(fileInfo.FullName); 65 context.Response.End(); 66 } 67 68 /// <summary> 69 /// Increments the counter. 70 /// </summary> 71 /// <param name="context">The <see cref="HttpContext"/> that contains t 72 /// he current request</param> 73 private void IncrementCounter(HttpContext context) 74 { 75 int value = int.MinValue; 76 string fileName = context.Server.MapPath(COUNTER_REPOSITORY); 77 using (XmlTextReader reader = new XmlTextReader(fileName)) 78 { 79 reader.ReadStartElement(SAMPLE_DOWNLOAD_COUNT_ELEMENT); 80 value = Convert.ToInt32(reader.ReadString()); 81 } 82 value++; 83 using (XmlTextWriter writer = new XmlTextWriter(fileName, Encoding.ASCII)) 84 { 85 writer.WriteElementString( 86 SAMPLE_DOWNLOAD_COUNT_ELEMENT, 87 value.ToString()); 88 } 89 } 90 91 92 93 94 } 95

How It Works

We have to add a line to the web.config to tell ASP.NET about the Handler and to only listen to the "GET" Http Requests.

1 <httpHandlers> 2 <add path="Download.ashx" verb="GET" type="DownloadHandler"/> 3 </httpHandlers>

When the app is fired up we first access the request, then open an XML file that looks like this:

<SampleDownloadCount>8</SampleDownloadCount>

The file is parsed, the value is incremented and then we write the value back to the file. I'm almost positive there is a more elegant solution to this but remember, I wanted this to get done FAST, I'm not looking for pretty in this implementation. Technically, this is throw away code if my test works (my 1 week MicroTesting phase). Then I go into full development.

After the counter is incremented we send the file to the end user through the response.

Note: The file "Example.pdf" is hard coded in this example but you could easily set this up so that the file represented an  identifier through the query string (download.ashx?DownloadId=1) where 1 might equate to "Example1.pdf" and 2 might equate to "Example2.pdf". Think about it awhile and run with it. Remember, this is an extremely simple example for how to count downloads.

Conclusion

It's very simple to implement this counter in a regular ASP.NET web site. Although its not production quality code, it will work for simple testing and monitoring of file downloads and should supply you with a starting point to move forward with download counters.

In a future post I will show you how you can use this inside of the Graffiti CMS to help track your download counts.

Downloads

You can download a sample example site here: FooTheory.DownloadHandler.Example.zip (7.39 KB)

#    Comments [0] |
 Monday, January 07, 2008

Sending Email in a Development Environment without an SMTP Server

Sometimes I'm not at a client location and I cannot check to make sure that an email component is working correctly. This results from not having the email server at my disposal to relay the message off of. I also run Vista, which does not come with an SMTP server.

 

Therefore my setting in the App.config wont work for development.

Here's what the settings look like at the client location:

  <system.net>
    <mailSettings>
      <smtp>
        <network host="mail.example.com"/>
      </smtp>
    </mailSettings>
  </system.net>

 

So, while I'm developing remotely I will change the configuration to this 

  <system.net>
    <
mailSettings>
      <
smtp deliveryMethod="SpecifiedPickupDirectory">
        <
specifiedPickupDirectory pickupDirectoryLocation="c:\temp\maildrop\"/>
      </
smtp>
    </
mailSettings>
  </
system.net>

 

What this setting will do is create the email file and drop it into the c:\temp\maildrop\ folder. This setting can be used if you have your SMTP server watch a directory for new mail and it will then send it, or you can use it like I am - for testing when I need to view the email without actually sending it.

Now, when I send an email, like this:

 

        [Test]
        public void SendMail()
        {
            SmtpClient smtpClient = new SmtpClient();
            MailMessage mailMessage = new MailMessage("from@example.com", "to@example.com");
            mailMessage.Subject = "This is a test";
            mailMessage.Body = "This is the body";
            smtpClient.Send(mailMessage); 
        }

 

The mail message will get generated to c:\temp\maildrop\ . Visit this location with Explorer and you'll see something similar to this:

image

 

Now open this .eml file with any text editor (I prefer Notepad++) and you will see the contents of your email.

 

Email Contents

x-sender: from@example.com
x-receiver: to@example.com
mime-version: 1.0
from: from@example.com
to: to@example.com
date: 7 Jan 2008 18:24:10 -0700
subject: This is a test
content-type: text/plain; charset=us-ascii
content-transfer-encoding: quoted-printable

This is the body

 

Now you can test the email functionality of your application without actually having a SMTP Server.

Note: If  you're using ASP.NET you will need to allow ASP.NET to write to the pickupDirectoryLocation.

#    Comments [3] |
 Sunday, November 18, 2007

Frustrating MS Test Issue - blah blah blah is not trusted

I'm not going to lie, I'm a NUnit guy. I've used it for years. But just recently I've decided to start using MSTest. I run Team Suite and I've been using it for some basic integration and functional tests, but not full on TDD. Well this weekend I embarked on a task that utilizes full on TDD and I decided to make the jump and try to do it with MS Test.

So the first thing I had to do was reference and import Rhino Mocks. I then ran the test real quick (I had a test that did not depend on mocks yet) to make sure nothing in the import broke it (it shouldn't, but hey, I'm ADD when it comes to this TDD business). To my utter amazement, something broke. I was seeing red.

This was the beautiful error:

Microsoft.VisualStudio.TestTools.TestManagement.ExecutionException: Test Run deployment issue: The location of the file or directory '(path omitted)\main\Source\SharedBinaries\Rhino.Mocks.dll' is not trusted.

WTF? It's on my local machine. It's not a file share, hell, its even included in my project in my SharedBinaries folder. WTF MAN!?

After some looking around, I was able to figure it out.

If you download a DLL from the Internet, or get it in an email or where ever and you saved it to your disk (including a DLL in a zip too) it has some extra info attached to it called an "AES" file. (see my reference below for more info)...

To fix this annoying issue, go to the DLL, right click and then click "Unblock". See the screen shot below. Once that is done, your test will work.

unblockdll

 

Source: MSDN Forums

#    Comments [3] |
 Monday, November 12, 2007

MODI - The Lightweight DOM Inspector Bookmarklet

There are a ton of HTML DOM inspectors out there. I mean, a lot of them. They're helpful as hell when you're in a pinch and you can't figure out what the DOM is doing. I've used all kinds, from FireFox add on's to IE gadgets, to custom ones... But for the last couple of years I seem to have stuck with one that just seems to work for all of my needs. Its the Slayer Office Mouse Over DOM Inspector - MODI. Its simple, clean and very lightweight.

How It Works

Here's what you do... go to this page: MODI Help Page

Right click on the "Bookmark this link for MODIv2". This screen shot is in FireFox. Internet Explorer will warn you that its not safe to book mark these type of items, but just click Ok because this one is safe.

modi1

 

Then go visit a web site where you want to view the DOM. For example: www.codeplex.com. Once at the site, click your book marks and click the MODI book mark as shown below:

modi2

You will now see the Mouse Over Dom Inspector action. Move your mouse around and you'll see the parent elements, the classes each element inherits, etc. It will look something like this: (click for larger image)

modi3

The text that is circled in red is the element that we are inspecting. The details of that element are present in the MODI box (blue box). Move your mouse around the screen and you will see that MODI gives you the information about the DOM element that your mouse is currently over.

If MODI is over the element you're interested in inspecting, just click and drag the top of it and you can put it anywhere on the screen.

 

Conclusion

The best part about this DOM inspector is that I don't have to install it on anything on my machine. If I'm on a clients machine or on a server - using the browser - and I need to inspect the DOM, I can hit the MODI web page and then run the bookmark and I'm off into the DOM.

Simple, yet effective.

#    Comments [1] |
 Friday, October 12, 2007

Auto-Insert Attribute Quotes in HTML

A quick tip for those of you who don't use tools like ReSharper that automatically put the attribute quotes in for you when typing HTML.

Don't you hate it when you're typing in the HTML window of an ASPX page and you have to type in the quotes for an attribute value on element? I do too.

You can fix this by going to Tools > Options > Text Editor > HTML > Format

and checking the box that says "Insert attribute value quotes when typing".

Check out this short clip that I made to see exactly what I'm talking about (there is NO AUDIO on this clip)

Click the image to view the screencast-clip.

image

#    Comments [0] |
 Monday, September 17, 2007

Watermarking Images in ASP.NET with an HttpHandler

This will be a first of a couple posts about this ImageHandler and ways it can be used.

At times I've worked for different very creative and artistic companies (here, here, here and here) and during those times I have wanted to watermark an image for one reason or another. Either the image was protected and was not supposed to be redistributed or the client just wanted to make sure their URL was present on the image. The reasons are plentiful.

The image(s) I'm using in this project is the logo for the company I work for.

You can download the handler and a sample web site to run this handler in at the bottom of this post.

How To

I've written a quick example of how to watermark all images (jpg, png, gif or bmp) on a web site. 

It uses a simple HttpHandler that you can drop into the App_Code folder of your ASP.NET Project. With a quick addition to your web.config file you start using this.

Video

To understand what I'm saying, watch this screen cast (this screen cast has no sound):

ImageHandlerScreenCast

Now lets look at some code...

Code

(web.Config)

<?xml version="1.0"?>
<configuration>
    <system.web>
        <httpHandlers>
            <add verb="GET" type="ImageHandler" path="*.jpg,*.png,*.gif,*.bmp"/>
        </httpHandlers>
        <compilation debug="true"/></system.web>
</configuration>

ImageHandler.cs

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Net.Mime;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;

/// <summary>
/// Summary description for ImageHandler
/// </summary>
public class ImageHandler : IHttpHandler
{
    public ImageHandler()
    {
    }

    public string GetContentType(String path)
    {
        switch (Path.GetExtension(path))
        {
            case ".bmp": return "Image/bmp";
            case ".gif": return "Image/gif";
            case ".jpg": return "Image/jpeg";
            case ".png": return "Image/png";
            default: break;
        }
        return String.Empty; 
    }

    public ImageFormat GetImageFormat(String path)
    {
        switch (Path.GetExtension(path).ToLower())
        {
            case ".bmp": return ImageFormat.Bmp;
            case ".gif": return ImageFormat.Gif;
            case ".jpg": return ImageFormat.Jpeg;
            case ".png": return ImageFormat.Png;
            default: return null;
        }
    }
    
    protected byte[] WatermarkImage(HttpContext context)
    {

        byte[] imageBytes = null;
        if (File.Exists(context.Request.PhysicalPath))
        {
            // Normally you'd put this in a config file somewhere.
            string watermark = "John Doe - © EXAMPLE Company 2007";

            Image image = Image.FromFile(context.Request.PhysicalPath);

            Graphics graphic;
            if (image.PixelFormat != PixelFormat.Indexed && 
image.PixelFormat != PixelFormat.Format8bppIndexed && 
image.PixelFormat != PixelFormat.Format4bppIndexed && 
image.PixelFormat != PixelFormat.Format1bppIndexed)
            {
                // Graphic is not a Indexed (GIF) image
                graphic = Graphics.FromImage(image);
            }
            else
            {
                /* Cannot create a graphics object from an indexed (GIF) image. 
                 * So we're going to copy the image into a new bitmap so 
                 * we can work with it. */
                Bitmap indexedImage = new Bitmap(image);
                graphic = Graphics.FromImage(indexedImage);

                // Draw the contents of the original bitmap onto the new bitmap. 
                graphic.DrawImage(image, 0, 0, image.Width, image.Height);
                image = indexedImage;
            }
            graphic.SmoothingMode = SmoothingMode.AntiAlias & SmoothingMode.HighQuality;

            Font myFont = new Font("Arial", 15);
            SolidBrush brush = new SolidBrush(Color.FromArgb(80, Color.White));

            /* This gets the size of the graphic so we can determine 
             * the loop counts and placement of the watermarked text. */
            SizeF textSize = graphic.MeasureString(watermark, myFont);

            // Write the text across the image. 
            for (int y = 0; y < image.Height; y++)
            {
                for (int x = 0; x < image.Width; x++)
                {
                    PointF pointF = new PointF(x, y);
                    graphic.DrawString(watermark, myFont, brush, pointF);
                    x += Convert.ToInt32(textSize.Width);
                }
                y += Convert.ToInt32(textSize.Height);
            }


            using (MemoryStream memoryStream = new MemoryStream())
            {
                image.Save(memoryStream, GetImageFormat(context.Request.PhysicalPath));
                imageBytes = memoryStream.ToArray();
            }

        }
        return imageBytes;
    }

    #region IHttpHandler Members

    public bool IsReusable
    {
        get { return false; }
    }

    public void ProcessRequest(HttpContext context)
    {
        context.Response.Clear();
        context.Response.ContentType = GetContentType(context.Request.PhysicalPath);
        byte[] imageBytes = WatermarkImage(context);
        if (imageBytes != null)
        {
            context.Response.OutputStream.Write(imageBytes, 0, imageBytes.Length);
        }
        else
        {
            // No bytes = no image which equals NO FILE. :)  
            // Therefore send a 404 - not found response. 
            context.Response.StatusCode = 404;
        }
        context.Response.End();
    }

    #endregion
}

Explanation

With the configuration above, we will capture all jpg, png, gif and bmp images as they are requested from the HttpRequest object.

As we encounter the image, we find it, then create an image and graphics object from it and then loop through the images height/width ratio and write some text on top of the image.

A key note about watermarking. You can simply write some text over the screen but then the image behind it would not be visible. You should ensure that the font you're overlaying is transparent therefore the image shows through the font that is on top. This is done by using the Color.FromArgpb method, which is in this piece of code:

Color.FromArgb(80, Color.White)

The "80" is a alpha value. Valid values are 0-255. If you're at 0, the color is invisible. If you're at 255, the color is solid. Here, I'm using 80. You can see the text, but you can see through it. Play with this number to see what works best for you.

Conclusion

Its easy to watermark images in ASP.NET. In future posts I'll show you how to protect certain directories images as well as how to do some other cool stuff with it like denying access (anti-leech and leech re-routing).

Download ImageHandler.zip (1.57 KB)  (This is just the cs file)

Download Example ImageHandlerDemoSite.zip (84.7 KB) (Demo site for demonstration)

#    Comments [15] |
 Thursday, September 13, 2007

Automated Form Posting with .NET / ASP.NET's CURL

At times you'll encounter an issue where a client, customer or merchant does not provide an endpoint to an application you're required to use. Basically, the application you're working with does not have a web service, nor do you have access to connect to the database directly but you HAVE TO get data into the system and the only thing available is a web form on a remote server. 

In PHP, you can utilize CURL to communicate with servers and post forms through code.  In .NET, we have the System.Net namespace which contains a ton of little goodies. It is ASP.NET's implementation that relates to PHP's CURL library. The MSDN describes this namespace perfectly:

The System.Net namespace provides a simple programming interface for many of the protocols used on networks today.

I've used this namespace many times and I have actually used the System.Net.WebClient class in my C# Google Geocode Class. Which brings me to...

Purpose of the blog entry...

In this example I'm going to show you how to use the HttpWebRequest class to post form data to a website to send a text message to your phone (utilizing the website as the service). I've already talked about how to send a text message to your phone from .NET, but this time I want to do it using an online service to demonstrate the use of these two classes.

Note: You need to be aware of how HTTP requests work. You need to know that there are headers being sent, Cookies, Content Types, etc. Without a basic grasp on the the request/response model concepts  you'll be left scratching your head wondering what the hell is going on behind the scenes.

Getting Started

We first need to identify what headers are being sent. I use Fiddler to do this. Fiddler allows you to view the HTTP request/responses down to a very fine grain of detail.

Then, I'll go to the web page where I'm trying to make the request and then I'll submit the form I'm working with. Once the form submission is complete, I'll look at the headers that are being sent (if any at all). In this case we have some because the site we're working with is PHP based, while we're working with .NET. INTEGRATION BABY!!

Here is a screen shot of Fiddler and the items I'm look at. (click to get a bigger shot)

Fiddler1

I'm looking for the HTTP Headers that are sent across the wire. I captured those in the red-highlighted area on the screen shot above. Now that I know what URL I need to send to (newsend.php - look on the left hand side of the screen shot). I can then set up my HttpWebRequest object and prepare to send it off.

Enough Jibba-Jabba, Lets see some code!

Please note, this is a Console App that was written to do this. You'd normally separate these concerns out into layers. Its a example app, what more did you want? :) (The entire example app can be downloaded at the bottom of this post).

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;

namespace HttpRequestExample
{
    class Program
    {
        static void Main(string[] args)
        {
            SendTextMessage();
            Console.WriteLine("Press [ENTER] to quit"); 
            Console.ReadLine(); 
        }

        /// <summary>
        /// Sends a Text Message
        /// </summary>
        private static void SendTextMessage()
        {
            string phoneNumber = "YourPhoneNumber"; // Put your phone number here, player.
            string provider = "tmobile"; // Possible Values: ... (see download for full comment) 
            string message = "Test from example application! DateTime:" + DateTime.Now.ToString();
            string headerVars = String.Format(
                "refer=txt2day&to={0}&from=Your+Email+%28optional%29&provider={1}&message={2}&submit.x=90&submit.y=50", 
                phoneNumber, provider, message); 

            
            HttpWebRequest textMessageRequest =  (HttpWebRequest)WebRequest.Create("http://www.txt2day.com/newsend.php");
            textMessageRequest.Method = "POST";
            textMessageRequest.ContentType = "application/x-www-form-urlencoded";
            textMessageRequest.ContentLength = headerVars.Length;
            AddPostVarsToHeader(textMessageRequest, headerVars);
            
            string responseString;
            using (StreamReader streamReader = new StreamReader(textMessageRequest.GetResponse().GetResponseStream()))
            {
                // The response string has the HTML response (if its HTML)
                responseString = streamReader.ReadToEnd();
                
                /* At this point the Txt Message should be sent. 
                 * Scrape the response to check for a thank you message.
                 * On the Txt2Day.com site, the thank you message looks like this:
                 * Thanks! Your Message Has Been Sent
                 */ 
                Console.WriteLine("Message Sent: {0}", responseString.Contains("Thanks"));
                Console.WriteLine("Note: This message can take a few minutes (to a couple hours from my testing) to arrive."); 
            }
        }


        /// <summary>
        /// Adds the <paramref name="values"/> to the the http headers. 
        /// </summary>
        /// <param name="request">The HTTP Request</param>
        /// <param name="values">The values to add to the headers</param>
        private static void AddPostVarsToHeader(WebRequest request,