Tuesday, July 20, 2004

JavaScript Functions for Working with Cookies


// Cookie handeling routines.

// name - name of the cookie
// value - value of the cookie
// [expires] - expiration date of the cookie (defaults to end of current session)
// [path] - path for which the cookie is valid (defaults to path of calling document)
// [domain] - domain for which the cookie is valid (defaults to domain of calling document)
// [secure] - Boolean value indicating if the cookie transmission requires a secure transmission
// * an argument defaults when it is assigned null as a placeholder
// * a null placeholder is not required for trailing omitted arguments
function setCookie(name, value, expires, path, domain, secure) {
var curCookie = name + "=" + escape(value) +
((expires) ? "; expires=" + expires.toGMTString() : "") +
((path) ? "; path=" + path : "") +
((domain) ? "; domain=" + domain : "") +
((secure) ? "; secure" : "");
document.cookie = curCookie;
}

// name - name of the desired cookie
// * return string containing value of specified cookie or null if cookie does not exist
function getCookie(name) {
var dc = document.cookie;
var prefix = name + "=";
var begin = dc.indexOf("; " + prefix);
if (begin == -1) {
begin = dc.indexOf(prefix);
if (begin != 0) return null;
} else
begin += 2;
var end = document.cookie.indexOf(";", begin);
if (end == -1)
end = dc.length;
return unescape(dc.substring(begin + prefix.length, end));
}

// name - name of the cookie
// [path] - path of the cookie (must be same as path used to create cookie)
// [domain] - domain of the cookie (must be same as domain used to create cookie)
// * path and domain default if assigned null or omitted if no explicit argument proceeds
function deleteCookie(name, path, domain) {
if (getCookie(name)) {
document.cookie = name + "=" +
((path) ? "; path=" + path : "") +
((domain) ? "; domain=" + domain : "") +
"; expires=Thu, 01-Jan-70 00:00:01 GMT";
}
}

// date - any instance of the Date object
// * hand all instances of the Date object to this function for "repairs"
function fixDate(date) {
var base = new Date(0);
var skew = base.getTime();
if (skew > 0)
date.setTime(date.getTime() - skew);
}

Monday, July 19, 2004

JavaScript Form Validation Functions (validate input)

Here's a link to a file contaning the functions posted in the last 10 or so posts, as well as some additional functions that can be extremely useful in form validation. Pay particular attention to the ValidateField() function toward the bottom of the JavaScript include file. It allows you to pass the field object (represented by 'this' in a JavaScript event), the type of validation you want to perform (i.e. "date", "email", "phone", "numeric", etc.), a flag indicating whether or not the field is required (i.e. can accept blanks) and a custom error message to display if the field fails validation. I've used it in many of my web projects and hope you will get some use out of it to.

You can download the JavaScript include file with the validation functions here:

http://www.related-pages.com/form_validation.js

JavaScript Function to Validate U.S. State (valid state)

This function tests the passed text against the U.S. State abbreviations, returning True if the text is a U.S. State abbreviation, False otherwise.


// Make sure a state is valid.
function ValidState(sstate) {
sstates = "wa|or|ca|ak|nv|id|ut|az|hi|mt|wy" +
"co|nm|nd|sd|ne|ks|ok|tx|mn|ia|mo" +
"ar|la|wi|il|ms|mi|in|ky|tn|al|fl" +
"ga|sc|nc|oh|wv|va|pa|ny|vt|me|nh" +
"ma|ri|ct|nj|de|md|dc";

if (sstates.indexOf(sstate.toLowerCase() + "|") > -1) {
return true;
}

return false;
}

JavaScript Function Allow Number keys Only (only digit)

This function returns True if the passed keycode is a digit, False if it is not. This is useful when you want to limit the values of a input field to only numbers.


function isDigit(nKeyCode)
{
// Test for digit keycode (0-9).
if((nKeyCode > 47) && (nKeyCode < 58))
{
return true;
}

return false;
}

JavaScript Function Validate Date (valid date)

This function returns True if the passed text is a valid date, False if it is not.


/*
ValidDate - true for valid date, false for invalid
*/
function IsValidDate(PossibleDate)
{
var PDate = new String(PossibleDate);

var regex = /(^\d{1,2})\/(\d{1,2})\/(\d{4,4})|(^\d{1,2})\/(\d{1,2})\/(\d{2,2})/;

if( regex.test(PDate) )
{
var month = new String(RegExp.$1);
var day = new String(RegExp.$2);
var year = new String(RegExp.$3);
if( month.length == 0 )
{
month = new String(RegExp.$4);
day = new String(RegExp.$5);
year = new String(RegExp.$6);
}

var today = new Date();
var thisYear = new String(today.getFullYear());

if( year.length == 2 )
{
if( year > 50 )
{
year = String(Number(thisYear.substring(0,2))-1) + year;
}
else
{
year = thisYear.substring(0,2) + year;
}
}

if( month < 1 || month > 12 ) { return false; }

if( day < 1 || day > 31 ) { return false; }

if ((month==4 || month==6 || month==9 || month==11) && day>30) { return false; }

if (month == 2) // check for february 29th
{
var isleap = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
if (day>29 || (day==29 && !isleap))
{
return false;
}
}

if( (Number(year) < Number(thisYear) - 250) ||
(Number(year) > Number(thisYear) + 250) )
{ return false; }

return true;
}
return false;
}

JavaScript Function to Validate SSN (valid ssn)

This function validates whether or not the passed text is a U.S. social security number, returning True if it is, False if it is not.


/*
ValidSSN - true for valid SSN, false for invalid
*/
function ValidSSN(SSN)
{
var SSNum = new String(SSN);

var regex = /^[0-9]{3,3}\-[0-9]{2,2}\-[0-9]{4,4}$/;

return regex.test(SSNum);
}

JavaScript Function to Validate Phone Number (valid phone number)

This function tests the passed text, returning True if the text is formatted in a valid U.S.A. phone number format, False otherwise.


/*
ValidPhoneNumber - true for valid phone number, false for invalid
*/
function ValidPhoneNumber(PhoneNumber)
{
var PNum = new String(PhoneNumber);

// 555-555-5555
// (555)555-5555
// (555) 555-5555
// 555-5555

// NOTE: COMBINE THE FOLLOWING FOUR LINES ONTO ONE LINE.
var regex = /^[0-9]{3,3}\-[0-9]{3,3}\-[0-9]{4,4}$|
^\([0-9]{3,3}\) [0-9]{3,3}\-[0-9]{4,4}$|
^\([0-9]{3,3}\)[0-9]{3,3}\-[0-9]{4,4}$|
^[0-9]{3,3}\-[0-9]{4,4}$/;

return regex.test(PNum);
}

JavaScript Function Is Numeric (validate number, valid number)

This function checks the passed text to see if it is a numeric value and returns True if it is, False if it is not.


/*
IsNumeric - true for all numeric, false if not
*/
function IsNumeric(PossibleNumber)
{
var PNum = new String(PossibleNumber);
var regex = /[^0-9]/;
return !regex.test(PNum);
}

JavaScript Function to Validate Email Addresses (valid email address)

This function validates an email address, returning True if it is a properly formatted email address, False if it is not.


