|
 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] |
 Friday, September 14, 2007

Belkin is Gangster

A fun little tidbit for today...

nwa I can't believe I didn't notice this before, but... BELKIN IS GANGSTER.

They're Corporate Headquarters is located in Compton CA. That, my friends, is gangster in my book. 

 

 

 

 

Don't believe me? Check out this screen shot form their web site:

BelkinHqScreenshot

#    Comments [0] |
 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, string values)
        {
            using (StreamWriter streamWriter = new StreamWriter(request.GetRequestStream(), ASCIIEncoding.ASCII))
            {
                streamWriter.Write(values);
                streamWriter.Close();
            }
        }
    }
}

Code Explanation

Setting Up The HttpWebRequest Object

            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;

The HttpWebRequest object uses the static Create method of its Abstract Base Class, WebRequest.

The  "phoneNumber" variable is the phone number you want to text.

The "provider" variable is the name of the provider. The attached solution has the full list of available providers (there are a ton of them in there).

The variable "headerVars" is actually what is posted to the server when the submit button is clicked. If you look at the format of the string you'll see "to" and "from" and "provider" and "message". If you look at the source of the website, you'll notice that the form elements share thsoe names. That's not a coincidence. These are the actual POST vars that are posted to the server. In this portion of the code we are rebuilding the Headers.

Since we are sending headers we need to specify the length, which is done by setting the ContentLength of the HttpWebRequest.

Next, we put the values into the request object itself by calling:

AddPostVarsToHeader(textMessageRequest, headerVars);

Which is defined as:

/// <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, string values)
{
    using (StreamWriter streamWriter = new StreamWriter(request.GetRequestStream(), ASCIIEncoding.ASCII))
    {
       streamWriter.Write(values);
       streamWriter.Close();
    }
}

Here, we write the values into the request stream itself. Now we are ready to send it off and get a request. Here's how we do that:

             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."); 
            }

There are many ways to obtain the Response from the Request, but at this point I just want to take the Response Stream and write it to the string. What this is doing is sending a web request to the page we defined (newsend.php) with our form variables placed into the headers, and then after the server processes it, it will return a response, in which we capture it from the response stream.