/*
ValidEmail - true for valid email, false for invalid
*/
function ValidEmail(EmailAddr) {
var reg1 = /(@.*@)|(\.\.)|(@\.)|(\.@)|(^\.)/;
var reg2 = /^.+\@(\[?)[a-zA-Z0-9\-\.]+\.([a-zA-Z]{2,3}|[0-9]{1,3})(\]?)$/;

var SpecChar="!#$%^&*()'+{}[]\|:;?/><,~`" + "\"";
var frmValue = new String(EmailAddr);
var len = frmValue.length;

if( len < 1 ) { return false; }
for (var i=0;i {
temp=frmValue.substring(i,i+1)
if (SpecChar.indexOf(temp)!=-1)
{
return false;
}
}

if(!reg1.test(frmValue) && reg2.test(frmValue))
{
return true;
}

return false;
}

JavaScript Function to Validate Zip codes (valid zipcode)

This function returns True if a Zipcode is a validly formatted U.S. Zip code, false if it is not.


/*
ValidZipCode - true for valid zip codes, false for invalid ones
*/
function ValidZipCode(ZipCode)
{
//Your zip code must contain 5 or 9 digits.
// 9 digit zip codes should contain no spaces and a
// hyphen before the last 4 digits.

var stringValue = new String(ZipCode);
var stringLength = stringValue.length;

if ((stringLength!=5)&&(stringLength!=9)&&(stringLength!=10))
{
return false;
}

if(stringLength==5)
{

for (var i = 0; i < stringLength; i++)
{
value = stringValue.charAt(i)
if (!((value >= 0) && (value <=9)))
{
return false;
}
}
}

if(stringLength==9)
{
for (var i = 0; i < stringLength; i++)
{
value = stringValue.charAt(i)
if (!((value >= 0) && (value <=9)))
{
return false;
}
}
}

if(stringLength==10)
{
var zip=stringValue.substring(0,5)
var symbol=stringValue.substring(5,6)
var plus4=stringValue.substring(6,10)

if(symbol!="-")
{
return false;

}


for (var i = 0; i < zip.length; i++)
{
value = zip.charAt(i)
if (!((value >= 0) && (value <=9)))
{
return false;
}
}
for (var i = 0; i < plus4.length; i++)
{
value = plus4.charAt(i)
if (!((value >= 0) && (value <=9)))
{
return false;
}
}

}
return true;
}

JavaScript Function to Trim White Space (RTRIM)

Like the LTRIM previously posted, this function trims white space, but from the right of the text.


/*
RTrim - Trims whitespace from right of a string
*/
function RTrim(str)
{
var whitespace = new String(" \t\n\r");

var s = new String(str);

if (whitespace.indexOf(s.charAt(s.length-1)) != -1)
{
var i = s.length - 1; // Get length of string
while (i >= 0 && whitespace.indexOf(s.charAt(i)) != -1)
i--;
s = s.substring(0, i+1);
}

return s;
}

JavaScript Function To Trim White Space (LTRIM)

This function trims the white space from the left of a string, a useful function when removing extra spaces typed in by a user into a form.


/*
LTrim - Trims whitespace from left of a string
*/
function LTrim(str)
{
var whitespace = new String(" \t\n\r");

var s = new String(str);

if (whitespace.indexOf(s.charAt(0)) != -1)
{
var j=0, i = s.length;
while (j < i && whitespace.indexOf(s.charAt(j)) != -1)
{
j++;
}
s = s.substring(j, i);
}

return s;
}

JavaScript Function to Format Currency (Money)

I am going to be posting a series of useful JavaScript functions for form validation and input formatting. This is the first in that series: a function which formats a number as currency.


function formatCurrency(num)
{
num = num.toString().replace(/\$|\,/g,'');
if(isNaN(num)) num = "0";
sign = (num == (num = Math.abs(num)));
num = Math.floor(num*100+0.50000000001);
cents = num%100;
num = Math.floor(num/100).toString();
if(cents<10) cents = "0" + cents;
return (((sign)?'':'-') + num + '.' + cents);
}

Tuesday, July 06, 2004

PHP Function For Adding Hours To A Date / Time

One of the things that I've always thought was a little lacking in PHP is its ability to easily perform date/time math.

In Visual Basic.NET there are functions called AddDays, AddHours, AddMinutes, etc. that make it very easy to adjust a date. In PHP there are no such functions. You have to go through a little more complexity to make this work.

Since I often needed a function to add hours to a date in my PHP applications, I wrote this little function to do it for me. Just pass in the date structure (returned by the getdate() function) that you want to add the hours to and the number of hours (positive or negative) that you want to add to the date.


// Returns the date adjusted by the number of hours specified.
function AddHours($date, $hours)
{
$newdate = date("m/d/y H:i:s", mktime($date["hours"] + $hours,
$date["minutes"],
$date["seconds"],
$date["mon"],
$date["mday"],
$date["year"]));
return $newdate;
}

Wednesday, June 23, 2004

Get the Compression Method of a TIFF in VB.NET (Visual Basic.NET)

This really bugged me. I found code all over the web for how to convert a TIFF from one compression format to another and how to convert a TIFF to every other kind of file format that .NET supports, but nowhere could I find a code snippet showing me how to know what compression method a TIFF is currently using except for a lone post on Google Groups.

So I am posting the code for knowing what the compression method of a TIFF is in VB.NET.



Private Function GetTiffCompressionType(ByVal FilePath As String) As Int32

Dim img As Bitmap
Dim c As Int32

Try
img = img.FromFile(FilePath)
c = Int32.Parse(img.GetPropertyItem(259).Value(0))
img.Dispose()
Catch ex As Exception
c = -1
End Try

'Return value:
'1 = ???
'2 = CCIT 3 (modified)
'3 = CCIT 3
'4 = CCIT 4
'5 = PackBits
'That's all I've discovered so far.
Return c

End Function

Wednesday, May 19, 2004

PHP Html Parser

A couple of days ago I posted some code for a VB.NET HTML Parser. If you need to do something similar using PHP, I've used this class with good success:

http://php-html.sourceforge.net/

It operates a little differently than the VB.NET parser, since it actually provides a means for you to walk down the document object model tree, but it works well.

Tuesday, May 18, 2004

Web Stats Tracking Script

Here's a groovy little script if you'd rather log your web stats to your own database than use a third party tracking script:


var asUrl = 'http://www.mysite.com/myscript.php?' +
'&url=' + escape(document.location.href) + '&pv=1' +
'&r=' + escape(document.referrer) +
'&dt=' + new Date().valueOf();
var bug = new Image();
bug.src = asUrl;


Paste that script onto your pages (or, preferably, put it into a javscript include file and use the SRC property of the SCRIPT tag to include it onto the page. It sends the URL of the page and the referrer to your script.

Here's a handy function that extracts the search keywords from the referrer if the referrer is a search engine. It captures all of the major search engines (Google, Yahoo, MSN, etc.) and most of the smaller ones. For your convenience I've included both the PHP and ASP.NET versions.

PHP Version

function getSearchKeywords($url)
{
$keywords = "";
$qvar = "";

if (strlen($url) == 0)
{
return "";
}

$url = trim($url);

$qloc = strpos($url, "?");
if (strpos($url, "/search/web"))
{
$keywords = substr($url, strpos($url, "/search/web/") + 12);
if (strpos($keywords, "/"))
{
$keywords = substr($keywords, 0, strpos($keywords, "/"));
}
$keywords = urldecode($keywords);
$keywords = str_replace(",", "", $keywords);
$keywords = str_replace(".", "", $keywords);
$keywords = str_replace("-", "", $keywords);
$keywords = str_replace("+", " ", $keywords);
$keywords = str_replace('"', "", $keywords);
return $keywords;
}
elseif (substr($url, 0, 24) == "http://search.yahoo.com/")
{
if (strpos($url, "&p=", $kloc + 1))
{
$kloc = strpos($url, "&p=", $kloc + 1);
}
if (!$kloc)
{
$kloc = strpos($url, "?p=");
}
$qvar = "p";
}
elseif (strpos($url, "&query=") | strpos($url, "?query="))
{
$kloc = strpos($url, "&query=");
if (strpos($url, "&query=", $kloc + 1))
{
$kloc = strpos($url, "$query=", $kloc + 1);
}
if (!$kloc)
{
$kloc = strpos($url, "?query=");
}
$qvar = "query";
}
elseif (strpos($url, "&searchfor=") | strpos($url, "?searchfor="))
{
$kloc = strpos($url, "&searchfor=");
if (strpos($url, "&searchfor=", $kloc + 1))
{
$kloc = strpos($url, "&searchfor=", $kloc + 1);
}
if (!$kloc)
{
$kloc = strpos($url, "?searchfor=");
}
$qvar = "searchfor";
}
else
{
$kloc = strpos($url, "&q=");
if (strpos($url, "&q=", $kloc + 1))
{
$kloc = strpos($url, "&q=", $kloc + 1);
}
if (!$kloc)
{
$kloc = strpos($url, "?q=");
}
$qvar = "q";
}

if (!$qloc | !$kloc)
{
return "";
}

$nextloc = strpos($url, "&", $kloc + 1);
if (!$nextloc)
{
$nextloc = strlen($url);
}

$keywords = substr($url, $kloc + strlen($qvar) + 2,
$nextloc - ($kloc + strlen($qvar) + 2));
$keywords = urldecode($keywords);
$keywords = str_replace(" ", " ", $keywords);
$keywords = str_replace(",", "", $keywords);
$keywords = str_replace(".", "", $keywords);
$keywords = str_replace("-", "", $keywords);
$keywords = str_replace("+", " ", $keywords);
$keywords = str_replace('"', "", $keywords);
$keywords = trim($keywords);

return $keywords;

}


ASP.NET Version

Public Function GetSearchKeywords(ByVal Url As String) As String

Dim keywords As String = ""
Dim kloc As Int32
Dim qloc As Int32
Dim nextloc As Int32
Dim qvar As String = ""

If Url Is Nothing Then
Return ""
End If

Url = Url.Trim()

qloc = Url.IndexOf("?")
If Url Like "*/search/web/*" Then
keywords = Url.Substring(Url.IndexOf("/search/web/") + 12)
If keywords.IndexOf("/") > -1 Then
keywords = keywords.Substring(0, keywords.IndexOf("/"))
End If
keywords = System.Web.HttpUtility.UrlDecode(keywords)
keywords = keywords.Replace(",", "")
keywords = keywords.Replace(".", "")
keywords = keywords.Replace("-", "")
keywords = keywords.Replace("+", " ")
keywords = keywords.Replace(Chr(34), "")
Return keywords
ElseIf Url.StartsWith("http://search.yahoo.com/") Then
kloc = Url.IndexOf("&p=")
If Url.IndexOf("&p=", kloc + 1) > -1 Then
kloc = Url.IndexOf("&p=", kloc + 1)
End If
If kloc = -1 Then
kloc = Url.IndexOf("?p=")
End If
qvar = "p"
ElseIf Url.IndexOf("&query=") > -1 Or _
Url.IndexOf("?query=") > -1 Then
kloc = Url.IndexOf("&query=")
If Url.IndexOf("&query=", kloc + 1) > -1 Then
kloc = Url.IndexOf("&query=", kloc + 1)
End If
If kloc = -1 Then
kloc = Url.IndexOf("?query=")
End If
qvar = "query"
ElseIf Url.IndexOf("&searchfor=") > -1 Or _
Url.IndexOf("?searchfor=") > -1 Then
kloc = Url.IndexOf("&searchfor=")
If Url.IndexOf("&searchfor=", kloc + 1) > -1 Then
kloc = Url.IndexOf("&searchfor=", kloc + 1)
End If
If kloc = -1 Then
kloc = Url.IndexOf("?searchfor=")
End If
qvar = "searchfor"
Else
kloc = Url.IndexOf("&q=")
If Url.IndexOf("&q=", kloc + 1) > -1 Then
kloc = Url.IndexOf("&q=", kloc + 1)
End If
If kloc = -1 Then
kloc = Url.IndexOf("?q=")
End If
qvar = "q"
End If

If qloc = -1 Or kloc = -1 Then
Return ""
End If

nextloc = Url.IndexOf("&", kloc + 1)
If nextloc = -1 Then
nextloc = Url.Length
End If

keywords = System.Web.HttpUtility.UrlDecode( _
Url.Substring(kloc + Len(qvar) + 2, _
nextloc - (kloc + Len(qvar) + 2))).Replace(" ", " ")
keywords = keywords.Replace(",", "")
keywords = keywords.Replace(".", "")
keywords = keywords.Replace("-", "")
keywords = keywords.Replace("+", " ")
keywords = keywords.Replace(Chr(34), "")
keywords = keywords.Trim

Return keywords

End Function

Monday, May 17, 2004

Web Based Remote SQL Server Administration

Here's a cool new free download from Microsoft. It allows you to (mostly) remotely administer your SQL Server installations over the web.

It lacks some of the more advanced management features like adding and running configured jobs and setting up detailed user security, but it's not too shabby for the price--free.

Click here to check it out.

Saturday, May 15, 2004

Visual Basic.NET Html Parser (VB.NET)

There have been numerous projects and tools that I've written in which I really needed the ability to extract a list of certain tags and their properties from an HTML document. For example, extracting all anchor (A) tags from a document and get their HREF property value.

When this became a need in a set of search engine optimization tools that I've written, I wrote a class that handles the parsing of an HTML document and the returning of an ArrayList of HtmlTag class objects. Here's an example of how it works:

Dim parser As New HtmlParser
Dim tags As New ArrayList
Dim tag As HtmlTag

tags = parser.GetTags(html, "A")

For Each tag in tags
MsgBox(tag.Properties("href"))
Next

The HtmlTag class requires some explanation:

Public Class HtmlTag
Public TagName As String = ""
Public Properties As New Hashtable
Public InnerHtml As String = ""
Public InnerText As String = ""
Public InnerTerms As String = ""
Public Sub New(ByVal TagName As String, _
ByVal Properties As Hashtable, _
ByVal InnerHtml As String, _
ByVal InnerText As String)
Me.TagName = TagName
Me.Properties = Properties
Me.InnerHtml = InnerHtml
Me.InnerText = InnerText
End Sub
Public Sub New()
End Sub
End Class

The TagName is set to the tag name (i.e. "A", "FONT", "H1", etc.).

The Properties property is a hashtable of all properties found in that tag (i.e. the "href" and "target" properties of an "A" tag). They're referenced easily with tag.Properties("[propertyname]") as shown in the example above.

The InnerHtml property contains all of the HTML code found between the opening and closing tags. So, for example, if I had a this tag in the HtmlTag object:

<a href="http://www.somesite.com">This is a<BR>link</a>

The InnerHtml property would contain This is a<BR>link

The InnerText property contains all of the text between the opening and closing tags with the HTML code stripped. So, using the example for InnerHtml, the InnerText property would contain This is a link.

The InnerTerms property may not be very useful to most people. It is a list of all of the "words" found in the InnerText property, separated by spaces. I used this when measuring keyword density in my search engine optimization tools.

The class code can be downloaded here. Just rename the file from ".txt" to ".vb" and add it to your project.

Free Chart and Graph Control for ASP.NET

I've used WebChart, a free ASP.NET charting and graphing control, in one of my ASP.NET projects. It works very well, outputs the images in PNG format, and even cleans up the storage directory by itself. Very nice.

It's not really full featured like some of the professional controls, but you can't beat the price tag. ;)

Check it out.

Ten Steps to a Secure ASP.NET Web Application

Here is a list of steps to take to ensure that your data-driven ASP.NET web application is reasonably secure on Windows 2000 (there may be some changes for Windows 2003, as Microsoft has tightened security considerably).

Windows

1. Create a new user account. Do not grant any additional permission beyond putting it into the ‘Users’ group (which is done by default).

NOTE: If SQL Server and IIS are running on different servers, the account you create must be a domain account. If SQL Server and IIS are on the same server, it can be a local account (unless ASP.NET will need to access any network resources).

2. Grant the account that you create read/write access to the folder: C:\Documents and Settings\<machine name>\ASPNET\Local Settings\Temp. This is needed for ASP.NET to be able to do its work.

IIS

3. Change the impersonated user for anonymous access to your Web Application in IIS to the newly created user. The default user is IUSR_<machine name>. You change this by right-clicking on your Web Application in IIS and going to the properties. Under the Directory Security tab, edit the account used for anonymous access.

ASP.NET

4. In your Web Application’s Web.config file, add the following line to the system.web section:

<identity impersonate="true"/>

This will cause ASP.NET to run under the credentials of the anonymous IIS user (which in step 3 was set to the user you created).

SQL Server

5. In SQL Server, grant access to the user you’ve created only for the database(s) used in your Web Application. Give as little access as is necessary. I recommend that all of your SQL Server functionality take place in stored procedures, and that access be granted only to execute those stored procedures. Do not enable any direct table access at all if you can help it.

6. With the above done, remove any credentials passed in the connection strings to SQL Server and replace them with “Trusted_Connection=Yes”.

7. Finally, since ASP.NET is impersonating the user you have created, Windows authentication is all that is necessary for SQL Server, so if SQL Server is running in mixed mode, change it to Windows only mode. This is done in Enterprise Manger. Right-click on the server and go to Properties. The setting is found under the Security tab.

NOTE: Follow this step only if you are sure that all applications accessing SQL Server have made the necessary changes to use Windows authentication! Any application not switched to using trusted connections will be denied access once the change is made.

8. If you must operate SQL Server in a mixed mode environment, make sure the sa password is not blank and is set to an alphanumeric value of at least 8 characters. When in Windows authentication mode, the sa user cannot be used, so this is not an issue.

MS Access

9. If you are connecting to an Access database from your ASP.NET application, you will need to grant the new user Read and/or Write privileges to the MDB file. Only grant Write privileges if the web application will be inserting into the database.

Network Resources

10. If accessing any network resources from your web application, do so only through objects installed in Component Services running under accounts restricted to accessing only the necessary resources. As always, if write access is not needed, don’t grant it. Never grant the ASP.NET impersonated account access to network resources directly. If your COM objects are created in .NET, remember to register them using regsvcs.exe—lazy registration will fail if ASP.NET tries to create the COM object.

A while back I wrote a blog detailing how to securely access network resources from an ASP.NET web application.

Batch Scanning for your applications using Kofax Ascent Capture

In one of my projects at work I've recently introduced a batch scanning module that uses the Kofax Ascent Capture product. The project is a VB6 application.

In the past we had attempted to handle the batch scanning on our own, with, quite frankly, limited success. Kofax is the largest manufacturer of imaging products in the world, and as such their software is incredible. The move to a third party batch scanning solution was most definitely a good idea for us.

Kofax offers two distinct benefits: speed and flexibility.

Speed


Ascent Capture is very fast. We paired the software with Fujitsu 4097D scanners that support Virtual Rescan (VRS). VRS offers many advantages, the best of which is increased speed. The performance is increased by converting a grayscale image to black and white at the hardware level instead of having to do the conversion on the PC. In my tests I found that the 4097D scanned almost twice as fast when using VRS compared to having the VRS disabled. Since Kofax is the creators of VRS, their software knows how to handle it.

Flexibility


Ascent Capture allows you to write VB scripts for almost everything! I wrote a custom OCX that allowed me to call the Scan module from within our application seamlessly, as if it were a part of our own app. That was great.

Even without writing a line of code, though, Ascent Capture is very flexible. You can have the batch information written to any DSN data source, using the fields that you supply. It supports bar code scanning as well. We use it to pull the document number off of the documents we scan, tying them back to a record in our database and updating the image accordingly--no user input necessary.

Very cool stuff. If you're looking to implement batch scanning in any of your projects, I'd definitely look into Kofax and Ascent Capture.

Oh, by the way, did I mention that it's cheap? A 5,000 scans-per-month license is a little under $1,000. A 25,000 scans-per-month license is available, as well as an unlimited license. All very reasonably priced. Our sales-people were jumping for joy when they found out how much money we were going to make off of our new batch scanning module! :)

PHP versus ASP.NET (or) How PHP compares with ASP.NET

This issue has been hashed and rehashed around the web, but I wanted to throw my two cents in regarding the issue. That's because, unlike the authors of the other articles I've read on this subject, I've actually coded two almost identical projects using both PHP with MySQL and ASP.NET with SQL Server, so I feel like I can give a fair and unbiased point of view.

Why did I create the same project using both technologies? Quite simply, money. Some people wanted to buy the project I'd written in ASP.NET, but they needed it in PHP.

All that said, I'll compare the technologies from three vantage points: Availability, Performance and Development Speed.

Availability


PHP wins hands-down in the availability department. That's because most web servers still run some form of UNIX platform (a popular one being Linux), and PHP's birthplace is the UNIX world. ASP.NET, however, is only available for web servers running Microsoft's Internet Information Server (IIS).

So if you're wanting to sell the project you're creating to as many webmasters as possible, and if it's possible to create the project in either ASP.NET -or- PHP, I would suggest using PHP. A search for PHP at Google.com as of today returns 319,000,000 results compared to 5,520,000 for ASP.NET.

Performance


You PHP coders out there will probably scoff at this, but guess what--ASP.NET is faster. ASP.NET's caching and native SQL Server database access can't be beat if the bottom line is performance. But don't take my word for it, check out the benchmarks at eWeek.com. At first glance MySQL seems faster than SQL Server, but don't stop at the first graph! Click 'next' twice and look at the ASP.NET / IIS / SQL Server performance graph. The Microsoft trio was actually able to serve up 33% more page views per second than PHP/MySQL.

I've found this to be the case in my own tests as well. But I will say this: ASP.NET and SQL Server may perform better, but it's no "tortoise and the hair" comparison. PHP with MySQL still performs very well.

Development Speed


In the arena of development speed, it's a toss up. On the one hand, ASP.NET's code-behind makes handling the events surrounding form input very easy and fast. So that's great. ASP.NET also provides a large number of controls that make form validation and database connectivity a breeze.

However, in my opinion, the syntax of the PHP language is more powerful, including it's (oh-my-God-I-love-this) ability to do this:
print "Writing a variable’s value ($val1) to the output stream.";

Instead of this:
Response.Write("Writing a variable's value (" & val1 & ") to the output stream.")

You would be amazed at how much time and coding effort that one little syntax difference saves, not to mention how much more readable it makes the code. PHP is a full featured, mature language now (and an object-oriented one at that if you care to use classes). Also, PHP has a large number of very useful little functions that ASP.NET lacks, and other functions that are just easier to use. For example, the fopen() function has the ability to open a URL and read from the web page as it were a normal file. To do the same thing with ASP.NET takes about 5 or 6 lines of code.

PHP also has the ability to overlook various errors and drudge on despite problems, whereas ASP.NET will just die when a problem occurs and is not properly handled. On the one hand it can be argued that this is a bad thing that allows badly written code, but on the other hand if you're just trying to slam out some code to test an idea, it's much faster. I've written a web crawler for my little mini search engines (such as the weight loss search engine) in PHP, and frankly you just can't plan for everything when parsing and indexing vastly different web page designs. PHP gracefully slips past the problems and continues indexing. I can look at the warnings and modify the crawler accordingly, but I didn't have to loose hours of work because of it.

Is that your final answer?


In conclusion, I say that both PHP and ASP.NET are great development platforms. If you need wide availability, use PHP. If you want absolutely the best performance possible, use ASP.NET/SQL Server/IIS. If you need to get your project done in a hurry, either one is fine.

MySQL Full Text Indexing

I've been toying with the idea of writing my own "mini search engines" on targeted topics for some time now, primarily so that I can put advertisements on them and maybe make a few extra bucks. I initially thought I would use ASP.NET and MS SQL Server, but then I found out something wonderful:

MySQL 4.0 and later supports full text indexing!

And boy, talk about a breeze to implement. Using MySQL Control Center, you simply edit the table and create a new index, selecting the FULL TEXT radio button. Tada! Instant full text indexing. In fact, MySQL offers some significant advantages over SQL Server in the arena of full text indexing.


  1. In MySQL, full text is activated automatically (you have to turn it on for SQL Server, which is a pain if your site is hosted by somebody else).
  2. In MySQL, the full text indexes are updated automatically as records are inserted (in SQL Server you have to rebuild the indexes every time--yuck).


Querying your full text indexes is also simple. Let's say I've created a full text index on a table called "documents", with the full text index on two fields "title" and "body". You can query it like this:

SELECT title, body FROM documents WHERE MATCH(title, body) AGAINST ('keyword phrase')


The results are automatically sorted by relevancy. For an example of one of my mini search engines in action, click here.

FINAL NOTE: By default, MySQL considers any term that shows up in 50% or more of the indexed records as a stop term. What that means is that any term showing up in 50% or more of the records, if used as the search term, will return zero results. For an example, try searching for "diet" at my weight loss search engine. No results, because every page in the index is about weight loss.

Back to the Blog

After a dozen posts this blog stalled, but my interest has been rekindled, and so I've decided to pick it back up. I've actually been getting a few hundred visitors a month to the blog without doing anything, and it's hurting my feelings. If the masses want some information, well darnit, I'll give it to them. :)

I've revamped the look of the blog, too, and I like it much better this way.

I plan on creating some new posts for today so my visitors can have some extra code to chew on as well.

As always,
Jonathan

Thursday, January 29, 2004

Url Encoding / Decoding In ASP.NET

I noticed in my site logs that quite a few people are searching for how to encode and decode a url in ASP.NET, so I thought today I'd write a short blog on how to do that.

If you are wanting to do this from a WebForm, it's very simple:

Encoding:
encodedUrl = Server.UrlEncode(urlToEncode)


Decoding:
decodedUrl = Server.UrlDecode(urlToDecode)


If you are wanting to encode or decode a url from a project other than a web application, you will need to add a reference to System.Web to your project, and create an instance of a System.Web.HttpServerUtility, like so:


Imports System.Web

Public Class UrlDemoClass
Public Sub UrlDemo(ByVal url As String)


Dim o As System.Web.HttpServerUtility
Dim encodedUrl As String
Dim decodedUrl As String


encodedUrl = o.UrlEncode(urL)
decodedUrl = o.UrlDecode(encodedUrl)

End Sub
End Class


That's it!

Wednesday, January 28, 2004

A Few Tips About SQL Joins

First of all, let me apologize for being gone for a few days. Things have been hectic at work. We just demoed our new product and the demo went -great-.