This stream is actually the HTML that is returned from the "thanks.php" page (step through the code in the debugger and look at the HttpWewbRequest.GetResponse() method. It returns a WebResponse which has all kinds of good info about the response.

With the HTML that is returned we can scrape it to see if the word "Thanks!" is included, which signifies that a message was sent. (On the website, when you get to the thanks.php page after manually submitting a message the web site displays: "Thanks! Your message has been sent". Or something very similar. I know that the message does contain the "Thanks" word though. :)

*Note: This service on the website of sending SMS/Text Messages is not SUPER reliable. I was doing some testing and found that sometimes I'd send a message and I'd get it within 5 seconds, other times it was 5-6 hours  before I got my text message on my phone.

At this point you've connected to a web site through code, posted a request that contains variables for the server, and had it perform an action. You then received the page and scraped it to find the overall result.

Download

HttpRequestExample.zip (4 KB)

#    Comments [0] |
 Tuesday, September 11, 2007

Google AdWords & Discover Card

GoogleAdwordsLogo This last weekend I was setting up a Google AdWords campaign and I was blown away when I got to the payment screen. Google AdWords DOE S NOT support Discover Card as a payment method.

Yeah, for real. WHAT?!?!?! One of the largest web sites in the world does not discover_logo_gray accept Discover Card?!?! I can go to Walgreens, Cold Stone Creamery, and even Rubios and they all still accept Discover Card. But Google AdWords does not?

I'm not happy about this and I think its kind of ridiculous. Google has their own Checkout Service which allows users use a Discover Card for payment, but you cannot use it for AdWords? Oh COME ON NOW. I'm sure there is some good reason, but I even emailed support and here's their response:

Hello Donn,

Thank you for your email. I understand you would like to make payments towards your AdWords account with your Discover card. Please note that available payment options depend on your location and currency.


At this time, Google only accepts the forms of payment listed at
https://adwords.google.com/select/AfpoFinder.

We're continually working on expanding our payment options to better serve our advertisers' needs. We apologize for any inconvenience.

If you have additional questions, please visit our Help Center at
https://adwords.google.com/support to find answers to many frequently asked questions. Or, try our Learning Center at
http://www.google.com/adwords/learningcenter/ for self-paced lessons that cover the scope of AdWords.

We look forward to providing you with the most effective advertising available.


Sincerely,

The Google AdWords Team

 

So I go visit their forms of payment area and there is nothing in regards to Discover Card.

Normally this wouldn't upset me, but I have certain benefits that I receive when I use certain cards. So using this card delivers benefits that I'm trying to utilize. Anyway, I'll have to use a Visa or Mastercard for now.

But I'm still not happy about it - this is definitely WEAK SAUCE in my book.

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

Texting/SMS Your Mobile Phone From .NET (or any other technology)

This is mainly a post for myself so I can have all the resources in one area, but hopefully it helps someone else out too.

There are a few ways to send a Text Message to your phone. The most common way is to send an email to your mobile provider. Here's a list of some of the mobile providers:

T-Mobile: phonenumber@tmomail.net 
Virgin Mobile: phonenumber@vmobl.com 
Cingular: phonenumber@cingularme.com 
Sprint: phonenumber@messaging.sprintpcs.com 
Verizon: phonenumber@vtext.com 
Nextel: phonenumber@messaging.nextel.com 
Update: 2007-09-12 - I found more email domains for the text messaging sites. 
sms.3rivers.net, 3 River Wireless
mobile.mycingular.com,Cingular
messaging.nextel.com,Nextel
tmomail.net,T-Mobile
vtext.com,Verizon
paging.acswireless.com,ACS Wireles
message.alltel.com,Alltel
message.alltel.com, Alltel PCS
alphanow.net,AlphNow
paging.acswireless.com ,Ameritech
mobile.att.net,AT& T PCS
text.bell.ca,Bell Canada
wireless.bellsouth.com,Bell South
bellsouth.cl,Bellsouth
myboostmobile.com,Boost
mobile.celloneusa.com,CellularOne
sms.edgewireless.com,Edge Wireless
mymetropcs.com,Metro PCS
qwestmp.com,Qwest
messaging.sprintpcs.com ,Sprint PCS
vmobl.com,Virgin Mobile
Where phonenumber = your 10 digit phone number 

If you know your company is going to only use ______ carrier, then you can set it up in your system to automatically send emails to the carriers address as listed above.

BUT what if you don't know the carrier? There are other services out there -

TeleFlipTeleflip allows you to send a  text message to anyone that can accept a text message. The caveat here is they reserve the right to sell your email (the from email) and the destination email (the phone# to you which your sending a text message). This service works GREAT, and if you don't give a damn about letting your email and phone number get out into the wild world of SPAM, then this  is the service for  you.

How to use:

Send an email to phonenumber@teleflip.com 

where "phonenumber" is the destination phone number you want to text.

Then include a subject and body in the email and send away. The user will get the text very soon.

TeleFlip reserves the right to advertise in your text messages and reserves the right to keep and read all of your texts. So... be careful what you send. But then again, you should be careful anytime  you text something, especially when its business related.

TeleFlip was almost a "slam-dunk" for my applications until I realized they stored all source/destination addresses and reserved the right to sell them. Unfortunately, I think this is going to cost them a lot of business.

 

webservicexnet WebServiceX.NET

Web Service X provides a service that's called SendSMSWorld and SendSMSIndia. You can send a lot of messages through this web service. I've heard that it works, but I've never been able to get it working. I ran into a few issues and the support and contact areas of the site didn't even work. I received exceptions when I attempted to use them.

Hmmm.... Betsy... this sure doesn't sound like a place I can trust. Its a good place to keep an eye one just in case they fix everything and it becomes reliable.

Conclusion

I'm aware of a lot of other services out there that send text messages for you but it seems to me the best way to do it is through the carrier. Find the carrier name, and then allow the app to send it to the carrier address. That's going to be the most reliable method to get texting to work (IMO).

There are other sites out there that allow you to send text messages for free, but they are not web services that you can connect to - txt2day.com and textforfree.net are examples of these. You could use the WebRequest class in .NET (which I'll be writing about soon) and Fiddler to build an auto submission routine for you, but that's a royal pain in the arse if you ask me.

So use what works best, but for ease of use, try out the carrier method first.

#    Comments [1] |

Simulating Bandwidth in Web Applications

Many times during developing web applications you will need to simulate a users experience. Certain questions come up, such as:

  • Will this page load fast on a dial up connection?
  • What are the response times with my new AJAX functionality on a slow connection?
  • How long will it take the user to upload/download file xxx?
  • What happens when the connection is too slow?

Unfortunately its hard to simulate a users experience on 56k when you're running on a 100 megabit network. A lot of companies will simply just try to optimize their code for the worst possible scenario, whilst still being blind to what really happens on a connection that slow.

Fortunately there are ways to simulate a users slow connection.

[Note: I'm not getting any kickbacks from this company, its just a product that's helped me a TON.]

netlimiter-top-logo Net Limiter is one such product that helps us test these connection speeds. Net Limiter will allow you to select a product, such as Internet Explorer and limit it bandwidth usage. I can set it to 56k, so when I visit a web page (or a local development web server) it will only allow enough bits to pass through the wire to simulate a 56k connection.

Uses

I've used this to test web applications that utilize AJAX functions. I've used it to test uploads to my web site to see how long it would take. I've also used this to test Flash/ActionScript development.

You can also use this to test the speeds of your Silverlight Applications.

Here is a screen shot of the application in use. In the screen shot you can see the areas where you can limit the incoming or outgoing bandwidth. You can set a speed for either connection (incoming/outgoing) and you can limit one or the other.

nl2shot_limit

 

Conclusion

The question shouldn't be "why to test test network speed" but more or less "why would you NOT test your functional speed of your web application?" So before your users say your site is too slow, experience it yourself, first hand.

#    Comments [0] |
 Sunday, September 09, 2007

SubSonic on GoDaddy Shared Hosting

subsonic_logo I was trying to get SubSonic to run on a Free Hosting Environment (on GoDaddy) but unfortunately I kept getting a SecurityException when the code would attempt to save an entity. The code looked like this:

faq.Save(); // Saves a faq item to the database. 

This code would save the new FAQ entity to the database. Unfortunately this code would throw an exception. The reason the code threw an exception is because in SubSonic, the Utility.cs file trys to write to the Trace listeners. The Trace listeners have a SecurityAction that demands a security permission. On GoDaddy, they lock down this access. Why? Who knows. They're hosters, they do what they do. GoDaddy usually has everything locked down to a medium trust environment.

The documentation states that you can turn off the trace by setting a flag called "enableTrace" to false, BUT... because this doesn't work because the SecurityAction Demand walks the call stack right when its referenced in code at runtime. Since the flag is being checked in the calling code, the security action is being checked in the stack when the class is called.

This has been addressed in the SubSonic forums and it looks like it will be fixed soon. Here's how to get around it for now.

Open the source for the SubSonic project. Go into the AbstractRecord.cs and comment out any line that has the following call:

Utility.WriteTrace("...");

From my finds, those line numbers are:

  • 901
  • 948
  • 951
  • 957
  • 962
  • 995
  • 1002

Comment all of those lines out, and then recompile the SubSonic project.

Then in your project, delete your SubSonic.dll reference, and now reference the new SubSonic.dll that you just built. This DLL will not have the call that writes to the trace.

This will avoid the Security call that eventually throws the exception.
The .Save() call will now succeed.

Good luck. :)

#    Comments [2] |
 Wednesday, September 05, 2007

CodeSmith Screencast

CodeSmithLogoI recently finished a screen cast that demonstrates the use of Code Smith to create files based upon a database schema.

The Haps

In this screen cast I make a connection to the database, loop through each table in the database create a file based upon the table information. The file is exported to a directory (such as c:\temp). The files are named with the following nomenclature.

TableName.txt

Therefore, if we have a table by the name of "Customers" we will have a file in the c:\temp directory with the name of "TableName.txt".

Inside of the table file I have the names of the columns in the database.

This screen cast will introduce the Database Schema object as well as the process for registering templates and setting template properties at run time.

Click the image below or this link to view the screen cast.

CodeSmithScreenCastThumb

Downloads

CodeSmithScreenCastFiles.zip (1.17 KB)
#    Comments [0] |