Today I thought I would write about the various kinds of joins available to programmers using SQL. It's a bit of a beginners blog, really. But an important one if you're not familiar with the details of how joins work.

Most databases using SQL support three kinds of joins:


  1. Inner Join
  2. Left Join
  3. Right Join


I'll talk a little about each one.

Inner Join


The inner join is the most efficient join. It pulls two tables together based on key values. Only records that actually contain the same key value in both tables are pulled into the result set. For instance, if I have a customers table and a customer with a customerid of 1, and a purchases table with 10 purchases for customer id 1, then doing an Inner Join on the customers and purchases table using the customerid would return 10 records. If I have a customer id of 2, but no purchases records, then doing an Inner Join on the two tables would return 0 records.

Left Join


Left Join is in some SQL syntax called Left Outer Join. It takes all of the records from the table on the left and joins it with all matching records on the table to the right, filling the field values with Null if there is no matching record. So, for example, a left join statement like...

Select * from customers left join purchases on customers.customerid = puchases.cusomterid where customers.customerid = 1

...would return at least one record for customer 1, even if the customer had no purchases. If the customer did have purchases, all of those purchase records would be returned.

Right Join


Right Join is in some SQL syntax called Right Outer Join. It takes all of the records from the table on the right and joins it with all matching records on the table to the left, filling the field values with Null if there is no matching record. Right Joins aren't used very often since they aren't as intuitive as left joins. So, for example, a right join statement like...

Select * from purchases right join customers on customers.customerid = puchases.cusomterid where customers.customerid = 1

...would accomplish the same thing as the Left Join example above.

Join Performance


As stated early, the Inner Join gives you the best performance, since only matching records are joined. This allows efficient use of the table indexes. Right and Left joins join all records regardless of whether or not they match, and so the indexes don't help as much. There's also the issue of Nulls, which different databases handle differently, and different programming languages handle differently. Nulls are messy and require extra code to deal with in VB.Net, so I would suggest avoiding Right and Left joins unless you just absolutely need them. Don't use them as a short cut.

NOTE: Always join on columns that are indexed! Joining on unindexed columns for tables with large amounts of records is horribly inefficient and slow.

Thursday, January 22, 2004

Get The Filesize Of A Url's Target File In VB.Net

Not too long ago I wrote a program that had the requirement of displaying the size of a file targetted by a URL without actually downloading the file (the filesize was to help the user determined whether or not they wanted to take the time to download the file).

I wrote a little function for Visual Basic.Net to accomplish this, and found it so darn handy that I thought I would post it here for you. Here it is:

     Public Function GetFileSize(ByVal url As String) As Long


Dim request As System.Net.HttpWebRequest
Dim response As System.Net.HttpWebResponse
Dim fileSize As Long

Try
request = System.Net.WebRequest.Create(url)
response = request.GetResponse()
fileSize = response.ContentLength
response.Close()
Return fileSize
Catch ex As Exception
Return -1
End Try

End Function



Some related properties that you can get from an HttpWebResponse object are:

  • CharacterSet - Gets the character set of the response.

  • ContentEncoding - Gets the method used to encode the body of the response.

  • ContentType - Gets the content type of the response.

  • Cookies - Gets or set cookies associated with the response.

  • LastModified - Gets the last date and time the contents of the response was modified.

  • Method - Gets the method used to return the response.

  • ProtocolVersion - Gets the version of the HTTP protocol used in the response.

  • Server - Gets the name of the server that sent the response.



All of the above information can be extracted from the response without actually having to pull down the entire file.

Wednesday, January 21, 2004

Load And Print A Multipage TIFF From VB.NET

The .NET framework added a groovy wrapper for the GDI+ graphics library. One of the things that this has really helped me with in my job is the handling of TIFFs. In our VB6 applications I had to use a third party control (usually from LEAD) for loading, manipulating and finally printing TIFFs (the principal image format for our client base). No more! .NET has plenty of routines for handling every kind of image manipulation you can imagine: loading, rotating, printing, converting, and drawing. It's great.

When it came to the manipulation of multipage TIFFs, I found that the documentation wasn't easily understandable, and I only found little bits and pieces on the 'net for how to do it. I eventually pieced it all together into something workable, but I wanted to make it a little easier on you. So here's a class that loads and prints a multipage TIFF to the default printer.

Imports System

Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.Drawing.Printing

Public Class PrintTiff

Private m_tiff As Drawing.Image
Private m_fd As FrameDimension

Public Sub New()

End Sub

' Sends a multi-page TIFF document to the default printer.
Public Function PrintTiffDocument(ByVal PathToTIFF As String) As String

Dim tiffDocument As New PrintDocument

Try
' Load TIFF from file.
m_tiff = System.Drawing.Image.FromFile(PathToTIFF)

' Get the frame dimensions.
m_fd = New FrameDimension(m_tiff.FrameDimensionsList(0))

' Setup the print handler.
AddHandler tiffDocument.PrintPage, AddressOf PrintTiffPage

' Print the image.
tiffDocument.Print()
Catch
' Throw all exceptions to caller.
Throw
End Try

End Function

' Print each page of the tiff to the printer.
Private Sub PrintTiffPage(ByVal sender As Object, ByVal e As PrintPageEventArgs)

Static pageNum As Int32 = 0

m_tiff.SelectActiveFrame(m_fd, pageNum)
e.Graphics.DrawImage(m_tiff, New PointF(0, 0))

pageNum += 1
If pageNum < m_tiff.GetFrameCount(m_fd) Then
e.HasMorePages = True
Else
pageNum = 0
End If

End Sub

End Class


-

I'm not sure why, though I'm sure there's a good historic reason, but GDI+ refers to an image page as a "frame". So all of the methods with "frame" in the name are actually used in manipulating the pages. The PrintTiffPage needs a little bit of explaining, so here's what's happening there:

First of all, I keep the current page number in a static variable so that for each subsequent call the method knows which page to print (this number is reset to zero once the image has been completely printed). The SelectActiveFrame() method sets the page number that is the current page for the TIFF (starting with zero). You have to have a FrameDimension object for the TIFF to pass to this method. After selecting the page to print, I draw the page onto the printer using the DrawImage method. I put the page in the upper-left corner of the printer. Finally, if there are any pages left to print, I set the event flag indicating this is the case, otherwise I reset the page number to zero so the next call to the PrintTiffDocument() method will start on the first page.

Questions or comments? Email me!

Tuesday, January 20, 2004

Microsoft vs. MikeRoweSoft

I'll try to post some code later today, but in the mean time check out this interesting article:

MikeRoweSoft garners funds to fight back

Monday, January 19, 2004

How To Disable Browser HotKeys Using JavaScript

I noticed that a number of people were getting to my blog searching at Google for how to disable web browser hotkeys, so I figured I'd make them happy by writing a blog showing how to do this.

NOTICE: I saw in my logs that some people searching for disabling CTRL-P were sent to this page by Google. If you're interested in disabling printing from a web page using JavaScript, see this blog entry instead.

First of all, let me say that disabling hotkeys in a browser is by no means a certain prospect using javascript. I have never had success disabling the alt-keys for built-in browser menu items, and the way the browser handles the hotkeys tends to vary from browser to browser. So...

NOTICE: Do not rely on JavaScript attempts to disable hotkeys as an iron-clad security measure! There's no guarantees!

However, that said, here's a method that works in IE 4 and above. I have the code setup for Netscape as well, just in case you want to use the code to trap keys in Netscape and IE (but they will only be successfully disabled in IE).

function keyTrap(e)

{
// Netscape uses e, IE window.event
var eventObj = (e)?e:window.event;
var keyPressed = (e)?e.which:window.event.keyCode;
var ctrlPressed = (e)?(e.ctrlKey):window.event.ctrlKey;

if (ctrlPressed)
{
switch (keyPressed)
{
case 80: // CTRL-P.
window.event.keyCode = 0;
return false;
}
}
}
if(document.layers)
{
//NS4+
document.captureEvents(Event.ONKEYDOWN);
}
document.onkeydown=keyTrap;


-

This code disables only CTRL-P, which can be useful in preventing printing of a page. It doesn't work at all if the user has Netscape. The key is trapped, but the Print dialog still comes up. Even if you try to set e.which to 0, it still does not work.

SOAPBOX: Herein lies one of the most frustrating aspects of building web applications. You have to have six copies of various versions of browsers available if you want 100% compatability. ASP.NET promised it would take care of this problem, and it does a very good job at doing so for its built-in components. However, despite ASP.NET's greatly expanded capabilities over ASP, I have yet to create a web project that does not need at least some javascript coding. And Microsoft and Netscape bicker and fuss over who's version of javascript is correct. Microsoft is notorious for adding in their own functionality without anybody else's approval, and Netscape is notorious for not duplicating it in the same way (even if they think it's a great idea--it's like they give a property its own name in javascript that is different from the one Microsoft gave it--even if it does exactly the same thing!--just so it will be different from Microsoft).

Okay, off the soapbox.

That's it for today. Feel free to email me with any questions or comments.

Friday, January 16, 2004

Accessing Network Files And Resources From ASP.NET

Accessing network resources through ASP/ASP.NET has never been a terribly simple task, not if you want to maintain a high level of security for your server.

Despite not being as simple as I'd like, it is not difficult to do. Microsoft's recommendation comes in the following steps:

  1. Create an assembly that works with the network resources.
  2. Add the assembly to Component Services (COM+).
  3. Set the user the COM+ object runs under to one with rights to the network resources.
  4. Add a reference to your COM+ assembly to the web project and access the network resources using it.


I'm going to show you step by step how to do these things. It really isn't difficult, but breaking up your network resources library into a seperate project does fragment things a bit.

1. Create an assembly that works with the network resources.

This is pretty simple. Here's some example code:

Imports System

Imports System.Reflection
Imports System.EnterpriseServices

<Assembly: AssemblyKeyFileAttribute("example.snk")>
<Assembly: ApplicationActivation(ActivationOption.Server)>
<Assembly: ApplicationName("NetworkResourcesExample")>
Public Class AccessNetworkResources
Inherits ServicedComponent

Public Function ExampleFunction() As String

' Put your code that access the network resources here.

End Function

End Class


---
An assembly that will go into Component Services is not much different than any other assembly. All you have to do is have your class inherit from ServicedComponent and add a few properties. The AssemblyKeyFileAttribute property gives the name/path of the key pair file that will be used to strong name the assembly. To create a new key pair file, use the sn.exe utility like so:
sn -k example.snk

---
The ApplicationActivation property tells COM+ to run the application as a Server (as opposed to a Library). You need to do this so that you can assign a different user for the assembly to run under other than the caller. The third property, ApplicationName, tells COM+ the name to list your assembly under in the component services manager (also, if you use any legacy code to access the object, such as classic ASP, you can call the object using CreateObject("ApplicationName.ClassName")).


2. Add the assembly to Component Services (COM+).
This is a very simple step. Once you've compiled the assembly, go the command line and change into the directory where the compiled assembly is located. Then execute the following:
regsvcs [assemblyname]

---
This will install the assembly as a COM+ component.


3. Set the user the COM+ object runs under to one with rights to the network resources.
Now you need to set the COM+ object to run under a user who has rights to the network resources that you need to access. To do this go to: Control Panel -> Administrative Tools -> Component Services. Drill into the Component Services node and into the computer the component is installed on. In the COM+ Applications folder you will find your newly installed component under the name you gave it in the ApplicationName property mentioned earlier.

Right-click on the application and go to properties. In the properties dialog go to the Identity tab. Select either the "Network Service" account under "System Account" or select "This User" and enter the username and password that has access to the resources. I would recommend creating a seperate account that only has access to the specific resources needed, just for safety. Click OK.


4. Add a reference to your COM+ assembly to the web project and access the network resources using it.
This step is pretty self explanatory, but I do have one comment about it. When you add the reference, you don't need to go to the COM tab of the references dialog and select your object from there (in fact, you can't do that, it wont work). You just browse to the compiled assembly you created for the library and select it directly. The .NET framework recognizes that the assembly was installed under COM+ and will activate it using the proper credentials.


That's it! You should be securely accessing the network resources from your web project now.

Thursday, January 15, 2004

Retreiving Performance Counter Values With VB.Net

From Windows 98 on Microsoft has included performance counters in their O/S that measure all kinds of things going on in the system: memory usage, disk access, network bandwidth use, etc. Your software can access these values through the performance counters for reporting, notification of system problems, etc. I've recently finished an application which sits on a server and regularly pulls performance counter values from all of the computers on the network, alerting the network administrator if any of the values rise above of fall below values set as the acceptable range.

It's very easy to access performance counters in Visual Basic .NET. Here's how you do it:


Imports System.Diagnostics

Public Class CounterTest

Public CounterTestValue As Single

Public Sub New()

Dim counter As New PerformanceCounter("Processor", _
"% Processor Time", _
"_Total")

CounterTestValue = counter.NextValue

End Sub

End Class


---

Here's the syntax of the PerformanceCounter constructor:

New PerformanceCounter(CategoryName, CounterName, InstanceName)


---

The category name is which group of counters the counter you want belongs to (such as Processor or Memory or Thread), the counter name is the text name of a counter that is in that category. Those two things are pretty obvious.

The InstanceName is not always so obvious, because it's not always required. A counter's instances break down the counter into all of its smaller parts. For example, the Process category has a counter called "% Processor Time" that has instances for each application currently running, as well as one called "_Total" (a common instance name) which gives you the processor time of all processes. Not all counters have an instance. A few do. If a counter does not have an instance you can pass an empty string, or better yet, just use the constructor overload that does not require the instance name.

The PerformanceCounter class also has a constructor overload which takes the network id of the machine you want to get the counter from. So, for example, if I wanted to get the processor usage on a machine on the LAN called "Loris", I would change the above class like so:


Dim counter As New PerformanceCounter("Processor",
"% Processor Time",
"_Total",
"Loris")


---

It's that simple.

NOTE: Be careful when pulling performance counters from machines on the LAN running different versions of Windows. Microsoft introduced new performance counters with each new Windows version, so make sure that the counter you are able to pull from your Windows XP machine exists on that Windows 2000 box before you try it. Also, the user under which your program is running must have rights to access performance counters on other machines. It is usually best to run the program under a Domain Administrator user, as this guarantees its ability to read the counters.

To find out what performance counters are available to you, go to Control Panel -> Administrative Tools -> Performance. Right-click in the graph and select "Add Counters". There you can see a list of all of the categories and their specified counters and the instances of those counters.

TIP: Many of Microsoft's Software Packages create their own performance counters when installed. A big one is MS Sql Server, which creates dozens of counters for database administrators to use in monitoring the performance of their databases. Your software can access these as well. In addition, .NET makes it very easy to create your own performance counters. How to create your own counter will be the subject of a future blog.

Wednesday, January 14, 2004

Code Generation

In a recent project I decided to explore the idea of code generation, even writing a tool that automatically generated the classes for all the database access to a SQL Server database.

My tool was pretty specific to my needs, and in a later project I found that it just wasn't flexible enough. In a link sent to me I happened to find a great freeware code generation tool created by Eric J. Smith called CodeSmith. It lets you write your own templates for code generation based on tables from any database (OLE DB or ODBC, including Access and SQL Server). I used it to generate dozens of database access classes for one of my team's projects. Saved literally hundreds of hours of coding.

Check it out and let me know what you think.

CodeSmith
http://www.ericjsmith.net/codesmith/

Tuesday, January 13, 2004

Disable Printing Of A Web Page

And still another trick I have learned while working on my current ASP.NET project is how to prevent a user from printing the web page that they are viewing. This is a two step process. The first step is very simple. Add this tag in the HEAD section of your page:

<style media="print">
BODY { DISPLAY: none }
</style>


---

What this stylesheet entry does is tell the browser to replace everything in between the BODY tags of the page with nothing when the user tries to print the page. All the user will get is a blank page.

However, nothing prevents a user from saving the page to his hard drive and editing the style tag out of the page. So much for that security. JavaScript to the rescue! Add this code to HEAD of the page:


// Prevent selecting text for copying.
function deselect(){
if (document.selection) {
document.selection.empty();
Copied=document.body.createTextRange();
}
else if (window.getSelection){window.getSelection().removeAllRanges();}}

timerID=setTimeout('setInterval("deselect()", 1)',1);

// Capture right-click and prevent it to prevent copying text or viewing source.
function clickIE4(){
if (event.button==2){
return false;
}
}

function clickNS4(e){
if (document.layers||document.getElementById&&!document.all){
if (e.which==2||e.which==3){
return false;
}
}
}

if (document.layers){
document.captureEvents(Event.MOUSEDOWN);
document.onmousedown=clickNS4;
}
else if (document.all){
document.onmousedown=clickIE4;
}

document.oncontextmenu=new Function("return false")



---

This code prevents right-clicking and prevents selecting of text in Netscape (and IE). To make absolutely sure that nobody can copy text from the page in IE, add this code to the BODY tag:

<BODY onselectstart="return false;" ondragstart="return false;">


---

That will prevent any selecting of text at all (but it is not supported in Netscape, so you'll need the previous code for that).

Of course, this still does not prevent the user from doing a File->Save on your page. To prevent that, just use javascript to open the page that should not be printed in a new window without the toolbars. This also has the added security of preventing the user from disabling javascript as a trick to get the page contents. If javascript is disabled in the browser, the page will not come up since javascript is used to launch the window.

The final possibility is that the user could enable javascript, open the window, disable javascript while the window is open and then hit F5 to refresh the opened page. With the javascript disabled, the user would be able to copy the page contents.

Now, you can trap the refresh key with javascript, but you cannot prevent refreshing the page. All you can do is preform some task before the page is refreshed. In order to be totally secure, you can have the window close if the user tries to refresh the page. Here's the code (goes in the HEAD tag):


function preventRefresh(e)
{
// Netscape uses e, IE window.event
var eventObj = (e)?e:window.event;

if(eventObj.which)
{
//NS4+
if(e.which==116)
{
alert('No refreshing of this page is allowed.' +
'This window will now close.');
window.close();
}
} else {
//IE
if(window.event.keyCode==116)
{
alert('No refreshing of this page is allowed.' +
'This window will now close.');
window.close();
}
}
}
if(document.layers)
{
//NS4+
document.captureEvents(Event.ONKEYDOWN);
}
document.onkeydown=preventRefresh;



---

Voila! An unprintable, uncopyable secured web page.

Monday, January 12, 2004

Creating PDF Reports For ASP.NET with Active PDF

Yet another winning product I discovered while working on my current ASP.NET project is Active PDF (www.activepdf.com), an incredible toolset for creating PDF reports from .NET. The tool I'm using from Active PDF is Active PDF Server.

Active PDF Server sets up a printer which can be selected and print to from your application. You print to the PDF printer, and the object returns the path to where the PDF was created. Very spiffy.

It took a little bit of tinkering and a few registry entries to get everything working right (since I have it installed on Windows 2003 running in terminal server mode--Active PDF requires some registry entries to run it in terminal server mode). But once it was all setup and installed--WOOHOO! I was very impressed.

Great compression, support for linearization (where the PDF is downloaded in pieces so that the web user can be reading the first page or two while the rest of the file downloads in the background), security settings (disable printing--which I needed--disable copy/paste, password protection, etc.), very fast PDF creation.

If you need a solid PDF solution, check 'em out.

NOTE: I tried using a companion product, Active PDF DocConverter, but I had little success. Although it purports to convert MS Word files and Rich Text Format files, I could never get either to convert with any consistancy. I must say, though, that after talking (well, typing) to their excellent tech support guys, that it appears to be a Windows 2003 permissions issue. I have had more Win2003 permissions issue than you can shake a stick at... At any rate, I gave up on the DocConverter and just wrote my own custom reports in .NET (I was originally creating HTML templates and trying to convert them to PDF using the DocConverter). One more thing: When I was able to get DocConverter to convert MS Word, RTF and HTML files, the quality of the output PDF was not good. Most of the lines and tables were misformatted, fonts were wrong and missized. It just wasn't a good solution.

Sunday, January 11, 2004

JavaScript Hotkeys

I am currently working on an ASP.NET project that required the use of hot keys. Using a JavaScript include on each page I was able to implement the hotkeys. A sample of the code follows:

As you will see by looking through the code, the KeyDown event is captured and various ALT-Key combinations are trapped which redirect the user to different pages of the site. On the page I have the first letter of the hotkey underlined in the menu so that users know which ALT-Key is the hotkey.

In addition to global hotkeys, certain pages of the site also implement their own set of additional hotkeys, and the script checks to see if the page that the user is on has its own set of hotkeys, and if so it calls the function for handling those hot keys (those functions are not shown, but are very similiar to the one shown).

The Code:


function searchHotkeys(e)
{
try
{
// Netscape uses e.which, IE event.keyCode
var eventObj = (e)?e:window.event;
var keyPressed = (e)?e.which:window.event.keyCode;
var altPressed = (e)?(e.altKey):window.event.altKey;

if (altPressed)
{
switch(keyPressed)
{
case 67: // C - Civil
{
if(document.getElementById("SiteMenu_searchCivil"))
{
document.location = "searchCivil.aspx";
}
return;
}
case 82: // R - Criminal.
{
if(document.getElementById("SiteMenu_searchCriminal"))
{
document.location = "searchCriminal.aspx";
}
return;
}
case 77: // M - Marriage.
{
if(document.getElementById("SiteMenu_searchMarriage"))
{
document.location = "searchMarriage.aspx";
}
return;
}
case 80: // P - Property.
{
if(document.getElementById("SiteMenu_searchRecords"))
{
document.location = "searchRecords.aspx";
}
return;
}
}
}

// If we're on a civil suit, check those hot keys.
if (document.location.href.indexOf("Civil") > - 1)
{
return civilHotkeys(altPressed, keyPressed);
}

// If we're on a criminal suit, check those hot keys.
if (document.location.href.indexOf("Criminal") > - 1)
{
return criminalHotKeys(altPressed, keyPressed);
}

// If we're on a marriage license listing, check those hot keys.
if (document.location.href.indexOf("Marriage") > - 1)
{
return marriageHotKeys(altPressed, keyPressed);
}

// Check records hot keys.
if (document.location.href.indexOf("Records") > -1 |
document.location.href.indexOf("Mortgage") > -1 |
document.location.href.indexOf("Chattel") > -1)
{
return recordsHotKeys(altPressed, keyPressed);
}
}
catch(er)
{
// Trap all errors in case of browser javascript
// incompatibilities. We don't want ugly error
// messages popping up everywhere.
//alert(er);
}
}

// Trap key down for hot-key functionality.
if (document.layers)
{
//Needed for Netscape.
document.captureEvents(Events.ONKEYDOWN);
}
document.onkeydown=searchHotkeys;

Saturday, January 10, 2004

ODBC Connectivity For COBOL databases using Relativity

I wanted to share with you some information about a product that I am currently using on a project that pulls its data from RM COBOL databases. It's called Relativity, and it's created by Liant Software Corp of Austin, Texas.

Relativity wraps an ODBC interface around Cobol databases. It comes in two parts: a Windows client for building the catalogs and a Unix / Linux client for serving up the data. I have found it to be exceptionally fast. I am currently using it in an ASP.NET project.

TIP: I used an Access database which linked to all of the COBOL ODBC tables for creating the complex views necessary for my project. It works great, and has the added advantage of letting you transform some of the fields which are different in COBOL (i.e., date fields) before the data is served to your program. Converting the data at the database level is faster and less complicated than converting it once the data hase been received.